jedis pipeline byte_Jedis源码浅析

Jedis是Redis官方推荐的Java连接工具。

Jedis通过Tcp协议来连接Redis,并有一套特有的解析协议,Jedis通过socket连接Redis服务,每个连接服务称为Jedis(类名),Jedis类又包装了Client,Transaction和pipeline,每个Jedis实例都支持三种操作方式:普通命令操作,事务操作,管道操作。其中普通命令操作和事务操作是Redis服务实现的,Jedis将对应的命令发送给服务即可,而管道操作是由客户端实现的。

按照Redis的部署方式,Jedis的连接方式可以又分为单机模式,集群模式和分片模式,其中单机模式和集群模式也是Redis服务实现的,分片模式是Jedis客户端实现的

一. 普通命令操作,核心类:Client.class

01a66fa34dcb2ff32308289195ae4113.png
Client类的关系图
  1. Connection类

Connection类用于支持单个Redis服务连接,并且支持SSL连接。Connection实现了Closeable,是因为他包装了Socket,其成员变量引用了Socket的输出流和输入流。其成员变量如下

private 

connect()方法用于连接单个Redis服务,调用这个方法用于初始化socket和inputStream和outPutStream,他会与Redis服务建立一个长连接,用RedisOutputStream和RedisInputStream对socket的TelnetOutputStream和TelnetInputStream做引用包装,并配置一块缓存,默认大小为8M。

这个方法并不是在实例构造时调用,而是在需要发送命令时才调用,相当于懒加载。如下:

public 

Connection还提供了三个基础的命令发送方法,子类可以基于这三个方法丰富扩展更多的方法。这三个命令的区别是入参不同,Connection会把他们处理成标准的输入格式(byte数组),然后通过协议类Protocol将命令写入到outStream中,但是不会立即推流,等待调用flush()方法或者缓存区满才推流。

//cmd是需要发送的命令,例如:get/set等,args是命令的入参

flush()的作用是推流,Redis服务响应的结果会缓存在inputstream中,用户去inputstream取结果就可以了。

protected 

2. BinaryClient类

BinaryClient继承了Connection类,相比父类,它提供了密码鉴权功能,并且丰富了命令方法

//重写了父类的connect()方法,并添加了密码鉴权服务
//这些方法都是通过父类的sendCommand扩展的,特点是入参都是byte数组

3. Client类

Client类继承了BinaryClient类并实现了Commands接口。它的作用是基于父类的方法上进一步拓展,与父类有很大不同的是,父类的拓展方法入参都是byte数组,而Client的的拓展方法都是常用的封装数据类型。

//子类对封装数据类型进行了编码,然后调用父类的方法

二. 管道操作,核心类Pipeline.class

Redis本身并不支持管道操作,Jedis是通过客户端实现的。Jedis的管道和Linux的管道不同的是,上一次命令的执行结果不能作为下一次命令的入参,因为他是将增量累积的命令一次性发送给Redis客户端,由Redis按照顺序执行后统一返回结果,然后再读取,相当于一个有序的操作集。

Jedis的管道实现是基于一个有序先进先出的有序队列,Pipeline每次新增命令时,Pipeline就将命令编码后的byte数组增量写入输出流,并在队列中增加一个Reponse类,用于标记此次命令的返回类型和获取最终的结果。

6e7766a538c3005df7efd1e081e132e1.png
Pipeline关系图
  1. 管道类Queable

Queable使用了队列,Queue的类型为LinkedList

