Redis集群搭建准备,了解一致性哈希算法和哈希槽
一致性哈希是在哈希取余算法基础之上优化的,分布式缓存数据变动和映射问题,某个机器宕机了,分母数量改变了,自然取余数不对了。用于解决分布式缓存系统中的数据选择节点存储问题和数据选择节点读取问题以及在增删节点后减少数据缓存的消失范畴,防止雪崩的发生。
哈希槽是在redis cluster集群方案中采用的,redis cluster集群没有采用一致性哈希方案,而是采用数据分片中的哈希槽来进行数据存储与读取的。
一致性哈希
一致性hash是指将 “存储节点” 和 “数据” 都映射到一个首尾相连的hash环上。如果增删节点,仅影响该节点在hash环上顺时针相邻的后继节点,其他数据不会受到影响。
三步:
1:算法构建一致性哈希环。
对存储节点进行哈希计算,也就是对存储节点做哈希映射,比如根据节点的 IP 地址进行哈希(用ip地址对环上的节点个数进行hash,比如ip地址为1.2.3.4,节点个数N,那么可映射到第1234%N的位置上)
2:服务器IP节点映射。
当对数据进行存储或访问时,对数据进行哈希映射; 将存储的key进行hash(key),然后将其值要分布在这个闭合圆上。
一致性Hash算法使用 “取模法”,且是对 2^32 次方取模,其可表示的范围为:0 ~ 2^32-1。
3:key落到服务器的落键规则。
将数据key使用Hash函数计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器!
比如:a、b、c三个key,经过哈希计算后,在环空间上的位置如下:key-a存储在node1,key-b存储在node2,key-c存储在node3。
与普通的hash算法有何不同?
普通的hash算法是对节点数进行hash,而一致性hash是对固定值2^32进行取模。
优点:
将数据key使用Hash函数计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器!
比如:a、b、c三个key,经过哈希计算后,在环空间上的位置如下:key-a存储在node1,key-b存储在node2,key-c存储在node3。一致性Hash算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。
新增服务器节点/删除服务器节点:
在hash环中新增服务器,也是通过hash算法确认分布,只需要移动一小部分数据即可;移除也是同理(比如下图中想删除4节点,只需把原本1~4节点之间的数据移动到2节点上即可)。
缺点:(数据倾斜)
但是一致性哈希算法不能够均匀的分布节点,会出现大量请求都集中在一个节点的情况,在这种情况下进行容灾与扩容时,容易出现雪崩的连锁反应。
当在服务器节点数量太少的时候,容易出现分布不均而导致数据倾斜。
例如:系统中只有两台服务器,此时必然造成大量数据集中到Node 2上,而只有极少量会定位到Node 1上。所以用一致性哈希算法时,节点太少容易出现数据倾斜,就达不到了分布式效果。其环分布如下
总结:一致性Hash算法,主要是考虑到分布式系统每个节点都有可能失效,并且新的节点很可能动态的增加进来的情况。如何保证当系统的节点数目发生变化的时候(新增/删减),我们的系统仍然能够对外提供良好的服务(不用停掉所有redis服务),这是值得考虑的!
哈希槽
哈希槽解决了数据均匀分布的问题,在数据和节点之间又加入了一层,把这层称之为哈希槽,用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放数据。
槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。
原理
- 只是进行不重复的基数统计,不是集合也不保存数据,只记录数量而不是具体内容。
- 有误差,非精准统计,牺牲准确率来换取空间,误差仅仅只是0.81%左右。误差来自Redis之父回答。
redis cluster采用数据分片的哈希槽来进行数据存储和数据的读取。redis cluster一共有2^14(16384)个槽,所有的master节点都会有一个槽区比如0~1000,槽数是可以迁移的。master节点的slave节点不分配槽,只拥有读权限。但是注意在代码中redis cluster执行读写操作的都是master节点,并不是读是从节点,写是主节点。
为什么是16384个槽?
hash方式:
一个redis集群包含 16384 个哈希槽,编号0-16383(0-2*14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求,可以指定哪些编号的槽分配给哪个主节点。集群会记录节点槽的对应关系,解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取余,余数是及,那么Key就落入对应的槽里。集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽。
一个redis节点包含N个槽,数据通过hash算法哈希到固定的槽里,所以槽只是决定了数据的存放位置,当多个数据hash出来的结果相同时,他们就被分配到相同的槽里,即也会映射到相同的服务节点上。
在握手成功后,两个节点之间会定期发送ping/pong消息,交换数据信息,在redis节点发送心跳包时需要把所有的槽信息放到这个心跳包里,以便让节点知道当前集群信息,在发送心跳包时使用char进行bitmap压缩后是2k(16384÷8÷1024=2kb),也就是说使用2k的空间创建了16k的槽数。
虽然使用CRC16算法最多可以分配65535(2^16-1)个槽位,65535=65k,压缩后就是8k(8 * 8 (8 bit) * 1024(1k) = 8K),也就是说需要需要8k的心跳包,作者认为这样做不太值得;并且一般情况下一个redis集群不会有超过1000个master节点,所以16k的槽位是个比较合适的选择。
再来一个图
①如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。
②redis的集群主节点数量基本不可能超过1000个。集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者,不建议redis cluster节点数量超过1000个。
③槽位越小,节点少的情况下,压缩率高
Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。
如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。
哈希槽和一致性哈希相比
并不是闭合的,key的定位规则是根据CRC-16(key)%16384的值来判断属于哪个槽区,从而判断该key属于哪个节点,而一致性哈希是根据hash(key)的值来顺时针找第一个hash(ip)的节点,从而确定key存储在哪个节点。
一致性哈希是创建虚拟节点来实现节点宕机后的数据转移并保证数据的安全性和集群的可用性的。redis cluster是采用master节点有多个slave节点机制来保证数据的完整性的,master节点写入数据,slave节点同步数据。当master节点挂机后,slave节点会通过选举机制选举出一个节点变成master节点,实现高可用。但是这里有一点需要考虑,如果master节点存在热点缓存,某一个时刻某个key的访问急剧增高,这时该mater节点可能操劳过度而死,随后从节点选举为主节点后,同样宕机,进入fail状态。
扩容和缩容
一致性哈希算法在新增和删除节点后,数据会按照顺时针来重新分布节点。而redis cluster的新增和删除节点都需要手动来分配槽区。
为什么redis集群不采用一致性哈希算法?
一致性哈希的节点分布基于圆环,无法很好的手动控制数据分布,比如有些节点的硬件差,希望少存一点数据,这种很难操作(还得通过虚拟节点映射,总之较繁琐)。
而redis集群的槽位空间是可以用户手动自定义分配的,类似于 windows 盘分区的概念,可以手动控制大小。
其实,无论是一致性哈希还是哈希槽的方式,在增减节点的时候,都会对一部分数据产生影响,都需要我们迁移数据,当然,redis集群也提供了相关手动迁移槽数据的命令。