一致性哈希(Consistent hashing)算法是由 MIT 的Karger 等人与1997年在一篇学术论文(《Consistent hashing and random trees: distributed caching protocols for relieving hot spots on the World Wide Web》)中提出来的,用于解决分布式缓存数据分布问题。在传统的哈希算法下,每条缓存数据落在那个节点是通过哈希算法和服务器节点数量计算出来的,一旦服务器节点数量发生增加或者介绍,哈希值需要重新计算,此时几乎所有的数据和服务器节点的对应关系也会随之发生变化,进而会造成绝大多数缓存的失效。一致性哈希算法通过环形结构和虚拟节点的概念,确保了在缓存服务器节点数量发生变化时大部分数据保持原地不动,从而大幅提高了缓存的有效性。下面我们通过例子来解释一致性哈希的原理。

​   比如有 n 个节点,对于缓存 数据(k,v)具体存在哪个节点往往 hash(k) % n 来计算处理,举一个例子如下表所示,一共有个3个节点,hash函数采用 md5 。

缓存key hash(k) % n 服务器节点
user_nick_rommel 1 192.168.56.101
user_nick_pandy 0 192.168.56.100
user_nick_sam 2 192.168.56.102

Md5 的计算结果一般是一串32位的16进制字符串,做取模运算时原始数字较长,实际使用时,可以只截取最后4位或者8位使用,因为hash函数具有随机性,当数据量足球大时,截取部分数据也能保证数据的均匀分布。比如 md5('user_nick_rommel'),对应字符串为 29e4fd2a0f05bd63343ae2276ca5038e,取最后4位 038e 转成10进制整数在进行取模运算,038e 对应的10进制数为 910,取模计算得 1 (9102 % 3 = 1)。

​ 如果此时对缓存服务器进行扩容,添加一个新节点如 192.168.56.103,那么按照上面的计算方式,n 变为4, 得到的结果如下:

缓存key hash(k) % n 服务器节点
user_nick_rommel 2 192.168.56.102
user_nick_pandy 2 192.168.56.102
user_nick_sam 3 192.168.56.103

​ 从结果中可见,缓存对应关系完全发生改变,比如 user_nick_rommel 这个可以,添加节点钱可以从 192.168.56.101 中读到,添加节点后却读不到了,。一般缓存失效时应用程序都会重新从后端服务加载数据(比如数据库),以这种这种方式分配缓存,当缓节点数量发生改变时,会造成大面积的缓存失效,这回造成后端服务瞬间压力上升,压力过大会造成服务不可以用,如果服务出于关键节点,甚至还会引发雪崩效应(TODO)。

​ 在实际应用中,缓存节点由于故障挂掉,或者空间不足而进行扩容,缓存节点的增减是比较常见的事情,但上面传统方式会使服务的不可靠,下面看下一致性哈希是如何解决这个问题的。

​ 在一致性哈希算法中,首先将哈希空间映射到一个虚拟的环上,环上的数值分从 0 到 2^32-1(哈希值的范围),如下图:

在一致性哈希算法刚提出来的时候,32位系统还是主流,2^32-1 相当于最大Integer,现在的应用服务器普遍都是64位系统,在使用使用一致性哈希算法时可以根据实际情况适当变通,比如将哈希值空间放大到 2^64-1。

​ 然后使用同样的哈希算法将缓存服务节点(通常通过服务器IP+端口作为节点的key)和数据键映射到环上的位置。再决定数据落在那台服务器上时,使用一致的方向(比如顺时针方向)沿环查找,遇到的第一个有效服务器就是缓存保存的地方,如图:

​ 当有新的服务器节点加入时,按照同样的哈希算法将新节点映射到环上的某处位置,和新节点相邻的数据逆时针节点会进行迁移,其他节点保持不变。如下图,当新加入一台新服务器 192.168.56.104 时,user_nick_pandy 这条缓存数据的请求根据算法会落在192.168.56.104 这台及其上,其他节点不受任何影响。

​ 另外,由于哈希计算的记过通常都比较随机,如果缓存服务器比较少的话,可能会出现数据分配冷热不均的问题,如下图所示,大部分数据都会存储在 node3 节点上。