/**

管道内每次新增一条命令,都会调用getResponse()方法,pipelinedResponses中就会多一个Response<T>实例,这个T就是这次命令执行成功后返回的数据类型。

//每次新增命令后都会调用这个方法

Reponse类的设计是比较有特点的,他有三个重要的成员变量:

//build前的数据

data是推流后从inputStream中截取的数据,response则是调用builder(),将data处理成特定的数据类型,而builder就是处理器,Builder是一个抽象类有且只有一个抽象方法build(),这个方法由子类去实现,builder通过Response的构造方法入参得到。

2. PipelineBase

PipelineBase是一个抽象类,继承自Queable类。

上面说到,每次新增命令都要调用Queable的getResponse,这是两次方法调用,于是PipelineBase就对这个逻辑进行了封装,PipelineBase封装后的方法如下:

@Override
  

重点来了,PipelineBase是需要先获取Client,然后才能写入命令,Client从哪里来?答案是进一步抽象,由抽象方法获得,让这个类拥有更高的拓展性,于是,PipelineBase不仅仅可以用于队列,也可以用于事务,还可以用于分片模式!

PipelineBase获取Client的抽象方法

//交给子类去实现

3. MultiKeyPipelineBase

MultiKeyPipelineBase也是一个抽象类,继承自PipelineBase,它相比PipelineBase的不同是多了一个成员变量,但是不提供其的set方法,其他大体无异。

//看到protected就应该警觉,大概率是让子类去操作的

4. Pipeline

Pipeline就是最终类了,Pipeline算是MultiKeyPipelineBase的包装类了,他继承了MultiKeyPipelineBase的同时也有MultiKeyPipelineBase的成员变量

Pipeline提供了Client的set方法,并实现了get方法

public 

当用户完成了所有命令的写入操作,如何获取返回结果呢?

通过Connection的getMany()方法:

public 

输入流是一整块,里面包含了多次命令的返回结果,如何区分开它们呢?协议通过关键字划分拆开:

private 

以处理无返回值的processStatusCodeReply(is)为例:

public 

这里以一个简单的例子来看,Jedis给Redis服务发送了什么,Jedis又从Redis接收到了什么内容:

向管道中插入两条命令,设置“foo”的value为“bar”,然后获取“foo”的值,如下:

Pipeline 

发送出去的命令如下:

*

接收到的命令如下:

+OK
$3
bar

其实管道操作也是支持事务的,因为他也提供了开启事务、取消事务和结束事务的方法,通过给Redis发送对应的命令实现:

//开启事务
//结束事务
//取消事务

三. 事务操作,核心类Transaction.class

Transaction继承自MultiKeyPipelineBase,MultiKeyPipelineBase的子类一共有两个,一个是它,还有一个是Pipeline,Transaction和Pipeline的实现如出一辙,Transaction有一个标志事务是否已经结束的标志,在实例构造时设置为true

//标志事务是否已经结束

Transaction只提供对inTransaction设置为false的方法,一旦设置为false了就不能再变为true了,所以,一个Transaction只能执行一个事务,执行完就没用了,而Pipeline是可以复用的。

//Transaction的exec方法

四. 分片模式,核心类ShardedJedis.class

上面说了,单个连接可以做普通命令操作,管道操作,事务操作,在实际使用场景中,开发人员并不会单一使用某一种操作,大多数情况下都是混着用,所以需要有一个实例能够通知支持这三种使用操作,这样就会方便很多,Jedis类就封装了这三种操作,它的原理是每种操作都有一个对应的成员变量,Jedis类是一个非常重要的类,分片模式和集群模式都对它有依赖。

Jedis类的三个成员变量如下:

//普通命令

Redis分片模式是由客户端Jedis来实现的,Redis本身并不支持这种功能。

ShardedJedis在初始化时,会基于每个Redis服务的信息计算一遍hash(这个hash是一致性hash),然后将计算出来的hash和服务信息放在一个TreeMap中,hash为key,服务信息为value,这是第一个容器,然后每个服务信息都会生成一个Jedis实例,放在一个LinkedHashMap中,服务信息为key,Jedis实例为value,这是第二个容器。当我们需要调用命令时,ShardedJedis会取这个命令的key,例如set("a","b"),就会取a,然后对key做一次hash,用key去第一个容器里面拿服务信息,然后根据服务信息去第二个容器里面拿Jedis实例。

83478d0571301e2a00d6a441a4faeebf.png
ShardedJedis关系图
  1. Sharded

切片模式下Sharded主要提供基础服务支持,hash计算,上面说的两个容器也都是它提供的,这些都体现在它的成员变量中:

//k是ShardInfo的哈希值,v是对应的ShardInfo

Sharded在实例构造时,就会初始化两个容器,一旦完成初始化,容器大小不再变化,不提供动态拓展的方法。初始化方法如下:

private 

计算key获取服务信息的方法如下:

public 

根据服务信息获取Jedis实例的方法如下:

//获取key对应的服务信息,如果未匹配到,就取第一个

2. BinaryShardedJedis

BinaryShardedJedis主要是提供扩展的服务,什么get,set这类方法,这个类会做一层封装,特点是入参都是byte数组,例如:

@Override
    

3. ShardedJedis

ShardedJedis继承自BinaryShardedJedis,上面说了BinaryShardedJedis的入参都是byte数组,而ShardedJedis的入参都是包装数据类型,其他区别不大,例如:

@Override
    

五. 集群模式,核心类JedisCluster.class

Redis集群内一共有16384个solt,中文名是插槽,这些插槽均匀分布在所有的Node上,每个Node都有数量相等的Slot,当向Redis中set一个k-v时,Redis会通过crc16算法计算一次结果,然后取16384的余,确保key不论是什么结果都能落在0-16384上,通过slot再去对应的Node进行命令操作,有了这个基本思想之后看源码就会简单很多。

f4ed9a4c5ed1582882fa8f28b36c5ada.png
JedisCluster关系图
  1. JedisClusterInfoCache

JedisClusterInfoCache类是用来存储Node信息,Slot信息,和连接池的,通过两个类型为HashMap的成员变量来缓存,如下:

//key是node计算出来的hash值,value是这个node的连接池

那么这些信息是怎么被放进去的呢,来看看他的初始化方法:

private 

从上面可以看到,只循环了一次,调用一次discoverClusterNodesAndSlots方法就可以拿到所有的信息了,深入一点,discoverClusterNodesAndSlots方法如下:

public 

上面的代码表示,它向Redis服务发送了命令请求到了Slot信息,所以循环一遍就可以拿到所有的信息了

2. JedisClusterConnectionHandler

JedisClusterConnectionHandler是JedisClusterInfoCache的包装类,它的主要功能是获取connection,其他没有特殊的了

abstract 

3. BinaryJedisCluster

BinaryJedisCluster又是JedisClusterConnectionHandler的包装类,看这么名字也大概知道是干嘛的了,提供以byte数组为入参的Redis命令,如下:

@Override
  

注意上面用了一个特殊的设计模式,每次调用get,set等这些操作,都会new 一个JedisClusterCommand实例,重写它的execute方法,然后调用runBinary方法,为什么呢?每个命令都new一下,不吃资源吗,来看一下它的runBinary方法:

public 

看到这里就恍然大悟了,JedisClusterCommand这个类是带着重试机制的,它不感知命令的内容,只知道如果execute方法出错了,就要重试,所以它的execute方法给子类去实现,这样还有一个好处,由于每条命令都是new了一个实例,就可以隔离每次操作了,这是很特别的重试实现机制。

4. JedisCluster

JedisCluster是BinaryJedisCluster的子类,这两个最大的区别就是,一个方法的入参是byte数组,一个是包装数据类型,其他的没区别了。

@Override
    

总结

  • connection是线程不安全的,如果向connection添加了命令而没有及时推流,就可能会存在多个命令混在一起的情况。
  • Jedis是如何反序列化Map的:Jedis接收一个byte[][],然后可以转成List<String>,其中偶数为key,奇数为value,例如,list.get(0)为key,list.get(1)为value。
  • Jedis有监控吗?有,但是没有什么功能,JedisMonitor只在BinaryJedis.class中用了一下,也仅仅是用来查询连接状态。
  • Jedis有哨兵模式吗?有,类为JedisSentinelPool.class,但是整个项目都没有用到这个类,只有测试用例中有使用。
  • Jedis有连接池吗?有,Jedis使用apache-common-pool2来管理连接对象,在集群模式下才会使用连接池,其他模式下不会使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值