Replication,也就是我们所说的主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。可以联想一下MySQL的主从复制(读写分离),理念都是相通的。
主要业务场景 :读写分离和容灾恢复。
Redis的Replication往往是配从(库)不配主(库)。
这里为三台服务器演示做准备,将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
port 6379
pidfile /var/run/redis_6379.pid
logfile "/hme/data/redis-log/redis6379.log"
dbfilename dump6379.rdb
① 以不同配置文件启动三个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服务示意如下:
查看redis启动日志:
日志文件路径下分别生成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:
③ slave命令转换角色
这里假设79 为主机,80 和81为从机。
第一步在6379中分别放入三个键值:
第二步分别在6380和6381中执行如下命令:
//将6380 6381作为从机依赖6379主机
slaveof 127.0.0.1 6379
#查看此时角色
info replication
需要注意的是,命令方式配置完,从机每次与master断开之后,都需要重新连接,除非配置进redis.conf文件。
如下图所示,此时形成了一主二仆:
第三步,6379主机放入新的键值,然后测试6380和6381是否能够获取新旧键值
如上图所示,从机可以获取到主机的新旧键值(新旧以执行slave命令为分割)。
④ 如果三个服务执行一样的命令呢?
比如,三个服务都要执行如下命令:
set k5 v5
主机6379正常执行,从机尝试set操作时则会报异常:
⑤ 如果主机shutdown呢?从机是上位还是原地待命?
如下图所示:
从机可以正常读取信息,但是角色未发生变化(仍旧为从机),主机连接状态已经从up转变为了down。
⑥ 主机又回来了后,主机新增记录,从机还能否顺利复制?
如下所示,将6379启动,查看从机6380状态:
6380中master_link_status:up
不再是down。
主机6379放入新的键值,从机尝试获取:
如上图所示,从机成功获取主机新的键值!
⑦ 如果从机down掉,然后恢复还能跟上大部队吗?
从机down掉,主机6379放入新的键值,启动从机然后查看其状态信息并尝试获取6379的新的键值。
如上图所示,此时从机角色为master,再次执行命令slave后才能获取主机新放入的键值(包括主机旧的键值)。
接【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的主机:
需要注意的是,6380此时显示的角色为slave。
主机6379放入新的键值,6380和6381尝试获取:
这种方式可以减轻主机的压力,但是也有个明显的缺陷,暂且不说主机6379宕掉,如果从机6380宕掉呢?接在6380上的从机将不能够获取主机6379新的键值!!
接上面,将6381重新切回到6379上面:
127.0.0.1:6381>slaveof 127.0.0.1 6379
如【2】中演示,主机挂掉,从机原地待命,这就有个可能性,为什么从机不能反客为主作为主机呢?
命令如下:
slaveof no one
# 使当前数据库停止与其他数据库的同步,转成主数据库
演示如下:将6379 shutdown,6380执行如上命令:
如上图所示,此时6380已经成为master,可以正常存取键值。
6381的主机6379已经down掉,那么需要切换主机到6380:
如上图所示,从6380中再次重新同步数据,正常获取6380中的键值!
此时6380与6381形成了一主一从的格局,即使6379此时恢复正常,已经无力回天。
反客为主可以解决主机down掉的问题,看起来很友好,但是需要手动切换为mater,能不能自动?
哨兵模式:反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
① 恢复环境,6379下挂着6380和6381
② 在/etc/redis下面建立sentinel.conf文件,名字绝不能错
目录可以自定义,这里将配置文件统一放在了etc/redis下面。
命令如下:
touch sentinel.conf
一组sentinel能同时监控多个Master。
③ 配置哨兵,填写内容
sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1
上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机。
解释:如果主机6379挂掉后,剩下的从机谁的票数多于一票,谁就成为新的master。
④ 启动哨兵
如下图所示,切换到/usr/local/bin
目录下,执行命令:
redis-sentinel /etc/redis/sentinel.conf
#上述目录依照各自的实际情况配置,可能目录不同
⑤ 正常的一主二仆中6379挂掉,查看新master选举
此时查看6380和6381状态:
如上图所示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复制功能实现
Redis 的复制功能分为同步(sync)
和命令传播(command propagate)
两个阶段。具体来说同步指的是主将自己的现有状态复制给从的过程,将从服务器的数据库状态更新成主服务器当前的数据库状态。而命令传播指的是当主服务器数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的过程。
在 Redis 2.8 版本之前,进行主从复制时一定会顺序执行上述两个步骤,而从 2.8 开始则可能只需要执行命令传播即可。
当需要从去复制主的时候,从会首先执行同步操作(因为这时候一般主从间的数据差异较大)步骤如下:
这之后,从跟主之间已经完成同步,但是如果主接受写操作,那么瞬间二者又会不一致,主需要通过命令传播的方式将新的写命令传递给从,此时的更新量已经较少,不需要依靠 RDB 文件进行大量的同步,而只需要依靠命令为单位就可以。
如果再次执行同步操作而非命令传播是否可以将从的状态与主保持一致呢?
的确可以,但是没必要。因为实现同步的 sync 命令是一个非常消耗资源的操作,具体说明如下(每次执行SYNC命令,主从服务器需要执行以下动作):
②增量/全量机制-PSYNC优化
如果主从链接断了怎么办,或者说从机down掉又恢复,究竟是从重新全量同步主的状态,直接舍弃之前已经同步过的数据;还是采用新的方法记录断线前已经同步完的位置,只要重连之后同步新的操作就可以?实际上前者是旧版的复制功能,后者就是新版(3.X之后)的改进。
主从同步实际分 2 种情况:
在断线后重复制的情况下,在 2.8 版本之前,会再次执行同步(sync 命令)和命令传播。如果说,在断线期间,主服务器(已有上万键值对)只执行了几个写命令,为了让从服务器弥补这几个命令,却要重新执行 sync 来生成新的 rdb 文件,这也是非常低效的。
为了解决这个问题,2.8 开始就使用 psync 命令来代替 sync 命令去执行同步操作。其中 PSYNC 具有全量重同步和部分重同步
两种模式。其中完整重同步用于处理初次复制的情况
,类似于之前旧版的 SYNC ;而部分重同步则是用于处理断线重连的情
况,只需要同步新的更改,采用 CONTINUE 标记是否进行部分重同步,提高了效率。
部分重同步具体实现
主要有以下三部分构成:
③ 心跳检测
刚才提到,主从同步有同步和命令传播 2 个步骤。
当完成了同步之后,主从服务器就会进入命令传播阶段,此时从服务器会以每秒 1 次的频率,向主服务器发送命令:REPLCONF ACK
,其中 replication_offset
是从服务器当前的复制偏移量
发送这个命令主要有三个作用:
① 单个Sentinel进程是否可行?
答案:当然不可行,而且上面sentinel配置也是很简陋。参考博文:深入学习Redis高可用之Sentinel相关概念
② 一主二从三哨兵配置下,当进行主从切换时,前台应用怎么办?
比如master-6379挂了,哨兵选举了slave-6380为新的master。那么此时SpringBoot怎么办?SpringBoot中配置的redis host还是6379。