🌟 后端 | Redis 集群



单节点的 Redis 并发能力是有上限的,要进一步提高 Redis 的并发能力,就需要搭建主从集群,实现读写分离。

集群中有一个 master 节点、两个 slave 节点(现在叫 replica)。当我们通过 Redis 的 Java 客户端访问主从集群时,应该做好路由:

  • 如果是写操作,应该访问master节点,master会自动将数据同步给两个slave节点
  • 如果是读操作,建议访问各个slave节点,从而分担并发压力

搭建主从集群

使用 docker-compose.yaml 构建著丛集群,直接放在 /root/redis 目录下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
version: "3.2"

services:
r1:
image: redis
container_name: r1
network_mode: "host"
entrypoint: ["redis-server", "--port", "7001"]
r2:
image: redis
container_name: r2
network_mode: "host"
entrypoint: ["redis-server", "--port", "7002"]
r3:
image: redis
container_name: r3
network_mode: "host"
entrypoint: ["redis-server", "--port", "7003"]

执行启动集群:

1
docker compose up -d

如上启动 3 个redis 实例,需要手动配置主从关系:

1
2
3
4
# Redis5.0以前
slaveof <masterip> <masterport>
# Redis5.0以后
replicaof <masterip> <masterport>

有临时和永久两种模式:

  • 永久生效:在 redis.conf 文件中利用 slaveof 命令指定 master 节点
  • 临时生效:直接利用 redis-cli 控制台输入 slaveof 命令,指定master 节点

将 r1 作为 master,r2 和 r3 为 slave:

1
2
3
4
5
6
7
8
9
10
# 连接r2
docker exec -it r2 redis-cli -p 7002
# 认r1主,也就是7001
slaveof 192.168.150.101 7001


# 连接r3
docker exec -it r3 redis-cli -p 7003
# 认r1主,也就是7001
slaveof 192.168.150.101 7001

连接 r1 后查看集群状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 连接r1
docker exec -it r1 redis-cli -p 7001
# 查看集群状态
info replication

# 返回如下:
127.0.0.1:7001> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.150.101,port=7002,state=online,offset=140,lag=1
slave1:ip=192.168.150.101,port=7003,state=online,offset=140,lag=1
master_failover_state:no-failover
master_replid:16d90568498908b322178ca12078114e6c518b86
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:140
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:140

可以看到,当前节点 r1:7001 的角色是 master,有两个 slave 与其连接:

  • slave0:port是7002,也就是r2节点
  • slave1:port是7003,也就是r3节点

测试可以发现,r1 这个节点上可以执行 set 命令(写操作),其它两个节点只能执行 get 命令(读操作)。也就是说读写操作已经分离了。

主从同步原理

全量同步

主从第一次建立连接时,会执行全量同步,将 master 节点的所有数据都拷贝给 slave 节点。

Phase 1,
slave can execute the replicaof command to build a connection, and then request data synchronization.The master will judge if it is the first synchronization.If it is , the master will return the its own data version infomation, and the slave will save it.

Phase 2,
Master execute bgsave to generate RDB , record the all commands during RDB period , and then master send RDB to slave . slave clean up the local data and load RDB file.

Phase 3,
Master send the command in repl_baklog to slave. Slave execute this command.

slave 做数据同步,必须向 master 声明自己的 replication id 和offset(用来判断第一次同步),master 才可以判断到底需要同步哪些数据。

在执行 slaveof 命令之前,所有 redis 节点都是 master,有自己的replid 和 offset。
当我们第一次执行 slaveof 命令,与 master 建立主从关系时,发送的replid 和 offset 是自己的,与 master 肯定不一致。
master 判断发现 slave 发送来的 replid 与自己的不一致,说明这是一个全新的 slave,就知道要做全量同步了。
master 会将自己的 replid 和 offset 都发送给这个 slave,slave 保存这些信息到本地。自此以后 slave 的 replid 就与 master 一致了。

因此,master 判断一个节点是否是第一次同步的依据,就是看 replid 是否一致。

增量同步

全量同步需要先做 RDB,将 RDB 文件通过网络传输给每个 Slave,成本太高了。因此除了第一次做全量同步,其它大多数时候 slave 与 master 都是做增量同步。

所谓增量同步就是只更新 slave 与 master 存在差异的部分数据。

通过 repl_baklog 可以获取 slave 和 master 数据差异信息。

repl_baklog 文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从 0 开始读写,这样数组头部的数据就会被覆盖。

repl_baklog 中会记录 Redis 处理过的命令及 offset,包括master 当前的 offset,和 slave 已经拷贝到的 offset。

slave 与 master 的 offset 之间的差异,就是 salve 需要增量拷贝的数据了。

随着不断有数据写入,master 的 offset 逐渐变大,slave 也不断的拷贝,追赶 master 的offset,直到数组被填满。

此时,如果有新的数据写入,就会覆盖数组中的旧数据。不过,旧的数据只要是绿色的,说明是已经被同步到 slave 的数据,即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分。

repl_baklog 大小有上限,写满后会覆盖最早的数据。如果 slave 断开时间过久,导致尚未备份的数据被覆盖,则无法基于 repl_baklog 做增量同步,只能再次全量同步。

主从同步优化

  • 在 master 中配置 repl-diskless-sync:yes 启用无磁盘复制,避免全量同步时的磁盘 IO。
  • Redis 单节点上的内存占用不要太大,减少 RDB 导致的过多磁盘 IO
  • 适当提高 repl_baklog 的大小,发现 slave 宕机时尽快实现故障恢复,尽可能避免全量同步
  • 限制一个 master 上的 slave 节点数量,如果实在是太多 slave,则可以采用主-从-从链式结构,减少 master 压力

全量同步和增量同步区别:

  • 全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
  • 增量同步:slave 提交自己的 offset 到 master,master获取repl_baklog中从offset之后的命令给slave

什么时候执行全量同步:

  • slave节点第一次连接master节点时
  • slave节点断开时间太久,repl_baklog中的offset已经被覆盖时

什么时候执行增量同步:

  • slave节点断开又恢复,并且在repl_baklog中能找到offset时