Redis高可用—主从复制

一、为什么需要主从复制?

前一篇文章讲了Redis高可用—持久化,将内存数据持久化到硬盘,即使Redis服务器宕机,也能够根据AOF或者RDB文件恢复数据库状态。但是持久化的数据仍然只在一台服务器上,当服务器故障无法重启时,持久化就不能保证数据安全性了,这时候就需要进行主从复制。
主从复制是将一台服务器的数据复制到其他服务器上,前者称为主节点master,后者称为从节点slave

主从复制有以下作用:

  1. 故障恢复:当主节点出现故障时,从节点可以提供服务,实现快速故障恢复;
  2. 数据冗余:实现了数据备份,是持久化之外的一种数据冗余方式;
  3. 负载均衡:主从复制+读写分离,可以由主节点提供写服务,从节点提供读服务,通过多个从节点分担负载,可以大大提高并发量。

二、配置主从复制

默认每个节点都是主节点,通过slaveof <ip> <port>开启主从复制关系,slaveof no one 关闭主从复制。

开启主从复制有3种方式:

  1. redis.conf:在配置文件中添加slaveof ip port;
  2. 启动命令:redis-server启动命令后加 slaveof ip port;
  3. 客户端命令:通过在客户端执行slaveof ip port 使该节点成为从节点。

例如,我们在本机采用多个端口来模拟主从复制:主节点(6379)、从节点1(6380)、从节点2(6381)。

  • 首先,在redis6380.conf、redis6381.conf中配置6380和6381为6379的从节点:
slaveof 127.0.0.1 6379
  • 然后,启动3个Redis服务节点;
#启动6379
redis-server /opt/software/redis-3.2/myconf/redis6379.conf
#启动6380
redis-server /opt/software/redis-3.2/myconf/redis6380.conf
#启动6381
redis-server /opt/software/redis-3.2/myconf/redis6381.conf
  • 分别连接6379、6380、6381,通过info replication查看主从复制关系:
[root@mq bin]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=2227,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=2227,lag=1
master_repl_offset:2227
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:2226
[root@mq bin]# redis-cli -p 6380
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_repl_offset:2367
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
[root@mq bin]# redis-cli -p 6381
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:2479
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
  • 在6379节点执行命令:
127.0.0.1:6381> set k1 v1
127.0.0.1:6379> keys *
1) "k1"
  • 查看6380、6381节点的数据,能够看到从节点同步了主节点的数据:
127.0.0.1:6380> keys *
1) "k1"
127.0.0.1:6380> keys *
1) "k1"
  • 如果主节点6379出现故障,我们可以将6380设置为主节点,让6381成为6380的从节点,新的主从关系建立,可以通过info replication 查看。
127.0.0.1:6380> slaveof no one
127.0.0.1:6381> slaveof 127.0.0.1 6380

注意:Redis非集群模式下的主从复制支持树状结构(中间的节点既是其主节点的从节点,也是其从节点的主节点)。这种拓扑结构可以减少主节点的直接从节点,降低主节点的负担;但多层从节点的延迟增大,数据一致性变差,结构复杂,维护比较困难。

三、主从复制实现原理

通过向从服务器发送SLAVEOF命令,可以让从服务器去复制主服务器。Redis的复制功能分为:建立连接、同步、命令传播

1. 建立连接

这个阶段会进行以下操作:保存主节点信息、主从节点建立socket连接、从节点向主节点发送ping命令、从节点向主节点进行身份验证、发送从节点端口信息等。这里暂不详细展开说明。

2. 同步

同步过程分为:完整重同步部分重同步

2.1 完整重同步

完整重同步:用于处理初次复制情况(从服务器以前没有复制过任何服务器,或从服务器当前要复制的主服务器和上次复制的主服务器不同)。

当客户端向从服务器发送slaveof ip port命令,要求从服务器复制主服务器时,从服务器首先需要执行完整重同步操作,该过程需要从服务器向主服务器发送PSYNC命令实现(Redis2.8以前是SYNC命令)。

完整重同步的执行步骤如下:

  1. 从服务器向主服务器发送 PSYNC 命令;
  2. 主服务器收到 PSYNC 命令,执行BGSAVE 命令在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写操作;
  3. 当BGSAVE命令执行完毕时,主服务器会将RDB文件发送给从服务器,从服务器接收RDB文件,清空老数据,载入RDB文件。从服务器载入RDB文件的过程是阻塞的,无法响应客户端请求。
  4. 主服务器将记录在缓冲区里的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至和主服务器一致。

完整重同步缺点:消耗CPU、内存、磁盘IO、网络带宽等。

2.2 部分重同步

