Redis - 主从复制那些事与高可用sentinel

Replication,也就是我们所说的主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。可以联想一下MySQL的主从复制(读写分离),理念都是相通的。

主要业务场景 :读写分离和容灾恢复。

Redis的Replication往往是配从(库)不配主(库)。

【1】Replication准备–conf文件复制与修改

这里为三台服务器演示做准备,将conf文件复制多份,模拟启动多个redis服务。

① 拷贝多个redis.conf文件

[root@localhost redis]# cp 6379.conf 6380.conf
[root@localhost redis]# cp 6379.conf 6381.conf
[root@localhost redis]# ll
总用量 180
-rw-r--r-- 1 root root 57828 6月  29 16:12 6379.conf
-rw-r--r-- 1 root root 57828 8月  19 16:06 6380.conf
-rw-r--r-- 1 root root 57828 8月  19 16:06 6381.conf

② 修改conf文件

这里以6379.conf为例,其他两个类同。

  • 开启daemonize yes
daemonize yes
  • 修改端口
port 6379
  • pid文件名字
pidfile /var/run/redis_6379.pid
  • log文件名字
logfile "/hme/data/redis-log/redis6379.log"
  • dump.rdb名字
dbfilename dump6379.rdb

【2】Replication常见模式 - 一主二仆

① 以不同配置文件启动三个redis服务

# 在6379窗口下
[root@localhost bin] redis-server /etc/redis/6379.conf

# 在6380窗口下
[root@localhost bin] redis-server /etc/redis/6380.conf

# 在6381窗口下
[root@localhost bin] redis-server /etc/redis/6381.conf

Redis - 主从复制那些事与高可用sentinel_第1张图片


查看启动的redis服务示意如下:

Redis - 主从复制那些事与高可用sentinel_第2张图片


查看redis启动日志:

Redis - 主从复制那些事与高可用sentinel_第3张图片

日志文件路径下分别生成redis6379.conf redis6380.conf和redis6381.conf。


② info replication命令

分别在三个窗口中连接三个redis client 并执行命令info replication

6379中执行命令示意如下:

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:ddd1085ab0d6b1ade8261b82ad966b2ee208bac5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379>

如图所示,此时为三个master:

Redis - 主从复制那些事与高可用sentinel_第4张图片


③ slave命令转换角色

这里假设79 为主机,80 和81为从机。

第一步在6379中分别放入三个键值:

Redis - 主从复制那些事与高可用sentinel_第5张图片

第二步分别在6380和6381中执行如下命令:

//将6380 6381作为从机依赖6379主机
slaveof 127.0.0.1 6379

#查看此时角色
info replication

需要注意的是,命令方式配置完,从机每次与master断开之后,都需要重新连接,除非配置进redis.conf文件。
Redis - 主从复制那些事与高可用sentinel_第6张图片

如下图所示,此时形成了一主二仆:

Redis - 主从复制那些事与高可用sentinel_第7张图片

第三步,6379主机放入新的键值,然后测试6380和6381是否能够获取新旧键值

Redis - 主从复制那些事与高可用sentinel_第8张图片

如上图所示,从机可以获取到主机的新旧键值(新旧以执行slave命令为分割)。

查看主机6379日志:
Redis - 主从复制那些事与高可用sentinel_第9张图片

查看从机6380日志:
Redis - 主从复制那些事与高可用sentinel_第10张图片


④ 如果三个服务执行一样的命令呢?

比如,三个服务都要执行如下命令:

set k5 v5

主机6379正常执行,从机尝试set操作时则会报异常:

Redis - 主从复制那些事与高可用sentinel_第11张图片


⑤ 如果主机shutdown呢?从机是上位还是原地待命?

如下图所示:

Redis - 主从复制那些事与高可用sentinel_第12张图片

从机可以正常读取信息,但是角色未发生变化(仍旧为从机),主机连接状态已经从up转变为了down。


⑥ 主机又回来了后,主机新增记录,从机还能否顺利复制?

如下所示,将6379启动,查看从机6380状态:

Redis - 主从复制那些事与高可用sentinel_第13张图片

6380中master_link_status:up不再是down。

主机6379放入新的键值,从机尝试获取:

Redis - 主从复制那些事与高可用sentinel_第14张图片

如上图所示,从机成功获取主机新的键值!


⑦ 如果从机down掉,然后恢复还能跟上大部队吗?

