lettuce连接池很香,撸撸它的源代码(上)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: lettuce连接池很香,撸撸它的源代码

springboot中lettuce配置

lettuce初始化

使用netty创建连接

管理连接

actuator健康检查获取连接

释放不掉的连接

共享连接

总结

微信图片_20221212111846.jpg

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类图

微信图片_20221212112010.png

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);
    //省略部分代码    
    }

管理连接


执行请求命令的时候首先要获取连接,流程图如下

微信图片_20221212112238.png

关键代码

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);
  }
}


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
22天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
5月前
|
缓存 Java 数据库连接
我要手撕mybatis源码
该文章深入分析了MyBatis框架的初始化和数据读写阶段的源码,详细阐述了MyBatis如何通过配置文件解析、建立数据库连接、映射接口绑定、动态代理、查询缓存和结果集处理等步骤实现ORM功能,以及与传统JDBC编程相比的优势。
我要手撕mybatis源码
|
8月前
|
druid 网络协议 Java
再有人问你数据库连接池的原理,这篇文章甩给他!
在 Spring Boot 项目中,数据库连接池已经成为标配,然而,我曾经遇到过不少连接池异常导致业务错误的事故。很多经验丰富的工程师也可能不小心在这方面出现问题。 在这篇文章中,我们将探讨数据库连接池,深入解析其实现机制,以便更好地理解和规避潜在的风险。
|
8月前
|
缓存 NoSQL Java
手撸的 SpringBoot缓存系统,性能杠杠的
手撸的 SpringBoot缓存系统,性能杠杠的
57 0
|
消息中间件 Java Maven
当面试官问你Spring Boot 中的监视器是什么?把这篇文章甩给他
多年来,随着新功能的增加,spring 变得越来越复杂。只需访问 https://spring.io/projects 页面,我们就会看到可以在我们的应用程序中使用的所有 Spring 项目的不同功能。如果必须启动一个新的 Spring 项目,我们必须添加构建路径或添加 Maven 依赖关系,配置应用程序服务器,添加 spring 配置。因此,开始一个新的 spring 项目需要很多努力,因为我们现在必须从头开始做所有事情。
99 0
|
Java 容器 Spring
Spring5源码 - 05 invokeBeanFactoryPostProcessors 源码解读_3细说invokeBeanDefinitionRegistryPostProcessors
Spring5源码 - 05 invokeBeanFactoryPostProcessors 源码解读_3细说invokeBeanDefinitionRegistryPostProcessors
80 0
Spring5源码 - 05 invokeBeanFactoryPostProcessors 源码解读_3细说invokeBeanDefinitionRegistryPostProcessors
|
XML 设计模式 缓存
面试必问系列之最强源码分析,带你一步步弄清楚Spring如何解决循环依赖
面试必问系列之最强源码分析,带你一步步弄清楚Spring如何解决循环依赖
29633 6
面试必问系列之最强源码分析,带你一步步弄清楚Spring如何解决循环依赖
|
SQL 缓存 前端开发
撸了一套基于SpringBoot的最小管理系统,小白也能快速学会【附教程+源码】,视频以后有时间就录!
撸了一套基于SpringBoot的最小管理系统,小白也能快速学会【附教程+源码】,视频以后有时间就录!
167 0
|
消息中间件 缓存 安全
读懂HikariCP一百行代码,多线程就是个孙子!
读懂HikariCP一百行代码,多线程就是个孙子!
|
监控 Java uml
lettuce连接池很香,撸撸它的源代码(下)
lettuce连接池很香,撸撸它的源代码
449 0
lettuce连接池很香,撸撸它的源代码(下)