​ 为了解决这个问题,我们引入虚拟节点的概念,在实体服务器不增加的情况下,用多个虚拟节点替代原来的单个实体节点,一台服务服务器在环上就对应多个位置,这样可以让数据存储更加均匀,各服务器的负载页更加平衡,如图:

使用 Java 代码实现一致性哈希的例子如下:

import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.commons.codec.digest.DigestUtils; public class ConsistentHashingTest { // 真实缓存地址
private final String[] cacheServers = { "192.168.56.101:11211", "192.168.56.102:11211", "192.168.56.103:11211" }; // 保存虚拟节点
private final SortedMap<Long, String> nodes = new TreeMap<Long, String>(); // 每个虚拟节点的数量
private final int VIRTUAL_NODE_NUM = 3; public ConsistentHashingTest(){
//初始化
for(String eachServer : cacheServers) {
this.addNode(eachServer);
}
} // 创建虚拟节点
public void addNode(String nodeKey) {
//为每一个实体节点创建3个虚拟节点
for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
long eachHashCode = this.hash(nodeKey + ":" + i);
nodes.put(eachHashCode, nodeKey);
}
} // hash 函数 可以使用 md5, sha-1, sha-256 等
// 虽然 md5, sha-1 哈希算法在签名领域已经不再安全,但运算速度比较快,在非安全领域是可以使用的。
// DigestUtils 是来自于 apache 中的 org.apache.commons.codec.digest 中的工具类
private long hash(String key) {
Stringmd5key = DigestUtils.md5Hex(key);
return Long.parseLong(md5key.substring(0, 15), 16) % ((long) Math.pow(2, 32));
} //按照同一个方向寻找
public String getRealServer(String key) {
long hashCode = this.hash(key);
SortedMap<Long,String> tailMap =
nodes.tailMap(hashCode);
long serverKey = tailMap.isEmpty() ? nodes.firstKey() : tailMap.firstKey();
return nodes.get(serverKey);
} public static void main(String[] args) {
ConsistentHashingTestt = new ConsistentHashingTest();
System.out.println(t.getRealServer("my-cache-key"));
}
}

另外,前文提到的(TODO 章节)Guava Cache 框架支持一致性哈希,实例代码如下:


//实体缓存服务器
String[]cacheServers = { "192.168.56.101:11211", "192.168.56.102:11211", "192.168.56.103:11211" }; // 缓存数据的key
String key = "my-test-cache-key"; // 计算缓存 key 对应的 hash 值,这里使用 MurmurHash 算法,MurmurHash 是一种高性能低碰撞的算法。此外,还支持 md5、sha1/sha256/sha512、orc32、adler32 等哈希算法。
HashCode hashCode = Hashing.murmur3_32().newHasher().putString(key, Charsets.UTF_8).hash(); // 通过一致性哈希方式计算,缓存key对应的服务器主机是那一台,bucket 的范围在 0 ~ cacheServers.length -1
int bucket = Hashing.consistentHash(hashCode, cacheServers.length);

转载自 https://coderxing.gitbooks.io/architecture-evolution/di-san-pian-ff1a-bu-luo/631-yi-zhi-xing-ha-xi.html