部分重同步:用于断线后重复制情况(当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器将从服务器断开期间的写命令发送给从服务器,从服务器接收并执行这些写命令,将数据库状态更新至和主服务器一致)。

redis2.8之前没有部分重同步,断线后仍然采用完整重同步,通过从服务器向主服务器发送SYNC命令实现。

部分重同步实现是基于:复制偏移量复制积压缓冲区服务器运行ID

(1)复制偏移量
主从服务器都维护一个复制偏移量。主服务器每次向从服务器传播N个字节的数据,就将自己的复制偏移量加N;从服务器每次接收到主服务器的N个字节的数据,就将自己的复制偏移量加N。因此可以根据主从服务器的复制偏移量是否相等判断主从服务器是否处于一致状态。

(2)复制积压缓冲区
复制积压缓存区是主服务器维护的一个固定长度先进先出的队列,默认大小1M。主服务器进行命令传播时,不仅会将写命令发送给所有从服务器,还会将写命令写入复制积压缓冲区。复制积压缓冲区记录了写命令和每个字节对应的偏移量。
当从服务器断线后重新连上主服务器,从服务器会发送PSYNC命令将自己的复制偏移量offset发送给主服务器,主服务器根据这个复制偏移量决定执行完整重同步还是部分重同步。如果offset之后的数据还在复制积压缓冲区中,则执行部分重同步;否则执行完整重同步。

复制积压缓冲区构造
(3)服务器运行ID
每个Redis服务器都有自己的运行ID,从服务器对主服务器进行初次复制时,主服务器会将自己的运行ID发送给从服务器,从服务器将这个ID保存起来。当从服务器断线后重连上一个主服务器时,从服务器会向当前连接的主服务器发送之前保存的主服务器运行ID。
如果从服务器保存的运行ID和当前连接的主服务器ID相同,说明主服务器断线前连接的主服务器就是这个主服务器,可以尝试进行部分重同步;如果不相同,则进行完整重同步。

综上,一个PSYNC命令执行完整重同步和部分重同步的过程如下图:

在这里插入图片描述

3. 命令传播

完成同步之后,主从服务器就会进入命令传播阶段,主服务器一直将自己执行的写命令发送给从服务器,从服务器接收并执行主服务器发来的写命令,保证主从服务器数据库状态一致。

心跳检测

命令传播期间,从服务器默认会以每秒一次的频率,向主服务发送命令:REPLCONF ACK <replication_offset>,其中replication_offset是从服务器的复制偏移量。
发送这个命令有以下3个作用:

  1. 检测主从服务器的网络连接状态。
    如果主服务器超过一秒钟没有收到从服务器发来的REPLCONF ACK命令,主服务器就知道主从之间发生了连接问题。 在主服务器上执行info replication 命令,可以看到 lag=1,表示1秒之前发过REPLCONF ACK命令。
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=2227,lag=1  #1秒之前发过REPLCONF ACK命令
slave1:ip=127.0.0.1,port=6381,state=online,offset=2227,lag=1
  1. 辅助实现min-slaves选项。
    Redis的min-slaves-to-writemin-slaves-max-lag 两个选项可以 防止主服务器在不安全的情况下执行写命令。
min-slaves-to-write 3   #从服务器数量不少于3个
min-slaves-max-lag 10   #从服务器发送REPLCONF ACK命令的延迟时间不超过10秒

当以上2个条件中任意一个条件不满足时,主服务器将拒绝执行写命令。

  1. 检测命令丢失。
    如果因网络故障,主服务器发送给从服务器的命令在半路丢失,那么当从服务器向主服务器发送REPLCONF ACK命令时,主服务器会发现从服务器的复制偏移量少于自己的复制偏移量,主服务器会从复制积压缓冲区里找到从服务器缺少的数据,将这些数据重新发送给从服务器。

四、读写分离存在的问题

1. 延迟与不一致

主从复制的命令传播是异步的,读写分离时很可能会发生数据的不一致。可能的优化方案有:
(1)优化主从节点之间的网络环境;
(2)如果主从节点延迟过大,不再读从;
(3)使用集群同时扩展写负载和读负载。

2. 数据过期

单机版Redis的数据过期策略包括:惰性删除、定期删除。
主从复制时,为了主从节点数据的一致性,从节点不会主动删除缓存,而是由主节点控制从节点中过期数据的删除。由于主节点的惰性删除和定期删除都不能保证主节点及时对过期数据进行删除,因此当客户端通过从节点读取数据时,很可能会读到过期的数据。Redis3.2中,从节点读取数据时,增加了数据是否过期的判断,如果数据过期,不再返给客户端。升级到Redis3.2可以解决数据过期问题。

五、参考文献

  1. 《Redis设计与实现》
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值