DATAREDIS-667 - Align Lettuce connection-pooling with LettuceClientConfiguration.

We now support Lettuce connection pooling by configuring a specialized client configuration via LettucePoolingClientConfiguration.builder(). Pooling settings can be configured through the builder and used with LettuceConnectionFactory.

Connection pooling is supported with Standalone and Sentinel-managed connections.

LettucePoolingClientConfiguration.builder()
   .poolConfig(poolConfig)
   .and() // allows configuration of further settings.

Original Pull Request: #262
This commit is contained in:
Mark Paluch
2017-08-03 16:11:32 +02:00
committed by Christoph Strobl
parent 5ac7f8eb19
commit d731dfcf81
17 changed files with 586 additions and 78 deletions

View File

@@ -57,6 +57,14 @@ class DefaultLettuceClientConfiguration implements LettuceClientConfiguration {
return useSsl;
}
/* (non-Javadoc)
* @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isUsePooling()
*/
@Override
public boolean isUsePooling() {
return false;
}
/* (non-Javadoc)
* @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isVerifyPeer()
*/

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2017 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.ClientOptions;
import io.lettuce.core.resource.ClientResources;
import java.time.Duration;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
/**
* Default implementation of {@literal LettucePoolingClientConfiguration}.
*
* @author Mark Paluch
* @since 2.0
*/
class DefaultLettucePoolingClientConfiguration extends DefaultLettuceClientConfiguration
implements LettucePoolingClientConfiguration {
private final GenericObjectPoolConfig poolConfig;
DefaultLettucePoolingClientConfiguration(boolean useSsl, boolean verifyPeer, boolean startTls,
ClientResources clientResources, ClientOptions clientOptions, Duration timeout, Duration shutdownTimeout,
GenericObjectPoolConfig poolConfig) {
super(useSsl, verifyPeer, startTls, clientResources, clientOptions, timeout, shutdownTimeout);
this.poolConfig = poolConfig;
}
/* (non-Javadoc)
* @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isUsePooling()
*/
@Override
public boolean isUsePooling() {
return true;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration#getPoolConfig()
*/
@Override
public GenericObjectPoolConfig getPoolConfig() {
return poolConfig;
}
}

View File

@@ -54,6 +54,11 @@ public interface LettuceClientConfiguration {
*/
boolean isUseSsl();
/**
* @return {@literal true} to use connection-pooling.
*/
boolean isUsePooling();
/**
* @return {@literal true} to verify peers when using {@link #isUseSsl() SSL}.
*/
@@ -217,15 +222,15 @@ public interface LettuceClientConfiguration {
class DefaultLettuceClientConfigurationBuilder
implements LettuceClientConfigurationBuilder, LettuceSslClientConfigurationBuilder {
private boolean useSsl;
private boolean verifyPeer = true;
private boolean startTls;
private ClientResources clientResources;
private ClientOptions clientOptions;
private Duration timeout = Duration.ofSeconds(RedisURI.DEFAULT_TIMEOUT);
private Duration shutdownTimeout = Duration.ofMillis(100);
boolean useSsl;
boolean verifyPeer = true;
boolean startTls;
ClientResources clientResources;
ClientOptions clientOptions;
Duration timeout = Duration.ofSeconds(RedisURI.DEFAULT_TIMEOUT);
Duration shutdownTimeout = Duration.ofMillis(100);
private DefaultLettuceClientConfigurationBuilder() {}
DefaultLettuceClientConfigurationBuilder() {}
/*
* (non-Javadoc)

View File

@@ -38,7 +38,6 @@ 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.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.redis.ExceptionTranslationStrategy;
import org.springframework.data.redis.PassThroughExceptionTranslationStrategy;
import org.springframework.data.redis.RedisConnectionFailureException;
@@ -281,8 +280,8 @@ public class LettuceConnectionFactory
}
LettuceConnection connection;
if (pool != null) {
if (pool != null) {
connection = new LettuceConnection(getSharedConnection(), getTimeout(), null, pool, getDatabase());
} else {
connection = new LettuceConnection(getSharedConnection(), connectionProvider, getTimeout(), getDatabase());
@@ -779,6 +778,16 @@ public class LettuceConnectionFactory
private LettuceConnectionProvider createConnectionProvider(AbstractRedisClient client, RedisCodec<?, ?> codec) {
LettuceConnectionProvider connectionProvider = doConnectionProvider(client, codec);
if (this.clientConfiguration.isUsePooling()) {
return new LettucePoolingConnectionProvider(connectionProvider, this.clientConfiguration);
}
return connectionProvider;
}
private LettuceConnectionProvider doConnectionProvider(AbstractRedisClient client, RedisCodec<?, ?> codec) {
if (isClusterAware()) {
return new ClusterConnectionProvider((RedisClusterClient) client, codec);
}
@@ -852,11 +861,6 @@ public class LettuceConnectionFactory
@Override
public RedisSentinelConnection getSentinelConnection() {
if (!(connectionProvider instanceof StandaloneConnectionProvider)) {
throw new InvalidDataAccessResourceUsageException(
"Unable to connect to sentinels using " + connectionProvider.getClass());
}
return new LettuceSentinelConnection(connectionProvider);
}
@@ -895,6 +899,14 @@ public class LettuceConnectionFactory
return useSsl;
}
/* (non-Javadoc)
* @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#isUsePooling()
*/
@Override
public boolean isUsePooling() {
return false;
}
public void setUseSsl(boolean useSsl) {
this.useSsl = useSsl;
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2017 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 org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.util.Assert;
/**
* @author Mark Paluch
*/
public interface LettucePoolingClientConfiguration extends LettuceClientConfiguration {
/**
* Creates a new {@link LettucePoolingClientConfigurationBuilder} to build {@link LettucePoolingClientConfiguration}
* to be used with the jedis client.
*
* @return a new {@link LettucePoolingClientConfigurationBuilder} to build {@link LettucePoolingClientConfiguration}.
*/
static LettucePoolingClientConfigurationBuilder builder() {
return new DefaultLettucePoolingClientConfigurationBuilder();
}
/**
* Creates a default {@link LettucePoolingClientConfiguration} with:
* <dl>
* <dt>SSL</dt>
* <dd>no</dd>
* <dt>Pooling</dt>
* <dd>yes</dd>
* <dt>Peer Verification</dt>
* <dd>yes</dd>
* <dt>Start TLS</dt>
* <dd>no</dd>
* <dt>Client Options</dt>
* <dd>none</dd>
* <dt>Client Resources</dt>
* <dd>none</dd>
* <dt>Connect Timeout</dt>
* <dd>60 Seconds</dd>
* <dt>Shutdown Timeout</dt>
* <dd>2 Seconds</dd>
* </dl>
*
* @return a {@link LettucePoolingClientConfiguration} with defaults.
*/
static LettucePoolingClientConfiguration defaultConfiguration() {
return builder().build();
}
/**
* @return the optional {@link GenericObjectPoolConfig}.
*/
GenericObjectPoolConfig getPoolConfig();
/**
* Builder for Pooling-related {@link LettuceClientConfiguration}.
*/
interface LettucePoolingClientConfigurationBuilder {
/**
* @param poolConfig must not be {@literal null}.
* @return {@literal this} builder.
* @throws IllegalArgumentException if poolConfig is {@literal null}.
*/
LettucePoolingClientConfigurationBuilder poolConfig(GenericObjectPoolConfig poolConfig);
/**
* Return to {@link LettuceClientConfigurationBuilder}.
*
* @return {@link LettuceClientConfigurationBuilder}.
*/
LettuceClientConfigurationBuilder and();
/**
* Build the {@link JedisClientConfiguration} with the configuration applied from this builder.
*
* @return a new {@link JedisClientConfiguration} object.
*/
LettucePoolingClientConfiguration build();
}
/**
* Default {@link LettuceClientConfigurationBuilder} implementation to build an immutable
* {@link LettuceClientConfiguration}.
*/
class DefaultLettucePoolingClientConfigurationBuilder extends DefaultLettuceClientConfigurationBuilder
implements LettucePoolingClientConfigurationBuilder {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
@Override
public LettucePoolingClientConfigurationBuilder poolConfig(GenericObjectPoolConfig poolConfig) {
Assert.notNull(poolConfig, "GenericObjectPoolConfig must not be null!");
this.poolConfig = poolConfig;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder#build()
*/
@Override
public LettucePoolingClientConfiguration build() {
return new DefaultLettucePoolingClientConfiguration(useSsl, verifyPeer, startTls, clientResources, clientOptions,
timeout, shutdownTimeout, poolConfig);
}
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2017 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.api.StatefulConnection;
import io.lettuce.core.support.ConnectionPoolSupport;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.data.redis.connection.PoolException;
/**
* {@link LettuceConnectionProvider} with connection pooling support. This connection provider holds multiple pools (one
* per connection type) for contextualized connection allocation.
* <p />
* Each allocated connection is tracked and to be returned into the pool which created the connection. Instances of this
* class require {@link #destroy() disposal} to de-allocate lingering connections that were not returned to the pool and
* to close the pools.
*
* @author Mark Paluch
* @since 2.0
* @see #getConnection(Class)
*/
class LettucePoolingConnectionProvider implements LettuceConnectionProvider, DisposableBean {
private final static Log log = LogFactory.getLog(LettucePoolingConnectionProvider.class);
private final LettuceConnectionProvider connectionProvider;
private final GenericObjectPoolConfig poolConfig;
private final Map<StatefulConnection<?, ?>, GenericObjectPool<StatefulConnection<?, ?>>> poolRef = new ConcurrentHashMap<>(
32);
private final Map<Class<?>, GenericObjectPool<StatefulConnection<?, ?>>> pools = new ConcurrentHashMap<>(32);
LettucePoolingConnectionProvider(LettuceConnectionProvider connectionProvider,
LettuceClientConfiguration clientConfiguration) {
this.connectionProvider = connectionProvider;
this.poolConfig = ((LettucePoolingClientConfiguration) clientConfiguration).getPoolConfig();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#getConnection(java.lang.Class)
*/
@Override
@SuppressWarnings("rawtypes")
public StatefulConnection<?, ?> getConnection(Class<? extends StatefulConnection> connectionType) {
GenericObjectPool<StatefulConnection<?, ?>> pool = pools.computeIfAbsent(connectionType, poolType -> {
return ConnectionPoolSupport.createGenericObjectPool(() -> connectionProvider.getConnection(connectionType),
poolConfig, false);
});
try {
StatefulConnection<?, ?> connection = pool.borrowObject();
poolRef.put(connection, pool);
return connection;
} catch (Exception e) {
throw new PoolException("Could not get a resource from the pool", e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#release(io.lettuce.core.api.StatefulConnection)
*/
@Override
public void release(StatefulConnection<?, ?> connection) {
GenericObjectPool<StatefulConnection<?, ?>> pool = poolRef.remove(connection);
if (pool == null) {
throw new PoolException("Returned connection " + connection
+ " was either previously returned or does not belong to this connection provider");
}
pool.returnObject(connection);
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
@Override
public void destroy() throws Exception {
if (!poolRef.isEmpty()) {
log.warn("LettucePoolingConnectionProvider contains unreleased connections");
poolRef.forEach((connection, pool) -> pool.returnObject(connection));
poolRef.clear();
}
pools.forEach((type, pool) -> pool.close());
pools.clear();
}
}