
io.lettuce.core.ConnectionBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lettuce-core Show documentation
Show all versions of lettuce-core Show documentation
Advanced and thread-safe Java Redis client for synchronous, asynchronous, and
reactive usage. Supports Cluster, Sentinel, Pipelining, Auto-Reconnect, Codecs
and much more.
The newest version!
/*
* Copyright 2011-Present, Redis Ltd. and Contributors
* All rights reserved.
*
* Licensed under the MIT License.
*
* This file contains contributions from third-party contributors
* 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
*
* https://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 io.lettuce.core;
import java.net.SocketAddress;
import java.net.SocketOption;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import jdk.net.ExtendedSocketOptions;
import reactor.core.publisher.Mono;
import io.lettuce.core.internal.LettuceAssert;
import io.lettuce.core.protocol.CommandEncoder;
import io.lettuce.core.protocol.CommandHandler;
import io.lettuce.core.protocol.ConnectionInitializer;
import io.lettuce.core.protocol.ConnectionWatchdog;
import io.lettuce.core.protocol.Endpoint;
import io.lettuce.core.protocol.ReconnectionListener;
import io.lettuce.core.protocol.RedisHandshakeHandler;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.EpollProvider;
import io.lettuce.core.resource.IOUringProvider;
import io.lettuce.core.resource.KqueueProvider;
import io.lettuce.core.resource.Transports;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.socket.nio.NioChannelOption;
import io.netty.util.AttributeKey;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
/**
* Connection builder for connections. This class is part of the internal API.
*
* @author Mark Paluch
* @author Bodong Ybd
*/
public class ConnectionBuilder {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ConnectionBuilder.class);
public static final AttributeKey REDIS_URI = AttributeKey.valueOf("RedisURI");
public static final AttributeKey INIT_FAILURE = AttributeKey.valueOf("ConnectionBuilder.INIT_FAILURE");
private Mono socketAddressSupplier;
private ConnectionEvents connectionEvents;
private RedisChannelHandler, ?> connection;
private Endpoint endpoint;
private Supplier commandHandlerSupplier;
private ChannelGroup channelGroup;
private Bootstrap bootstrap;
private ClientOptions clientOptions;
private Duration timeout;
private ClientResources clientResources;
private ConnectionInitializer connectionInitializer;
private ReconnectionListener reconnectionListener = ReconnectionListener.NO_OP;
private ConnectionWatchdog connectionWatchdog;
private RedisURI redisURI;
public static ConnectionBuilder connectionBuilder() {
return new ConnectionBuilder();
}
/**
* Apply settings from {@link RedisURI}
*
* @param redisURI
*/
public void apply(RedisURI redisURI) {
this.redisURI = redisURI;
timeout(redisURI.getTimeout());
bootstrap.attr(REDIS_URI, redisURI.toString());
}
protected List buildHandlers() {
LettuceAssert.assertState(channelGroup != null, "ChannelGroup must be set");
LettuceAssert.assertState(connectionEvents != null, "ConnectionEvents must be set");
LettuceAssert.assertState(connection != null, "Connection must be set");
LettuceAssert.assertState(clientResources != null, "ClientResources must be set");
LettuceAssert.assertState(endpoint != null, "Endpoint must be set");
LettuceAssert.assertState(connectionInitializer != null, "ConnectionInitializer must be set");
List handlers = new ArrayList<>();
connection.setOptions(clientOptions);
handlers.add(new ChannelGroupListener(channelGroup, clientResources.eventBus()));
handlers.add(new CommandEncoder());
handlers.add(getHandshakeHandler());
handlers.add(commandHandlerSupplier.get());
handlers.add(new ConnectionEventTrigger(connectionEvents, connection, clientResources.eventBus()));
if (clientOptions.isAutoReconnect()) {
handlers.add(createConnectionWatchdog());
}
return handlers;
}
protected ChannelHandler getHandshakeHandler() {
return new RedisHandshakeHandler(connectionInitializer, clientResources, timeout);
}
protected ConnectionWatchdog createConnectionWatchdog() {
if (connectionWatchdog != null) {
return connectionWatchdog;
}
LettuceAssert.assertState(bootstrap != null, "Bootstrap must be set for autoReconnect=true");
LettuceAssert.assertState(socketAddressSupplier != null, "SocketAddressSupplier must be set for autoReconnect=true");
ConnectionWatchdog watchdog = new ConnectionWatchdog(clientResources.reconnectDelay(), clientOptions, bootstrap,
clientResources.timer(), clientResources.eventExecutorGroup(), socketAddressSupplier, reconnectionListener,
connection, clientResources.eventBus(), endpoint);
endpoint.registerConnectionWatchdog(watchdog);
connectionWatchdog = watchdog;
return watchdog;
}
public ChannelInitializer build(SocketAddress socketAddress) {
return new PlainChannelInitializer(this::buildHandlers, clientResources);
}
public ConnectionBuilder socketAddressSupplier(Mono socketAddressSupplier) {
this.socketAddressSupplier = socketAddressSupplier;
return this;
}
public Mono socketAddress() {
LettuceAssert.assertState(socketAddressSupplier != null, "SocketAddressSupplier must be set");
return socketAddressSupplier;
}
public ConnectionBuilder timeout(Duration timeout) {
this.timeout = timeout;
return this;
}
public Duration getTimeout() {
return timeout;
}
public ConnectionBuilder reconnectionListener(ReconnectionListener reconnectionListener) {
LettuceAssert.notNull(reconnectionListener, "ReconnectionListener must not be null");
this.reconnectionListener = reconnectionListener;
return this;
}
public ConnectionBuilder clientOptions(ClientOptions clientOptions) {
this.clientOptions = clientOptions;
return this;
}
public ConnectionBuilder connectionEvents(ConnectionEvents connectionEvents) {
this.connectionEvents = connectionEvents;
return this;
}
public ConnectionBuilder connection(RedisChannelHandler, ?> connection) {
this.connection = connection;
return this;
}
public ConnectionBuilder channelGroup(ChannelGroup channelGroup) {
this.channelGroup = channelGroup;
return this;
}
public ConnectionBuilder commandHandler(Supplier supplier) {
this.commandHandlerSupplier = supplier;
return this;
}
public ConnectionBuilder bootstrap(Bootstrap bootstrap) {
this.bootstrap = bootstrap;
return this;
}
public ConnectionBuilder endpoint(Endpoint endpoint) {
this.endpoint = endpoint;
return this;
}
public ConnectionBuilder clientResources(ClientResources clientResources) {
this.clientResources = clientResources;
return this;
}
public ConnectionBuilder connectionInitializer(ConnectionInitializer connectionInitializer) {
this.connectionInitializer = connectionInitializer;
return this;
}
/**
* Initialize the {@link Bootstrap}.
*
* @since 6.1
*/
public void configureBootstrap(boolean domainSocket,
Function, EventLoopGroup> eventLoopGroupProvider) {
LettuceAssert.assertState(bootstrap != null, "Bootstrap must be set");
LettuceAssert.assertState(clientOptions != null, "ClientOptions must be set");
Class extends EventLoopGroup> eventLoopGroupClass = Transports.eventLoopGroupClass();
Class extends Channel> channelClass = Transports.socketChannelClass();
if (domainSocket) {
Transports.NativeTransports.assertDomainSocketAvailable();
eventLoopGroupClass = Transports.NativeTransports.eventLoopGroupClass(true);
channelClass = Transports.NativeTransports.domainSocketChannelClass();
} else {
bootstrap.resolver(clientResources.addressResolverGroup());
}
SocketOptions options = clientOptions.getSocketOptions();
EventLoopGroup eventLoopGroup = eventLoopGroupProvider.apply(eventLoopGroupClass);
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(options.getConnectTimeout().toMillis()));
if (!domainSocket) {
bootstrap.option(ChannelOption.SO_KEEPALIVE, options.isKeepAlive());
bootstrap.option(ChannelOption.TCP_NODELAY, options.isTcpNoDelay());
if (options.isEnableTcpUserTimeout()) {
SocketOptions.TcpUserTimeoutOptions tcpUserTimeoutOptions = options.getTcpUserTimeout();
if (IOUringProvider.isAvailable()) {
IOUringProvider.applyTcpUserTimeout(bootstrap, tcpUserTimeoutOptions.getTcpUserTimeout());
} else if (io.lettuce.core.resource.EpollProvider.isAvailable()) {
EpollProvider.applyTcpUserTimeout(bootstrap, tcpUserTimeoutOptions.getTcpUserTimeout());
} else {
logger.warn("Cannot apply TCP User Timeout options to channel type " + channelClass.getName());
}
}
}
bootstrap.channel(channelClass).group(eventLoopGroup);
if (options.isKeepAlive() && options.isExtendedKeepAlive()) {
SocketOptions.KeepAliveOptions keepAlive = options.getKeepAlive();
if (IOUringProvider.isAvailable()) {
IOUringProvider.applyKeepAlive(bootstrap, keepAlive.getCount(), keepAlive.getIdle(), keepAlive.getInterval());
} else if (io.lettuce.core.resource.EpollProvider.isAvailable()) {
EpollProvider.applyKeepAlive(bootstrap, keepAlive.getCount(), keepAlive.getIdle(), keepAlive.getInterval());
} else if (ExtendedNioSocketOptions.isAvailable() && !KqueueProvider.isAvailable()) {
ExtendedNioSocketOptions.applyKeepAlive(bootstrap, keepAlive.getCount(), keepAlive.getIdle(),
keepAlive.getInterval());
} else {
logger.warn("Cannot apply extended TCP keepalive options to channel type " + channelClass.getName());
}
}
}
public RedisChannelHandler, ?> connection() {
return connection;
}
public Bootstrap bootstrap() {
return bootstrap;
}
public ClientOptions clientOptions() {
return clientOptions;
}
public ClientResources clientResources() {
return clientResources;
}
public Endpoint endpoint() {
return endpoint;
}
public RedisURI getRedisURI() {
return redisURI;
}
static class PlainChannelInitializer extends ChannelInitializer {
private final Supplier> handlers;
private final ClientResources clientResources;
PlainChannelInitializer(Supplier> handlers, ClientResources clientResources) {
this.handlers = handlers;
this.clientResources = clientResources;
}
@Override
protected void initChannel(Channel channel) {
doInitialize(channel);
}
private void doInitialize(Channel channel) {
for (ChannelHandler handler : handlers.get()) {
channel.pipeline().addLast(handler);
}
clientResources.nettyCustomizer().afterChannelInitialized(channel);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().attr(INIT_FAILURE).set(cause);
super.exceptionCaught(ctx, cause);
}
}
/**
* Utility to support Java 11 {@link ExtendedSocketOptions extended keepalive options}.
*/
@SuppressWarnings("unchecked")
static class ExtendedNioSocketOptions {
private static final SocketOption TCP_KEEPCOUNT;
private static final SocketOption TCP_KEEPIDLE;
private static final SocketOption TCP_KEEPINTERVAL;
static {
SocketOption keepCount = null;
SocketOption keepIdle = null;
SocketOption keepInterval = null;
try {
keepCount = (SocketOption) ExtendedSocketOptions.class.getDeclaredField("TCP_KEEPCOUNT").get(null);
keepIdle = (SocketOption) ExtendedSocketOptions.class.getDeclaredField("TCP_KEEPIDLE").get(null);
keepInterval = (SocketOption) ExtendedSocketOptions.class.getDeclaredField("TCP_KEEPINTERVAL")
.get(null);
} catch (ReflectiveOperationException e) {
logger.trace("Cannot extract ExtendedSocketOptions for KeepAlive", e);
}
TCP_KEEPCOUNT = keepCount;
TCP_KEEPIDLE = keepIdle;
TCP_KEEPINTERVAL = keepInterval;
}
public static boolean isAvailable() {
return TCP_KEEPCOUNT != null && TCP_KEEPIDLE != null && TCP_KEEPINTERVAL != null;
}
/**
* Apply Keep-Alive options.
*
*/
public static void applyKeepAlive(Bootstrap bootstrap, int count, Duration idle, Duration interval) {
bootstrap.option(NioChannelOption.of(TCP_KEEPCOUNT), count);
bootstrap.option(NioChannelOption.of(TCP_KEEPIDLE), Math.toIntExact(idle.getSeconds()));
bootstrap.option(NioChannelOption.of(TCP_KEEPINTERVAL), Math.toIntExact(interval.getSeconds()));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy