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 Show documentation
Show all versions of redisson Show documentation
Redis Java client with features of In-Memory Data Grid
/**
* 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