Redis集群模式
集群模式:主从模式、哨兵模式和分片集群模式。
众所周知,redis可以作为单机模式部署,支持大量用户访问,存在并发能力上线问题,在大规模并发场景下(如双11电商项目),存在性能瓶颈和宕机风险等问题,那么redis集群模式应运而生,解决单台redis服务器无法承受大规模高并发下的QPS压力。
主从模式
至少两台redis服务器,一主(master)一从(slave/replica)。
原理:实现读写分离,主节点负责写操作,从节点负责读操作。
为保持主从数据同步,从节点会定时更新主节点的数据。同步数据分为全量更新和增量更新。
主从同步原理:
全量同步原理:
第一阶段涉及知识点:
-
如何判断首次同步?
Replication Id
:简称replid,是数据集的标记,ID一致则代表是同一数据集。每个master都有自己唯一的replid,slave连接后会继承master的replid。Offset
:偏移量。随着记录在repl_backlog中的数据增多而增大。salve完成同步时,也会记录当前同步的offset。如果slave中的offset小于master的offset,则代表slave的数据落后于master,需要进行增量更新。总结:在此根据Replication Id即可判断是否首次同步。由于salve未执行replicaof指令前,slave的replid是自身id,执行replicaof指令后,会将replid=master id。
-
为要增加repl_backlog机制?
在进行全量备份时,redis必须执行rdb备份机制,会阻塞主节点,若阻塞期间执行写操作,可能丢失数据,所以将主节点进行rdb备份阻塞时,将随后的写操作记录在repl_backlog中。
使用命令实践:
在7002节点执行
replicaof 127.0.0.1 7001
主节点命令如下:
- 从节点首次连接主节点时,先尝试增量更新,主节点校验replid不匹配时,拒绝增量更新
- 生成repl_backlog文件
- 开始bgsave生成全量备份RDB文件
- 将RDB文件写入磁盘
从节点命令如下:
- 首先尝试连接主节点7001
- 尝试增量更新,并发送自身replid:offset
- 主节点拒绝后,接收来自主节点的replid:offset用来更新自身replid:offset
- 移除自身的缓存数据
- 加载RDB文件
增量同步原理
第二阶段知识点:
6. repl_backlog是什么数据结构?
是一种环形数组结构。主节点记录该数组的offset(偏移量),从节点进行备份时会同时更新offset(**从节点的offset永远小于等于主节点的offset**)。当从节点的offset小于主节点的offset时,代表从节点数据落后,需要进行增量更新数据。
相关repl_backlog配置如下:
```xml
# 复制缓冲区大小,这是一个环形复制缓冲区,用来保存最新复制的命令。
# 这样在slave离线的时候,不需要完全复制master的数据,
# 如果可以执行部分同步,只需要把缓冲区的部分数据复制给slave,就能恢复正常复制状态。
# 缓冲区的大小越大,slave离线的时间可以更长,复制缓冲区只有在有slave连接的时候才分配内存。
# 没有slave的一段时间,内存会被释放出来,默认1m
repl-backlog-size 1mb
# master没有slave一段时间会释放复制缓冲区的内存,repl-backlog-ttl用来设置该时间长度。单位为秒。
repl-backlog-ttl 3600
```
实现
-
准备两个不同port的redis节点A,B配置文件
-
使用
redis-server
/config-path 分别启动A,B节点 -
使用
redis-cli
连接B节点,执行replicaof host-b port-b
总结:
-
全量更新:
1、从节点首次连接上主节点时进行一次全量更新;2、当从节点宕机或长时间没更新主节点数据时,且当repl_backlog 中的主节点的offset(偏移量)第二次>= 从节点的offset时,导致部分主节点备份数据丢失,此时需要全量更新;
-
增量更新:
1、当从节点连上主节点后,且数据集ID一致时,使用offset(偏移量)进行增量更新;
缺点:
1、主节点无法故障自动恢复;
2、高并发写存在上限
哨兵模式
至少三台redis服务器,一主(master)一从(slave/replica)一哨兵(sentinel)。
:
该模式 = 主从模式 + 哨兵
哨兵也可以有多台,组成哨兵集群。哨兵之间互相监听各自存活(keeplive)状态。
哨兵作用:
- 监听节点状态,通过心跳机制不断检查主从节点的工作状态,默认每隔一秒ping一次;
- 故障自动恢复;
- 通知redis客户端,主从节点IP(故障恢复)发生变化;
故障恢复
- 主观下线:
当哨兵ping节点A时,超时时间内未响应,则认为节点A主观下线。 - 客观下线:
当哨兵集群内的**哨兵数量超过半数(默认)**认为节点A主观下线,则判定节点A为客观下线,需执行故障恢复机制。由quorum 参数决定多少数量的哨兵认为节点主观下线则判定为客观下线,quorum=( count(哨兵数量) / 2 ) + 1
选举Master机制
一旦发现master宕机,sentinel需要选举出先的master,依据如下:
-
首先判断slave节点各自离线时长,如果超过指定值(down-after-milliseconds*10),则有限排-除该slave节点。(因为离线时长久则代表数据越旧)
-
其次判断slave的优先级,由配置
slave-priority
值决定,越小优先级越高,如果是0,则永不参与选举。 -
紧接着判断slave的offset值(即repl_backlog中的offset),值越大优先级越高,代表数据越新。(较重要)
-
最后若上述都相同,则判断salve运行id大小,每个slave创建时系统生成的运行id。
故障转移机制
当哨兵选举出新master节点后,如何实现故障转移?
若宕机的主节点为A, 被选举的从节点为C, 其余从节点为B,D
-
首先,哨兵发送
slaveof no one
给节点C,让C成为master -
其次,sentinel发送
slaveof c-ip c-port
给B D节点,让B D 节点成为C的从节点 -
最后,将故障节点A标记为从节点,修改其配置文件,添加
slaveof c-ip c-port
指令。这样A重启后会自动成为C的从节点。
实现
-
准备两个不同port的redis节点A,B配置文件;在准备一个redis哨兵节点C配置文件
-
使用
redis-server
/config-path 分别启动A,B节点 -
使用
redis-sentinel
/sentinel-config-path 启动哨兵节点C -
使用
redis-cli
连接B节点,执行replicaof host-b port-b
## 哨兵配置文件 port 6383 daemonize yes # mymaster 名称; <master-ip> 主节点IP;<master-port>主节点Port;<quorum>选举数=哨兵数量/2+1 sentinel monitor mymaster <master-ip> <master-port> <quorum> sentinel down-after-milliseconds mymaster 5000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 10000
总结:
- 优点:
- 解决节点高可用
- 解决高并发读
- 缺点:
- 海量数据存储问题 (由于主从模式需要同步数据,若单节点redis服务器设置内存过大,必将生成很大的RDB文件,消耗大量IO资源,所以一般会设置内存小一些的单节点服务器,那么海量数据就无法支持。)
- 高并发写存在上限问题 (只有单个主节点支持写操作,并发能力存在上限)
分片集群模式
至少两台redis服务器,一主(master)+ 一主(master)。
仅存在主节点和从节点,主节点之间互相发送ping心跳
监听各自存活(keeplive)状态。
分片集群特征
-
存在多个master,每个master保存不同的数据(
通过散列插槽离散数据
),支持高并发写,一个master支持20G内存的话,多个master组成可支持20 * n的内存,即支持海量资源存储问题。(解决哨兵模式的缺点) -
每个master都支持多个从几点,即支持高并发读。
-
每个master之间互相发送
ping心跳
监听各自存活(keeplive)状态。支持故障节点主从切换(类似哨兵作用
) -
客户端访问任一集群节点,最终都被路由到正确的节点上。
散列插槽
redis会将每一个master节点映射到【0-16383】共16384个插槽(hash slot)上。redis的key是存在插槽上。
-
插槽可重新分配
-
redis中的key是基于hash散列到各个插槽中,基于有效值hash算法
{}
-> 括号内的才算有效值。eg:
{user}test:1 -> 有效值为 user;不具备大括号的
-> 整个key作为有效值。eg:
usertest:1 -> 有效值为 usertest:1;根据有效值,利用CRC16算法得到一个hash,再对16384取余,达到散列数据。
集群伸缩
顾名思义:向集群添加节点或删除节点。
向集群添加节点和删除节点指令如下:
add-node new_host:new_port existing_host:existing_port --添加节点
--(需要指定一对已经存在集群的host和port,用来获取集群信息通知集群各个节点)
--cluster-slave --若不指定,则默认为主节点
--cluster-master-id <arg> --指定的集群主节点的ID
del-node host:port node_id --删除节点
reshard host:port --移动插槽节点
--cluster-from <arg>
--cluster-to <arg>
--cluster-slots <arg>
--cluster-yes
--cluster-timeout <arg>
--cluster-pipeline <arg>
--cluster-replace
故障恢复
1、当集群中有一个master宕机,如何恢复?
-
首先该实例与其他实例断开连接
-
其次,该节点从疑似宕机
fail?
转为 确认宕机fail
-
最后,自动将一个slave提升为新的master
2、当需要升级节点A时,可手动执行故障转移。可实现无感知节点升级
-
首先,需要新增redis节点B,让节点B 作为节点A的从节点,即 replicaof 节点A-IP 节点 A-port
-
其次,在节点B客户端执行
cluster failover
指令,即可让节点B成为主节点
原理时序图如下:
FailOver指令支持三种不同的模式:
-
缺省:默认的模式,如图:1-6步骤。(一般使用默认即可)
-
force:省略对offset的一致性校验,即跳过步骤2,3。不管数据是否一致,直接开始故障转移
-
tackover:直接执行第5步骤,忽略数据一致性、忽略master的状态和其他master的意见。
实现
1 搭建分片集群
(1)首先创建基础配置文件redis.conf
[root@centos7 shard-cluster]# cat redis.conf
port 6379
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /root/env/redis/cluster-mode/shard-cluster/6379/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /root/env/redis/cluster-mode/shard-cluster/6379
# 绑定地址
bind 0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip
replica-announce-ip 172.28.0.126
# 保护模式
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /root/env/redis/cluster-mode/shard-cluster/6379/run.log
(2)3主3从的分片集群,创建对应的端口文件夹mkdir 7001 7002 7003 8001 8002 8003
(3)copy基础配置文件redis.conf到上述文件夹,使用管道命令,统一copy
# 该命令的作用是将 `redis.conf` 文件复制到指定的目录下。
echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf
解释一下命令的各个参数和选项:
- `echo 7001 7002 7003 8001 8002 8003`:通过 `echo` 命令输出一串空格分隔的端口号。
- `|`:管道操作符,将前一个命令的输出作为后一个命令的输入。
- `xargs`:从标准输入读取参数,并将其作为命令行参数传递给后续的命令。
- `-t`:打印 `xargs` 命令执行的每个命令。
- `-n 1`:每次传递一个参数执行命令。
- `cp redis.conf`:`cp` 命令用于复制文件,将 `redis.conf` 复制到指定的目录下。
因此,整个命令的含义是将 `redis.conf` 文件复制到每个指定的端口号对应的目录下。
假设 `redis.conf` 文件位于当前目录,执行该命令时,会将 `redis.conf` 复制到以下目录:
- `7001/redis.conf`
- `7002/redis.conf`
- `7003/redis.conf`
- `8001/redis.conf`
- `8002/redis.conf`
- `8003/redis.conf`
(4)批量替换各个配置文件中的6379
端口和目录
#该命令的作用是通过 `sed` 命令在每个 `redis.conf` 文件中将 `6379` 替换为指定的端口号。
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -t -I{} sed -i 's/6379/{}' {}/redis.conf
解释一下命令的各个参数和选项:
- `printf '%s\n' 7001 7002 7003 8001 8002 8003`:通过 `printf` 命令输出每个端口号,并以换行符分隔。
- `|`:管道操作符,将前一个命令的输出作为后一个命令的输入。
- `xargs`:从标准输入读取参数,并将其作为命令行参数传递给后续的命令。
- `-I{}`:指定占位符 `{}`,用于替换每个参数的位置。
- `-t`:打印 `xargs` 命令执行的每个命令。
- `sed -i 's/6379/{}/g' {}/redis.conf`:`sed` 命令用于在文件中进行文本替换。`-i` 选项表示直接修改源文件,而不是将结果输出到标准输出。`'s/6379/{}/g'` 是替换规则,将文件中的 `6379` 替换为占位符 `{}` 的值。`{}/redis.conf` 指定了要替换的文件路径。
-
假设当前目录下有多个 `redis.conf` 文件,执行该命令时,会对每个 `redis.conf` 文件进行替换操作,将其中的 `6379` 替换为指定的端口号。
(5)批量启动6个redis节点
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -t -I{} sed -i 's/6379/{}/g' {}/redis.conf
(6)执行redis-cli --cluster create
指令,创建分片集群
[root@centos7 shard-cluster] redis-cli --cluster create --cluster-replicas 1 \
172.28.0.126:7001 172.28.0.126:7002 172.28.0.126:7003 \
172.28.0.126:8001 172.28.0.126:8002 172.28.0.126:8003
语法如下:
redis-cli --cluster create <host1>:<port1> \
<host2>:<port2> ... <hostN>:<portN> --cluster-replicas <replicas>
其中 <host1>:<port1>、<host2>:<port2> 等表示 Redis 节点的主机名或 IP 地址以及端口号。
<replicas> 是可选的,它指定了每个主节点应该有多少个从节点。如果不指定 <replicas>,则默认为1。
请注意,创建 Redis 集群需要至少3个主节点,并且端口号应该是不同的
(7)查看集群节点状态
[root@centos7 shard-cluster]# redis-cli -p 7001 cluster nodes
2fd5e2de589142890d1892af64937cd458fc7c64 172.28.0.126:8002@18002 slave b080d97b7d3a114558e4c55da3f9173a73a1c980 0 1707899774000 2 connected
a4aa04ef50af3264f027ab220486832232b11f44 172.28.0.126:7003@17003 master - 0 1707899774569 3 connected 10923-16383
b080d97b7d3a114558e4c55da3f9173a73a1c980 172.28.0.126:7002@17002 master - 0 1707899774000 2 connected 5461-10922
5b8c1d18b60729c9ee6db549997eed7d783a0aec 172.28.0.126:7001@17001 myself,master - 0 1707899775000 1 connected 0-5460
ce0138f7ab6d0ed4f234d85a066eb6aceef2ed22 172.28.0.126:8001@18001 slave 5b8c1d18b60729c9ee6db549997eed7d783a0aec 0 1707899775672 1 connected
6e04491088eb89a31c83f41a208abf6a0016e7ba 172.28.0.126:8003@18003 slave a4aa04ef50af3264f027ab220486832232b11f44 0 1707899774669 3 connected
2 分片集群下,使用普通连接设置key,导致失败,连接时必须指定-c
使用集群链接模式
3 添加新的master,并将指定数据移动到新master节点
(1)首先使用帮助命令,查看集群相关命令
[root@centos7 shard-cluster]# redis-cli -p 7001 --cluster help
Cluster Manager Commands:
create host1:port1 ... hostN:portN #创建集群
--cluster-replicas <arg> #从节点个数
check host:port #检查集群
--cluster-search-multiple-owners #检查是否有槽同时被分配给了多个节点
info host:port #查看集群状态
fix host:port #修复集群
--cluster-search-multiple-owners #修复槽的重复分配问题
reshard host:port #指定集群的任意一节点进行迁移slot,重新分slots
--cluster-from <arg> #需要从哪些源节点上迁移slot,可从多个源节点完成迁移,以逗号隔开,传递的是节点的node id,还可以直接传递--from all,这样源节点就是集群的所有节点,不传递该参数的话,则会在迁移过程中提示用户输入
--cluster-to <arg> #slot需要迁移的目的节点的node id,目的节点只能填写一个,不传递该参数的话,则会在迁移过程中提示用户输入
--cluster-slots <arg> #需要迁移的slot数量,不传递该参数的话,则会在迁移过程中提示用户输入。
--cluster-yes #指定迁移时的确认输入
--cluster-timeout <arg> #设置migrate命令的超时时间
--cluster-pipeline <arg> #定义cluster getkeysinslot命令一次取出的key数量,不传的话使用默认值为10
--cluster-replace #是否直接replace到目标节点
rebalance host:port #指定集群的任意一节点进行平衡集群节点slot数量
--cluster-weight <node1=w1...nodeN=wN> #指定集群节点的权重
--cluster-use-empty-masters #设置可以让没有分配slot的主节点参与,默认不允许
--cluster-timeout <arg> #设置migrate命令的超时时间
--cluster-simulate #模拟rebalance操作,不会真正执行迁移操作
--cluster-pipeline <arg> #定义cluster getkeysinslot命令一次取出的key数量,默认值为10
--cluster-threshold <arg> #迁移的slot阈值超过threshold,执行rebalance操作
--cluster-replace #是否直接replace到目标节点
add-node new_host:new_port existing_host:existing_port #添加节点,把新节点加入到指定的集群,默认添加主节点, 需要已存在集群节点的ip:port来获取集群信息
--cluster-slave #新节点作为从节点,默认随机一个主节点
--cluster-master-id <arg> #给新节点指定主节点
del-node host:port node_id #删除给定的一个节点,成功后关闭该节点服务
call host:port command arg arg .. arg #在集群的所有节点执行相关命令
set-timeout host:port milliseconds #设置cluster-node-timeout
import host:port #将外部redis数据导入集群
--cluster-from <arg> #将指定实例的数据导入到集群
--cluster-copy #migrate时指定copy
--cluster-replace #migrate时指定replace
help
(2)使用add node
指令,添加新的节点
[root@centos7 shard-cluster] redis-cli -p 7001 --cluster add-node 127.0.0.1:7004 127.0.0.1:7001
>>> Adding node 127.0.0.1:7004 to cluster 127.0.0.1:7001
>>> Performing Cluster Check (using node 127.0.0.1:7001)
M: 5b8c1d18b60729c9ee6db549997eed7d783a0aec 127.0.0.1:7001
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 2fd5e2de589142890d1892af64937cd458fc7c64 172.28.0.126:8002
slots: (0 slots) slave
replicates b080d97b7d3a114558e4c55da3f9173a73a1c980
M: a4aa04ef50af3264f027ab220486832232b11f44 172.28.0.126:7003
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
M: b080d97b7d3a114558e4c55da3f9173a73a1c980 172.28.0.126:7002
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: ce0138f7ab6d0ed4f234d85a066eb6aceef2ed22 172.28.0.126:8001
slots: (0 slots) slave
replicates 5b8c1d18b60729c9ee6db549997eed7d783a0aec
S: 6e04491088eb89a31c83f41a208abf6a0016e7ba 172.28.0.126:8003
slots: (0 slots) slave
replicates a4aa04ef50af3264f027ab220486832232b11f44
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 127.0.0.1:7004 to make it join the cluster.
[OK] New node added correctly.
(3)查看集群节点信息
上图可看出,若新加入的节点未指定是从节点,则默认为主节点,且默认没有分配槽位。
(4)为新加入的master分配槽位
上图可看出k2
在7001节点,且槽位为449。那么我们将 0-600的槽位从7001 转移到7004节点中,使用reshard
指令。其指令用法:
--cluster reshard host:port #指定集群的任意一节点进行迁移slot,重新分slots
--cluster-from <arg> #需要从哪些源节点上迁移slot,可从多个源节点完成迁移,以逗号隔开,传递的是节点的node id,还可以直接传递--from all,这样源节点就是集群的所有节点,不传递该参数的话,则会在迁移过程中提示用户输入
--cluster-to <arg> #slot需要迁移的目的节点的node id,目的节点只能填写一个,不传递该参数的话,则会在迁移过程中提示用户输入
--cluster-slots <arg> #需要迁移的slot数量,不传递该参数的话,则会在迁移过程中提示用户输入。
--cluster-yes #指定迁移时的确认输入
--cluster-timeout <arg> #设置migrate命令的超时时间
--cluster-pipeline <arg> #定义cluster getkeysinslot命令一次取出的key数量,不传的话使用默认值为10
--cluster-replace #是否直接replace到目标节点
[root@centos7 shard-cluster] redis-cli --cluster reshard 127.0.0.1:7002 \ #reshard 指定集群的任意一节点进行迁移slot
--cluster-from 5b8c1d18b60729c9ee6db549997eed7d783a0aec \ #需要从哪些源节点上迁移slot,可从多个源节点完成迁移,以逗号隔开,传递的是节点的node id
--cluster-to e3d8a07850f61dc4d81f005910f26c614a7af8d8 \ #slot需要迁移的目的节点的node id,目的节点只能填写一个
--cluster-slots 600 #需要迁移的slot数量
至此,槽位迁移成功。
停止所有redis实例:printf '%s\n' 7001 7002 7003 7004 8001 8002 8003 | xargs -t -I{} redis-cli -p {} shutdown
4 删除master节点——需要将当前master槽位迁移到其他节点上,再删除
5 故障转移
步骤如下:
- 当前master7001宕机,cluster nodes 出现
master fail?
疑似宕机提示 - 经集群内部确认,将疑似
master fail?
改为确认宕机master fail
- 将7001节点断开和其他master的连接
- 自动将一个slave提升为master
- 后续7001节点启动后,将会作为slave节点。
- 可手动再slave节点执行
cluster failover
,即手动故障转移,将执行该命令的节点升级为主节点。
尾声
笔记技术有限,有错误描述请及时指出。
文章作为知识学习记录,望诸君共勉