1、Jedis 简介
Jedis是Redis官方推荐的首选Java客户端实现。目前支持redis各种数据结构的操作包括string,hash, list, set, sorted set;支持事务/管道/发布订阅等;支持客户端sharding和Redis 集群。 项目地址:https://github.com/xetorthio/jedis
2、Jedis class diagram
Jedis实例本身是线程不安全的,所以jedis项目使用了Apache commons-pool作为对象池来管理jedis实例。
3、Jedis sequence diagram (set command)
4、ShardedJedis class diagram
5、ShardedJedis sequence diagram(set command)
关于sharding
Sharded Jedis的一致性hash实现是将redis服务器节点按照一定规则进行hash算法切分并把(虚拟)节点信息保存在TreeMap中,通过算法实现如下图的闭环。
Sharded Jedis 一致性hash算法实现核心代码如下:
private void initialize(List<S> shards) { nodes = new TreeMap<Long, S>(); for (int i = 0; i != shards.size(); ++i) { final S shardInfo = shards.get(i); if (shardInfo.getName() == null) for (int n = 0; n < 160 * shardInfo.getWeight(); n++) { nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo); } else for (int n = 0; n < 160 * shardInfo.getWeight(); n++) { nodes.put(this.algo.hash(shardInfo.getName() + "*" + shardInfo.getWeight() + n), shardInfo); } resources.put(shardInfo, shardInfo.createResource()); } }
以上是TreeMap初始化的过程,主要根据是否定义了节点name产生了两条分支进行hash计算,通过代码可以了解到,如果在没有定义节点name的情况下,如果有节点挂掉要启动备份节点时,新节点在List<S> shards中的位置(index)不能变,这样才能保证整个集群一致性hash的结果与原来相同,当然最好的方式还是为每个节点定义一个name,这样就与节点在配置中的顺序无关了。
public R getShard(String key) { return resources.get(getShardInfo(key)); } public S getShardInfo(byte[] key) { SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key)); if (tail.size() == 0) { return nodes.get(nodes.firstKey()); } return tail.get(tail.firstKey()); } public S getShardInfo(String key) { return getShardInfo(SafeEncoder.encode(getKeyTag(key))); }
另外注意:在使用sharded jedis时,如果在JedisPoolConfig中配置testOnBorrow为true,那么在borrowObject时会调用validateObject方法来验证所有对象实例的有效性,此时redis cluster中只要有一个节点出问题,客户端将无法获取shardedJedis实例。如果配置testOnBorrow为false(默认值),在获取sharedJedis实例时将不会进行集群节点有效性的验证,只有散列到出问题节点的请求才会抛异常,其他节点的访问正常。下面是ShardedJedisPool.ShardedJedisFactory.validateObject方法的具体实现:
public boolean validateObject(final Object obj) { try { ShardedJedis jedis = (ShardedJedis) obj; for (Jedis shard : jedis.getAllShards()) { if (!shard.ping().equals("PONG")) { return false; } } return true; } catch (Exception ex) { return false; } }
ps:以上源码分析都依据版本2.1.0,目前Jedis又开始频繁更新来适配redis cluster版本。