浅谈一致性哈希(My转)的更多相关文章

  1. 浅谈一致性Hash原理及应用

    在讲一致性Hash之前我们先来讨论一个问题. 问题:现在有亿级用户,每日产生千万级订单,如何将订单进行分片分表? 小A:我们可以按照手机号的尾数进行分片,同一个尾数的手机号写入同一片/同一表中. 大佬 ...

  2. 浅谈一致性hash

    相信做过互联网应用的都知道,如何很好的做到横向扩展,其实是个蛮难的话题,缓存可横向扩展,如果采用简单的取模,余数方式的部署,基本是无法做到后期的扩展的,数据迁移及分布都是问题,举个例子: 假设采用取模 ...

  3. 浅谈字符串哈希 By cellur925

    前言 蒟蒻最近在复习字符串算法...但正如之前所说,我OI太菜被关起来了,本蒟蒻只能从最简单的哈希入手了TAT.而别的dalao都在学习AC自动机/后缀数组等高到不知哪里去的算法qwq. 基本思想 映 ...

  4. 一致性哈希算法(Consistent Hashing Algorithm)

    一致性哈希算法(Consistent Hashing Algorithm) 浅谈一致性Hash原理及应用   在讲一致性Hash之前我们先来讨论一个问题. 问题:现在有亿级用户,每日产生千万级订单,如 ...

  5. [转帖]浅谈分布式一致性与CAP/BASE/ACID理论

    浅谈分布式一致性与CAP/BASE/ACID理论 https://www.cnblogs.com/zhang-qc/p/6783657.html ##转载请注明 CAP理论(98年秋提出,99年正式发 ...

  6. 【架构】浅谈web网站架构演变过程

    浅谈web网站架构演变过程   前言 我们以javaweb为例,来搭建一个简单的电商系统,看看这个系统可以如何一步步演变.   该系统具备的功能:   用户模块:用户注册和管理 商品模块:商品展示和管 ...

  7. 【转】浅谈Java中的hashcode方法(这个demo可以多看看)

    浅谈Java中的hashcode方法 哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native i ...

  8. 【转】浅谈Java中的hashcode方法

    哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int hashCode(); 根据这个 ...

  9. 铁乐学Python_day07_集合and浅谈深浅copy

    1.[List补充] 在循环一个列表时,最好不要使用元素和索引进行删除操作,一旦删除,索引会随之改变,容易出错. 如果想不出错,可以采用倒着删除的方法,因为倒着删除进行的话,只是后面元素的位置发生了变 ...

随机推荐

  1. 用xshell ssh连接测试服务器时候出的问题

    问题还原:用ssh连接测试服务器 给我结结实实报了个错 FBIwarning: ------------------------------------------------------------ ...

  2. day 54 linux 常用指令入门

    Linux文件系统结构 Linux目录结构的组织形式和Windows有很大的不同.首先Linux没有“盘(C盘.D盘.E盘)”的概念.已经建立文件系统的硬盘分区被挂载到某一个目录下,用户通过操作目录来 ...

  3. 怎样优化调整innodb_log_buffer_size

    官方文档并没有直接告诉如何调整 innodb_log_buffer_size 大小, 根据对mysql 的状态信息了解知道  innodb_log_buffer_size 跟 Innodb_os_lo ...

  4. 【NOIP2013】货车运输 最大生成树+倍增

    题目大意:给你一张n个点m条边的图,有q次询问,每次让你找出一条从x至y的路径,使得路径上经过的边的最小值最大,输出这个最大的最小值. 显然,经过的路径必然在这张图的最大生成树上. 我们求出这个图的最 ...

  5. OS之进程管理 --- 死锁

    什么是死锁 在正常操作模式下,进程按如下顺序来使用资源: 申请:进程请求资源 使用:进程对资源进行操作 释放:进程释放资源 当一组进程中的每一个进程度在等待一个事件,而这事件只能有一组进程的另一个进程 ...

  6. 2018春招-今日头条笔试题5题(后附大佬答案-c++版)

    1题目描述 在n个元素的数组中,找到差值为k的除重后的数字对 输入描述 第一行:n和k,n表示数字的个数,k表示差值 第二行:n个整数 输入样例 输入: 5 2 1 5 3 4 2 输出: 3 说明: ...

  7. 如何删除Eclipse里某个工作空间?

    很多时候,一个Eclipse中或多或少的都会有那么几个工作空间(workspace),但是久而久之你会发现有些工作空间你觉得不再需要了或者觉得碍眼,怎么办? 其实很简单,方法有两种. 1.打开你的Ec ...

  8. mysql RC下不存在则插入

    mysql版本:5.7 目的:在RC下,name列上仅有key索引,并发插入name时不出现重复数据 RC不加gap lock,并且复合select语句是不加锁的快照读,导致两个事务同时进行都可插入, ...

  9. 在Linux系统安装Nodejs最简单步骤

    一.去官网下载和自己系统匹配的文件 英文网址:https://nodejs.org/en/download/ 中文网址:http://nodejs.cn/download/ 通过  uname -a ...

  10. 基于obs+nginx-rtmp-module搭建自己直播的系统

    前言 一句唠叨,工欲善其事,必先利其器,在程序员的工作里,搭建各种环境往往花费过多不必要的时间,这里建议搭建服务端环境时,尽量避开win.macos这种系统,个人比较推崇centos. 操作 下面以c ...