四、memcache的分布式 一致性hash

一致性hash算法

这个,看到好多地方都讲了。。

当年面试阿里就被问到了。。。 还把这个和 hashMap 里的hash 搞混了。。唉。。。

简单说,就是为了实现把应用的 所有Key, 分散到多个 memcache的算法。

最基本的 分散方式就是取余。。。 但取余对于 memcache 的增减,影响太大了。。。数量一变,全都完蛋。

所以一致性hash, 就采用圆环分段式。。。 将圆环的一段都归为落到自己的节点。

然后,引申出来 虚拟节点。。。。这样节点少的时候,可以分配的更均匀当增加memcache的 时候, 要迁移的key 更加少一些。

(这个一致性hash 和 hashMap 里的hash,扩容,equals概念,一点关系都没有)


课后扩展:
找一个memcache库,读一读 一致性hash的具体代码实现。

spymemcached 默认是用取余的 hash选择。代码在 DefaultConnectionFactory

点击查看代码
  public NodeLocator createLocator(List<MemcachedNode> nodes) {
    return new ArrayModNodeLocator(nodes, getHashAlg());
  }

选择连接的时候,简单粗暴:

点击查看代码
  private int getServerForKey(String key) {
    int rv = (int) (hashAlg.hash(key) % nodes.length);
    assert rv >= 0 : "Returned negative key for key " + key;
    assert rv < nodes.length : "Invalid server number " + rv + " for key "
        + key;
    return rv;
  }

然后是一致性hash算法的实现 KetamaLocator里面 setKetamaNodes方法。
默认 numReps = 160, 就是为每个 memcacheNode,在圆环上生成160 个虚拟节点。
原理就是 拼装出 160个 虚拟节点的key, 然后hash得到一个数字。 (md5 因为出16字节。可以分为4个数字, 所以一次出4个)
最后 newNodeMap 就是一个 160个kv 的map, key是一个数字,value都是同一个memcacheNode。

点击查看代码
 // Ketama does some special work with md5 where it reuses chunks.
          // Check to be backwards compatible, the hash algorithm does not
          // matter for Ketama, just the placement should always be done using
          // MD5
          if (hashAlg == DefaultHashAlgorithm.KETAMA_HASH) {
              for (int i = 0; i < numReps / 4; i++) {
                  for(long position : ketamaNodePositionsAtIteration(node, i)) {
                    newNodeMap.put(position, node);
                    getLogger().debug("Adding node %s in position %d", node, position);
                  }
              }
          } else {
              for (int i = 0; i < numReps; i++) {
                  newNodeMap.put(hashAlg.hash(config.getKeyForNode(node, i)), node);
              }
          }

查找key的时候,看 KetamaNodelocator的 getPrimary, 先计算key的hash值。然后在node环的treeMap中取tailMap
(tailMap(K fromKey)方法用于返回此映射中键大于或等于 fromKey 的部分的视图。)
tailMap 有值就取下一个 node。
如果找不到,说明落到最后一个节点后面,就取整个环的第一个node

点击查看代码
  public MemcachedNode getPrimary(final String k) {
    MemcachedNode rv = getNodeForKey(hashAlg.hash(k));
    assert rv != null : "Found no node for key " + k;
    return rv;
  }

  long getMaxKey() {
    return getKetamaNodes().lastKey();
  }

  MemcachedNode getNodeForKey(long hash) {
    final MemcachedNode rv;
    if (!ketamaNodes.containsKey(hash)) {
      // Java 1.6 adds a ceilingKey method, but I'm still stuck in 1.5
      // in a lot of places, so I'm doing this myself.
      SortedMap<Long, MemcachedNode> tailMap = getKetamaNodes().tailMap(hash);
      if (tailMap.isEmpty()) {
        hash = getKetamaNodes().firstKey();
      } else {
        hash = tailMap.firstKey();
      }
    }
    rv = getKetamaNodes().get(hash);
    return rv;
  }

项目里实际使用的时候,上层再封装了一下。分布式hash 获取到的是个集群对象memcacheCluster,而不是具体的 memcacheClient。
memcacheCluster 里面包含一组 readClients 和 writeClients。
再根据是读还是写,选其中的随机一个可连接的读。
或者对所有可写的进行更新。

这样封装的好处,是方便配置里进行缓存的迁移替换。
将新接入的mecache 加入 cluster,设置为可写。
写一段时间后,将其设置为可读。。。然后将准备要下线的memche,设置为不可读写。再下掉即可。

参考:
https://my.oschina.net/astute/blog/93492
https://www.jianshu.com/p/f78a31725582

参考资料:
https://cloud.tencent.com/developer/article/2040911?from=article.detail.1477161

posted @ 2022-12-15 23:45  应晚星  阅读(69)  评论(0)    收藏  举报