springboot中lettuce配置
lettuce初始化
使用netty创建连接
管理连接
actuator健康检查获取连接
释放不掉的连接
共享连接
总结
Lettuce是一个高性能的redis客户端,底层基于netty框架来管理连接,天然是非阻塞和线程安全的。比起jedis需要为每个实例创建物理连接来保证线程安全,lettuce确实很优秀。本文主要介绍springboot使用lettuce整合redis客户端。说明一下,本文的源代码是使用springboot2.1.6,对应lettuce版本是5.1.7.RELEASE。
springboot中lettuce配置
springboot中配置lettuce是非常容易的,代码如下:
pom.xml文件
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.0</version> </dependency>
application.properties配置
spring.redis.database=0 spring.redis.host=192.168.59.138 spring.redis.password= spring.redis.port=6379 spring.redis.timeout=5000 #最大连接数 spring.redis.lettuce.pool.max-active=50 #最大阻塞等待时间 spring.redis.lettuce.pool.max-wait=5000 #连接池中最大空闲连接 spring.redis.lettuce.pool.max-idle=50 #连接池中最小空闲连接 spring.redis.lettuce.pool.min-idle=5 #eviction线程调度时间间隔 spring.redis.lettuce.pool.time-between-eviction-runs=1
redis配置类RedisConfig.java
@Configuration public class RedisConfig { @Bean RedisTemplate redisTemplate(LettuceConnectionFactory factory){ factory.setShareNativeConnection(false); RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(factory); return redisTemplate; } }
上面3步就能完成springboot使用lettuce连接池整合redis的配置,之后我们就可以在业务类中注入RedisTemplate来使用了。
lettuce初始化
我们看一下整个初始化流程相关类的UML类图
LettuceConnectionConfiguration类是lettuce初始化的起始类,这个类是spring的管理的配置类,它初始化了lettuce连接工厂类,见如下代码
@Bean @ConditionalOnMissingBean(RedisConnectionFactory.class) public LettuceConnectionFactory redisConnectionFactory(ClientResources clientResources) throws UnknownHostException { LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(clientResources, this.properties.getLettuce().getPool()); return createLettuceConnectionFactory(clientConfig); }
初始化的过程会判断是单点模式/集群模式/哨兵模式,来初始化连接工厂,本文以单点模式为例来讲解
private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) { if (getSentinelConfig() != null) { return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration); } if (getClusterConfiguration() != null) { return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration); } return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration); }
获取到工厂类以后,lettuce会用如下2个Provider来获取和释放连接,分别管理普通模式和交互模式的连接。本示例采用单机的redis模式,所以初始化后的Provider是StandaloneConnectionProvider。
private @Nullable LettuceConnectionProvider connectionProvider; private @Nullable LettuceConnectionProvider reactiveConnectionProvider; public void afterPropertiesSet() { this.client = createClient(); this.connectionProvider = createConnectionProvider(client, LettuceConnection.CODEC); this.reactiveConnectionProvider = createConnectionProvider(client, LettuceReactiveRedisConnection.CODEC); //省略部分代码 }
注意:上面创建的provider类型是LettucePoolingConnectionProvider,它是StandaloneConnectionProvider的装饰器类,每次获取和释放连接,工厂类都会通过LettucePoolingConnectionProvider类调用LettucePoolingConnectionProvider的获取和释放操作
使用netty创建连接
lettuce的连接是靠netty来管理的,这或许是它性能优秀的重要原因。我们看一下通过netty来创建连接的代码,看一下StandaloneConnectionProvider的下面方法:
public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> connectionType) { //省略部分代码 if (StatefulConnection.class.isAssignableFrom(connectionType)) { return connectionType.cast(readFrom.map(it -> this.masterReplicaConnection(redisURISupplier.get(), it)) .orElseGet(() -> client.connect(codec))); } throw new UnsupportedOperationException("Connection type " + connectionType + " not supported!"); }
上面的client.connect(codec)是创建连接的代码,一直跟踪这个方法
private void initializeChannelAsync0(ConnectionBuilder connectionBuilder, CompletableFuture<Channel> channelReadyFuture, SocketAddress redisAddress) { logger.debug("Connecting to Redis at {}", redisAddress); Bootstrap redisBootstrap = connectionBuilder.bootstrap(); RedisChannelInitializer initializer = connectionBuilder.build(); redisBootstrap.handler(initializer); clientResources.nettyCustomizer().afterBootstrapInitialized(redisBootstrap); CompletableFuture<Boolean> initFuture = initializer.channelInitialized(); ChannelFuture connectFuture = redisBootstrap.connect(redisAddress); //省略部分代码 }
管理连接
执行请求命令的时候首先要获取连接,流程图如下
关键代码
LettucePoolingConnectionProvider中getConnection
public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> 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 connectionType.cast(connection); } catch (Exception e) { throw new PoolException("Could not get a resource from the pool", e); } }