从机down掉,主机6379放入新的键值,启动从机然后查看其状态信息并尝试获取6379的新的键值。

Redis - 主从复制那些事与高可用sentinel_第15张图片

如上图所示,此时从机角色为master,再次执行命令slave后才能获取主机新放入的键值(包括主机旧的键值)。


【3】一主二仆演变之薪火相传

接【2】继续演示,如果主机down了,从机虽然可以读,但是业务没法再往redis里面写数据了。这是很可怕的一件事情!

而且从另外一个角度考虑,一主X仆有个明显的缺陷就是中心化太严重,master承担压力和风险较高。

薪火相传是这种思想,上一个Slave可以是下一个slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力。

如果中途变更转向则会清除之前的数据,重新建立拷贝最新的。

#6380不变,6381切换宿主
127.0.0.1:6381>slaveof 127.0.0.1 6380

如下图所示,6379为6380的主机,6380为6381的主机:

Redis - 主从复制那些事与高可用sentinel_第16张图片

需要注意的是,6380此时显示的角色为slave。


主机6379放入新的键值,6380和6381尝试获取:

Redis - 主从复制那些事与高可用sentinel_第17张图片

这种方式可以减轻主机的压力,但是也有个明显的缺陷,暂且不说主机6379宕掉,如果从机6380宕掉呢?接在6380上的从机将不能够获取主机6379新的键值!!


【4】一主二仆演变之反客为主

接上面,将6381重新切回到6379上面:

127.0.0.1:6381>slaveof 127.0.0.1 6379

Redis - 主从复制那些事与高可用sentinel_第18张图片


如【2】中演示,主机挂掉,从机原地待命,这就有个可能性,为什么从机不能反客为主作为主机呢?

命令如下:

slaveof no one 
# 使当前数据库停止与其他数据库的同步,转成主数据库

演示如下:将6379 shutdown,6380执行如上命令:

Redis - 主从复制那些事与高可用sentinel_第19张图片

如上图所示,此时6380已经成为master,可以正常存取键值。


6381的主机6379已经down掉,那么需要切换主机到6380:

Redis - 主从复制那些事与高可用sentinel_第20张图片

如上图所示,从6380中再次重新同步数据,正常获取6380中的键值!

此时6380与6381形成了一主一从的格局,即使6379此时恢复正常,已经无力回天。

反客为主可以解决主机down掉的问题,看起来很友好,但是需要手动切换为mater,能不能自动?


【5】哨兵模式大宝剑

哨兵模式:反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

① 恢复环境,6379下挂着6380和6381

Redis - 主从复制那些事与高可用sentinel_第21张图片


② 在/etc/redis下面建立sentinel.conf文件,名字绝不能错

目录可以自定义,这里将配置文件统一放在了etc/redis下面。

命令如下:

touch sentinel.conf

Redis - 主从复制那些事与高可用sentinel_第22张图片

一组sentinel能同时监控多个Master。


③ 配置哨兵,填写内容

sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1

上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机。

Redis - 主从复制那些事与高可用sentinel_第23张图片

解释:如果主机6379挂掉后,剩下的从机谁的票数多于一票,谁就成为新的master。


④ 启动哨兵

如下图所示,切换到/usr/local/bin目录下,执行命令:

redis-sentinel /etc/redis/sentinel.conf 

#上述目录依照各自的实际情况配置,可能目录不同

Redis - 主从复制那些事与高可用sentinel_第24张图片


⑤ 正常的一主二仆中6379挂掉,查看新master选举

Redis - 主从复制那些事与高可用sentinel_第25张图片

此时查看6380和6381状态:

Redis - 主从复制那些事与高可用sentinel_第26张图片

如上图所示6381成为了新的master,6380自动切换了宿主到6381!

如下所示,是再次测试6381挂掉之后,6379被选举为master的哨兵日志:

