org.redisson.connection.ServiceManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redisson-all Show documentation
Show all versions of redisson-all Show documentation
Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC
/**
* Copyright (c) 2013-2024 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.connection;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.kqueue.KQueueDatagramChannel;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.kqueue.KQueueSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.incubator.channel.uring.IOUringDatagramChannel;
import io.netty.incubator.channel.uring.IOUringEventLoopGroup;
import io.netty.incubator.channel.uring.IOUringSocketChannel;
import io.netty.resolver.AddressResolver;
import io.netty.resolver.AddressResolverGroup;
import io.netty.resolver.DefaultAddressResolverGroup;
import io.netty.resolver.dns.DnsServerAddressStreamProviders;
import io.netty.util.Timer;
import io.netty.util.TimerTask;
import io.netty.util.*;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.internal.PlatformDependent;
import org.redisson.ElementsSubscribeService;
import org.redisson.QueueTransferService;
import org.redisson.RedissonShutdownException;
import org.redisson.Version;
import org.redisson.api.NatMapper;
import org.redisson.api.RFuture;
import org.redisson.cache.LRUCacheMap;
import org.redisson.client.RedisNodeNotFoundException;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.config.Config;
import org.redisson.config.MasterSlaveServersConfig;
import org.redisson.config.Protocol;
import org.redisson.config.TransportMode;
import org.redisson.liveobject.resolver.MapResolver;
import org.redisson.misc.CompletableFutureWrapper;
import org.redisson.misc.RandomXoshiro256PlusPlus;
import org.redisson.misc.RedisURI;
import org.redisson.remote.ResponseEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
*
* @author Nikita Koksharov
*
*/
public final class ServiceManager {
private final Logger log = LoggerFactory.getLogger(getClass());
public static final Timeout DUMMY_TIMEOUT = new Timeout() {
@Override
public Timer timer() {
return null;
}
@Override
public TimerTask task() {
return null;
}
@Override
public boolean isExpired() {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean cancel() {
return true;
}
};
private final ConnectionEventsHub connectionEventsHub = new ConnectionEventsHub();
private final String id = UUID.randomUUID().toString();
private final EventLoopGroup group;
private final Class extends SocketChannel> socketChannelClass;
private final AddressResolverGroup resolverGroup;
private final ExecutorService executor;
private final Config cfg;
private MasterSlaveServersConfig config;
private HashedWheelTimer timer;
private IdleConnectionWatcher connectionWatcher;
private final AtomicBoolean shutdownLatch = new AtomicBoolean();
private final ElementsSubscribeService elementsSubscribeService = new ElementsSubscribeService(this);
private NatMapper natMapper = NatMapper.direct();
private static final Map> SCRIPT_SHA_CACHE = new ConcurrentHashMap<>();
private static final Map SHA_CACHE = new LRUCacheMap<>(500, 0, 0);
private final Map responses = new ConcurrentHashMap<>();
private final QueueTransferService queueTransferService = new QueueTransferService();
public ServiceManager(MasterSlaveServersConfig config, Config cfg) {
Version.logVersion();
if (cfg.getTransportMode() == TransportMode.EPOLL) {
if (cfg.getEventLoopGroup() == null) {
if (cfg.getNettyExecutor() != null) {
this.group = new EpollEventLoopGroup(cfg.getNettyThreads(), cfg.getNettyExecutor());
} else {
this.group = new EpollEventLoopGroup(cfg.getNettyThreads(), new DefaultThreadFactory("redisson-netty"));
}
} else {
this.group = cfg.getEventLoopGroup();
}
this.socketChannelClass = EpollSocketChannel.class;
if (PlatformDependent.isAndroid()) {
this.resolverGroup = DefaultAddressResolverGroup.INSTANCE;
} else {
this.resolverGroup = cfg.getAddressResolverGroupFactory().create(EpollDatagramChannel.class, socketChannelClass, DnsServerAddressStreamProviders.platformDefault());
}
} else if (cfg.getTransportMode() == TransportMode.KQUEUE) {
if (cfg.getEventLoopGroup() == null) {
if (cfg.getNettyExecutor() != null) {
this.group = new KQueueEventLoopGroup(cfg.getNettyThreads(), cfg.getNettyExecutor());
} else {
this.group = new KQueueEventLoopGroup(cfg.getNettyThreads(), new DefaultThreadFactory("redisson-netty"));
}
} else {
this.group = cfg.getEventLoopGroup();
}
this.socketChannelClass = KQueueSocketChannel.class;
this.resolverGroup = cfg.getAddressResolverGroupFactory().create(KQueueDatagramChannel.class, socketChannelClass, DnsServerAddressStreamProviders.platformDefault());
} else if (cfg.getTransportMode() == TransportMode.IO_URING) {
if (cfg.getEventLoopGroup() == null) {
this.group = createIOUringGroup(cfg);
} else {
this.group = cfg.getEventLoopGroup();
}
this.socketChannelClass = IOUringSocketChannel.class;
this.resolverGroup = cfg.getAddressResolverGroupFactory().create(IOUringDatagramChannel.class, socketChannelClass, DnsServerAddressStreamProviders.platformDefault());
} else {
if (cfg.getEventLoopGroup() == null) {
if (cfg.getNettyExecutor() != null) {
this.group = new NioEventLoopGroup(cfg.getNettyThreads(), cfg.getNettyExecutor());
} else {
this.group = new NioEventLoopGroup(cfg.getNettyThreads(), new DefaultThreadFactory("redisson-netty"));
}
} else {
this.group = cfg.getEventLoopGroup();
}
this.socketChannelClass = NioSocketChannel.class;
if (PlatformDependent.isAndroid()) {
this.resolverGroup = DefaultAddressResolverGroup.INSTANCE;
} else {
this.resolverGroup = cfg.getAddressResolverGroupFactory().create(NioDatagramChannel.class, socketChannelClass, DnsServerAddressStreamProviders.platformDefault());
}
}
if (cfg.getExecutor() == null) {
int threads = Runtime.getRuntime().availableProcessors() * 2;
if (cfg.getThreads() != 0) {
threads = cfg.getThreads();
}
executor = Executors.newFixedThreadPool(threads, new DefaultThreadFactory("redisson"));
} else {
executor = cfg.getExecutor();
}
this.cfg = cfg;
this.config = config;
if (cfg.getConnectionListener() != null) {
this.connectionEventsHub.addListener(cfg.getConnectionListener());
}
this.connectionEventsHub.addListener(new ConnectionListener() {
@Override
public void onConnect(InetSocketAddress addr) {
// empty
}
@Override
public void onDisconnect(InetSocketAddress addr) {
SCRIPT_SHA_CACHE.remove(addr);
}
});
initTimer();
}
// for Quarkus substitution
private static EventLoopGroup createIOUringGroup(Config cfg) {
return new IOUringEventLoopGroup(cfg.getNettyThreads(), new DefaultThreadFactory("redisson-netty"));
}
private void initTimer() {
int minTimeout = Math.min(config.getRetryInterval(), config.getTimeout());
if (minTimeout % 100 != 0) {
minTimeout = (minTimeout % 100) / 2;
} else if (minTimeout == 100) {
minTimeout = 50;
} else {
minTimeout = 100;
}
timer = new HashedWheelTimer(new DefaultThreadFactory("redisson-timer"), minTimeout, TimeUnit.MILLISECONDS, 1024, false);
connectionWatcher = new IdleConnectionWatcher(group, config);
}
public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
try {
return timer.newTimeout(task, delay, unit);
} catch (IllegalStateException e) {
if (isShuttingDown()) {
return DUMMY_TIMEOUT;
}
throw e;
}
}
public boolean isShuttingDown() {
return shutdownLatch.get();
}
public boolean isShuttingDown(Throwable e) {
return e instanceof RedissonShutdownException
|| e.getCause() instanceof RedissonShutdownException;
}
public boolean isShutdown() {
return group.isTerminated();
}
public ConnectionEventsHub getConnectionEventsHub() {
return connectionEventsHub;
}
public String getId() {
return id;
}
public EventLoopGroup getGroup() {
return group;
}
public CompletableFuture> resolveAll(RedisURI uri) {
if (uri.isIP()) {
RedisURI mappedUri = toURI(uri.getScheme(), uri.getHost(), "" + uri.getPort());
return CompletableFuture.completedFuture(Collections.singletonList(mappedUri));
}
AddressResolver resolver = resolverGroup.getResolver(group.next());
Future> future = resolver.resolveAll(InetSocketAddress.createUnresolved(uri.getHost(), uri.getPort()));
CompletableFuture> result = new CompletableFuture<>();
future.addListener((GenericFutureListener>>) f -> {
if (!f.isSuccess()) {
log.error("Unable to resolve {}", uri, f.cause());
result.completeExceptionally(f.cause());
return;
}
List nodes = future.getNow().stream().map(addr -> {
return toURI(uri.getScheme(), addr.getAddress().getHostAddress(), "" + addr.getPort());
}).collect(Collectors.toList());
result.complete(nodes);
});
return result;
}
public AddressResolverGroup getResolverGroup() {
return resolverGroup;
}
public ExecutorService getExecutor() {
return executor;
}
public Config getCfg() {
return cfg;
}
public HashedWheelTimer getTimer() {
return timer;
}
public IdleConnectionWatcher getConnectionWatcher() {
return connectionWatcher;
}
public Class extends SocketChannel> getSocketChannelClass() {
return socketChannelClass;
}
private final AtomicInteger lastFuturesCounter = new AtomicInteger();
private final Deque> lastFutures = new ConcurrentLinkedDeque<>();
public void addFuture(CompletableFuture> future) {
lastFutures.addLast(future);
if (lastFuturesCounter.incrementAndGet() > 100) {
lastFutures.pollFirst();
lastFuturesCounter.decrementAndGet();
}
}
public void shutdownFutures(long timeout, TimeUnit unit) {
CompletableFuture future = CompletableFuture.allOf(lastFutures.toArray(new CompletableFuture[0]));
try {
future.get(timeout, unit);
} catch (Exception e) {
// skip
}
lastFutures.forEach(f -> f.completeExceptionally(new RedissonShutdownException("Redisson is shutdown")));
lastFutures.clear();
}
public void close() {
shutdownLatch.set(true);
}
public RedisNodeNotFoundException createNodeNotFoundException(NodeSource source) {
RedisNodeNotFoundException ex;
if (cfg.isClusterConfig()
&& source.getSlot() != null
&& source.getAddr() == null
&& source.getRedisClient() == null) {
ex = new RedisNodeNotFoundException("Node for slot: " + source.getSlot() + " hasn't been discovered yet. Check cluster slots coverage using CLUSTER NODES command. Increase value of retryAttempts and/or retryInterval settings.");
} else {
ex = new RedisNodeNotFoundException("Node: " + source + " hasn't been discovered yet. Increase value of retryAttempts and/or retryInterval settings.");
}
return ex;
}
public MasterSlaveServersConfig getConfig() {
return config;
}
public ElementsSubscribeService getElementsSubscribeService() {
return elementsSubscribeService;
}
public CompletableFuture resolveIP(RedisURI address) {
return resolveIP(address.getScheme(), address);
}
public CompletableFuture resolveIP(String scheme, RedisURI address) {
if (address.isIP()) {
RedisURI addr = toURI(scheme, address.getHost(), "" + address.getPort());
return CompletableFuture.completedFuture(addr);
}
CompletableFuture result = new CompletableFuture<>();
AddressResolver resolver = resolverGroup.getResolver(group.next());
InetSocketAddress addr = InetSocketAddress.createUnresolved(address.getHost(), address.getPort());
Future future = resolver.resolve(addr);
future.addListener((FutureListener) f -> {
if (!f.isSuccess()) {
log.error("Unable to resolve {}", address, f.cause());
result.completeExceptionally(f.cause());
return;
}
InetSocketAddress s = f.getNow();
RedisURI uri = toURI(scheme, s.getAddress().getHostAddress(), "" + address.getPort());
result.complete(uri);
});
return result;
}
public CompletableFuture resolve(RedisURI address) {
if (address.isIP()) {
try {
InetAddress ip = InetAddress.getByName(address.getHost());
InetSocketAddress addr = new InetSocketAddress(InetAddress.getByAddress(address.getHost(), ip.getAddress()), address.getPort());
return CompletableFuture.completedFuture(addr);
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
}
CompletableFuture result = new CompletableFuture<>();
AddressResolver resolver = resolverGroup.getResolver(group.next());
InetSocketAddress addr = InetSocketAddress.createUnresolved(address.getHost(), address.getPort());
Future future = resolver.resolve(addr);
future.addListener((FutureListener) f -> {
if (!f.isSuccess()) {
log.error("Unable to resolve {}", address, f.cause());
result.completeExceptionally(f.cause());
return;
}
InetSocketAddress s = f.getNow();
// apply natMapper
RedisURI uri = toURI(address.getScheme(), s.getAddress().getHostAddress(), "" + address.getPort());
if (!uri.getHost().equals(s.getAddress().getHostAddress())) {
InetAddress ip = InetAddress.getByName(uri.getHost());
InetSocketAddress mappedAddr = new InetSocketAddress(InetAddress.getByAddress(s.getAddress().getHostAddress(), ip.getAddress()), uri.getPort());
result.complete(mappedAddr);
return;
}
result.complete(s);
});
return result;
}
public RedisURI toURI(String scheme, String host, String port) {
// convert IPv6 address to unified compressed format
if (NetUtil.isValidIpV6Address(host)) {
byte[] addr = NetUtil.createByteArrayFromIpAddressString(host);
try {
InetAddress ia = InetAddress.getByAddress(host, addr);
host = ia.getHostAddress();
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
RedisURI uri = new RedisURI(scheme + "://" + host + ":" + port);
try {
return natMapper.map(uri);
} catch (Exception e) {
log.error(e.getMessage(), e);
return uri;
}
}
public void setNatMapper(NatMapper natMapper) {
this.natMapper = natMapper;
}
public NatMapper getNatMapper() {
return natMapper;
}
public boolean isCached(InetSocketAddress addr, String script) {
Set values = SCRIPT_SHA_CACHE.computeIfAbsent(addr, k -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
String sha = calcSHA(script);
return values.contains(sha);
}
public void cacheScripts(InetSocketAddress addr, Set scripts) {
Set values = SCRIPT_SHA_CACHE.computeIfAbsent(addr, k -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
for (String script : scripts) {
values.add(calcSHA(script));
}
}
public String calcSHA(String script) {
return SHA_CACHE.computeIfAbsent(script, k -> {
try {
MessageDigest mdigest = MessageDigest.getInstance("SHA-1");
byte[] s = mdigest.digest(script.getBytes(StandardCharsets.UTF_8));
return ByteBufUtil.hexDump(s);
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
}
public RFuture execute(Supplier> supplier) {
CompletableFuture result = new CompletableFuture<>();
int retryAttempts = config.getRetryAttempts();
AtomicInteger attempts = new AtomicInteger(retryAttempts);
execute(attempts, result, supplier);
return new CompletableFutureWrapper<>(result);
}
private void execute(AtomicInteger attempts, CompletableFuture result, Supplier> supplier) {
CompletionStage future = supplier.get();
future.whenComplete((r, e) -> {
if (e != null) {
if (e.getCause() != null
&& e.getCause().getMessage() != null
&& e.getCause().getMessage().equals("None of slaves were synced")) {
if (attempts.decrementAndGet() < 0) {
result.completeExceptionally(e);
return;
}
newTimeout(t -> execute(attempts, result, supplier),
config.getRetryInterval(), TimeUnit.MILLISECONDS);
return;
}
result.completeExceptionally(e);
return;
}
result.complete(r);
});
}
public void transfer(CompletionStage source, CompletableFuture dest) {
source.whenComplete((res, e) -> {
if (e != null) {
dest.completeExceptionally(e);
return;
}
dest.complete(res);
});
}
private final Random random = RandomXoshiro256PlusPlus.create();
public Long generateValue() {
return random.nextLong();
}
public String generateId() {
return ByteBufUtil.hexDump(generateIdArray());
}
public byte[] generateIdArray() {
return generateIdArray(16);
}
public byte[] generateIdArray(int size) {
byte[] id = new byte[size];
random.nextBytes(id);
return id;
}
private final AtomicBoolean liveObjectLatch = new AtomicBoolean();
public AtomicBoolean getLiveObjectLatch() {
return liveObjectLatch;
}
public boolean isResp3() {
return cfg.getProtocol() == Protocol.RESP3;
}
private static final Map, RedisCommand>> RESP3MAPPING = new HashMap<>();
static {
RESP3MAPPING.put(RedisCommands.XREADGROUP_BLOCKING, RedisCommands.XREADGROUP_BLOCKING_V2);
RESP3MAPPING.put(RedisCommands.XREADGROUP, RedisCommands.XREADGROUP_V2);
RESP3MAPPING.put(RedisCommands.XREADGROUP_BLOCKING_SINGLE, RedisCommands.XREADGROUP_BLOCKING_SINGLE_V2);
RESP3MAPPING.put(RedisCommands.XREADGROUP_SINGLE, RedisCommands.XREADGROUP_SINGLE_V2);
RESP3MAPPING.put(RedisCommands.XREAD_BLOCKING_SINGLE, RedisCommands.XREAD_BLOCKING_SINGLE_V2);
RESP3MAPPING.put(RedisCommands.XREAD_SINGLE, RedisCommands.XREAD_SINGLE_V2);
RESP3MAPPING.put(RedisCommands.XREAD_BLOCKING, RedisCommands.XREAD_BLOCKING_V2);
RESP3MAPPING.put(RedisCommands.XREAD, RedisCommands.XREAD_V2);
RESP3MAPPING.put(RedisCommands.HRANDFIELD, RedisCommands.HRANDFIELD_V2);
}
public RedisCommand resp3(RedisCommand command) {
if (isResp3()) {
return (RedisCommand) RESP3MAPPING.getOrDefault(command, command);
}
return command;
}
public Map getResponses() {
return responses;
}
public QueueTransferService getQueueTransferService() {
return queueTransferService;
}
public Codec getCodec(Codec codec) {
if (codec == null) {
return cfg.getCodec();
}
return codec;
}
private final Map addersUsage = new ConcurrentHashMap<>();
public Map getAddersUsage() {
return addersUsage;
}
private final Map addersCounter = new ConcurrentHashMap<>();
public Map getAddersCounter() {
return addersCounter;
}
private final MapResolver mapResolver = new MapResolver(this);
public MapResolver getLiveObjectMapResolver() {
return mapResolver;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy