Lettuce客户端结构学习

概要

工作中涉及到需要了解一下Lettuce的工作流程,然后还要封装一下,涉及到Lettuce的连接实现,主要结构等等。

目前Spring2.x已经集成了Lettuce作为连接redis的默认客户端,此前是jedis。这两者最大的不同就是Lettuce是线程安全的,可以用一个TCP连接实现类似管道模式的命令执行。

结构图

在这里插入图片描述
2. Lettuce 6.3.2结构
在这里插入图片描述

Lettuce主要结构

lettuce支持同步阻塞、异步、reactive三种模式。
lettuce的几个核心类,包含了lettuce主要的功能逻辑,主要为:RedisURIRedisClientStatefulRedisConnectionImplRedisCommands

// 创建RedisClient实例,指向IP为localhost的redis服务,端口默认为6379
RedisClient client = RedisClient.create("redis://localhost");          

// 基于上面的客户端,打开一个单节点的连接
StatefulRedisConnection<String, String> connection = client.connect(); 

// 获取同步执行的命令API
RedisCommands<String, String> commands = connection.sync();            

// 执行GET命令
String value = commands.get("foo");                                    

...
// 关闭连接,这个操作一般在应用销毁的时候,lettuce设计的连接是长连接
connection.close();
                                                    
//关闭客户端实例,释放线程和其他资源,这个操作一般在应用销毁的时候
client.shutdown();        

Redus URI

主要涉及一些参数配置, 这里可以包装一层configFactory,配置的参数也很重要,比如连接池、socket相关、拓扑刷新等等。

redis.mode=standalone
.redis.host=127.0.0.1
# .redis.password=yourpassword
.redis.port=6379
.redis.timeout=500

# .redis.cluster-nodes=
# .redis.max-redirects=3
# .redis.topology-refresh-period=20


.redis.use-connection-pool=true
.redis.pool-max-active=16
.redis.pool-max-idle=16
.redis.pool-min-idle=3
.redis.pool-max-wait=100
.redis.is-block-when-exhausted=true
.redis.is-test-while-idle=true
.redis.pool-time-between-eviction-runs-millis=30000
.redis.pool-min-evictable-idle-time-millis=60000


.redis.is-auto-reconnect=true
.redis.socket-timeout=1000
.redis.socket-keep-alive-idle=30
.redis.socket-keep-alive-interval=10
.redis.socket-keep-alive-count=3
.redis.tcp-user-timeout=30
.redis.request-queue-size=50000

RedisClient

在创建实例的同时会初始化基于netty的底层基础通信设施,比如netty的EventLoopGroup。RedisClient持有了一个ClientResources实例的引用,RedisClient在创建的实例的时候会调用父类AbstractRedisClient的初始化方法创建ClientResources的实现类DefaultClientResources的实例对象,而netty的初始化操作正是在DefaultClientResources的构造方法中实现的。

ClientResources

ClientResourcesRedisClient 提供了共享的基础设施和资源,这些资源包括 Netty 的事件循环、线程池、DNS 解析器、计时器等。ClientResources 的作用是为 RedisClient 提供一个高效、可配置的运行环境。
ClientResources是可共享的。

protected AbstractRedisClient(ClientResources clientResources) {

      if (clientResources == null) {
          sharedResources = false;
          // 创建ClientResources的实例对象
          this.clientResources = DefaultClientResources.create();
      } else {
          sharedResources = true;
          // 使用外部的ClientResources实例对象
          this.clientResources = clientResources;
      }

      genericWorkerPool = this.clientResources.eventExecutorGroup();
      channels = new DefaultChannelGroup(genericWorkerPool.next());
      timer = (HashedWheelTimer) this.clientResources.timer();
  }

StatefulRedisConnectionImpl

