Redis集群模式

集群模式:主从模式、哨兵模式和分片集群模式。

众所周知,redis可以作为单机模式部署,支持大量用户访问,存在并发能力上线问题,在大规模并发场景下(如双11电商项目),存在性能瓶颈和宕机风险等问题,那么redis集群模式应运而生,解决单台redis服务器无法承受大规模高并发下的QPS压力。

主从模式

至少两台redis服务器,一主(master)一从(slave/replica)。

原理:实现读写分离,主节点负责写操作,从节点负责读操作。

为保持主从数据同步,从节点会定时更新主节点的数据。同步数据分为全量更新和增量更新。

主从同步原理:

全量同步原理:

redis sentinel 集群状态检查命令 查看redis集群模式_缓存


第一阶段涉及知识点:

  • 如何判断首次同步?
    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

主节点命令如下:

redis sentinel 集群状态检查命令 查看redis集群模式_缓存_02

  • 从节点首次连接主节点时,先尝试增量更新,主节点校验replid不匹配时,拒绝增量更新
  • 生成repl_backlog文件
  • 开始bgsave生成全量备份RDB文件
  • 将RDB文件写入磁盘

从节点命令如下:

redis sentinel 集群状态检查命令 查看redis集群模式_数据_03

  1. 首先尝试连接主节点7001
  2. 尝试增量更新,并发送自身replid:offset
  3. 主节点拒绝后,接收来自主节点的replid:offset用来更新自身replid:offset
  4. 移除自身的缓存数据
  5. 加载RDB文件

增量同步原理

redis sentinel 集群状态检查命令 查看redis集群模式_配置文件_04


第二阶段知识点:

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
```

实现

  1. 准备两个不同port的redis节点A,B配置文件
  2. 使用redis-server /config-path 分别启动A,B节点
  3. 使用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的从节点。

实现

  1. 准备两个不同port的redis节点A,B配置文件;在准备一个redis哨兵节点C配置文件
  2. 使用redis-server /config-path 分别启动A,B节点
  3. 使用redis-sentinel /sentinel-config-path 启动哨兵节点C
  4. 使用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成为主节点

原理时序图如下:

redis sentinel 集群状态检查命令 查看redis集群模式_数据_05


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使用集群链接模式

redis sentinel 集群状态检查命令 查看redis集群模式_配置文件_06

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)查看集群节点信息

redis sentinel 集群状态检查命令 查看redis集群模式_redis_07


上图可看出,若新加入的节点未指定是从节点,则默认为主节点,且默认没有分配槽位。(4)为新加入的master分配槽位

redis sentinel 集群状态检查命令 查看redis集群模式_数据_08


上图可看出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 sentinel 集群状态检查命令 查看redis集群模式_redis_09


至此,槽位迁移成功。

停止所有redis实例:printf '%s\n' 7001 7002 7003 7004 8001 8002 8003 | xargs -t -I{} redis-cli -p {} shutdown

4 删除master节点——需要将当前master槽位迁移到其他节点上,再删除
5 故障转移

步骤如下:

  1. 当前master7001宕机,cluster nodes 出现 master fail? 疑似宕机提示
  2. 经集群内部确认,将疑似master fail?改为确认宕机master fail
  3. 将7001节点断开和其他master的连接
  4. 自动将一个slave提升为master
  5. 后续7001节点启动后,将会作为slave节点。
  6. 可手动再slave节点执行cluster failover ,即手动故障转移,将执行该命令的节点升级为主节点。

尾声

笔记技术有限,有错误描述请及时指出。

文章作为知识学习记录,望诸君共勉