Redis - 集群
集群
定义:由于数据量过大,单个 Master 复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展,每个复制集只负责存储整个数据集的一部分,这就是 Redis 的集群,其作用是提供在多个 Redis 节点间共享数据的程序集。
官网: https://redis.io/docs/reference/cluster-spec/
一句话:Redis 集群是一个提供在多个 Redis 节点间共享数据的程序集,Redis 集群可以支持多个 master。
Redis 集群支持多个 master,每个 master 又可以挂载多个 slave。
- 读写分离
- 支持数据的高可用
- 支持海量数据的读写存储操作
- 由于 Cluster 自带 Sentinel 的故障转移机制,内置了高可用的支持,无需再去使用哨兵功能。
- 客户端与 Redis 的节点连接,不再需要连接集群中所有的节点,只需要任意连接集群中的一个可用节点即可。
- 槽位 slot 负责分配到各个物理服务节点,由对应的集群来负责维护节点、插槽和数据之间的关系。
集群算法 - 分片 - 槽位 slot
redis 集群的槽位 slot
Redis 集群没有使用一致性 hash 而是引入了哈希槽的概念。
Redis 集群有 16384 个哈希槽每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽,举个例子,比如当前集群有 3 个节点,那么:
redis 集群的分片
分片是什么 | 使用 Redis 集群时我们会将存储的数据分散到多台 redis 机器上,这称为分片。简言之,集群中的每个 Redis 实例都被认为是整个数据的一个分片。 |
---|---|
如何找到给定 key 的分片 | 为了找到给定 key 的分片,我们对 key 进行 CRC16 (key) 算法处理并通过对总分片数量取模。然后,使用确定性哈希函数,这意味着给定的 key将多次始终映射到同一个分片,我们可以推断将来读取特定 key 的位置。 |
分片和槽位的优势
最大优势,方便扩缩容和数据分派查找。
这种结构很容易添加或者删除节点,比如如果我想添加个节点 D,我需要从节点 A,B,C 中得部分槽位到 D 上。如果我想移除节点 A,需要将 A 中的槽移动到 B 和 C 节点上,然后将没有任何槽的节点从集群中移除即可。由于一个结点将哈希槽移动到另一个节点不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。
slot 槽位映射方案
slot 槽位映射,一般业界有三种解决方案
哈希取余分区
(小厂)
2 亿条记录就是 2 亿个 k,v,我们单机不行必须要分布式多机,假设有 3 台机器构成一个集群,用户每次读写操作都是根据公式:hash (key) % N 个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。
优点:简单粗暴,直接有效,只需要预估好数据规划好节点,例如 3 台、8 台、10 台,就能保证一段时间的数据 支撑。使用 Hash 算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求 (并维护这些请求的信息), 起到负载均衡 + 分而治之的作用。
缺点:原来规划好的节点,进行扩容或者缩容就比较麻烦了额,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化: Hash (key) / 3 会 变成 Hash (key) / ?。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。 某个 redis 机器宕机了,由于台数数量变化,会导致 hash 取余全部数据重新洗牌。
一致性哈希算法分区
(中厂)
是什么?
一致性 Hash 算法背景是在 1997 年由麻省理工学院提出的,设计目标是 为了解决分布式缓存数据变动和映射问题,某个机器宕机了,分母数量改变了,自然取余数不行了。
能干嘛?
提出一致性 Hash 解决方案。目的是当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系。
3 大步骤
算法构建一致性哈希环
一致性哈希算法必然有个 hash 函数并按照算法产生 hash 值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个 hash 空间 [0,2^32-1],这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连 (O= 2^32), 这样让它逻辑上形成了一个环形空间。 它也是按照使用取模的方法,前面笔记介绍的节点取模法是对节点(服务器)的数量进行取模。而一致性 Hash 算法是对 2^32 取模,简单来说,一致性 Hash 算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数 H 的值空间为 0-2^32-1 (即哈希值是一个 32 位无符号整形),整个哈希环如下图:整个空间 按顺时针方向组织,圆环的正上方的点代表 0,O 点右侧的第一个点代表 1,以此类推,2、3、4、…… 直到 2^32-1,也就是说 0 点左侧的第一个点代表 2^32-1,0 和 2 个 32-1 在零点中方向重合,我们把这个由 2^32 个点组成的圆环称为 Hash 环。
服务器 IP 节点映射
将集群中各个 IP 节点映射到环上的某一个位置。
将各个服务器使用 Hash 进行一个哈希,具体可以选择服务器的 IP 或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如 4 个节点 NodeA、B、C、D,经过 IP 地址的 哈希函数 计算 (hash (ip)),使用 IP 地址哈希后在环空间的位置如下:
落到服务器的落键规则
当我们需要存储一个 kv 键值对时,首先计算 key 的 hash 值,hash (key),将这个 key 使用相同的函数 Hash 计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针 “行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。
如我们有 Object A、 Object B、 Object C、 Object D 四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性 Hash 算法,数据 A 会被定为到 Node A 上,B 被定为到 Node B 上,C 被定为到 Node C 上,D 被定为到 Node D 上。
优点
一致性哈希算法的容错性:假设 Node C 宕机,可以看到此时对象 A、B、D 不会受到影响。一般的,在一致性 Hash 算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务悉 (即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。简单说,就是 C 挂了,受到影响的只是 B、C 之间的数据 且这些数据会转移到 D 进行存储。
一致性哈希算法的扩展性
数据量增加了,需要增加一台节点 NodeX,X 的位置在 A 和 B 之间,那收到影响的也就是 A 到 X 之间的数据,重新把 A 到 X 的数据录入到 X 上即可,不会导致 hash 取余全部数据重新洗牌。
缺点
一致性哈希算法的数据倾斜问题
一致性 Hash 算法在服务 节点太少时,容易因为节点分布不均匀而造成 数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上) 问题,例如系统中只有两台服务器:
小总结
为了在节点数目发生改变时尽可能少的迁移数据
将所有的存储节点排列在收尾相接的 Hash 环上,每个 key 在计算 Hash 后会顺时针找到临近的存储节点存放。而当有节点加入或退出时仅影响该节点在 Hash 环上顺时针相邻的后续节点。
优点:加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。
缺点:数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。
哈希槽分区
(大厂)
介绍
是什么
HASH_SLOT = CRC16 (key) mod 16384
为什么出现
哈希槽实质就是一个数组,数组 [0, 2^14 - 1] 形成 hash slot 空间。
能干什么
解决均匀分配的问题,在 数据和节点之间又加入了一层,把这层称为哈希槽 (slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里面放的是数据。
槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。哈希解决的是映射问题,使用 key 的哈希值来计算所在的槽,便于数据分配。
多少个 hash
一个集群只能有 16384 个槽,编号 0-16383 (0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。
集群会记录节点和槽的对应关系,解决了节点和槽的关系后,接下来就需要对 key 求哈希值,然后对 16384 取模,余数是几 key 就落入对应的槽里。HASH_SLOT = CRC16 (key) mod 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。
哈希槽计算
Redis 集群中内置了 16384 个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-valuel 时,redis 先对 key 使用 crc16 算法算出一个结果然后用结果对 16384 求余数 [ CRC16 (key) % 16384],这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key 之 A、B 在 Node2, key 之 C 落在 Node3 上
经典面试题
为什么 Redis 集群的最大槽数是 16384 个?
Redis 集群并没有使用一致性 hash 而是引入了哈希槽的概念。Redis 集群有 16384 个哈希糟,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。但为什么哈希槽的数量是 16384 (2^14)个呢?
CRC16 算法产生的 hash 值有 16bit,该算法可以产生 2^16=65536 个值。换句话说值是分布在 0~65535 之间,有更大的 65536 不用为什么只用 16384 就够?
作者在做 mod 运算的时候,为什么不 mod 65536,而选择 mod 16384?
HASH_SLOT = CRC16 (key) mod 65536 为什么没启用?
作者回答:https://github.com/redis/redis/issues/2576
说明 1:
正常的心跳数据包带有节点的完整配置,可以用幂等方式用旧的节点替换旧节点,以便更新旧的配置。 这意味着它们包含原始节点的插槽配置,该节点使用 2k 的空间和 16k 的插槽,但是会使用 8k 的空间(使用 65k 的插槽)。同时,由于其他设计折衷,Redis 集群不太可能扩展到 1000 个以上的主节点。
因此 16k 处于正确的范围内,以确保每个主机具有足够的插槽,最多可容纳 1000 个矩阵,但数量足够少,可以轻松地将插槽配置作为原始位图传播。请注意,在小型群集中,位图将难以压缩,因为当 N 较小时,位图将设置的 slot / N 位占设置位的很大百分比。
说明 2:
(1) 如果槽位为 65536,发送心跳信息的消息头达 8k,发送的心跳包过于庞大。
在消息头中最占空间的是 myslots[CLUSTER_SLOTS/8]。当槽位为 65536 时,这块的大小是:65536 ÷ 8 ÷ 1024=8kb
在消息头中最占空间的是 myslots[CLUSTER_SLOTS/8]。当槽位为 16384 时,这块的大小是:16384 ÷ 8 ÷ 1024=2kb
因为每秒钟,redis 节点需要发送一定数量的 ping 消息作为心跳包,如果槽位为 65536,这个 ping 消息的消息头太大了,浪费带宽。
(2) redis 的集群主节点数量基本不可能超过 1000 个。
集群节点越多,心跳包的消息体内携带的数据越多。如果节点过 1000 个,也会导致网络拥堵。因此 redis 作者不建议 redis cluster 节点数量超过 1000 个。那么,对于节点数在 1000 以内的 redis cluster 集群,16384 个槽位够用了。没有必要拓展到 65536 个。
(3) 槽位越小,节点少的情况下,压缩比高,容易传输。
Redis 主节点的配置信息中它所负责的哈希槽是通过一张 bitmap 的形式来保存的,在传输过程中会对 bitmap 进行压缩,但是如果 bitmap 的填充率 slots /N 很高的话 (N 表示节点数), bitmap 的压缩率就很低。如果节点数很少,而哈希槽数量很多的话,bitmap 的压缩率就很低。
计算结论
Redis 集群中内置了 16384 个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置 一个 key-value 时, redis 先对 key 使用 crc16 算法算出一个结果然后用结果对 16384 求余数 [ CRC16 (key) % 16384], 这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key 之 A、B 在 Node2, key 之 C 落在 Node3 上
Redis集群不保证强一致性
redis 集群不保证强一致性,这意味着在特定的条件下,Redis 集群可能会丢掉一些被系统收到的写入请求命令。
Redis主从扩容和缩容
扩容
将新增节点加入原集群
语法如下:
|
|
其中,命令选项和参数的含义如下:
redis-cli
: Redis命令行工具,用于执行Redis命令。--cluster add-node
: 添加节点到集群的命令选项,指示redis-cli执行添加节点的操作。<新节点IP>:<新节点端口>
: 新增节点的IP地址和端口号。这是指向新节点的地址。<任意一个集群中的节点IP>:<任意一个集群中的节点端口>
: 任意一个已经在集群中的节点的IP地址和端口号。这是用于连接到现有集群的一个节点。
执行这个命令后,Redis集群会将新节点添加到现有的集群中,并进行节点间的数据同步和分片调整,以确保数据的一致性和集群的稳定性。
检查Redis集群情况
语法如下:
|
|
其中,命令选项和参数的含义如下:
redis-cli
: Redis命令行工具,用于执行Redis命令。--cluster check
: 检查集群情况的命令选项,指示redis-cli执行检查集群状态的操作。<任意一个集群中的节点IP>:<任意一个集群中的节点端口>
: 任意一个已经在集群中的节点的IP地址和端口号。这是用于连接到现有集群的一个节点。
执行这个命令后,Redis集群会检查集群中所有节点的状态,包括主节点和从节点的信息,以及分片的分布情况。这个命令通常用于检查集群的健康状态和故障排查。
重新分配槽位
语法如下:
|
|
其中,命令选项和参数的含义如下:
redis-cli
: Redis命令行工具,用于执行Redis命令。--cluster reshard
: 重新分配槽位的命令选项,指示redis-cli执行重新分配槽位的操作。<任意一个集群中的节点IP>:<任意一个集群中的节点端口>
: 任意一个已经在集群中的节点的IP地址和端口号。这是用于连接到现有集群的一个节点。
执行这个命令后,Redis集群会进入重新分配槽位的模式,并提示用户输入相关的选项,包括要移动的槽位数量、目标节点等。用户根据需要输入相关选项后,集群会根据输入的信息重新分配槽位,并进行数据迁移,以实现槽位的重新分配。
槽位分派说明
为什么 6387 是 3 个新的区间,以前的还是连续?
重新分配成本太高,所以前 3 家各自匀出来一部分,从 6381 / 6383 / 6385 三个旧节点分别匀出 1367 个坑位给信节点 6387。
为新增的主节点添加从节点
语法如下:
|
|
其中,命令选项和参数的含义如下:
redis-cli
: Redis命令行工具,用于执行Redis命令。--cluster add-node
: 添加节点到集群的命令选项,指示redis-cli执行添加节点的操作。<新从节点IP>:<新从节点端口>
: 新增的从节点的IP地址和端口号。这是指向新增从节点的地址。<已存在的任一主节点IP>:<已存在的任一主节点端口>
: 已经在集群中存在的任意一个主节点的IP地址和端口号。这是用于连接到集群的一个主节点。--cluster-slave
: 指定新节点将作为从节点加入到集群中。
执行这个命令后,Redis集群会将新的从节点添加到指定的主节点上,并将其设置为从节点。新增的从节点会与主节点进行数据同步,成为主节点的备份。
缩容
在Redis集群中,如果要进行主节点的缩容(删除),需要注意以下槽位分派的说明:
-
手动迁移槽位: 在删除主节点之前,需要手动将该主节点上的所有槽位迁移到其他主节点上。可以使用
redis-cli --cluster reshard
命令来手动迁移槽位,将要删除主节点的槽位重新分配给其他主节点。这个过程可能需要一段时间,取决于槽位的数量和数据量的大小。 -
数据同步: 一旦槽位迁移完成,集群会自动进行数据同步,确保所有数据在集群中的一致性。这包括同步数据和同步复制偏移量等信息。
-
从节点提升: 在删除主节点之前,需要将与该主节点关联的所有从节点提升为主节点。这可以通过在从节点上执行
redis-cli --cluster replicate
命令来实现,将从节点升级为主节点。这样做可以确保数据的持久性和高可用性。 -
删除主节点: 一旦所有槽位迁移和数据同步都完成,并且从节点已经提升为主节点,就可以安全地删除要缩减的主节点了。可以通过执行
redis-cli --cluster del-node
命令来删除主节点,从而从集群中移除该节点。删除主节点后,集群会自动重新分配槽位和重新平衡数据,确保集群的稳定性和性能。
需要注意的是,在进行主节点缩容时,需要确保集群处于稳定状态,并且尽量避免影响生产环境的正常运行。可以采取逐步迭代的方式,先在测试环境进行验证,然后再在生产环境进行缩容操作。此外,在进行节点缩减时,需要特别注意数据的一致性和迁移过程中可能出现的问题,以确保集群的稳定性和可用性。