RedisClient.connect --> RedisClient.connectStandaloneAsync
【new DefaultEndpoint()[创建一个closeFuture,CompletableFuture对象] 
    --> RedisClient.newStatefulRedisConnection
        【(StatefulRedisConnectionImpl继承RedisChannelHandler--> StatefulRedisConnectionImpl初始化方法中,初始化四个组件
        this.codec = codec;
        this.async = newRedisAsyncCommandsImpl();
        this.sync = newRedisSyncCommandsImpl(); // 代理对象
        this.reactive = newRedisReactiveCommandsImpl();--> 新建ConnectionFuture connectStatefulAsync
     【初始化CommandHandler(继承于ChannelDuplexHandler,属于netty类) 
      --> RedisClient.getConnectionBuilder构建新的ConnectionBuilder 
      --> AbstractRedisClient.connectionBuilder(构建netty bootstrap) 
      --> connectionBuilder.connection将StatefulRedisConnectionImpl设置到ConnectionBuilder的connection属性 
      --> AbstractRedisClient.initializeChannelAsync「创建netty的channel,新建socketAddressFuture和channelReadyFuture,都是CompletableFuture类型」
      --> 创建future(DefaultConnectionFuture类型,异步新建StatefulRedisConnectionImpl对象)
      】
    --> 返回future(DefaultConnectionFuture类型)
】 

--> AbstractRedisClient.getConnection【调用connectionFuture.get()获取StatefulRedisConnectionImpl对象连接对象】

RedisCommands

命令执行核心流程源码(同步)connection.sync()这个方法返回的是StatefulRedisConnectionImpl对象的sync属性值,StatefulRedisConnectionImpl会在初始化的时候设值,方法如下:

    /**
     * Initialize a new connection.
     *
     * @param writer the channel writer
     * @param codec Codec used to encode/decode keys and values.
     * @param timeout Maximum time to wait for a response.
     */
    public StatefulRedisConnectionImpl(RedisChannelWriter writer, RedisCodec<K, V> codec, Duration timeout) {

        super(writer, timeout);

        this.codec = codec;
        this.async = newRedisAsyncCommandsImpl();
        this.sync = newRedisSyncCommandsImpl();
        this.reactive = newRedisReactiveCommandsImpl();
    }

newRedisSyncCommandsImpl方法返回的就是一个代理对象【JDK动态代理】,源码如下:

    /**
     * Create a new instance of {@link RedisCommands}. Can be overriden to extend.
     *
     * @return a new instance.
     */
    protected RedisCommands<K, V> newRedisSyncCommandsImpl() {
        return syncHandler(async(), RedisCommands.class, RedisClusterCommands.class);
    }
syncHandler源码:

protected <T> T syncHandler(Object asyncApi, Class<?>...interfaces) {
        FutureSyncInvocationHandler h = newFutureSyncInvocationHandler((StatefulConnection<?, ?>) this, asyncApi, interfaces);
        return (T) Proxy.newProxyInstance(AbstractRedisClient.class.getClassLoader(), interfaces, h);
    }

调用connection.sync()方法时,实际上返回的是StatefulRedisConnectionImpl对象的sync属性值,即前面创建的代理对象。这个代理对象会拦截所有的方法调用,并通过FutureSyncInvocationHandler将其转换为异步调用,等待结果返回
调用get流程图
在这里插入图片描述

命令执行流程图

在这里插入图片描述
Lettuce读写命令流程

  1. 通过RedisClient初始化连接(netty参数,重试机制等等):
    通过RedisClient.connect获取到连接StatefulRedisConnectionImpl
    在创建连接StatefulRedisConnectionImpl过程中,会初始化BootStrap、Channel和DefaultEndpoint等重要成员。StatefulRedisConnectionImpl绑定了一个Channel,Channel也绑定了IO线程(Bootstrap#connect)。

  2. 通过StatefulRedisConnectionImpl获取同步/异步/响应式连接,再执行Redis命令。
    命令首先被封装成AsyncCommand,然后通过DefaultEndpoint发送命令,DefaultEndpoint内部会依靠Netty IO线程使用Channel异步发送命令,此时用户线程已经能获取到一个AsyncCommand,只是没有响应的数据。
    命令的响应首先被Netty IO线程接收到,从绑定的对应channel的CommandHandler里面取出对应的AsyncCommand,解析且封装响应到output,此时之前用户线程就能从AsyncCommand拿到响应了。

连接正常时的有序性
lettuce是通过DefaultEndpoint缓存本地命令,CommandHandler与redis服务器进行读写命令。每次在DefaultEndpoint会用sharedLock保证命令有序性,防止有重连或者未flush的情况;每次CommandHandler向服务端发送一条命令,就会往stack底部压入一条命令,TCP是有序的,Redis是单线程处理命令,所以从服务端响应过来了结果,能够对应上之前往stack压入的命令,这时CommandHandler只需要pop出来顶端的命令,即可对应上。
参考 Lettuce是如何发送Command命令到redis的 https://blog.csdn.net/weixin_45145848/article/details/103710855
https://developer.aliyun.com/article/1502748

小结

整理的内容有些杂乱,后面有时间就再梳理一下吧。

参考文档

Redis客户端Lettuce深度分析介绍 https://developer.aliyun.com/article/1502748
lettuce文档 https://lettuce.io/core/release/reference/
Lettuce源码分析 https://segmentfault.com/a/1190000041391240
Spring-Redis-验证lettuce共享和独占连接特性 https://blog.csdn.net/chl87783255/article/details/103293273
Lettuce 共享连接实现及command发送流程 https://blog.csdn.net/singgel/article/details/105552962

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值