一、开篇
Redis作为目前通用的缓存选型,因其高性能而倍受欢迎。Redis的2.x版本仅支持单机模式,从3.0版本开始引入集群模式。
Redis的Java生态的客户端当中包含Jedis、Redisson、Lettuce,不同的客户端具备不同的能力是使用方式,本文主要分析Jedis客户端。
Jedis客户端同时支持单机模式、分片模式、集群模式的访问模式,通过构建Jedis类对象实现单机模式下的数据访问,通过构建ShardedJedis类对象实现分片模式的数据访问,通过构建JedisCluster类对象实现集群模式下的数据访问。
Jedis客户端支持单命令和Pipeline方式访问Redis集群,通过Pipeline的方式能够提高集群访问的效率。
本文的整体分析基于Jedis的3.5.0版本进行分析,相关源码均参考此版本。
二、Jedis访问模式对比
Jedis客户端操作Redis主要分为三种模式,分表是单机模式、分片模式、集群模式。
-
单机模式主要是创建Jedis对象来操作单节点的Redis,只适用于访问单个Redis节点。
-
分片模式(ShardedJedis)主要是通过创建ShardedJedisPool对象来访问分片模式的多个Redis节点,是Redis没有集群功能之前客户端实现的一个数据分布式方案,本质上是客户端通过一致性哈希来实现数据分布式存储。
-
集群模式(JedisCluster)主要是通过创建JedisCluster对象来访问集群模式下的多个Redis节点,是Redis3.0引入集群模式后客户端实现的集群访问访问,本质上是通过引入槽(slot)概念以及通过CRC16哈希槽算法来实现数据分布式存储。
单机模式不涉及任何分片的思想,所以我们着重分析分片模式和集群模式的理念。
2.1 分片模式
-
分片模式本质属于基于客户端的分片,在客户端实现如何根据一个key找到Redis集群中对应的节点的方案。
-
Jedis的客户端分片模式采用一致性Hash来实现,一致性Hash算法的好处是当Redis节点进行增减时只会影响新增或删除节点前后的小部分数据,相对于取模等算法来说对数据的影响范围较小。
-
Redis在大部分场景下作为缓存进行使用,所以不用考虑数据丢失致使缓存穿透造成的影响,在Redis节点增减时可以不用考虑部分数据无法命中的问题。
分片模式的整体应用如下图所示,核心在于客户端的一致性Hash策略。
(引用自:www.cnblogs.com)
2.2 集群模式
集群模式本质属于服务器分片技术,由Redis集群本身提供分片功能,从Redis 3.0版本开始正式提供。
集群的原理是:一个 Redis 集群包含16384 个哈希槽(Hash slot), Redis保存的每个键都属于这16384个哈希槽的其中一个, 集群使用公式CRC16(key)%16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键key的CRC16校验和 。
集群中的每个节点负责处理一部分哈希槽。举个例子, 一个集群可以有三个哈希槽, 其中:
-
节点 A 负责处理 0 号至 5500 号哈希槽。
-
节点 B 负责处理 5501 号至 11000 号哈希槽。
-
节点 C 负责处理 11001 号至 16383 号哈希槽。
Redis在集群模式下对于key的读写过程首先将对应的key值进行CRC16计算得到对应的哈希值,将哈希值对槽位总数取模映射到对应的槽位,最终映射到对应的节点进行读写。以命令set("key", "value")为例子,它会使用CRC16算法对key进行计算得到哈希值28989,然后对16384进行取模得到12605,最后找到12605对应的Redis节点,最终跳转到该节点执行set命令。
集群模式的整体应用如下图所示,核心在于集群哈希槽的设计以及重定向命令。
(引用自:www.jianshu.com)
三、Jedis的基础用法
// Jedis单机模式的访问
public void main(String[] args) {
// 创建Jedis对象
jedis = new Jedis("localhost", 6379);
// 执行hmget操作
jedis.hmget("foobar", "foo");
// 关闭Jedis对象
jedis.close();
}
// Jedis分片模式的访问
public void main(String[] args) {
HostAndPort redis1 = HostAndPortUtil.getRedisServers().get(0);
HostAndPort redis2 = HostAndPortUtil.getRedisServers().get(1);
List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>(2);
JedisShardInfo shard1 = new JedisShardInfo(redis1);
JedisShardInfo shard2 = new JedisShardInfo(redis2);
// 创建ShardedJedis对象
ShardedJedis shardedJedis = new ShardedJedis(shards);
// 通过ShardedJedis对象执行set操作
shardedJedis.set("a", "bar");
}
// Jedis集群模式的访问
public void main(String[] args) {
// 构建redis的集群池
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("127.0.0.1", 7001));
nodes.add(new HostAndPort("127.0.0.1", 7002));
nodes.add(new HostAndPort("127.0.0.1", 7003));
// 创建JedisCluster
JedisCluster cluster = new JedisCluster(nodes);
// 执行JedisCluster对象中的方法
cluster.set("cluster-test", "my jedis cluster test");
String result = cluster.get("cluster-test");
}
Jedis通过创建Jedis的类对象来实现单机模式下的数据访问,通过构建JedisCluster类对象来实现集群模式下的数据访问。
要理解Jedis的访问Redis的整个过程,可以通过先理解单机模式下的访问流程,在这个基础上再分析集群模式的访问流程会比较合适。
四、Jedis单机模式的访问
Jedis访问单机模式Redis的整体流程图如下所示,从图中可以看出核心的流程包含Jedis对象的创建以及通过Jedis对象实现Redis的访问。
熟悉Jedis访问单机Redis的过程,本身就是需要了解Jedis的创建过程以及执行Redis命令的过程。
-
Jedis的创建过程核心在于创建Jedis对象以及Jedis内部变量Client对象。
-
Jedis访问Redis的过程在于通过Jedis内部的Client对象访问Redis。
4.1 创建过程
Jedis本身的类关系图如下图所示,从图中我们能够看到Jedis继承自BinaryJedis类。
在BinaryJedis类中存在和Redis对接的Client类对象,Jedis通过父类的BinaryJedis的Client对象实现Redis的读写。
Jedis类在创建过程中通过父类BinaryJedis创建了Client对象,而了解Client对象是进一步理解访问过程的关键。
public class Jedis extends BinaryJedis implements JedisCommands, MultiKeyCommands,
AdvancedJedisCommands, ScriptingCommands, BasicCommands, ClusterCommands, SentinelCommands, ModuleCommands {
protected JedisPoolAbstract dataSource = null;
public Jedis(final String host, final int port) {
// 创建父类BinaryJedis对象
super(host, port);
}
}
public class BinaryJedis implements BasicCommands, BinaryJedisCommands, MultiKeyBinaryCommands,
AdvancedBinaryJedisCommands, BinaryScriptingCommands, Closeable {
// 访问redis的Client对象
protected Client client = null;
public BinaryJedis(final String host, final int port) {
// 创建Client对象访问redis
client = new Client(host, port);
}
}
Client类的类关系图如下图所示,Client对象继承自BinaryClient和Connection类。在BinaryClient类中存在Redis访问密码等相关参数,在Connection类在存在访问Redis的socket对象以及对应的输入输出流。本质上Connection是和Redis进行通信的核心类。