一:集群
1.1 基本概念
上述哨兵模式提升了系统的可用性,但存储数据的焦点依然是主节点和从节点。所有数据都必要存储在单个主节点和从节点中,当数据量过大靠近或超出机器的物理内存时,大概会导致严峻问题。虽然硬件成本不停低落,部分企业的服务器内存已经达到 TB 级别,但在当今“大数据”时代,1TB 内存往往不敷以满足需求。我们的解决方法很简单:增加机器!“大数据”的焦点思想就是一台机器无法搞定时,用多台机器来分担。基于这一思绪,Redis 引入了集群模式(Cluster),通过多组主从节点存储数据全集的不同部分,从而构成一个更大的团体,即 Redis 集群。例如,如果整个数据全集为 1TB,利用三组主从节点存储数据,那么每组只需存储全集的三分之一即可。
主从节点组数据内容占总数据比例Master1, Slave11, Slave12生存相同的数据占总数据的 1/3Master2, Slave21, Slave22生存相同的数据占总数据的 1/3Master3, Slave31, Slave32生存相同的数据占总数据的 1/3 这三组机器存储的数据彼此不同,每个从节点都是对应主节点的备份。当主节点发生故障时,其对应的从节点会补位成为新的主节点。每个红框部分可以被称为一个分片(Sharding)。如果全量数据进一步增加,只需通过增加更多的分片即可解决存储需求问题,从而实现数据的水平扩展。
1.2 数据分片算法
Redis Cluster 的焦点思想是通过多组机器存储数据的不同部分,因此关键问题在于如何确定数据的分片位置。具体来说,给定一个数据(即某个具体的 key),必要明确它应该存储在哪个分片,以及在读取时应该从哪个分片获取数据。针对这一问题,业界主要有三种主流的实现方式。
1.2.1 哈希求余
假设有 N 个分片,利用 [0, N-1] 的序号进行编号。对于给定的 key,首先计算其 hash 值,然后对 N 取模(% N),得到的效果即为对应的分片编号。例如,当 N 为 3,给定 key 为 “hello”,通过计算其 hash 值(例如利用 MD5 算法)得到效果为 bc4b2a76b9719d91,再对 3 取模,效果为 0,因此将 “hello” 这个 key 存储在 0 号分片上。同样,在必要读取某个 key 的记载时,也通过计算其 hash 值并对 N 取模,就能快速找到对应的分片编号。实际系统中计算 hash 的方式大概不是 MD5,但焦点思想是一致的。
该方法的长处是简单高效且数据分布均匀,但缺点在于扩容时会引发较大的数据迁移成本。当分片数量 N 改变时,原有的映射规则被破坏,必要节点之间相互传输和重新排列数据,以满足新的映射规则,导致数据搬迁量大,开销较高。例如,当 N 为 3 时,[100, 120] 这 21 个 hash 值均匀分布在 3 个分片上(假设 hash 值为简单整数,便于观察);但当新增一个分片,N 从 3 增加到 4 时,由于 key % 3 和 key % 4 的效果不同,大量 key 必要重新映射到新机器,增加了数据搬迁量。
1.2.2⼀致性哈希算法
为了解决扩容时大量数据搬迁的问题,业界提出了一致性哈希算法。与传统的取模方式不同,一致性哈希算法通过改进 key 映射到分片序号的方式,显着低落了扩容或缩容时的数据搬迁量,仅需重新映射少部分数据,从而实现更高效的扩容和数据分布。
- 将数据空间 [0, 2^32-1] 映射到一个圆环上,数据按照顺时针方向递增。
- 假设当前存在三个分片,则将这三个分片映射到圆环上的指定位置。
- 假设有一个 key,其 hash 值为 H,那么该 key 的映射规则是:从 H 所在位置开始,沿圆环顺时针查找,找到的第一个分片即为该 key 所属的分片。
- 这相当于将圆环按照 N 个分片的位置划分为 N 个管辖区间,key 的 hash 值落在哪个区间内,就归该区间对应的分片管理。
- 在这种环境下,如果必要扩容一个分片,可以直接在圆环上新增一个分片的位置,而原有分片的位置保持不变即可。
1.2.3 哈希槽分区算法 (Redis 利用)
为了解决上述问题(搬迁成本高和数据分布不均),Redis Cluster 引入了哈希槽(hash slots)算法。该算法将整个哈希空间划分为 16384 个槽位(即 [0, 16383]),并将这些槽位较为均匀地分配给各个分片。每个分片的节点都会记载自己持有的哈希槽范围,假设当前有三个分片,一种大概的分配方式是:
- 0 号分⽚: [0, 5461] 共 5462 个槽位
- 1 号分⽚: [5462, 10923] 共 5462 个槽位
- 2 号分⽚: [10924, 16383] 共 5460 个槽位
如果必要扩容,比如新增一个 3 号分片,可以通过重新分配原有槽位来实现扩展。例如,从每个现有分片中分出一部分槽位分配给新分片。这种方式不仅可以大概保持数据的均匀分布,还能最大程度淘汰数据迁移量。在实际利用 Redis 集群分片时,无需手动指定哪些槽位分配给某个分片,只需指定某个分片应持有的槽位数量,Redis 会自动完成槽位的分配以及相关 key 的搬运工作。
- 0 号分⽚: [0, 4095] 共 4096 个槽位
- 1 号分⽚: [5462, 9557] 共 4096 个槽位
- 2 号分⽚: [10924, 15019] 共 4096 个槽位
- 3 号分⽚: [4096, 5461] + [9558, 10923] + [15019, 16383] 共 4096 个槽位
1.3 基于 Docker 集群搭建
接下来将基于 Docker 搭建一个 Redis 集群,其中每个节点都对应一个容器。集群的拓扑结构如下:
1.3.1 创建目次和设置
- 创建 redis-cluster ⽬录,内部创建两个文件:
- redis-cluster/
- ├── docker-compose.yml
- └── generate.sh
复制代码- # 创建 9 个 Redis 节点配置文件
- for port in $(seq 1 9); do
- # 创建对应节点的目录
- mkdir -p redis${port}/
- # 创建 Redis 配置文件
- touch redis${port}/redis.conf
- # 写入配置文件内容
- cat << EOF > redis${port}/redis.conf
- port 6379 # Redis 节点对外服务的端口
- bind 0.0.0.0 # 允许外部访问
- protected-mode no # 关闭保护模式
- appendonly yes # 开启 AOF 持久化
- cluster-enabled yes # 启用集群模式
- cluster-config-file nodes.conf # 集群节点的配置文件
- cluster-node-timeout 5000 # 集群节点通信超时时间(毫秒)
- cluster-announce-ip 172.30.0.10${port} # 每个节点的对外 IP 地址(注意 IP 动态变化)
- cluster-announce-port 6379 # 节点对外服务的 Redis 端口
- cluster-announce-bus-port 16379 # 节点的集群总线通信端口
- EOF
- done
- # 创建第 10 和第 11 个 Redis 节点配置文件(注意 IP 格式不同)
- for port in $(seq 10 11); do
- # 创建对应节点的目录
- mkdir -p redis${port}/
- # 创建 Redis 配置文件
- touch redis${port}/redis.conf
- # 写入配置文件内容
- cat << EOF > redis${port}/redis.conf
- port 6379 # Redis 节点对外服务的端口
- bind 0.0.0.0 # 允许外部访问
- protected-mode no # 关闭保护模式
- appendonly yes # 开启 AOF 持久化
- cluster-enabled yes # 启用集群模式
- cluster-config-file nodes.conf # 集群节点的配置文件
- cluster-node-timeout 5000 # 集群节点通信超时时间(毫秒)
- cluster-announce-ip 172.30.0.1${port} # 每个节点的对外 IP 地址(注意 IP 动态变化)
- cluster-announce-port 6379 # 节点对外服务的 Redis 端口
- cluster-announce-bus-port 16379 # 节点的集群总线通信端口
- EOF
- done
复制代码
- 执行 bash generate.sh 命令后天生如下目次:
- redis-cluster/
- ├── docker-compose.yml # Docker Compose 配置文件,用于定义集群的服务
- ├── generate.sh # 用于批量生成 Redis 配置文件的脚本
- ├── redis1/ # Redis 节点 1 的配置目录
- │ └── redis.conf # Redis 节点 1 的配置文件
- ├── redis2/ # Redis 节点 2 的配置目录
- │ └── redis.conf # Redis 节点 2 的配置文件
- ├── redis3/ # Redis 节点 3 的配置目录
- │ └── redis.conf # Redis 节点 3 的配置文件
- ├── redis4/ # Redis 节点 4 的配置目录
- │ └── redis.conf # Redis 节点 4 的配置文件
- ├── redis5/ # Redis 节点 5 的配置目录
- │ └── redis.conf # Redis 节点 5 的配置文件
- ├── redis6/ # Redis 节点 6 的配置目录
- │ └── redis.conf # Redis 节点 6 的配置文件
- ├── redis7/ # Redis 节点 7 的配置目录
- │ └── redis.conf # Redis 节点 7 的配置文件
- ├── redis8/ # Redis 节点 8 的配置目录
- │ └── redis.conf # Redis 节点 8 的配置文件
- ├── redis9/ # Redis 节点 9 的配置目录
- │ └── redis.conf # Redis 节点 9 的配置文件
- ├── redis10/ # Redis 节点 10 的配置目录
- │ └── redis.conf # Redis 节点 10 的配置文件
- ├── redis11/ # Redis 节点 11 的配置目录
- │ └── redis.conf # Redis 节点 11 的配置文件
复制代码
- 每个 redis.conf 文件的内容险些相同,唯一的区别在于每个设置文件中的 cluster-announce-ip 设置不同,以区分每个节点的 IP 地址。
- port 6379 # Redis 服务端口,用于客户端连接
- bind 0.0.0.0 # 绑定所有网络接口,允许外部访问
- protected-mode no # 关闭保护模式,适用于集群模式
- appendonly yes # 开启 AOF(Append Only File)持久化
- cluster-enabled yes # 启用集群模式
- cluster-config-file nodes.conf # 存储集群节点配置的文件,Redis 会自动生成和管理
- cluster-node-timeout 5000 # 集群节点之间通信的超时时间(毫秒)
- cluster-announce-ip 172.30.0.101 # 节点对外广播的 IP 地址,用于集群间通信
- cluster-announce-port 6379 # 节点对外广播的服务端口
- cluster-announce-bus-port 16379 # 节点用于集群总线通信的端口
复制代码 1.3.2 编写 docker-compose.yml
首先创建网络,并将网段分配为 172.30.0.0/24。接下来设置每个节点,包括设置文件映射、端口映射以及容器的固定 IP 地址。设置固定 IP 地址是为了便于后续的观察和操作。端口映射可以选择设置或不设置,设置的目标是方便通过宿主机的 IP 加映射的端口进行访问,而通过容器自身的 IP 地址加默认端口 6379 也同样可以访问。
编写完后启动容器:
1.3.3 构建集群
- # 使用 redis-cli 创建 Redis 集群
- redis-cli --cluster create \
- 172.30.0.101:6379 \ # 节点 1 的 IP 和端口
- 172.30.0.102:6379 \ # 节点 2 的 IP 和端口
- 172.30.0.103:6379 \ # 节点 3 的 IP 和端口
- 172.30.0.104:6379 \ # 节点 4 的 IP 和端口
- 172.30.0.105:6379 \ # 节点 5 的 IP 和端口
- 172.30.0.106:6379 \ # 节点 6 的 IP 和端口
- 172.30.0.107:6379 \ # 节点 7 的 IP 和端口
- 172.30.0.108:6379 \ # 节点 8 的 IP 和端口
- 172.30.0.109:6379 \ # 节点 9 的 IP 和端口
- --cluster-replicas 2 # 为每个主节点分配 2 个从节点
复制代码 执行创建命令后,各容器会自动加入集群,此时客户端毗连集群中的任意节点,就相当于毗连了整个集群。客户端毗连时必要利用 -c 选项,否则当操作的 key 不属于当前节点时,无法完成哀求。而利用 -c 选项可以自动将哀求重定向到对应的节点。别的,通过执行 cluster nodes 命令,可以查看整个集群的状态和节点分布环境。
1.3.4 故障判定
集群中的所有节点会通过定期发送心跳包来进行通信。
步调形貌1节点 A 向节点 B 发送 ping 包,节点 B 会返回一个 pong 包。ping 和 pong 除了消息范例不同外,其他内容相同,包罗集群设置信息(如节点 ID、所属分片、节点角色是主节点还是从节点、从节点的主节点 ID、持有的哈希槽位图等)。2每个节点每秒钟会随机向部分节点发送 ping 包,而不是向所有节点发送,避免当节点数量较多时心跳包过多。例如,在 9 个节点的集群中,如果每个节点都向其他所有节点发送心跳包,则会产生 72 组通信,且通信量随节点数按 N^2 增长。3当节点 A 向节点 B 发送 ping 包,但未能收到 B 的 pong 相应时,A 会尝试重置与 B 的 TCP 毗连。如果重置后仍然失败,A 会将 B 标志为 PFAIL 状态(即主观下线)。4A 将 B 标志为 PFAIL 后,会通过 Redis 内置的 Gossip 协议与其他节点通信,确认 B 的状态。每个节点维护自己的“下线列表”,但由于网络视角不同,各节点的下线列表大概有所不同。5如果 A 发现多数节点(超过集群总节点数的一半)也认为 B 是 PFAIL,则 A 会将 B 标志为 FAIL(即客观下线),并将此消息同步到其他节点,其他节点接收后也会将 B 标志为 FAIL。 至此,节点 B 被彻底判定为故障节点。在某些环境下,节点的故障大概会导致整个集群进入宕机状态(称为 fail 状态)。以下三种环境会导致集群宕机:第一,某个分片的所有主节点和从节点都故障;第二,某个分片的主节点故障,但没有从节点进行替补;第三,超过半数的主节点故障。集群的焦点原则是确保每个哈希槽(slots)都能正常工作,包管数据的存取功能不受影响。
1.3.5 故障迁移
当节点 B 故障,并且节点 A 将 B 的 FAIL 消息同步给集群中的其他节点时,根据 B 的角色处理方式不同:如果 B 是从节点,则无需进行故障迁移;如果 B 是主节点,则由 B 的从节点(如 C 和 D)触发故障迁移。故障迁移的过程是将从节点提升为主节点,以继续支持 Redis 集群的正常运行。
步调形貌1从节点会首先判断自己是否具有参选资格。如果从节点与主节点通信中断的时间超过设定阈值(数据差别过大),则失去竞选资格。2具有参选资格的从节点(如 C 和 D)会进入休眠状态一段时间。休眠时间由以下公式计算:500ms 底子时间 + [0, 500ms] 的随机时间 + 排名 * 1000ms。排名基于数据同步偏移量(offset),offset 值越大,排名越靠前(休眠时间越短)。3当某个从节点(如 C)的休眠时间竣事后,C 会向集群中的所有节点发起拉票哀求。只有主节点才有投票资格。4主节点会将自己的票投给 C(每个主节点只有 1 票)。当 C 得到的票数超过主节点总数的一半时,C 会被提升为新的主节点。C 随后会执行 slaveof no one,并指示 D 执行 slaveof C,完成主从切换。5C 会将自己成为主节点的信息同步给集群中的其他节点,所有节点都会更新其本地生存的集群结构信息。 1.3.6 集群扩容
扩容是开发中常见的场景之一。随着业务的发展,现有集群大概无法容纳日益增长的数据,这时可以通过加入更多的节点来扩展集群,增加存储容量和计算能力。本质上,分布式系统的焦点思想就是通过引入更多的机器和硬件资源来满足不停增长的需求。
前面已经将 redis1 至 redis9 重新组建为集群,接下来必要将 redis10 和 redis11 加入集群。其中,将 redis10 设置为主节点,redis11 设置为其从节点。
- redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379
复制代码- redis-cli --cluster reshard 172.30.0.101:6379
复制代码 在执行 reshard 操作时,提供的地址可以是集群中的任意节点地址。执行后将进入交互式操作,Redis 会提示用户输入以下信息:必要迁移的 slots 数量(此处填写 4096);接收这些 slots 的目标节点(填写 172.30.0.110 的集群节点 ID);以及必要从哪些节点迁移 slots(填写 all,体现从集群中的所有节点进行搬运)。
目前集群已新增主节点,开端实现了扩容目标。但为了提升集群的高可用性,还必要为新的主节点添加从节点,以确保主节点宕机时,从节点可以大概顶替其角色。操作完成后,从节点会成功添加到集群中,保障了集群的容错能力。
- redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 \ # 将新节点加入到 Redis 集群
- --cluster-slave \ # 指定新加入的节点为从节点
- --cluster-master-id [172.30.0.110 节点的 nodeId] # 指定该从节点的主节点 ID(需要填写主节点 172.30.0.110 的 nodeId)
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |