博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !
Hash也称散列、哈希,对应的英文都是Hash,它既是一种重要的存储方式(散列),也是一种常见的检索方法。
Hash可以有多重理解和定义
Hash方法的主要思想是根据结点的关键码值来确定其存储地址,它有两个重要的概念散列表(hash table )和散列函数(hash function)。
以元素X的key为自变量,通过一定的函数关系h(X.key)(称为散列函数),计算出对应的函数hash值来(hash值就是key的指纹,是一个二进制串),那么hash值就是在hash table中的存储地址,改地址名叫散列地址
同样,在检索时用同样的方法计算hash值,然后到相应的槽去取要找的结点。通过散列方法可以对结点进行快速高效检索。
数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?
答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法。
又名hash table,是按散列存储方式构造的一种物理存储结构,因应用了哈希算法被称为哈希表。它是一种线性数据结构,拥有查找速度快的特性。
它的实现逻辑是这样的:根据设定的哈希函数H(key)和处理冲突方法将一组关键字映射到一个有限的地址区间上,并以关键字在地址区间中的象作为记录在表中的存储位置
它通过把关键码值映射到表中一个位置即哈希地址,这种通过哈希地址直接进行访问的数据结构能够加快数据的查找速度。
补充:
1. 物理存储结构共4种:顺序、链式、索引、散列
2. 其中顺序和链式最常见,这两种存储结构的共同特征是元素之间有着映射关系
3. 而哈希表(散列存储结构)的元素之间相互独立。
4. 索引存储结构类似现实世界中的字典目录。
槽
哈希表中的一个单元称作槽(slot),它的对应地址被叫做散列地址/哈希地址。
负载因子
如我们存储70个元素,但我们可能为这70个元素申请了100个元素的散列表空间。70/100=0.7,0.7即为负载因子,其范围0~1,也叫装填因子。
一般情况下,散列表的存储空间是一个一维数组HT[M],散列地址是数组的下标。设计散列方法的目标,就是设计某个散列函数h,0<=h( K ) < M;对于关键码值K,得到HT[i] = K。建立散列表时,若关键码与散列地址是一对一的关系。
例如:给定一个字符串参数 “str”,该键对应的元素是"jack",通过hash算法对"str"进行加工生成的一个存储地址,其对应内容存储着"jack"
使用hash table 存取元素时不会像传统的数据结构逐个遍历、一一对比,而是通过哈希算法直接获取元素的存储地址,因此哈希表会比传统的数据结构更为高效,这也是使用哈希表的原因。
在一般情况下,散列表的空间必须比结点的集合大,此时虽然浪费了一定的空间,但换取的是检索效率。
设散列表的空间大小为M,填入表中的结点数为N,则称为散列表的负载因子(load factor,也有人翻译为“装填因子”)。
散列表的查找过程基本上和造表过程相同。一些关键码可通过散列函数转换的地址直接找到,另一些关键码在散列函数得到的地址上产生了冲突,需要按处理冲突的方法进行查找。
对散列表查找效率的量度,依然用平均查找长度来衡量,散列表的平均查找长度是装填因子α的函数,只是不同处理冲突的方法有不同的函数。
查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。
因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素:
散列函数,也叫哈希函数、或者hash算法,其实也不一定是数学函数。散列技术是在记录的存储位置和它的关键字之间建立一个明确的对应关系f 函数,使得每个关键字 key 对应一个存储位置 f(key) 且这个位置是唯一的,使用散列技术非常适合1对1查找。
定义:
它是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。
简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
哈希算法将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。
MD4
MD4(RFC 1320)是 MIT 的 Ronald L. Rivest 在 1990 年设计的,MD 是 Message Digest 的缩写。它适用在32位字长的处理器上用高速软件实现–它是基于 32 位操作数的位操作来实现的。
MD5
MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本。它对输入仍以512位分组,其输出是4个32位字的级联,与 MD4 相同。MD5比MD4来得复杂,并且速度较之要慢一点,但更安全,在抗分析和抗差分方面表现更好
SHA-1 及其他
SHA1是由NIST NSA设计为同DSA一起使用的,它对长度小于264的输入,产生长度为160bit的散列值,因此抗穷举(brute-force)性更好。SHA-1 设计时基于和MD4相同原理,并且模仿了该算法。
散列函数优点
散列函数缺点
散列函数有自身的缺点:
2. 会存在关键字重复的问题,比如说男女为关键字的时候就不合适了。
3. 同样不适合查找范围的,比如说查找18-20岁之间的同学。
它有两个重要的特性需要我们重视,碰撞和抗篡改。
1. 抗篡改
从hash值不可以反向推导出原始的数据。这个从上面MD5的例子里可以明确看到,经过映射后的数据和原始数据没有对应关系
输入数据的微小变化会得到完全不同的hash值,相同的数据会得到相同的值,如下:
输入:
System.out.println("内容的hash值 =".hashCode());
String string2 = "内容的hash值 =";
System.out.println( string2.hashCode());
System.out.println("内容的hbsh值 =".hashCode());
输出:
627393883
627393883
656023034
2. 碰撞
任一hash函数都会出现哈希冲突,在后面会专门讲解哈希冲突。
hash函数有很多种,开发中经常使用的MD5和SHA都是历史悠久的Hash算法。
优秀的散列函数的选取原则需要考虑各种因素,其常见的因素有如下几种:
“好的散列函数 = 计算简单 + 分布均匀”。
其中计算简单指的是散列函数的计算时间不应该超过其他查找技术与关键字比较的时间,而分布均匀指的是散列地址分布均匀。
我们假设处理的是值为整型的关键码,这样我们就可以建立一种关键码与正整数之间的一一对应关系,从而把该关键码的检索转化为对与其对应的正整数的检索。同时,进一步假定散列函数的值落在0到M-1之间。
除了MD5和SHA等方案,再次介绍几种其他的散列函数。 假设有一哈希表长度11,有如下数据 26 、5、14、35、29、3、9、16
1. 除留余数法
取关键字X被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。除余法几乎是最简单的散列方法,不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。
对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。
2. 乘余取整法
一种乘法运算,此方法先让关键码key乘上一个常数A (0< A < 1),提取乘积的小数部分。然后,再用整数n乘以这个值,对结果向下取整,把它做为散列的地址。散列函数为: hash ( key ) = _LOW( n × ( A × key % 1 ) )
其中,“A × key % 1”表示取 A × key 小数部分,即: A × key % 1 = A × key - _LOW(A × key),而_LOW(X)是表示对X取下整。
3. 平方取中法
由于整数相除的运行速度通常比相乘要慢,所以有意识地避免使用除余法运算可以提高散列算法的运行时间。
平方取中法的具体实现是:先通过求关键码的平方值,从而扩大相近数的差别,然后根据表长度取中间的几位数(往往取二进制的比特位)作为散列函数值。因为一个乘积的中间几位数与乘数的每一数位都相关,所以由此产生的散列地址较为均匀。
关键字 | 关键字的平方 | 哈希函数值 |
---|---|---|
1234 | 1522756 | 227 |
2143 | 4592449 | 924 |
4132 | 17073424 | 734 |
3214 | 10329796 | 297 |
这种方法适用于不知道关键字的分布,且数值的位数又不是很大的情况
4. 数字分析法
假设关键字集合中的每个关键字都是由 s 位数字组成 (u1, u2, …, us),分析关键字集中的全体,并从中提取分布均匀的若干位或它们的组合作为地址。数字分析法是取数据元素关键字中某些取值较均匀的数字位作为哈希地址的方法。
即当关键字的位数很多时,可以通过对关键字的各位进行分析,丢掉分布不均匀的位,作为哈希值。它只适合于所有关键字值已知的情况。通过分析分布情况把关键字取值区间转化为一个较小的关键字取值区间。
举个例子:要构造一个数据元素个数n=80,哈希长度m=100的哈希表。不失一般性,我们这里只给出其中8个关键字进行分析,8个关键字如下所示:
K1=61317602
K2=61326875
K3=62739628
K4=61343634
K5=62706815
K6=62774638
K7=61381262
K8=61394220
分析上述8个关键字可知,关键字从左到右的第1、2、3、6位取值比较集中,不宜作为哈希地址,剩余的第4、5、7、8位取值较均匀,可选取其中的两位作为哈希地址。设选取最后两位作为哈希地址,则这8个关键字的哈希地址分别为:2,75,28,34,15,38,62,20。
同样的案例:比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相 同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会 明显降低。
此法适于:能预先估计出全体关键字的每一位上各种数字出现的频度。
通过数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。
5. 折叠法
有时关键码所含的位数很多,采用平方取中法计算太复杂,则可将关键码分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为散列地址,这方法称为折叠法。类型分为两种:
将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。
6. 直接寻址法
取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a?key + b,其中a和b为常数(这种散列函数叫做自身函数)
7. 随机数法
选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。
f(key) = random(key)。这里的random是随机函数,当关键字的长度不等时,采用这个方法构造散列函数是比较合适的
8. 平方散列法
乘法的运算要比除法来得省时(对现在的CPU来说,估计我们感觉不出来),所以我们考虑把除法换成乘法和一个位移操作。
公式: index = (Key* Key) >> 28 (右移,除以2^28。记法:左移变大,是乘。右移变小,是除。)
如果数值分配比较均匀的话这种方法能得到不错的结果。
9.位运算法
这类型Hash函数通过利用各种位运算(常见的是移位和异或)来充分的混合输入元素
如公式:H(key)=Key>>14;
10. 斐波那契(Fibonacci)散列法
平方散列法的缺点是显而易见的,所以我们能不能找出一个理想的乘数,而不是拿value本身当作乘数呢?答案是肯定的。其公式为: H(key)= (Key* X) >> 28,X有如下几种理想数选择:
这几个“理想乘数”是如何得出来的呢?这跟一个法则有关,叫黄金分割法则,而描述黄金分割法则的最经典表达式无疑就是著名的斐波那契数列,即如此形式的序列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946,…。
哈希算法的实质是对原始数据的有损压缩,有损压缩后的固定字长用作唯一标识原始数据。若不同的原始数据被有损压缩后产生了相同的结果,该现象称为哈希碰撞。也叫作哈希冲突,Hash Collision
由于hash的原理是将输入空间的值映射成hash空间内,而hash值的空间远小于输入的空间。根据抽屉原理,一定会存在不同的输入被映射成相同输出的情况。
换一种理解即哈希函数可能对于不相等的关键码计算出相同的散列地址,即key1≠key2,即hash(key1)=hash(key2),我们称该现象为冲突(collision),也即hash碰撞。发生冲突的两个关键码称为该散列函数的同义词,一个好的hash算法,就需要这种冲突的概率尽可能小。
同义词
两个元素通过散列函数H(key)得到的散列值相同,那么这两个元素称为“同义词”。因为hash是一种压缩映射,所以当负载因子足够大是同义词一定会存在。
虽然任意两个不同的数据块,其hash值相同的可能性极小,且对于一个给定的数据块,找到和它hash值相同的数据块极为困难。
但是在实际应用中,我们必须考虑在冲突发生时的处理办法,因为每种hash 函数都存在hash碰撞的可能。
解决冲突是一个复杂问题。冲突主要取决于:
常见的两种处理方案是是开散列方法( open hashing,也称为拉链法,separate chaining )和闭散列方法( closed hashing,也称为开地址方法,open addressing )。
不同之处在于:
添加一个元素的时候,首先计算元素key的hash值,确定插入数组中的位置。如果当前位置下没有数据,则直接添加到当前位置。当遇到冲突的时候,添加到同一个hash值的元素后面,行成一个链表。这个链表的特点是同一个链表上的Hash值相同。
开散列方法的一种简单形式是把散列表中的每个槽定义为一个链表的表头。散列到一个特定槽的所有记录都放到这个槽的链表中。
例如如图,将12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34几个数据存放到有12个槽的散列表中,通过取余法散列函数
h(K) = K mod 12,得到的余数就是每个数据的储存地址,如图下:
在JDK1.8中HashMap就是使用的拉链法解决冲突的,当链表上数据超过8条时,使用了红黑树进行了优化
涉及到红黑树:
开放地址法是指大小为 M 的数组保存 N 个键值对,其中 M > N。我们需要依靠数组中的空位解决碰撞冲突。基于这种策略的所有方法被统称为“开放地址”哈希表。
简单来说就是:一旦发生冲突,就去寻找下 一个空的散列表地址,只要散列表足够大,空的散列地址总能找到。
下面介绍几种闭散列方法中的几种常见构造法:
1. 线性探测法
线性探测法,就是比较常用的一种“开放地址”哈希表的一种实现方式。线性探测法的核心思想是当冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。
线性探测法的数学描述是:h(k, i) = (h(k, 0) + i) mod m,i表示当前进行的是第几轮探查。i=1时,即是探查h(k, 0)的下一个;i=2,即是再下一个。这个方法是简单地向下探查。mod m表示:到达了表的底下之后,回到顶端从头开始。
后面举例假定:一组关键码为(26,36,38,44,15,68,12,06,51),散列表长度M= 13,使用取余法
将散列表看成是一个环形表,若在基地址d(即h(K)=d)发生冲突,则依次探查下述地址单元:d+1,d+2,…,M-1,0,1,…,d-1直到找到一个空闲地址或查找到关键码为key的结点为止。当然,若沿着该探查序列检索一遍之后,又回到了地址d,则无论是做插入操作还是做检索操作,都意味着失败。
示例:用线性探测法来构造散列表,设定构造函数H(key)=Key%13,那么散列表如下:
根据线性检测,当计算12是地址与38冲突,往后面找…0,1,此时1为空插入数据12。 当计算51时与38冲突,往后找…0,1,2,3,4,此时4位置空闲插入数据51。
2. 二次探测(Quadratic probing)
二次探查法的基本思想是:生成的后继散列地址不是连续的,而是跳跃式的,以便为后续数据元素留下空间从而减少聚集。如果有多个同义词,那么他们是对称分布在原始H(Key)两侧的
示例:用二次探测法来构造散列表,设定构造函数H(key)=Key%13,那么散列表如下:
当计算h(12)=12时已存在38冲突,此时index=0已有数据26,查找0相对12的对称点index=11空闲,将12填入index=11。
当计算h(51)=12时,冲突。查index=0冲突,index=11冲突,index=1时槽空闲,将51填入index=1。
3. 随机探测(Double hashing)
在探查序列中随机地从未访问过的槽中选择下一个位置,即探查序列应当是散列表位置的一个随机排列。
我们可以做一些类似于伪随机探查( pseudo-random probing )的事情。在伪随机探查中,探查序列中的第i个槽是h(Key) = (Key+ ri) mod M,其中ri是1到M - 1之间数的“随机”数序列。所有插入和检索都使用相同的“随机”数。
示例:用随机测法来构造散列表, 伪随机数列为2,5,9… , 设定构造函数H(key)=Key%13,那么散列表如下:
计算H(12)=12冲突,此时H(12+2)=1可填充
计算H(51)=12冲突,此时H(51+2)=1冲突,继续H(51+5)=4槽空闲可填充
4. 双散列探查法
伪随机探查和二次探查都能消除基本聚集——即基地址不同的关键码,其探查序列的某些段重叠在一起——的问题。
同时准备多个散列函数,当第一个散列函数发生冲突的时候可以用备选的散列函数进行计算。
双散列探查法利用第二个散列函数作为常数,每次跳过常数项,做线性探查。双散列函数法:在位置d冲突后,再次使用另一个散列函数产生一个与散列表桶容量m互质的数c,依次试探(d+n*c)%m,使探查序列跳跃式分布。
二级聚集
如果两个关键码散列到同一个基地址,那么采用这两种方法还是得到同样的探查序列,仍然会产生聚集。这个问题称为二级聚集( secondary clustering )。
产生二级聚集的原因是因为伪随机探查和二次探查产生的探查序列只是基地址的函数,而不是原来关键码值的函数。
为了避免二级聚集,我们需要使得探查序列是原来关键码值的函数,而不是基位置的函数。
假设关键字集合为{12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34},同样使用除留余数法求散列表,如下图所示:
没有冲突的元素放在左边的表,有冲突的元素,将多余的元素放在右边的那个表。
hash算法常用于数据查找、信息加密、数据校验、负载均衡。主要用于信息安全领域中,通过hash加密算法把一些不同长度的信息转化成杂乱的128位的编码,这些编码值叫做HASH值. 也可以说,Hash就是找到一种数据内容和数据存放地址之间的映射关系。
单向密码
它是一种单向密码体制,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。同时,哈希函数可以将任意长度的输入经过变化以后得到固定长度的输出。哈希函数的这种单向特征和输出数据长度固定的特征使得它可以生成消息或者数据。
抗篡改
对于一个数据块,哪怕只改动其一个比特位,其hash值的改动也会非常大。
哈希函数可以将任意长度的输入经过变化以后得到固定长度的输出。如果输入数据中有变化,则哈希值也会发生变化,哪怕只改动其一个比特位,其hash值的改动也会非常大。
哈希的这种特性可用于许多操作,包括身份验证和数字签名,也称为“消息摘要”。
不同的应用对Hash函数有着不同的要求;比如,用于加密的Hash函数主要考虑它和单项函数的差距,而用于查找的Hash函数主要考虑它映射到小范围的冲突率。
我们比较熟悉的校验算法有奇偶校验和CRC校验,这2种校验并没有抗数据篡改的能力,它们一定程度上能检测并纠正数据传输中的信道误码,但却不能防止对数据的恶意破坏。
MD5 Hash算法的"数字指纹"特性,使它成为目前应用最广泛的一种文件完整性校验和(Checksum)算法,不少Unix系统有提供计算md5 checksum的命令。
版权校验在数据校验方面的另一个应用场景就是版权的保护或者违禁信息的打击,比如某个小视频,第一个用户上传的时候,我们认为是版权所有者,计算一个hash值存下来。当第二个用户上传的时候,同样计算hash值,如果hash值一样的话,就算同一个文件。
这种方案其实也给用户传播违禁文件提高了一些门槛,不是简单的换一个名字或者改一下后缀名就可以躲避掉打击了。(当然这种方式也是可以绕过的,图片的你随便改一下颜色,视频去掉一帧就又是完全不同的hash值了。
另外我们在社区里,也会遇到玩家重复上传同一张图片或者视频的情况,使用这种校验的方式,可以有效减少cos服务的存储空间。
在p2p网络中会把一个大文件拆分成很多小的数据各自传输。这样的好处是如果某个小的数据块在传输过程中损坏了,只要重新下载这个块就好。为了确保每一个小的数据块都是发布者自己传输的,我们可以对每一个小的数据块都进行一个hash的计算,维护一个hash List,在收到所有数据以后,我们对于这个hash List里的每一块进行遍历比对。
这里有一个优化点是如果文件分块特别多的时候,如果遍历对比就会效率比较低。可以把所有分块的hash值组合成一个大的字符串,对于这个字符串再做一次Hash运算,得到最终的hash(Root hash)。在实际的校验中,我们只需要拿到了正确的Root hash,即可校验Hash List,也就可以校验每一个数据块了。
Hash 算法也是现代密码体系中的一个重要组成部分。由于非对称算法的运算速度较慢,所以在数字签名协议中,单向散列函数扮演了一个重要的角色。对 Hash 值,又称"数字摘要"进行数字签名,在统计上可以认为与对文件本身进行数字签名是等效的。通过对Hash值得校验能认为是对文本信息的校验。
签名原理
MD5-Hash-文件的数字文摘通过Hash函数计算得到。不管文件长度如何,它的Hash函数计算结果是一个固定长度的数字。与加密算法不 同,这一个Hash算法是一个不可逆的单向函数。采用安全性高的Hash算法,如MD5、SHA时,两个不同的文件几乎不可能得到相同的Hash结果。因 此,一旦文件被修改,就可检测出来。
不要以为用MD5加密数据后,你的数据就安全了。有这么一个牛逼网站https://www.cmd5.com/,有极大可能可以反向查询出你的原始加密内容。该网站的官网介绍是这样的:
本站针对md5、sha1等全球通用公开的加密算法进行反向查询,通过穷举字符组合的方式,创建了明文密文对应查询数据库,创建的记录约90万亿条,占用硬盘超过500TB,查询成功率95%以上,很多复杂密文只有本站才可查询。已稳定运行十余年,国内外享有盛誉.
为了防止信息被反向查询,一般针对这种问题,我们的解决之道就是引入salt(加盐),即利用特殊字符(盐)和用户的输入合在一起组成新的字符串进行加密。通过这样的方式,增加了反向查询的复杂度。
鉴权协议又被称作挑战–认证模式:在传输信道是可被侦听,但不可被篡改的情况下,这是一种简单而安全的方法。
在开发大数据用户量应用时,都会使用分库分表,针对用户的openid进行hashtime33取模,就可以得到对应的用户分库分表的节点了。也就是用户太多了,分多个表存储它们的数据。
如上假设应用设计了10张表,openid计算后的hash值取模10,得到对应的分表,在进行后续处理就好。对于一般的活动或者系统,我们一般设置10张表或者100张表就好。
假设我们活动初始分表了10张,运营一段时间以后发现需要10张不够,需要改到100张。这个时候我们如果直接扩容的话,那么所有的数据都需要重新计算Hash值,大量的数据都需要进行迁移。如果更新的是缓存的逻辑,则会导致大量缓存失效,发生雪崩效应,导致数据库异常。造成这种问题的原因是hash算法本身的缘故,只要是取模算法进行处理,则无法避免这种情况。针对这种问题,我们就需要利用一致性hash进行相应的处理了扩容问题了。
一致性hash的基本原理是将输入的值hash后,对结果的hash值进行2^32取模,这里和普通的hash取模算法不一样的点是在一致性hash算法里将取模的结果映射到一个环上。将缓存服务器与被缓存对象都映射到hash环上以后,从被缓存对象的位置出发,沿顺时针方向遇到的第一个服务器,就是当前对象将要缓存于的服务器,由于被缓存对象与服务器hash后的值是固定的,所以,在服务器不变的情况下,一个openid必定会被缓存到固定的服务器上,那么,当下次想要访问这个用户的数据时,只要再次使用相同的算法进行计算,即可算出这个用户的数据被缓存在哪个服务器上,直接去对应的服务器查找对应的数据即可。这里的逻辑其实和直接取模的是一样的。如下图所示:
初始情况如下:用户1的数据在服务器A里,用户2、3的数据存在服务器C里,用户4的数据存储在服务器B里
下面我们来看一下当服务器数量发生变化的时候,相应影响的数据情况:
服务器缩容
服务器B发生了故障,进行剔除后,只有用户4的数据发生了异常。这个时候我们需要继续按照顺时针的方案,把缓存的数据放在用户A上面。
服务器扩容
同样的,我们进行了服务器扩容以后,新增了一台服务器D,位置落在用户2和3之间。按照顺时针原则,用户2依然访问的是服务器C的数据,而用户3顺时针查询后,发现最近的服务器是D,后续数据就会存储到d上面。
博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !
相关链接