一、搭建主从架构

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

redis 分布式缓存配置 redis分布式缓存搭建_缓存


在 Redis 应用当中,大多数情况下,都是读多写少,因此,更多情况下,需要应对的是读的压力,那么在主从的基础上可进一步实现读写分离:在执行写操作时,访问 master 节点,如果是读操作,那么就分发到 slave 节点。这种一主多从的集群设计,可让多个从节点共同承担读的请求,从而使得读的并发能力就会有极大地提升。

二、搭建主从集群

2.1 集群结构

共包含三个节点,一个主节点,两个从节点。

这里我们会在同一台虚拟机中开启3个redis实例,模拟主从集群,信息如下:

IP

PORT

角色

虚拟机IP地址

7001

master

虚拟机IP地址

7002

slave

虚拟机IP地址

7003

slave

2.2.准备实例和配置

我的Redis安装目录是:/usr/local/src/redis-6.2.6

要在同一台虚拟机开启3个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。

1)创建目录

我们创建三个文件夹,名字分别叫7001、7002、7003:

# 进入//usr/local/src/目录
cd /usr/local/src/
# 创建目录 myredis
mkdir myredis
# 进入 /myredis 目录
cd myredis
# 创建目录 7001 7002 7003
mkdir 7001 7002 7003

如图:

redis 分布式缓存配置 redis分布式缓存搭建_缓存_02


2)恢复原始配置

修改redis.conf文件,将其中的持久化模式改为默认的RDB模式,AOF保持关闭状态。

# 开启RDB
# save ""
save 3600 1
save 300 100
save 60 10000

# 关闭AOF
appendonly no

3)拷贝配置文件到每个实例目录

然后将redis.conf文件拷贝到三个目录中(在/myredis目录执行下列命令):

# 方式一:逐个拷贝
cp /usr/local/src/redis-6.2.6/redis.conf 7001
cp /usr/local/src/redis-6.2.6/redis.conf 7002
cp /usr/local/src/redis-6.2.6/redis.conf 7003

# 方式二:管道组合命令,一键拷贝
echo 7001 7002 7003 | xargs -t -n 1 cp /usr/local/src/redis-6.2.6/redis.conf

redis 分布式缓存配置 redis分布式缓存搭建_redis_03

4)修改每个实例的端口、工作目录

修改每个文件夹内的配置文件,将端口分别修改为7001、7002、7003,将rdb文件保存位置都修改为自己所在目录(在/myredis目录执行下列命令):

sed -i -e 's/6379/7001/g' -e 's/dir .\//dir \/tmp\/7001\//g' 7001/redis.conf
sed -i -e 's/6379/7002/g' -e 's/dir .\//dir \/tmp\/7002\//g' 7002/redis.conf
sed -i -e 's/6379/7003/g' -e 's/dir .\//dir \/tmp\/7003\//g' 7003/redis.conf

5)修改每个实例的声明IP

虚拟机本身有多个IP,为了避免将来混乱,我们需要在redis.conf文件中指定每一个实例的绑定ip信息,格式如下:

# redis实例的声明 IP
replica-announce-ip 虚拟机IP地址

每个目录都要改,我们一键完成修改(在/tmp目录执行下列命令):

# 逐一执行
sed -i '1a replica-announce-ip 虚拟机IP地址' 7001/redis.conf
sed -i '1a replica-announce-ip 虚拟机IP地址' 7002/redis.conf
sed -i '1a replica-announce-ip 虚拟机IP地址' 7003/redis.conf

# 或者一键修改
printf '%s\n' 7001 7002 7003 | xargs -I{} -t sed -i '1a replica-announce-ip 虚拟机IP地址' {}/redis.conf

2.3.启动

为了方便查看日志,我们打开3个ssh窗口,分别启动3个redis实例,启动命令(在myredis目录下执行):

# 第1个
redis-server 7001/redis.conf
# 第2个
redis-server 7002/redis.conf
# 第3个
redis-server 7003/redis.conf

redis 分布式缓存配置 redis分布式缓存搭建_IP_04


如果要一键停止,可以运行下面命令:

printf '%s\n' 7001 7002 7003 | xargs -I{} -t redis-cli -p {} shutdown

2.4.开启主从关系

现在三个实例还没有任何关系,要配置主从可以使用replicaof 或者slaveof(5.0以前)命令。

有临时和永久两种模式:

  • 修改配置文件(永久生效)
  • 在redis.conf中添加一行配置:slaveof <masterip> <masterport>
  • 使用redis-cli客户端连接到redis服务,执行slaveof命令(重启后失效):
slaveof <masterip> <masterport>

注意:在5.0以后新增命令replicaof,与salveof效果一致。

这里我们为了演示方便,使用方式二。

通过redis-cli命令连接7002,执行下面命令:

# 连接 7002
redis-cli -p 7002
# 执行slaveof
slaveof 虚拟机IP地址 7001

通过redis-cli命令连接7003,执行下面命令:

# 连接 7003
redis-cli -p 7003
# 执行slaveof
slaveof 虚拟机IP地址 7001

然后连接 7001节点,查看集群状态:

# 连接 7001
redis-cli -p 7001
# 查看状态
info replication

结果:

redis 分布式缓存配置 redis分布式缓存搭建_redis 分布式缓存配置_05


当尝试向从节点存入数据时,会报错:

redis 分布式缓存配置 redis分布式缓存搭建_IP_06

2.5 总结

