拾贝

【赏码会】Redis的最佳拍档:Jedis

    coding     原创·赏码会

出门左拐:

Jedis简介

作为Redis官方推荐的三个Java Client之一,Jedis推出时间最早,使用最为广泛(Spring默认使用的Redis Client就是Jedis),同时Star数也遥遥领先于另外两个。和其他Redis Client一样,Jedis通过RESP协议向Redis发送命令请求和解析响应数据。

源码赏析

最新版本的Jedis代码行数超过18K,和Redis本身(20K)处于同一规模。面对如此庞大的项目,分模块阅读是必然之选。由于类的数量太多,本文只在类层面进行简单解读,不会涉及具体的源代码。值得一提的是,虽然Jedis的代码称不上规范,比如全局缺注释、某些类的长度过长,但由于绝大多数方法都很简短,加上清晰的命名和完善的单元测试,代码可读性并没有太大影响。

Core: 核心模块,实现RESP协议

  • Jedis/BinaryJedis: 入口类,封装Redis的各种命令。
  • Client/BinaryClient/Connection: 与Redis进行具体的交互工作。
  • Protocol, RedisInputStream, RedisOutputStream: 实现RESP协议。

Sharding: 提供Partitioning支持

  • ShardedJedis/BinaryShardedJedis: 首先对传入的Key进行Hash计算(默认使用高性能、低碰撞率的MurmurHash算法),然后根据计算结果找到相应的Jedis实例,最后执行命令。

Pool: 提供连接池和Sentinel支持

  • JedisPool: 基于Apache Commons Pool实现的连接池,通过JedisFactory获取Jedis实例。
  • JedisSentinelPool: 通过侦听”switch-master”事件,每当master切换时,调用JedisFactory重新初始化master连接信息。
  • ShardedJedisPool: 与JedisPool类似,通过ShardedJedisFactory获取ShardedJedis实例。

Pipeline: 提供Pipelining事务支持

  • Pipeline: 通过Jedis#pipelined()获取实例。以类型安全的方式获取执行结果,通过BuilderFactory将Object类型的Response转化为期望的结果类型。
    • 非事务模式:构建Response Queue,然后通过Client#getMany()批量获取结果。
    • 事务模式:通过MultiResponseBuilder缓存Response,然后批量获取结果。
  • Transaction: 通过Jedis#multi()获取实例。天然的事务属性,通过Client#getMany()批量获取结果,但无法获取单条命令的结果,且类型非安全。
  • ShardedJedisPipeline: ShardedJedis#pipelined()获取实例。不同于Pipeline和Transaction,由于请求可能落到多个Client上,只能通过Client#getOne()挨个获取结果,类型非安全。

Cluster: 提供Cluster支持

  • JedisCluster/BinaryJedisCluster: 通过JedisClusterConnectionHandler获取Jedis实例,然后执行命令。
  • JedisClusterConnectionHandler & JedisClusterInfoCache: 通过Collections#shuffle()随机返回一个Jedis实例。使用ReentrantReadWriteLock保证更新Cluster的Jedis实例列表时的线程安全性。
  • JedisClusterCommand: 通过retry机制获取有效的Jedis实例,然后再执行命令。

解惑

Q1:为什么有那么多的Binary*类(BinaryJedis, BinaryClient, BinaryShardedJedis, BinaryJedisCluster),它们看上去跟非Binary的子类差不多啊?

A: Binary的父类与非Binary的子类表面的区别是不管是key,还是value,只要涉及字符串语义的参数,前者都用byte[]类型传参,而后者使用String类型。而深层次的原因,我认为跟RESP协议有关,RESP协议是面向字节的协议,对于性能要求极高的场景,使用Binary类有助于提高性能(因为减少了一次String到byte[]的转换)。

Q2:Pipeline, Transaction以及普通的Jedis有何关联?

A: 简单来说,Pipeline和Transaction是批处理运行模式,一次获取多条命令的执行结果,而Jedis只能一条一条获取。而Pipeline和Transaction的区别主要有两点:1)Pipeline同时支持事务模式和非事务模式,而Transaction支持事务模式。2)Pipeline类型安全,Transaction类型非安全。

漫谈

上面提到Jedis的代码规模很大,进一步分析排名靠前的几个大类,可以发现两个明显的特点:

  1. 方法很多,最多的一个类有250+方法,直接结果就是导致类的长度也很长(3000+)
  2. 大多数方法实现不超过5行,并且遵从同一结构

单从缩减代码行数的角度来看,至少可以考虑两种方式:

  1. 使用代码生成工具自动生成享有同一结构的方法
  2. 使用Java 8引入的Functional Interface简化代码

传送门