23456:X 11 Jan 18:21:10.708 # Sentinel ID is 12854a3954a5d4e8697c7ba46a799b6ac0123f28
23456:X 11 Jan 18:21:10.709 # +monitor master host6379 127.0.0.1 6381 quorum 1
23456:X 11 Jan 18:23:17.993 # +sdown master host6379 127.0.0.1 6381
23456:X 11 Jan 18:23:17.993 # +odown master host6379 127.0.0.1 6381 #quorum 1/1
23456:X 11 Jan 18:23:17.993 # +new-epoch 2
23456:X 11 Jan 18:23:17.994 # +try-failover master host6379 127.0.0.1 6381
23456:X 11 Jan 18:23:18.040 # +vote-for-leader 12854a3954a5d4e8697c7ba46a799b6ac0123f28 2
23456:X 11 Jan 18:23:18.040 # +elected-leader master host6379 127.0.0.1 6381
23456:X 11 Jan 18:23:18.041 # +failover-state-select-slave master host6379 127.0.0.1 6381
23456:X 11 Jan 18:23:18.112 # +selected-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ host6379 127.0.0.1 6381
23456:X 11 Jan 18:23:18.112 * +failover-state-send-slaveof-noone slave 127.0.0.1:6379 127.0.0.1 6379 @ host6379 127.0.0.1 6381
23456:X 11 Jan 18:23:18.184 * +failover-state-wait-promotion slave 127.0.0.1:6379 127.0.0.1 6379 @ host6379 127.0.0.1 6381
23456:X 11 Jan 18:23:18.229 # +promoted-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ host6379 127.0.0.1 6381
23456:X 11 Jan 18:23:18.229 # +failover-state-reconf-slaves master host6379 127.0.0.1 6381
23456:X 11 Jan 18:23:18.267 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ host6379 127.0.0.1 6381
23456:X 11 Jan 18:23:19.221 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ host6379 127.0.0.1 6381
23456:X 11 Jan 18:23:19.221 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ host6379 127.0.0.1 6381
23456:X 11 Jan 18:23:19.308 # +failover-end master host6379 127.0.0.1 6381
23456:X 11 Jan 18:23:19.308 # +switch-master host6379 127.0.0.1 6381 127.0.0.1 6379
23456:X 11 Jan 18:23:19.308 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ host6379 127.0.0.1 6379
23456:X 11 Jan 18:23:19.308 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ host6379 127.0.0.1 6379
23456:X 11 Jan 18:23:49.324 # +sdown slave 127.0.0.1:6381 127.0.0.1 6381 @ host6379 127.0.0.1 6379
23456:X 11 Jan 18:25:26.992 # -sdown slave 127.0.0.1:6381 127.0.0.1 6381 @ host6379 127.0.0.1 6379
23456:X 11 Jan 18:25:36.950 * +convert-to-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ host6379 127.0.0.1 6379

这些命令很详细记录了主从变化已经选举过程,那么这些命令怎么解读?
参考博文:深入学习Redis高可用之Sentinel相关概念


⑥ 如果之前的master重启回来,会不会双master冲突?

其实从⑤中哨兵的日志中就应该可以猜测到答案,即使6379回来了,会成为6381的从机。

如下图所示,哨兵检测到6379复活后,会将其变为6381(新master)的从机:
这里写图片描述
Redis - 主从复制那些事与高可用sentinel_第27张图片


【6】几点理论

实践和理论永远是缺一不可的。

① Redis复制功能实现

Redis 的复制功能分为同步(sync)命令传播(command propagate)两个阶段。具体来说同步指的是主将自己的现有状态复制给从的过程,将从服务器的数据库状态更新成主服务器当前的数据库状态。而命令传播指的是当主服务器数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的过程。

在 Redis 2.8 版本之前,进行主从复制时一定会顺序执行上述两个步骤,而从 2.8 开始则可能只需要执行命令传播即可。

  • 同步

当需要从去复制主的时候,从会首先执行同步操作(因为这时候一般主从间的数据差异较大)步骤如下:

  • 从会向主发送 SYNC 命令
  • 主收到SYNC 之后执行 BGSAVE 命令,在后台生成一个 RDB 文件,并开始将这个时间点时候的写操作存储在缓冲区中
  • bgsave 执行完成后主将 RDB 文件发送给从,从载入这个文件,将自己的状态更新完成,此时从跟主 之间的差异就在于主新增的操作了
  • 主将自己新增的操作也发生给从,从逐步更新完成,愈发接近主的实际状态
    Redis - 主从复制那些事与高可用sentinel_第28张图片

  • 命令传播

这之后,从跟主之间已经完成同步,但是如果主接受写操作,那么瞬间二者又会不一致,主需要通过命令传播的方式将新的写命令传递给从,此时的更新量已经较少,不需要依靠 RDB 文件进行大量的同步,而只需要依靠命令为单位就可以。

如果再次执行同步操作而非命令传播是否可以将从的状态与主保持一致呢?

的确可以,但是没必要。因为实现同步的 sync 命令是一个非常消耗资源的操作,具体说明如下(每次执行SYNC命令,主从服务器需要执行以下动作):

  • 主服务器需要执行bgsave命令来生成rdb文件,这个生成操作会耗费主服务器大量的CPU、内存和磁盘IO资源。
  • 主服务器需要将自己生成的rdb文件发送给从服务器,这个发送操作会耗费主从服务器大量的网络资源(带宽和流量),并对主服务器响应命令请求的时间产生影响。
  • 接收到rdb文件的从服务器需要载入主服务器发来的rdb文件,并且在载入期间,从服务器会因为阻塞而没办法处理命令请求。

②增量/全量机制-PSYNC优化

如果主从链接断了怎么办,或者说从机down掉又恢复,究竟是从重新全量同步主的状态,直接舍弃之前已经同步过的数据;还是采用新的方法记录断线前已经同步完的位置,只要重连之后同步新的操作就可以?实际上前者是旧版的复制功能,后者就是新版(3.X之后)的改进。

主从同步实际分 2 种情况:

  • 初次复制:从服务器第一次复制当前主服务器(PS:主服务器是有可能更换的)
  • 断线后重复制:处于命令传播阶段的主从服务器,因为网络问题而中断复制,从服务器通过自动重连,重新连接上主服务器并继续复制。

在断线后重复制的情况下,在 2.8 版本之前,会再次执行同步(sync 命令)和命令传播。如果说,在断线期间,主服务器(已有上万键值对)只执行了几个写命令,为了让从服务器弥补这几个命令,却要重新执行 sync 来生成新的 rdb 文件,这也是非常低效的。

为了解决这个问题,2.8 开始就使用 psync 命令来代替 sync 命令去执行同步操作。其中 PSYNC 具有全量重同步和部分重同步两种模式。其中完整重同步用于处理初次复制的情况,类似于之前旧版的 SYNC ;而部分重同步则是用于处理断线重连的情况,只需要同步新的更改,采用 CONTINUE 标记是否进行部分重同步,提高了效率。

部分重同步具体实现

主要有以下三部分构成:

  • 主和从的复制偏移量,表示二者分别进行到哪里了。
    主侧每发送 N 个字节的数据,自身维护的 offset + N ; 从侧每次收到 N 个字节的数据,自身的 offset + N 。由此在重连之后,从主要上报一下自己的 offset ,主就可以知道从和主之间差了多少数据。
  • 主服务器的复制积压缓冲区
    表示断线期间的新增操作,是由主维护的一个 FIFO 的队列,默认大小为 1 MB 。 当进行命令传播操作时,主还会将传播的命令写进缓冲区,这里如果主收到了从断线重连之后上报的 offset ,会先看看缺少的数据是否还存在缓冲区中,若存在则恢复从进行部分重同步;反之进行全量重同步。
    Redis - 主从复制那些事与高可用sentinel_第29张图片
  • 服务器的运行 ID(run id)
    用于标识服务器身份,启动时自动生成,由 40 个随机的十六进制字符组成。在进行初次复制时从会保存主传过来的运行 ID ,表示从是具体同步哪个主。一旦发生断线重连,那么需要检测重连上的主是否是之前的主。如果是才决定接下来是部分重同步还是全量重同步;如果重连上的已经不是之间的主了,那么必须全量重同步。

③ 心跳检测

刚才提到,主从同步有同步和命令传播 2 个步骤。

当完成了同步之后,主从服务器就会进入命令传播阶段,此时从服务器会以每秒 1 次的频率,向主服务器发送命令:REPLCONF ACK ,其中 replication_offset 是从服务器当前的复制偏移量

发送这个命令主要有三个作用:

  • 检测主从服务器的网络状态
  • 辅助实现 min-slaves 选项
  • 检测命令丢失(若丢失,主服务器会将丢失的写命令重新发给从服务器)

【7】两个问题

① 单个Sentinel进程是否可行?

答案:当然不可行,而且上面sentinel配置也是很简陋。参考博文:深入学习Redis高可用之Sentinel相关概念

② 一主二从三哨兵配置下,当进行主从切换时,前台应用怎么办?

Redis - 主从复制那些事与高可用sentinel_第30张图片
比如master-6379挂了,哨兵选举了slave-6380为新的master。那么此时SpringBoot怎么办?SpringBoot中配置的redis host还是6379。

你可能感兴趣的:(Redis/Memcache)