假设有 A、B 两个 Redis 实例,如何让 B 作为 A 的 slave 节点?
在 B 节点执行命令:slaveof A的IP A的port

三、数据同步原理

3.1 全量同步

主从第一次同步是全量同步:

redis 分布式缓存配置 redis分布式缓存搭建_IP_07


slave 与 master 第一次建立连接时,slave 会执行 replicaof 命令,并指定主节点的 ip 地址与端口。一旦建立连接,slave 就可以向 master 发送数据同步的请求。master 在接收到 slave 的请求后,会判断 slave 是不是第一次请求,如果是第一次请求,master 则会将其版本信息返回给 slave,而 slave 在接收到 master 的版本信息后,会将其保存下来。

master 在向 slave 同步数据时,会执行 bgsave 命令,异步开辟独立线程来生成 RDB 文件。RDB 文件一旦生成,master 就会将 RDB 文件发送给 slave。slave 在接收到 master 发送过来的 RDB 文件后,首先会清空本地数据,然后再去加载 RDB 文件。这样 slave 中的数据就与 master 中的数据基本一致了。由于 RDB 文件是异步生成的,所以在生成 RDB 文件的过程中,master 中也有可能会执行新的命令,从而产生新的数据。而在生成 RDB 期间的所有命令, master 会将其记录到 repl_baklog 文件中。在 RDB 向 salve 同步完成且 repl_baklog 记录结束后,master 就会将 repl_baklog 文件发送给 slave,slave 接收到文件后,就会执行 repl_baklog 中记录的命令。

若后续仍有新的命令执行,则新的命令依旧会被记录到 repl_baklog 中,然后再由独立进程发送给 slave,slave接收后再执行 repl_baklog 中的命令。循环往复,以此来保证 slave 与 master 的数据一致性。

master 是如何判断 slave 是不是第一次来同步数据?
有两个很重要的概念:

  • Replication Id:简称 replid,是数据集的标记,id 一致则说明是同一数据集。每一个 master 都有唯一的 replid,slave 则会继承 master 节点的 replid
  • offset:偏移量,随着记录在 repl_baklog 中的数据增多而逐渐增大。slave 完成同步时也会记录当前同步的 offset。如果 slave 的 offset 小于 master 的 offset,说明 slave 数据落后于 master,需要更新。

因此 slave 做数据同步时,必须向 master 声明自己的 replication id 和 offset,master 才可以判断到底需要同步哪些数据。

因为slave原本也是一个master,有自己的replid和offset,当第一次变成slave,与master建立连接时,发送的replid和offset是自己的replid和offset。

master判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,就知道要做全量同步了。

master会将自己的replid和offset都发送给这个slave,slave保存这些信息。以后slave的replid就与master一致了。

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

redis 分布式缓存配置 redis分布式缓存搭建_redis 分布式缓存配置_08

完整全量同步流程描述:

  • slave 节点请求增量同步
  • master 节点判断 replid,发现不一致,拒绝增量同步
  • master 将完整内存数据生成 RDB,发送 RDB 到 slave
  • slave 清空本地数据,加载 master 的 RDB
  • master 将 RDB 期间的命令记录在 repl_baklog,并持续将 log 中的命令发送给 slave
  • slave 执行接收到的命令,保持与 master 之间的同步

3.2 增量同步

主从第一次同步是全量同步,但如果 slave 重启后同步,则执行增量同步。

slave 在重启过程中,master 仍然在接收数据,执行命令,那么当 slave 重启完成后,就需要从 master 同步数据。过程如下:

slave 向 master 发送同步请求,请求中会携带有自己的 replid 和 offset。由于 slave 并不是第一次请求,那么 replid 和 offset 就跟 master 中的是一致的。master 在判断得知 slave 并非第一次请求后,会去 repl_baklog 中获取 offset 后的数据,然后将其发送给 slave,slave 在接收到后执行相关命令。

redis 分布式缓存配置 redis分布式缓存搭建_缓存_09

3.3 repl_baklog 原理

master怎么知道slave与自己的数据差异在哪里呢?

这就要说到全量同步时的repl_baklog文件了。

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

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

redis 分布式缓存配置 redis分布式缓存搭建_缓存_10


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

随着不断有数据写入,master的offset逐渐变大,slave也不断的拷贝,追赶master的offset:

redis 分布式缓存配置 redis分布式缓存搭建_缓存_11


直到数组被填满:

redis 分布式缓存配置 redis分布式缓存搭建_redis_12


此时,如果有新的数据写入,就会覆盖数组中的旧数据。不过,旧的数据只要是绿色的,说明是已经被同步到slave的数据,即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分。但是,如果slave出现网络阻塞,导致master的offset远远超过了slave的offset:

redis 分布式缓存配置 redis分布式缓存搭建_IP_13


如果master继续写入新数据,其offset就会覆盖旧的数据,直到将slave现在的offset也覆盖:

redis 分布式缓存配置 redis分布式缓存搭建_redis_14


棕色框中的红色部分,就是尚未同步,但是却已经被覆盖的数据。此时如果slave恢复,需要同步,却发现自己的offset都没有了,无法完成增量同步了。只能做全量同步。


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

3.4 主从同步优化

可以从以下几个方面来优化 Redis 主从集群:

  • 在 master 中配置 repl_diskless-sync yes 启用无磁盘复制,避免全量同步时的磁盘 IO。(不再写入磁盘的 IO,而是直接写入到网络的 IO 流,然后发送给 slave)
  • 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 时。