一致性哈希环的理论实现

前言

最近阅读社区代码时,发现了一段富有创造性的程序算法–一致性哈希环。也就是一致性哈希算法的具体实现,由一位微软工程师在提交社区代码时,笔者review到的,感觉代码实现严谨简洁,并且把一致性哈希环的特点全考虑到了,是一段很不错的算法程序。本文简单对其进行分析,解释。一致性哈希算法这里就不多介绍了,可点击笔者之前写过的文章一致性哈希算法。一致性哈希算法在分布式系统中有很多的应用场景,主要是为了解决数据出现“热点”问题。目前这段算法是用于为待写入数据选择目标集羣位置的,目标集羣会有很多个,而写入的文件数据只能选择其中1个集羣。

一致性哈希环算法实现

/** * Consistent hash ring to distribute items across nodes (locations). If we add * or remove nodes, it minimizes the item migration. * 一致性哈希环,分散化实体项的节点位置选择,减少因为节点的变更导致的其上所属实体项的迁移。 */
public class ConsistentHashRing {
  private static final String SEPERATOR = "/";
  private static final String VIRTUAL_NODE_FORMAT = "%s" + SEPERATOR + "%d";

  /** Hash ring 哈希环. */
  private SortedMap<String, String> ring = new TreeMap<String, String>();
  /** 虚拟节点信息 -> 节点数 映射信息 */
  /** Entry -> num virtual nodes on ring. */
  private Map<String, Integer> entryToVirtualNodes =
      new HashMap<String, Integer>();

  /** Synchronization 锁同步. */
  private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  private final Lock readLock = readWriteLock.readLock();
  private final Lock writeLock = readWriteLock.writeLock();

  public ConsistentHashRing(Set<String> locations) {
    for (String location : locations) {
      // 在环内添加位置信息
      addLocation(location);
    }
  }

  /** * Add entry to consistent hash ring. * 添加实体项到哈希环内 * @param location Node to add to the ring. */
  public void addLocation(String location) {
    // 虚拟出100个节点插入
    addLocation(location, 100);
  }

  /** * Add entry to consistent hash ring. * 添加具体项到哈希环内 * @param location 需要添加的节点. * @param numVirtualNodes 需要添加的虚拟节点数。 */
  public void addLocation(String location, int numVirtualNodes) {
    writeLock.lock();
    try {
      // 更新虚拟节点列表信息
      entryToVirtualNodes.put(location, numVirtualNodes);
      for (int i = 0; i < numVirtualNodes; i++) {
        // 得到虚拟节点名
        String key = String.format(VIRTUAL_NODE_FORMAT, location, i);
        // 取其哈希值
        String hash = getHash(key);
        // 加入到哈希环内
        ring.put(hash, key);
      }
    } finally {
      writeLock.unlock();
    }
  }

  /** * Remove specified entry from hash ring. * 从哈希环内移除实体项 * @param location Node to remove from the ring. */
  public void removeLocation(String location) {
    writeLock.lock();
    try {
      // 移除给定节点位置,并获取其对应的虚拟节点数
      Integer numVirtualNodes = entryToVirtualNodes.remove(location);
      for (int i = 0; i < numVirtualNodes; i++) {
        // 得到虚拟节点key,并从哈希环内移除
        String key = String.format(VIRTUAL_NODE_FORMAT, location, i);
        String hash = getHash(key);
        ring.remove(hash);
      }
    } finally {
      writeLock.unlock();
    }
  }

  /** * Return location (owner) of specified item. Owner is the next * entry on the hash ring (with a hash value > hash value of item). * 从哈希环内去得其最近的节点位置 * @param item Item to look for. * @return The location of the item. */
  public String getLocation(String item) {
    readLock.lock();
    try {
      if (ring.isEmpty()) {
        return null;
      }
      // 计算输入路径的哈希值
      String hash = getHash(item);
      // 如果哈希环内不恰好包含此节点
      if (!ring.containsKey(hash)) {
        // 将哈希环定位到大于此key的首个位置
        SortedMap<String, String> tailMap = ring.tailMap(hash);
        // 并得到第一个大于此key的项目的key,也就是距离最近的key
        hash = tailMap.isEmpty() ? ring.firstKey() : tailMap.firstKey();
      }
      // 根据此key得到对应的虚拟节点信息
      String virtualNode = ring.get(hash);
      // 然后从虚拟节点信息中得到实际位置信息
      int index = virtualNode.lastIndexOf(SEPERATOR);
      if (index >= 0) {
        return virtualNode.substring(0, index);
      } else {
        return virtualNode;
      }
    } finally {
      readLock.unlock();
    }
  }

  public String getHash(String key) {
    return MD5Hash.digest(key).toString();
  }

  /** * Get the locations in the ring. * @return Set of locations in the ring. */
  public Set<String> getLocations() {
    return entryToVirtualNodes.keySet();
  }
}
点赞