主从复制概念与原理
核心概念
主节点(Master):唯一接受写操作的节点,数据修改后异步复制到从节点。
从节点(Replica):复制主节点数据的节点,默认只读(可配置为可写但不推荐)。
所以,主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
主要价值
主从复制主要有能实现如下价值:
数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
主从库之间采用的是读写分离的方式,即:
读操作:主库、从库都可以接收;
写操作:首先到主库执行,然后,主库将写操作同步给从库
复制流程
- 从节点连接主节点发送 PSYNC 命令
- 主节点执行 BGSAVE 生成 RDB 快照
- RDB 文件传输到从节点
- 从节点加载 RDB 文件
- 主节点将期间的写命令发送到从节点
- 持续增量同步
关键特性:
异步复制:主节点不等待从节点确认
全量复制:首次连接或复制中断时间过长时触发
部分复制:基于复制偏移量(offset)的增量同步
级联复制:从节点可以作为其他节点的主节点
复制原理
- 确立主从关系
如有 redis01 (172.24.8.11)实例和 redis02 (172.24.8.12),在 redis02 上执行以下这个命令后,redis02 就变成了 redis01 的从库,并从 redis01 上复制数据。
replicaof 172.24.8.11 6379
- 全量复制阶段
第一阶段:主从库间建立连接、协商同步的过程,主要是为全量复制做准备。在这一步,从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,主从库间就可以开始同步了。
具体来说,从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。
psync 命令包含了主库的 runID 和复制进度 offset 两个参数。
runID 是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。
当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为“?”。
offset,此时设为 -1,表示第一次复制。
主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。
从库收到响应后,会记录下这两个参数。这里有个地方需要注意,FULLRESYNC 响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库。
第二阶段,主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载。这个过程依赖于内存快照生成的 RDB 文件。
具体来说,主库执行 bgsave 命令,生成 RDB 文件,接着将文件发给从库。从库接收到 RDB 文件后,会先清空当前数据库,然后加载 RDB 文件。
这是因为从库在通过 replicaof 命令开始和主库同步前,可能保存了其他数据。
为了避免之前数据的影响,从库需要先把当前数据库清空。
在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求。否则,Redis 的服务就被中断了。
但是,这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从库的数据一致性,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。
第三个阶段,主库会把第二阶段执行过程中新收到的写命令,再发送给从库。
具体的操作是,当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修改操作发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了。
- 增量复制
如果主从库在命令传播时出现了网络闪断,那么,从库就会和主库重新进行一次全量复制,开销非常大。因此如果出现类似异常之后,主从库会采用增量复制的方式继续同步。
提示:在进行主从复制设置时,强烈建议在主服务器上开启持久化,主要是避免出现如下风险:
假设设置节点A为主服务器,关闭持久化,节点B和C从节点A复制数据。
这时出现了一个崩溃,但Redis具有自动重启系统,重启了进程,因为关闭了持久化,节点重启后只有一个空的数据集。
节点B和C从节点A进行复制,现在节点A是空的,所以节点B和C上的复制数据也会被删除。
读写分离概念与原理
读写分离说明
读写分离是在主从复制基础上实现的,可以实现Redis的读负载均衡:由主节点提供写服务,由一个或多个从节点提供读服务(多个从节点既可以提高数据冗余程度,也可以最大化读负载能力);在读负载较大的应用场景下,可以大大提高Redis服务器的并发量。
读写分离关注点
- 延迟与不一致问题
由于主从复制的命令传播是异步的,延迟与数据的不一致不可避免。如果应用对数据不一致的接受程度程度较低,可能的优化措施包括:优化主从节点之间的网络环境(如在同机房部署);监控主从节点延迟(通过offset)判断,如果从节点延迟过大,通知应用不再通过该从节点读取数据;使用集群同时扩展写负载和读负载等。
- 数据过期问题
在单机版Redis中,存在两种删除策略:
+ 惰性删除:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除。
+ 定期删除:服务器执行定时任务删除过期数据,但是考虑到内存和CPU的折中(删除会释放内存,但是频繁的删除操作对CPU不友好),该删除的频率和执行时间都受到了限制。
在主从复制场景下,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过期数据的删除。由于主节点的惰性删除和定期删除策略,都不能保证主节点及时对过期数据执行删除操作,因此,当客户端通过Redis从节点读取数据时,很容易读取到已经过期的数据。
提示:在Redis 3.2后,从节点在读取数据时,增加了对数据是否过期的判断:如果该数据已过期,则不返回给客户端。
- 故障切换问题
在没有使用哨兵的读写分离场景下,应用针对读和写分别连接不同的Redis节点;当主节点或从节点出现问题而发生更改时,需要及时修改应用程序读写Redis数据的连接;连接的切换可以手动进行,或者自己写监控程序进行切换,但前者响应慢、容易出错,后者实现复杂,成本较高。
- 总结
在使用读写分离之前,可以考虑其他方法增加Redis的读负载能力:如尽量优化主节点(减少慢查询、减少持久化等其他情况带来的阻塞等)提高负载能力;使用Redis集群同时提高读负载能力和写负载能力等。如果使用读写分离,可以使用哨兵,使主从节点的故障切换尽可能自动化,并减少对应用程序的侵入。
参考: Redis进阶 - 高可用:主从复制详解
主从复制架构实践
安全预设
根据实际情况,关闭防火墙和SELinux。
[root@redis01 ~]# systemctl disable firewalld --now
[root@redis01 ~]# sed -i 's/=enforcing/=disabled/g' /etc/selinux/config
注意:以上操作在所有Slave主机上也需要执行。
时钟服务
主从架构建议保证时钟服务正确,具体时钟配置参考:NTP服务器
001.Chrony时间服务器
- ntpd服务查看
[root@redis01 ~]# ntpq -npremote refid st t when poll reach delay offset jitter
==============================================================================
*116.62.13.223 100.100.61.92 2 u 28 64 377 34.750 24.098 4.261
+203.107.6.88 100.107.25.114 2 u 13 64 377 47.745 18.287 8.121
- chrony服务查看
[root@redis01 ~]# chronyc sources -v
Redis 安装
略,参考《001.Redis 简介及安装》。
提示:主从节点都需要安装Redis。
Redis 主节点配置
[root@redis01 ~]# mkdir -p /var/lib/redis/redis01/ /var/log/redis/ #创建Redis RDB持久化文件保存路径[root@redis01 ~]# vim /etc/redis/redis01_6379.conf
#……
# 核心配置项:
bind 0.0.0.0 -::1 # 允许所有IP连接
protected-mode no # 关闭保护模式
port 6379
daemonize yes
logfile /var/log/redis/redis01.log
dir /var/lib/redis/redis01# 主从复制相关
repl-backlog-size 64mb # 复制积压缓冲区大小(建议1-2倍内存)
repl-backlog-ttl 3600 # 缓冲区保留时间(秒)
repl-diskless-sync yes # 启用无盘复制(推荐)
repl-diskless-sync-delay 5 # 等待更多从节点加入(秒)
#requirepass "StrongPassword123!" [root@redis01 ~]# systemctl restart redis-server
Redis 从节点配置
[root@redis02 ~]# mkdir -p /var/lib/redis/redis01/ /var/log/redis/ #创建Redis RDB持久化文件保存路径[root@redis02 ~]# vim /etc/redis/redis01_6379.conf
#……
# 核心配置项:
bind 0.0.0.0 -::1 # 允许所有IP连接
protected-mode no # 关闭保护模式
port 6379
daemonize yes
logfile /var/log/redis/redis01.log
dir /var/lib/redis/redis01# 主从复制配置
replicaof 172.24.8.11 6379 # 关键配置:指定主节点
replica-read-only yes # 默认,从节点只读(推荐)
repl-diskless-load disabled # 默认,从节点禁用无盘加载(避免风险)
#masterauth "StrongPassword123!" # 如果主节点有密码需配置[root@redis02 ~]# systemctl restart redis-server
主从复制验证
- 状态验证
在主从节点上查看两个节点的状态。
[root@redisclient ~]# redis-cli -h redis01 -p 6379
redis01:6379> INFO replication
# Replication
role:master
connected_slaves:1
slave0:ip=172.24.8.12,port=6379,state=online,offset=306,lag=0
master_failover_state:no-failover
master_replid:a8f80c6ab90acb062021f2f0e589191c165022c1
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:306
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:67108864
repl_backlog_first_byte_offset:223
repl_backlog_histlen:84
redis01:6379> exit[root@redisclient ~]# redis-cli -h redis02 -p 6379
redis02:6379> INFO replication
# Replication
role:slave
master_host:172.24.8.11
master_port:6379
master_link_status:up
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_read_repl_offset:320
slave_repl_offset:320
replica_full_sync_buffer_size:0
replica_full_sync_buffer_peak:0
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:a8f80c6ab90acb062021f2f0e589191c165022c1
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:320
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:237
repl_backlog_histlen:84
- 数据验证
[root@redisclient ~]# redis-cli -h redis01 -p 6379
redis01:6379> SET master_key "value_from_master"
OK
redis01:6379> exit[root@redisclient ~]# redis-cli -h redis02 -p 6379
redis02:6379> GET master_key
"value_from_master"
redis02:6379> exit
模拟宕机
- 停止主节点
[root@redis01 ~]# systemctl stop redis-server
- 2从节点查看状态
[root@redisclient ~]# redis-cli -h redis02 -p 6379
redis02:6379> INFO replication
master_link_status:down # 显示连接断开
- 提升从节点为主节点(临时方案)
[root@redisclient ~]# redis-cli -h redis02 -p 6379
redis02:6379> REPLICAOF NO ONE # 取消复制关系
节点切换后,对应的应用程序也需要修改数据库IP。
提示;主从复制本身不提供自动故障切换,需要手动执行切换,然后程序需要手动重新指定IP。