All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory Maven / Gradle / Ivy

There is a newer version: 3.2.5
Show newest version
/*
 * Copyright 2011-2018 the original author or authors.
 *
 * 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.springframework.data.redis.connection.lettuce;

import io.lettuce.core.AbstractRedisClient;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.ReadFrom;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisException;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulConnection;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.codec.RedisCodec;
import io.lettuce.core.resource.ClientResources;
import lombok.RequiredArgsConstructor;

import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.ExceptionTranslationStrategy;
import org.springframework.data.redis.PassThroughExceptionTranslationStrategy;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.RedisConfiguration.ClusterConfiguration;
import org.springframework.data.redis.connection.RedisConfiguration.DomainSocketConfiguration;
import org.springframework.data.redis.connection.RedisConfiguration.WithDatabaseIndex;
import org.springframework.data.redis.connection.RedisConfiguration.WithPassword;
import org.springframework.data.util.Optionals;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * Connection factory creating Lettuce-based connections.
 * 

* This factory creates a new {@link LettuceConnection} on each call to {@link #getConnection()}. Multiple * {@link LettuceConnection}s share a single thread-safe native connection by default. *

* The shared native connection is never closed by {@link LettuceConnection}, therefore it is not validated by default * on {@link #getConnection()}. Use {@link #setValidateConnection(boolean)} to change this behavior if necessary. Inject * a {@link Pool} to pool dedicated connections. If shareNativeConnection is true, the pool will be used to select a * connection for blocking and tx operations only, which should not share a connection. If native connection sharing is * disabled, the selected connection will be used for all operations. *

* {@link LettuceConnectionFactory} should be configured using an environmental configuration and the * {@link LettuceConnectionFactory client configuration}. Lettuce supports the following environmental configurations: *

    *
  • {@link RedisStandaloneConfiguration}
  • *
  • {@link RedisStaticMasterReplicaConfiguration}
  • *
  • {@link RedisSocketConfiguration}
  • *
  • {@link RedisSentinelConfiguration}
  • *
  • {@link RedisClusterConfiguration}
  • *
* * @author Costin Leau * @author Jennifer Hickey * @author Thomas Darimont * @author Christoph Strobl * @author Mark Paluch * @author Balázs Németh * @author Ruben Cervilla */ public class LettuceConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory, ReactiveRedisConnectionFactory { private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy( LettuceConverters.exceptionConverter()); private final Log log = LogFactory.getLog(getClass()); private final LettuceClientConfiguration clientConfiguration; private @Nullable AbstractRedisClient client; private @Nullable LettuceConnectionProvider connectionProvider; private @Nullable LettuceConnectionProvider reactiveConnectionProvider; private boolean validateConnection = false; private boolean shareNativeConnection = true; private @Nullable SharedConnection connection; private @Nullable SharedConnection reactiveConnection; private @Nullable LettucePool pool; /** Synchronization monitor for the shared Connection */ private final Object connectionMonitor = new Object(); private boolean convertPipelineAndTxResults = true; private RedisStandaloneConfiguration standaloneConfig = new RedisStandaloneConfiguration("localhost", 6379); private @Nullable RedisConfiguration configuration; private @Nullable ClusterCommandExecutor clusterCommandExecutor; /** * Constructs a new {@link LettuceConnectionFactory} instance with default settings. */ public LettuceConnectionFactory() { this(new MutableLettuceClientConfiguration()); } /** * Constructs a new {@link LettuceConnectionFactory} instance with default settings. */ public LettuceConnectionFactory(RedisStandaloneConfiguration configuration) { this(configuration, new MutableLettuceClientConfiguration()); } /** * Constructs a new {@link LettuceConnectionFactory} instance given {@link LettuceClientConfiguration}. * * @param clientConfig must not be {@literal null} * @since 2.0 */ private LettuceConnectionFactory(LettuceClientConfiguration clientConfig) { Assert.notNull(clientConfig, "LettuceClientConfiguration must not be null!"); this.clientConfiguration = clientConfig; this.configuration = this.standaloneConfig; } /** * Constructs a new {@link LettuceConnectionFactory} instance with default settings. */ public LettuceConnectionFactory(String host, int port) { this(new RedisStandaloneConfiguration(host, port), new MutableLettuceClientConfiguration()); } /** * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisSocketConfiguration}. * * @param redisConfiguration must not be {@literal null}. * @since 2.1 */ public LettuceConnectionFactory(RedisConfiguration redisConfiguration) { this(redisConfiguration, new MutableLettuceClientConfiguration()); } /** * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisSentinelConfiguration}. * * @param sentinelConfiguration must not be {@literal null}. * @since 1.6 */ public LettuceConnectionFactory(RedisSentinelConfiguration sentinelConfiguration) { this(sentinelConfiguration, new MutableLettuceClientConfiguration()); } /** * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisClusterConfiguration} * applied to create a {@link RedisClusterClient}. * * @param clusterConfiguration must not be {@literal null}. * @since 1.7 */ public LettuceConnectionFactory(RedisClusterConfiguration clusterConfiguration) { this(clusterConfiguration, new MutableLettuceClientConfiguration()); } /** * @param pool * @deprecated since 2.0, use pooling via {@link LettucePoolingClientConfiguration}. */ @Deprecated public LettuceConnectionFactory(LettucePool pool) { this(new MutableLettuceClientConfiguration()); this.pool = pool; } /** * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisStandaloneConfiguration} and * {@link LettuceClientConfiguration}. * * @param standaloneConfig must not be {@literal null}. * @param clientConfig must not be {@literal null}. * @since 2.0 */ public LettuceConnectionFactory(RedisStandaloneConfiguration standaloneConfig, LettuceClientConfiguration clientConfig) { this(clientConfig); Assert.notNull(standaloneConfig, "RedisStandaloneConfiguration must not be null!"); this.standaloneConfig = standaloneConfig; this.configuration = this.standaloneConfig; } /** * Constructs a new {@link LettuceConnectionFactory} instance using the given * {@link RedisStaticMasterReplicaConfiguration} and {@link LettuceClientConfiguration}. * * @param redisConfiguration must not be {@literal null}. * @param clientConfig must not be {@literal null}. * @since 2.1 */ public LettuceConnectionFactory(RedisConfiguration redisConfiguration, LettuceClientConfiguration clientConfig) { this(clientConfig); Assert.notNull(redisConfiguration, "RedisConfiguration must not be null!"); this.configuration = redisConfiguration; } /** * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisSentinelConfiguration} and * {@link LettuceClientConfiguration}. * * @param sentinelConfiguration must not be {@literal null}. * @param clientConfig must not be {@literal null}. * @since 2.0 */ public LettuceConnectionFactory(RedisSentinelConfiguration sentinelConfiguration, LettuceClientConfiguration clientConfig) { this(clientConfig); Assert.notNull(sentinelConfiguration, "RedisSentinelConfiguration must not be null!"); this.configuration = sentinelConfiguration; } /** * Constructs a new {@link LettuceConnectionFactory} instance using the given {@link RedisClusterConfiguration} and * {@link LettuceClientConfiguration}. * * @param clusterConfiguration must not be {@literal null}. * @param clientConfig must not be {@literal null}. * @since 2.0 */ public LettuceConnectionFactory(RedisClusterConfiguration clusterConfiguration, LettuceClientConfiguration clientConfig) { this(clientConfig); Assert.notNull(clusterConfiguration, "RedisClusterConfiguration must not be null!"); this.configuration = clusterConfiguration; } /* * (non-Javadoc) * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ public void afterPropertiesSet() { this.client = createClient(); this.connectionProvider = createConnectionProvider(client, LettuceConnection.CODEC); this.reactiveConnectionProvider = createConnectionProvider(client, LettuceReactiveRedisConnection.CODEC); if (isClusterAware()) { this.clusterCommandExecutor = new ClusterCommandExecutor( new LettuceClusterTopologyProvider((RedisClusterClient) client), new LettuceClusterConnection.LettuceClusterNodeResourceProvider(this.connectionProvider), EXCEPTION_TRANSLATION); } } /* * (non-Javadoc) * @see org.springframework.beans.factory.DisposableBean#destroy() */ public void destroy() { resetConnection(); if (connectionProvider instanceof DisposableBean) { try { ((DisposableBean) connectionProvider).destroy(); } catch (Exception e) { if (log.isWarnEnabled()) { log.warn(connectionProvider + " did not shut down gracefully.", e); } } } try { Duration timeout = clientConfiguration.getShutdownTimeout(); client.shutdown(timeout.toMillis(), timeout.toMillis(), TimeUnit.MILLISECONDS); } catch (Exception e) { if (log.isWarnEnabled()) { log.warn((client != null ? ClassUtils.getShortName(client.getClass()) : "LettuceClient") + " did not shut down gracefully.", e); } } if (clusterCommandExecutor != null) { try { clusterCommandExecutor.destroy(); } catch (Exception ex) { log.warn("Cannot properly close cluster command executor", ex); } } } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnectionFactory#getConnection() */ public RedisConnection getConnection() { if (isClusterAware()) { return getClusterConnection(); } LettuceConnection connection; if (pool != null) { connection = new LettuceConnection(getSharedConnection(), getTimeout(), null, pool, getDatabase()); } else { connection = new LettuceConnection(getSharedConnection(), connectionProvider, getTimeout(), getDatabase()); } connection.setConvertPipelineAndTxResults(convertPipelineAndTxResults); return connection; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnectionFactory#getClusterConnection() */ @Override public RedisClusterConnection getClusterConnection() { if (!isClusterAware()) { throw new InvalidDataAccessApiUsageException("Cluster is not configured!"); } RedisClusterClient clusterClient = (RedisClusterClient) client; return getShareNativeConnection() ? new LettuceClusterConnection( (StatefulRedisClusterConnection) getOrCreateSharedConnection().getConnection(), connectionProvider, clusterClient, clusterCommandExecutor, clientConfiguration.getCommandTimeout()) : new LettuceClusterConnection(null, connectionProvider, clusterClient, clusterCommandExecutor, clientConfiguration.getCommandTimeout()); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.ReactiveRedisConnectionFactory#getReactiveConnection() */ @Override public LettuceReactiveRedisConnection getReactiveConnection() { return getShareNativeConnection() ? new LettuceReactiveRedisConnection(getSharedReactiveConnection(), reactiveConnectionProvider) : new LettuceReactiveRedisConnection(reactiveConnectionProvider); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.ReactiveRedisConnectionFactory#getReactiveClusterConnection() */ @Override public LettuceReactiveRedisClusterConnection getReactiveClusterConnection() { if (!isClusterAware()) { throw new InvalidDataAccessApiUsageException("Cluster is not configured!"); } RedisClusterClient client = (RedisClusterClient) this.client; return getShareNativeConnection() ? new LettuceReactiveRedisClusterConnection(getSharedReactiveConnection(), reactiveConnectionProvider, client) : new LettuceReactiveRedisClusterConnection(reactiveConnectionProvider, client); } /** * Initialize the shared connection if {@link #getShareNativeConnection() native connection sharing} is enabled and * reset any previously existing connection. */ public void initConnection() { resetConnection(); getSharedConnection(); getSharedReactiveConnection(); } /** * Reset the underlying shared Connection, to be reinitialized on next access. */ public void resetConnection() { Optionals.toStream(Optional.ofNullable(connection), Optional.ofNullable(reactiveConnection)) .forEach(SharedConnection::resetConnection); synchronized (this.connectionMonitor) { this.connection = null; this.reactiveConnection = null; } } /** * Validate the shared connections and reinitialize if invalid. */ public void validateConnection() { getOrCreateSharedConnection().validateConnection(); getOrCreateSharedReactiveConnection().validateConnection(); } private SharedConnection getOrCreateSharedConnection() { synchronized (this.connectionMonitor) { if (this.connection == null) { this.connection = new SharedConnection<>(connectionProvider, false); } return this.connection; } } private SharedConnection getOrCreateSharedReactiveConnection() { synchronized (this.connectionMonitor) { if (this.reactiveConnection == null) { this.reactiveConnection = new SharedConnection<>(reactiveConnectionProvider, true); } return this.reactiveConnection; } } public DataAccessException translateExceptionIfPossible(RuntimeException ex) { return EXCEPTION_TRANSLATION.translate(ex); } /** * Returns the current host. * * @return the host. */ public String getHostName() { return standaloneConfig.getHostName(); } /** * Sets the hostname. * * @param hostName the hostname to set. * @deprecated since 2.0, configure the hostname using {@link RedisStandaloneConfiguration}. */ @Deprecated public void setHostName(String hostName) { standaloneConfig.setHostName(hostName); } /** * Returns the current port. * * @return the port. */ public int getPort() { return standaloneConfig.getPort(); } /** * Sets the port. * * @param port the port to set. * @deprecated since 2.0, configure the port using {@link RedisStandaloneConfiguration}. */ @Deprecated public void setPort(int port) { standaloneConfig.setPort(port); } /** * Returns the connection timeout (in milliseconds). * * @return connection timeout. */ public long getTimeout() { return getClientTimeout(); } /** * Sets the connection timeout (in milliseconds). * * @param timeout the timeout. * @deprecated since 2.0, configure the timeout using {@link LettuceClientConfiguration}. * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable. */ @Deprecated public void setTimeout(long timeout) { getMutableConfiguration().setTimeout(Duration.ofMillis(timeout)); } /** * Returns whether to use SSL. * * @return use of SSL. */ public boolean isUseSsl() { return clientConfiguration.isUseSsl(); } /** * Sets to use SSL connection. * * @param useSsl {@literal true} to use SSL. * @deprecated since 2.0, configure SSL usage using {@link LettuceClientConfiguration}. * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable. */ @Deprecated public void setUseSsl(boolean useSsl) { getMutableConfiguration().setUseSsl(useSsl); } /** * Returns whether to verify certificate validity/hostname check when SSL is used. * * @return whether to verify peers when using SSL. */ public boolean isVerifyPeer() { return clientConfiguration.isVerifyPeer(); } /** * Sets to use verify certificate validity/hostname check when SSL is used. * * @param verifyPeer {@literal false} not to verify hostname. * @deprecated since 2.0, configure peer verification using {@link LettuceClientConfiguration}. * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable. */ @Deprecated public void setVerifyPeer(boolean verifyPeer) { getMutableConfiguration().setVerifyPeer(verifyPeer); } /** * Returns whether to issue a StartTLS. * * @return use of StartTLS. */ public boolean isStartTls() { return clientConfiguration.isStartTls(); } /** * Sets to issue StartTLS. * * @param startTls {@literal true} to issue StartTLS. * @deprecated since 2.0, configure StartTLS using {@link LettuceClientConfiguration}. * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable. */ @Deprecated public void setStartTls(boolean startTls) { getMutableConfiguration().setStartTls(startTls); } /** * Indicates if validation of the native Lettuce connection is enabled. * * @return connection validation enabled. */ public boolean getValidateConnection() { return validateConnection; } /** * Enables validation of the shared native Lettuce connection on calls to {@link #getConnection()}. A new connection * will be created and used if validation fails. *

* Lettuce will automatically reconnect until close is called, which should never happen through * {@link LettuceConnection} if a shared native connection is used, therefore the default is {@literal false}. *

* Setting this to {@literal true} will result in a round-trip call to the server on each new connection, so this * setting should only be used if connection sharing is enabled and there is code that is actively closing the native * Lettuce connection. * * @param validateConnection enable connection validation. */ public void setValidateConnection(boolean validateConnection) { this.validateConnection = validateConnection; } /** * Indicates if multiple {@link LettuceConnection}s should share a single native connection. * * @return native connection shared. */ public boolean getShareNativeConnection() { return shareNativeConnection; } /** * Enables multiple {@link LettuceConnection}s to share a single native connection. If set to {@literal false}, every * operation on {@link LettuceConnection} will open and close a socket. * * @param shareNativeConnection enable connection sharing. */ public void setShareNativeConnection(boolean shareNativeConnection) { this.shareNativeConnection = shareNativeConnection; } /** * Returns the index of the database. * * @return the database index. */ public int getDatabase() { return RedisConfiguration.getDatabaseOrElse(configuration, standaloneConfig::getDatabase); } /** * Sets the index of the database used by this connection factory. Default is 0. * * @param index database index */ public void setDatabase(int index) { Assert.isTrue(index >= 0, "invalid DB index (a positive index required)"); if (RedisConfiguration.isDatabaseIndexAware(configuration)) { ((WithDatabaseIndex) configuration).setDatabase(index); return; } standaloneConfig.setDatabase(index); } /** * Returns the client name. * * @return the client name or {@literal null} if not set. * @since 2.1 */ @Nullable public String getClientName() { return clientConfiguration.getClientName().orElse(null); } /** * Sets the client name used by this connection factory. * * @param clientName the client name. Can be {@literal null}. * @since 2.1 * @deprecated configure the client name using {@link LettuceClientConfiguration}. * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable. */ @Deprecated public void setClientName(@Nullable String clientName) { this.getMutableConfiguration().setClientName(clientName); } /** * Returns the password used for authenticating with the Redis server. * * @return password for authentication or {@literal null} if not set. */ @Nullable public String getPassword() { return getRedisPassword().map(String::new).orElse(null); } private RedisPassword getRedisPassword() { return RedisConfiguration.getPasswordOrElse(configuration, standaloneConfig::getPassword); } /** * Sets the password used for authenticating with the Redis server. * * @param password the password to set * @deprecated since 2.0, configure the password using {@link RedisStandaloneConfiguration}, * {@link RedisSentinelConfiguration} or {@link RedisClusterConfiguration}. */ @Deprecated public void setPassword(String password) { if (RedisConfiguration.isPasswordAware(configuration)) { ((WithPassword) configuration).setPassword(password); return; } standaloneConfig.setPassword(RedisPassword.of(password)); } /** * Returns the shutdown timeout for shutting down the RedisClient (in milliseconds). * * @return shutdown timeout. * @since 1.6 */ public long getShutdownTimeout() { return clientConfiguration.getShutdownTimeout().toMillis(); } /** * Sets the shutdown timeout for shutting down the RedisClient (in milliseconds). * * @param shutdownTimeout the shutdown timeout. * @since 1.6 * @deprecated since 2.0, configure the shutdown timeout using {@link LettuceClientConfiguration}. * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable. */ @Deprecated public void setShutdownTimeout(long shutdownTimeout) { getMutableConfiguration().setShutdownTimeout(Duration.ofMillis(shutdownTimeout)); } /** * Get the {@link ClientResources} to reuse infrastructure. * * @return {@literal null} if not set. * @since 1.7 */ public ClientResources getClientResources() { return clientConfiguration.getClientResources().orElse(null); } /** * Sets the {@link ClientResources} to reuse the client infrastructure.
* Set to {@literal null} to not share resources. * * @param clientResources can be {@literal null}. * @since 1.7 * @deprecated since 2.0, configure {@link ClientResources} using {@link LettuceClientConfiguration}. * @throws IllegalStateException if {@link LettuceClientConfiguration} is immutable. */ @Deprecated public void setClientResources(ClientResources clientResources) { getMutableConfiguration().setClientResources(clientResources); } /** * @return the {@link LettuceClientConfiguration}. * @since 2.0 */ public LettuceClientConfiguration getClientConfiguration() { return clientConfiguration; } /** * @return the {@link RedisStandaloneConfiguration}. * @since 2.0 */ public RedisStandaloneConfiguration getStandaloneConfiguration() { return standaloneConfig; } /** * @return the {@link RedisSocketConfiguration} or {@literal null} if not set. * @since 2.1 */ @Nullable public RedisSocketConfiguration getSocketConfiguration() { return isDomainSocketAware() ? (RedisSocketConfiguration) configuration : null; } /** * @return the {@link RedisStandaloneConfiguration}, may be {@literal null}. * @since 2.0 */ @Nullable public RedisSentinelConfiguration getSentinelConfiguration() { return isRedisSentinelAware() ? (RedisSentinelConfiguration) configuration : null; } /** * @return the {@link RedisClusterConfiguration}, may be {@literal null}. * @since 2.0 */ @Nullable public RedisClusterConfiguration getClusterConfiguration() { return isClusterAware() ? (RedisClusterConfiguration) configuration : null; } /** * Specifies if pipelined results should be converted to the expected data type. If false, results of * {@link LettuceConnection#closePipeline()} and {LettuceConnection#exec()} will be of the type returned by the * Lettuce driver. * * @return Whether or not to convert pipeline and tx results. */ public boolean getConvertPipelineAndTxResults() { return convertPipelineAndTxResults; } /** * Specifies if pipelined and transaction results should be converted to the expected data type. If false, results of * {@link LettuceConnection#closePipeline()} and {LettuceConnection#exec()} will be of the type returned by the * Lettuce driver. * * @param convertPipelineAndTxResults Whether or not to convert pipeline and tx results. */ public void setConvertPipelineAndTxResults(boolean convertPipelineAndTxResults) { this.convertPipelineAndTxResults = convertPipelineAndTxResults; } /** * @return true when {@link RedisStaticMasterReplicaConfiguration} is present. * @since 2.1 */ private boolean isStaticMasterReplicaAware() { return RedisConfiguration.isStaticMasterReplicaConfiguration(configuration); } /** * @return true when {@link RedisSentinelConfiguration} is present. * @since 1.5 */ public boolean isRedisSentinelAware() { return RedisConfiguration.isSentinelConfiguration(configuration); } /** * @return true when {@link RedisSocketConfiguration} is present. * @since 2.1 */ private boolean isDomainSocketAware() { return RedisConfiguration.isDomainSocketConfiguration(configuration); } /** * @return true when {@link RedisClusterConfiguration} is present. * @since 1.7 */ public boolean isClusterAware() { return RedisConfiguration.isClusterConfiguration(configuration); } /** * @return the shared connection using {@literal byte} array encoding for imperative API use. {@literal null} if * {@link #getShareNativeConnection() connection sharing} is disabled. */ @Nullable protected StatefulRedisConnection getSharedConnection() { return shareNativeConnection ? (StatefulRedisConnection) getOrCreateSharedConnection().getConnection() : null; } /** * @return the shared connection using {@link ByteBuffer} encoding for reactive API use. {@literal null} if * {@link #getShareNativeConnection() connection sharing} is disabled. * @since 2.0.1 */ @Nullable protected StatefulConnection getSharedReactiveConnection() { return shareNativeConnection ? getOrCreateSharedReactiveConnection().getConnection() : null; } private LettuceConnectionProvider createConnectionProvider(AbstractRedisClient client, RedisCodec codec) { LettuceConnectionProvider connectionProvider = doCreateConnectionProvider(client, codec); if (this.clientConfiguration instanceof LettucePoolingClientConfiguration) { return new LettucePoolingConnectionProvider(connectionProvider, (LettucePoolingClientConfiguration) this.clientConfiguration); } return connectionProvider; } /** * Create a {@link LettuceConnectionProvider} given {@link AbstractRedisClient} and {@link RedisCodec}. Configuration * of this connection factory specifies the type of the created connection provider. This method creates either a * {@link LettuceConnectionProvider} for either {@link RedisClient} or {@link RedisClusterClient}. Subclasses may * override this method to decorate the connection provider. * * @param client either {@link RedisClient} or {@link RedisClusterClient}, must not be {@literal null}. * @param codec used for connection creation, must not be {@literal null}. By default, a {@code byte[]} codec. * Reactive connections require a {@link java.nio.ByteBuffer} codec. * @return the connection provider. * @since 2.1 */ protected LettuceConnectionProvider doCreateConnectionProvider(AbstractRedisClient client, RedisCodec codec) { ReadFrom readFrom = getClientConfiguration().getReadFrom().orElse(null); if (isStaticMasterReplicaAware()) { List nodes = ((RedisStaticMasterReplicaConfiguration) configuration).getNodes().stream() // .map(it -> createRedisURIAndApplySettings(it.getHostName(), it.getPort())) // .peek(it -> it.setDatabase(getDatabase())) // .collect(Collectors.toList()); return new StaticMasterReplicaConnectionProvider((RedisClient) client, codec, nodes, readFrom); } if (isClusterAware()) { return new ClusterConnectionProvider((RedisClusterClient) client, codec, readFrom); } return new StandaloneConnectionProvider((RedisClient) client, codec, readFrom); } protected AbstractRedisClient createClient() { if (isStaticMasterReplicaAware()) { RedisClient redisClient = clientConfiguration.getClientResources() // .map(RedisClient::create) // .orElseGet(RedisClient::create); clientConfiguration.getClientOptions().ifPresent(redisClient::setOptions); return redisClient; } if (isRedisSentinelAware()) { RedisURI redisURI = getSentinelRedisURI(); RedisClient redisClient = clientConfiguration.getClientResources() // .map(clientResources -> RedisClient.create(clientResources, redisURI)) // .orElseGet(() -> RedisClient.create(redisURI)); clientConfiguration.getClientOptions().ifPresent(redisClient::setOptions); return redisClient; } if (isClusterAware()) { List initialUris = new ArrayList<>(); for (RedisNode node : ((ClusterConfiguration) configuration).getClusterNodes()) { initialUris.add(createRedisURIAndApplySettings(node.getHost(), node.getPort())); } RedisClusterClient clusterClient = clientConfiguration.getClientResources() // .map(clientResources -> RedisClusterClient.create(clientResources, initialUris)) // .orElseGet(() -> RedisClusterClient.create(initialUris)); clientConfiguration.getClientOptions() // .filter(clientOptions -> clientOptions instanceof ClusterClientOptions) // .ifPresent(clientOptions -> clusterClient.setOptions((ClusterClientOptions) clientOptions)); return clusterClient; } RedisURI uri = isDomainSocketAware() ? createRedisSocketURIAndApplySettings(((DomainSocketConfiguration) configuration).getSocket()) : createRedisURIAndApplySettings(getHostName(), getPort()); RedisClient redisClient = clientConfiguration.getClientResources() // .map(clientResources -> RedisClient.create(clientResources, uri)) // .orElseGet(() -> RedisClient.create(uri)); clientConfiguration.getClientOptions().ifPresent(redisClient::setOptions); return redisClient; } private RedisURI getSentinelRedisURI() { RedisURI redisUri = LettuceConverters .sentinelConfigurationToRedisURI((org.springframework.data.redis.connection.RedisSentinelConfiguration) configuration); getRedisPassword().toOptional().ifPresent(redisUri::setPassword); clientConfiguration.getClientName().ifPresent(redisUri::setClientName); redisUri.setTimeout(clientConfiguration.getCommandTimeout()); redisUri.setDatabase(getDatabase()); return redisUri; } private RedisURI createRedisURIAndApplySettings(String host, int port) { RedisURI.Builder builder = RedisURI.Builder.redis(host, port); getRedisPassword().toOptional().ifPresent(builder::withPassword); clientConfiguration.getClientName().ifPresent(builder::withClientName); builder.withSsl(clientConfiguration.isUseSsl()); builder.withVerifyPeer(clientConfiguration.isVerifyPeer()); builder.withStartTls(clientConfiguration.isStartTls()); builder.withTimeout(clientConfiguration.getCommandTimeout()); return builder.build(); } private RedisURI createRedisSocketURIAndApplySettings(String socketPath) { RedisURI.Builder builder = RedisURI.Builder.socket(socketPath); getRedisPassword().toOptional().ifPresent(builder::withPassword); builder.withTimeout(clientConfiguration.getCommandTimeout()); return builder.build(); } @Override public RedisSentinelConnection getSentinelConnection() { return new LettuceSentinelConnection(connectionProvider); } private MutableLettuceClientConfiguration getMutableConfiguration() { Assert.state(clientConfiguration instanceof MutableLettuceClientConfiguration, () -> String.format("Client configuration must be instance of MutableLettuceClientConfiguration but is %s", ClassUtils.getShortName(clientConfiguration.getClass()))); return (MutableLettuceClientConfiguration) clientConfiguration; } private long getClientTimeout() { return clientConfiguration.getCommandTimeout().toMillis(); } /** * Wrapper for shared connections. Keeps track of the connection lifecycleThe wrapper is thread-safe as it * synchronizes concurrent calls by blocking. * * @param connection encoding. * @author Mark Paluch * @author Christoph Strobl * @since 2.1 */ @RequiredArgsConstructor class SharedConnection { private final LettuceConnectionProvider connectionProvider; private final boolean shareNativeClusterConnection; /** Synchronization monitor for the shared Connection */ private final Object connectionMonitor = new Object(); private @Nullable StatefulConnection connection; /** * Returns a valid Lettuce connection. Initializes and validates the connection if * {@link #setValidateConnection(boolean) enabled}. * * @return the connection. */ @Nullable StatefulConnection getConnection() { synchronized (this.connectionMonitor) { if (this.connection == null) { this.connection = getNativeConnection(); } if (getValidateConnection()) { validateConnection(); } return this.connection; } } /** * Obtain a connection from the associated {@link LettuceConnectionProvider}. * * @return the connection. */ private StatefulConnection getNativeConnection() { try { StatefulConnection connection = connectionProvider.getConnection(StatefulConnection.class); if (connection instanceof StatefulRedisConnection && getDatabase() > 0) { ((StatefulRedisConnection) connection).sync().select(getDatabase()); } return connection; } catch (RedisException e) { throw new RedisConnectionFailureException("Unable to connect to Redis", e); } } /** * Validate the connection. Invalid connections will be closed and the connection state will be reset. */ void validateConnection() { synchronized (this.connectionMonitor) { boolean valid = false; if (connection != null && connection.isOpen()) { try { if (connection instanceof StatefulRedisConnection) { ((StatefulRedisConnection) connection).sync().ping(); } if (connection instanceof StatefulRedisClusterConnection) { ((StatefulRedisConnection) connection).sync().ping(); } valid = true; } catch (Exception e) { log.debug("Validation failed", e); } } if (!valid) { if (connection != null) { connectionProvider.release(connection); } log.warn("Validation of shared connection failed. Creating a new connection."); resetConnection(); this.connection = getNativeConnection(); } } } /** * Reset the underlying shared Connection, to be reinitialized on next access. */ void resetConnection() { synchronized (this.connectionMonitor) { if (this.connection != null) { this.connectionProvider.release(this.connection); } this.connection = null; } } } /** * Mutable implementation of {@link LettuceClientConfiguration}. * * @author Mark Paluch * @author Christoph Strobl */ static class MutableLettuceClientConfiguration implements LettuceClientConfiguration { private boolean useSsl; private boolean verifyPeer = true; private boolean startTls; private @Nullable ClientResources clientResources; private @Nullable String clientName; private Duration timeout = Duration.ofSeconds(RedisURI.DEFAULT_TIMEOUT); private Duration shutdownTimeout = Duration.ofMillis(100); /* * (non-Javadoc) * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isUseSsl() */ @Override public boolean isUseSsl() { return useSsl; } void setUseSsl(boolean useSsl) { this.useSsl = useSsl; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isVerifyPeer() */ @Override public boolean isVerifyPeer() { return verifyPeer; } void setVerifyPeer(boolean verifyPeer) { this.verifyPeer = verifyPeer; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isStartTls() */ @Override public boolean isStartTls() { return startTls; } void setStartTls(boolean startTls) { this.startTls = startTls; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getClientResources() */ @Override public Optional getClientResources() { return Optional.ofNullable(clientResources); } void setClientResources(ClientResources clientResources) { this.clientResources = clientResources; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getClientOptions() */ @Override public Optional getClientOptions() { return Optional.empty(); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getReadFrom() */ @Override public Optional getReadFrom() { return Optional.empty(); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getClientName() */ @Override public Optional getClientName() { return Optional.ofNullable(clientName); } /** * @param clientName can be {@literal null}. * @since 2.1 */ void setClientName(@Nullable String clientName) { this.clientName = clientName; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getTimeout() */ @Override public Duration getCommandTimeout() { return timeout; } void setTimeout(Duration timeout) { this.timeout = timeout; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getShutdownTimeout() */ @Override public Duration getShutdownTimeout() { return shutdownTimeout; } void setShutdownTimeout(Duration shutdownTimeout) { this.shutdownTimeout = shutdownTimeout; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy