jredis是redis的java客户端,经过sharde实现负载路由,一直很好奇jredis的sharde如何实现,翻开jredis源码研究了一番,所谓sharde其实就是一致性hash算法。其实,经过其源码能够看出一致性hash算法实现仍是比较简单的。主要实现类是redis.clients.util.Sharded<R, S>,关键的地方添加了注释: java
public class Sharded<R, S extends ShardInfo<R>> { //S类封装了redis节点的信息 ,如name、权重 public static final int DEFAULT_WEIGHT = 1;//默认权重为1 private TreeMap<Long, S> nodes;//存放虚拟节点 private final Hashing algo;//hash算法 ...... public Sharded(List<S> shards, Hashing algo, Pattern tagPattern) { this.algo = algo; this.tagPattern = tagPattern; initialize(shards); } private void initialize(List<S> shards) { nodes = new TreeMap<Long, S>();//基于红黑树实现排序map, 是根据key排序的 ,注意这里key放的是long类型,最多放2^32个 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++) { //一个真实redis节点关联多个虚拟节点 , 经过计算虚拟节点hash值,可很好平衡把它分散到2^32个整数上 nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo); } else for (int n = 0; n < 160 * shardInfo.getWeight(); n++) { //一个真实redis节点关联多个虚拟节点 , 经过计算虚拟节点hash值,可很好平衡把它分散到2^32个整数上 nodes.put(this.algo.hash(shardInfo.getName() + "*" + shardInfo.getWeight() + n), shardInfo); } resources.put(shardInfo, shardInfo.createResource()); } } /** * 计算key的hash值查找实际实际节点S * @param key * @return */ public S getShardInfo(byte[] key) { SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key));//取出比较key的hash大的 if (tail.isEmpty()) {//取出虚拟节点为空,直接取第一个 return nodes.get(nodes.firstKey()); } return tail.get(tail.firstKey());//取出虚拟节点第一个 } ...... }
整个算法可总结为:首先生成一个长度为2^32个整数环,经过计算虚拟节点hash值映射到整数环上,间接也把实际节点也放到这个环上(由于虚拟节点会关联上一个实际节点)。而后根据须要缓存数据的key的hash值在整数环上查找,环顺时针找到距离这个key的hash值最近虚拟节点,这样就完成了根据key到实际节点之间的路由了。 node
一致性hash核心是思想是增长虚拟节点这一层来解决实际节点变更而不破坏总体的一致性。这种增长层的概念来解决问题对于咱们来讲一点都不陌生,如软件开发中分层设计,操做系统层解决了应用层和硬件的协调工做,java虚拟机解决了跨平台。 redis
还有一个问题值得关注是一个实际节点虚拟多少个节点才是合适呢?认真看过上述代码同窗会注意160这个值,这个其实是经验值,太多会影响性能,太少又会影响不均衡。经过调整weight值,可实现实际节点权重,这个很好理解,虚拟出节点越多,落到这个节点几率越高。 算法
参考资料 缓存