一致性哈希算法
- 首先求出服务器(节点)的哈希值,并将其配置到0~2^32的圆(continuum)上。
- 然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。
- 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过2^32仍然找不到服务器,就会保存到第一台服务器上。
一致性哈希算法详情参考无双老师公众号文章[1]
Redis集群分片机制
Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念。
Redis Cluster 采用虚拟哈希槽分区,所有的键根据哈希函数映射到 0 ~ 16383 整数槽内,每个key通过CRC16校验后对16384取模来决定放置哪个槽(Slot),每一个节点负责维护一部分槽以及槽所映射的键值数据。在Redis Cluster中,只有Master才拥有槽的所有权,如果是某个Master的slave,这个slave只负责槽的使用,但是没有所有权。计算公式:slot = CRC16(key) % 16383。
(1)假设主节点的数量为3,将16384个槽位按照用户自己的规则手动去分配这3个节点,16384除以3,那么每个节点大约得到5460个槽。(用户自定义分配的原因在于有些机器的配置高,有些机器的配置低,配置高的可以分配多一点槽位,配置低的可以分配少一点槽位)
图中定义的规则是平均分配槽位:
- 节点1的槽位区间范围为0-5460,
- 节点2的槽位区间范围为5461-10922
- 节点3的槽位区间范围为10923-16383
(2)存储数据时,对要存储的键进行crc16哈希运算,得到一个值,并取模16384,判断这个值在哪个节点的范围区间。
假设crc16(“test_key”)384=3345,
因为3345在区间0-5460之间,
所以test_key数据写入到节点1里面。
(3)查询数据时,对要查询的键进行crc16哈希运算,得到一个值,并取模16384,判断这个值在哪个节点的范围区间。
假设crc16(“test_key”)384=3345,
因为3345在区间0-5460之间,
所以test_key数据应该从节点1里面获取。
以上就是redis集群采用的虚拟哈希槽的原理和计算规则说明,是不是没有想象的那么复杂。
这种结构很容易添加或者删除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。使用哈希槽的好处就在于可以方便的添加或移除节点。
- 当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了。
- 当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就可以了。
Redis虚拟槽的特点
- 解耦数据和节点之间的关系,简化了节点扩容和收缩难度。
- 节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据。
- 支持节点、槽和键之间的映射查询,用于数据路由,在线集群伸缩等场景。
Redis集群架构伸缩
Redis 集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。可以说,槽是 Redis 集群管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动。
Redis集群扩容
当一个 Redis 新节点运行并加入现有集群后,我们需要为其迁移槽和数据。首先要为新节点指定槽的迁移计划,确保迁移后每个节点负责相似数量的槽,从而保证这些节点的数据和负载相对均匀。
- 首先启动一个 Redis 节点,记为 Master4。但此时新增的节点还没有和其它节点进行通信,所以该节点现在还没有添加到Redis集群架构中。
- 使用 cluster meet 命令,让新 Redis 节点加入到集群中。新节点刚加入集群时都是主节点状态,由于没有负责的哈希槽,所以不能接受任何读写操作。除了cluster meet命令以外,还可以使用redis-trib.rb命令添加新节点,该命令可以直接支持添加从节点。在生产环境中我们推荐使用redis-trib.rb命令添加新节点,因为该命令在添加新节点时会执行检查,如果新节点已经加入集群或者包含数据,则该命令会放弃集群加入。
- 为新节点指定槽的迁移计划,也就是指定将当前Redis集群中的节点的哪些槽迁移到新节点中。并且迁移计划要确保每个节点负责哈希槽数量差不多,从而保证各节点的数据均匀。哈希槽迁移计划确定后开始逐个把每个待迁移的哈希槽内数据从源节点迁移到目标节点中。
- 数据迁移过程是按照每个哈希槽进行的,每个槽迁移的流程如下。 4.1 对目标节点发送cluster setslot {slot} importing {sourceNodeId}命令,让目标节点准备导入哈希槽的数据。 4.2 对源节点发送cluster setslot {slot} migrating {targetNodeId}命令,让源节点准备迁出哈希槽的数据。 4.3 源节点循环执行cluster getkeysinslot {slot} {count}命令,获取count个数据槽{slot}的键。 4.4 在源节点上执行migrate {targetIp} {targetPort} “” 0 {timeout} {keys} {keys …}命令。把获取的键通过流水线(pipeline)机制批量迁移到目标节点。 4.5 重复步骤4.3、4.4直到哈希槽下所有的键值数据迁移到目标节点。 4.6 向集群内所有主节点发送cluster setslot {slot} node {targetNodeId}命令,通知槽已经分配给目标节点。
上述内容就是手动执行槽迁移的过程。在实际的操作中因为会涉及到大量槽及键的迁移,所以会很不方便,因此redis-trib工具提供了槽分片功能,命令如下:
- redis-trib.rb reshard host:port --form <arg> --to <arg> --slots <arg> --yes --timeout <arg> ---pepeline <arg>
下面我们介绍一下上述命令参数说明:
- host:port:必须的参数,集群内任意节点地址,用来获取整个集群信息。
- –form:制定源节点的id,如果有多个源节点,则使用逗号分割。
- –to:需要迁移的目标节点id,并且目标节点只能指定一个。
- –slots:需要迁移槽的总数量。
- –yes:当打印出reshard执行计划时,是否需要用户输入yes确认后再执行reshard。
- –timeout:控制每次migrate操作的超时时间,默认为60000毫秒。
- –pipeline:控制每次批量迁移键的数量,默认为10。
Redis集群收缩
收缩节点就是将 Redis 节点下线,整个流程需要如下操作流程。
- 首先需要确认下线节点是否有负责的槽,如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽节点映射的完整性。
- 当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其他节点忘记下线节点,当所有的节点忘记改节点后可以正常关闭。
迁移完槽后,还需要通知集群内所有节点忘记下线的节点,也就是说让其他节点不再与要下线的节点进行 Gossip 消息交换。
Redis 集群使用 cluster forget { downNodeId } 命令来讲指定的节点加入到禁用列表中,在禁用列表内的节点不再发送 Gossip 消息。
为什么RedisCluster会设计成16384个槽呢? 理论上crc16算法可以得到2^16个数值,其数值范围在0-65535之间,取模运算key的时候,应该是crc16(key)e535 1.如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。 2.Redis的集群主节点数量基本不可能超过1000个。 3.槽位越小,节点少的情况下,压缩率高
- 一致性哈希算法详情参考无双老师公众号文章: https://mp.weixin.qq.com/s?__biz=MzA5OTY2NzEwOQ==&mid=2247489259&idx=1&sn=5cb32a96403549d204ec76fa78ab6e9b&chksm=90ff8b21a788023790eed3073845fc6aacb150f7a10871cb8289a9de3b4c17a65ca4117d953e&token=2024617680&lang=zh_CN#rd