Jedis的主要设计与实现

Posted by RussXia on March 2, 2021

Jedis的主要设计与实现

Redis地址:https://github.com/redis/redis

Jedis地址: https://github.com/redis/jedis

Jedis是Redis的Java客户端的一种实现,支持单机版Redis、Redis Cluster、Redis Sentinel等三种模式,以及客户端逻辑分片的ShardedJedis。

Jedis实例有3种请求模式,Pipeline,Transaction和Client。其实就是和Redis的普通client、Redis的Pipeline、Redis的事务三者大体对应。

Client

Client就是常见的一问一答模式,客户端发送一个命令,阻塞等待服务端执行,然后读取返回结果。

Pipeline

Pipeline模式是一次性发送多条命令给服务端,服务端依次处理完毕后,一条响应将结果一次性返回。Pipeline以队列的形式实现,命令是依次执行的。

Transaction

Transaction主要表示Redis的事务操作,开启事务后,命令并不是立即被执行的,而是会进入一个事务队列中,然后返回QUEUED,表示事务已入对队。在执行EXEC命令后以队列的形式返回给客户端。

非事务状态下,redis每个命令都是一个执行单位;事务状态,一个事务是一个执行单位。

  • MULTI 命令的执行标记着事务的开始:
  • DISCARD 命令用于取消事务。它清空整个事务队列,将事务状态调整为非事务状态。
  • WATCH 需要在事务外执行,用于监视任意数量的键(watch(String... keys))。当调用exec开始事务时,如果被监视的键被其他客户端修改了,那么整个事务被丢弃,调用exec返回nil
  • EXEC 命令触发事务,以FIFO的方式,执行事务队列中的所有命令,并将命令执行得到的回复队列作为自己的结果返回给客户端。

redis中并没有事务的回滚,只有中断机制。除了上述的四个命令外,还可以使用lua脚本去执行一组原子性的命令。在语义上和MULTI/EXEC是一样的。

https://redis.io/commands/eval#atomicity-of-scripts

Jedis的大体结构

e72120b1f6daf4a951e75c05b9191a0f

Jedis继承自BinaryJedis,BinaryJedis持有一个Client、Transaction、Pipeline。 Client负责最基础的Socket的维护和访问。Client继承自Connection,Connection中持有一个Socket实例和OutputStream、InputStream两个流。

  • JedisPool提供了池化的Jedis对象。
  • Jedis作为单个连接的操作入口,提供了Jedis支持的所有的API。
  • BinaryJedis聚合了ClientTransactionPipeline等功能对象,是Jedis的二进制版本。
  • Client封装了对Redis服务端的API访问。
  • Connection 同Socket通信实现了和Redis服务端的访问能力。
  • Protocol封装了redis协议,Client发送命令后,经过Protocol的包装,以符合Redis规范的格式请求到Redis服务端。

Jedis如何通过JedisPool管理连接

Jedis的连接池技术用的是commons-pool2实现的,核心一是定义池对象工厂PooledObjectFactory,二是定义对象池Object Pool

池对象工厂PooledObjectFactory负责创建池对象PooledObject,我们从对象池Object Pool,对象池会委托池对象工厂生产/复用池对象。

Jedis代表一个操作redis的连接,Jedis本身并不是线程安全的,通过JedisPool来保证线程安全。

具体可以参考:

Redis的三种集群模式

Redis支持三种集群模式:

  • Cluster 模式
  • 主从复制模式
  • Sentinel(哨兵)模式

RedisCluster分片集群

RedisCluster提供集群分片功能,RedisCluster的分片并不是一致性hash,而是引入了hash槽的概念。

Redis集群默认有16384(2^14)个hash槽,集群中的每个节点负责一部分hash槽。存储元素时,slot = CRC16(key) & 16383计算key落在哪个槽。

比如有A(1-5000)、B(5001-10000)、C(10001-16384)时,如果B节点宕机/失去联系了,那么集群中5001-10000这个范围内的hash槽是不可用,但不影响A(1-5000)、C(10001-16384)这两个节点的正常使用

因此,Redis集群的优势:

  • 数据分散在不同的节点上
  • 集群中部分节点不可用的情况下,不会影响其他可用节点的使用

Redis的主从复制

为了避免单点故障,Redis提供了主从复制功能。集群分为两类角色,master节点和slave节点。master提供读写功能,slave只提供读功能。

主节点和从节点之间的数据同步通过master-slave replication(主从复制)功能实现。

  • Redis的主从复制是异步的,同步数据时不会影响主节点的读写

  • 如果主节点宕机,需要手动切换master节点;
  • 主节点宕机时,未来得及同步的数据可能会丢失。
  • Slave重启或新增时,会发送sync请求和主节点全量同步;如果多个slave同时重启,会影响主节点的IO。

Sentinel(哨兵)模式

Redis的redis-sentinel是一个独立运行的进程,是用来监控redis集群,做到master-slave高可用(High-Available)的工具。当集群中master节点不可用时,sentinel会自动选举一个新的master节点继续处理服务。Sentinel模式为master-slave replication提供了节点发现、自动故障切换、master节点提升的能力。

  • 主观下线(Subjectively Down):单个Sentinel实例在特定时间内无法得到服务器的响应,该单个sentinel该这个服务器标记为主观下线
  • 客观下线(Objectively Down):多个Sentinel实例对同一个服务器都作出了主观下线的判断,该服务器则被标记为客观下线

当某个Sentinel监视到某个服务主观下线后,该Sentinel会询问其他Sentinel该服务是否也是主观下线。如果达到指定数量的Sentinel都认为该服务主观下线,则该服务被标记为客观下线,并对其做故障转移。

Redis的集群

Jedis支持ShardedJedis、JedisCluster、JedisSentinel等多种集群方式。其中Sentinel这一模式,是保证redis的HA和failover的。

而ShardedJedis、JedisCluster、codis等则是对数据进行分片。

  • ShardedJedisShardedJedis是redis最开始不支持集群时,客户端实现的一个数据分片的集群方式。ShardedJedis首先对key通过一致性hash计算对应的redis实例。
  • JedisCluster:JedisCluster是根据redis cluster特性实现的Java客户端,redis cluster的分片实现是hash槽:slot = CRC16(key) & 16383
  • Codis:Codis是一个支持redis集群分片的中间件,Codis会保存分片和redis集群的对应关系。Codis可以看成一个proxy,客户端请求Codis,Codis自动将其转发到对应分片的redis实例上。