目录
一、Redis主从复制
1.1 搭建主从复制架构
1.1.1 主从复制架构简介
1.1.2 搭建主从复制架构
1.2 主从复制工作流程
1.2.1 建立连接阶段
1.2.2 数据同步阶段
1.2.2.1 工作流程
1.2.2.2 增量同步原理
1.2.3 命令传播阶段
1.2.3.1 偏移量(offset)
1.2.3.2 运行id(runid)
1.2.3.3 复制积压缓冲区
1.2.3.4 心跳机制
一、Redis主从复制
1.1 搭建主从复制架构
1.1.1 主从复制架构简介
在实际开发中,redis通常会搭建集群,来提高redis的整体的性能。但在客户端访问时有可能多次访问到不同的redis,因此造成多台redis数据不一致问题,为了解决这种多台redis中数据不同步问题,我们提出了主、从的概念;
Master负责写的操作,Slave负责读的操作,Master与Slave直接保证数据的同步。
注:一个Master可以对应有多个Slave,一个Slave只能有一个Master
1.1.2 搭建主从复制架构
准备好两个Redis配置文件(一主一从):
6379(Master):
port 6379 dir "/root/redis-4.0.11/data" daemonize no dbfilename dump-6379.rdb appendonly yes appendfsync everysec appendfilename "appendonly-6379.aof"
6380(Slave):
port 6380 dir "/root/redis-4.0.11/data" dbfilename dump-6380.rdb daemonize no appendonly yes appendfsync everysec appendfilename "appendonly-6380.aof"
启动两台Redis服务器
redis-4.0.11/src/redis-server redis-4.0.11/conf/redis-6379.conf redis-4.0.11/src/redis-server redis-4.0.11/conf/redis-6380.conf
使用客户端连接:
redis-4.0.11/src/redis-cli -p 6379 redis-4.0.11/src/redis-cli -p 6380
- 主从复制命令:
Slave连接Master,在Slave的客户端输入如下命令:
Slaveof <Masterip> <Masterport>
示例:
127.0.0.1:6379> Slaveof 192.168.170.142 6379 OK 127.0.0.1:6379>
如果Master设置了密码,那么在Slave服务器启动的时候就要指定Master的密码:
redis-server ../config/redis-6380.conf --Masterauth admin
分别查看Slave与Master启动日志:
- Master日志:
23305:M 11 Apr 10:24:20.225 * Slave 127.0.0.1:6479 asks for synchronization 23305:M 11 Apr 10:24:20.225 * Partial resynchronization not accepted: Replication ID mismatch (Slave asked for '18b61ced5990337dd8377e8bf81fcd2b20e7d399', my replication IDs are 'fa4d40f8562c10acbbd7a9955a88f7cd6dc33182' and '0000000000000000000000000000000000000000') 23305:M 11 Apr 10:24:20.225 * Starting BGSAVE for SYNC with target: disk 23305:M 11 Apr 10:24:20.225 * Background saving started by pid 23339 23339:C 11 Apr 10:24:20.227 * DB saved on disk 23339:C 11 Apr 10:24:20.227 * RDB: 2 MB of memory used by copy-on-write 23305:M 11 Apr 10:24:20.315 * Background saving terminated with success 23305:M 11 Apr 10:24:20.315 * Synchronization with Slave 127.0.0.1:6479 succeeded
- Slave日志:
23279:S 11 Apr 10:24:20.224 * Connecting to Master 127.0.0.1:6379 23279:S 11 Apr 10:24:20.224 * Master <-> Slave sync started 23279:S 11 Apr 10:24:20.224 * Non blocking connect for SYNC fired the event. 23279:S 11 Apr 10:24:20.224 * Master replied to PING, replication can continue... 23279:S 11 Apr 10:24:20.224 * Trying a partial resynchronization (request 18b61ced5990337dd8377e8bf81fcd2b20e7d399:1). 23279:S 11 Apr 10:24:20.226 * Full resync from Master: f1d39130cfadf4b3e0eb192e4143ae6bcb01677b:0 23279:S 11 Apr 10:24:20.226 * Discarding previously cached Master state. 23279:S 11 Apr 10:24:20.315 * Master <-> Slave sync: receiving 176 bytes from Master 23279:S 11 Apr 10:24:20.315 * Master <-> Slave sync: Flushing old data 23279:S 11 Apr 10:24:20.315 * Master <-> Slave sync: Loading DB in memory 23279:S 11 Apr 10:24:20.316 * Master <-> Slave sync: Finished with success 23279:S 11 Apr 10:24:20.316 * Background append only file rewriting started by pid 23340 23279:S 11 Apr 10:24:20.356 * AOF rewrite child asks to stop sending diffs. 23340:C 11 Apr 10:24:20.356 * Parent agreed to stop sending diffs. Finalizing AOF... 23340:C 11 Apr 10:24:20.356 * Concatenating 0.00 MB of AOF diff received from parent. 23340:C 11 Apr 10:24:20.356 * SYNC append only file rewrite performed 23340:C 11 Apr 10:24:20.357 * AOF rewrite: 6 MB of memory used by copy-on-write 23279:S 11 Apr 10:24:20.426 * Background AOF rewrite terminated with success 23279:S 11 Apr 10:24:20.426 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB) 23279:S 11 Apr 10:24:20.426 * Background AOF rewrite finished successfully
- 当Master宕机后Slave会一直尝试连接Master,日志信息如下:
29511:S 11 Apr 10:37:37.567 # Error condition on socket for SYNC: Connection refused 29511:S 11 Apr 10:37:38.577 * Connecting to Master 127.0.0.1:6379 29511:S 11 Apr 10:37:38.577 * Master <-> Slave sync started 29511:S 11 Apr 10:37:38.577 # Error condition on socket for SYNC: Connection refused 29511:S 11 Apr 10:37:39.587 * Connecting to Master 127.0.0.1:6379 29511:S 11 Apr 10:37:39.587 * Master <-> Slave sync started 29511:S 11 Apr 10:37:39.587 # Error condition on socket for SYNC: Connection refused 29511:S 11 Apr 10:37:40.597 * Connecting to Master 127.0.0.1:6379 29511:S 11 Apr 10:37:40.597 * Master <-> Slave sync started 29511:S 11 Apr 10:37:40.597 # Error condition on socket for SYNC: Connection refused 29511:S 11 Apr 10:37:41.606 * Connecting to Master 127.0.0.1:6379
Slave端口与Master的连接(断开与Master连接后,Slave不再接收Master的同步数据):
Slaveof no one
1.2 主从复制工作流程
redis的主从复制分为三个阶段:
1)Slave连接Master(建立连接阶段)
2)Master同步数据到Slave(数据同步阶段)
3)期间Master接到来自客户端"写"的命令之后需要将数据同步到Slave(命令传播阶段)
1.2.1 建立连接阶段
在主从配置的建立连接阶段Master与Slave之间会做如下操作:
1)Slave发送Slaveof Masterhost Masterport命令连接Master
2)Master接到来自Slave的连接,并开始响应对方。
3)Slave得到响应之后将Masterhost与Masterport及一些其他的Master信息保存到Slave端。
4)Slave确保连接无误后开始创建socket通道,用于后续的数据复制工作。
5)Slave与Master之间周期性的发送ping心跳,检查Slave与Master之间是否通信正常。
6)Master接收到Slave的ping心跳后会给对应的Slave响应pong。ping/pong机制
7)Slave发送本机设置的Master密码Masterauth来到Master进行验证(Master有设置密码的情况下)。
8)Master进行身份识别,如果认证错误,尝试重新连接。Slave服务器端报如下错误:
Master aborted replication with an error: NOAUTH Authentication required.
9)Master身份识别成功后,Slave会将自己的ip、端口等信息发送到Master,Master将保存此Slave的ip、端口以及一些其他状态信息,记录在info Replication。
1.2.2 数据同步阶段
1.2.2.1 工作流程
在Master与Slave建立连接成功后,开始数据同步。
1)Slave发送psync2(psync1、sync)指令给Master,需要同步数据
2)Master接到指令后开始执行bgsave指令,并创建复制积压缓冲区,在此期间,Master接收到任何来自客户端的"写"操作都会记录在复制积压缓冲区一份。
3)Master将rdb文件通过前面创建的socket通道发送给Slave
4)Slave接收到rdb文件后,清空当前机器的所有数据,开始同步rdb中的数据
5)告知Master文件以及恢复完毕
6)Master将复制积压缓冲区中的指令发送给Slave
7)Slave将接收到的指令执行bgrewriteaof重写,之后进行数据恢复
步骤1-4属于全量同步
步骤5-7属于增量同步
1.2.2.2 增量同步原理
从上图可以看出,在Master执行bgsave期间接收到的所有命令都会存放在复制积压缓冲区一份,而复制积压缓冲区的大小是有限度的,默认是1MB,如果缓冲区已经满了,则会把最前面的数据挤出去(删除),后期进行增量同步时发现数据不一致,则会进行全量同步,从而造成同步死循环,因此复制积压缓冲区不易设置为太小。
- 查看复制积压缓冲区:
127.0.0.1:6379> config get repl-backlog-size 1) "repl-backlog-size" 2) "1048576" 127.0.0.1:6379>
- 调整复制积压缓冲区大小:
repl-backlog-size 2096576
1.2.3 命令传播阶段
Master与Slave保持连接后,此后Master接到来自客户端"写"的命令之后,需要将数据同步各个Slave端,此阶段叫命令传播阶段。
1.2.3.1 偏移量(offset)
在数据同步中,Master与Slave之间分别维护着一个offset偏移量
- Master的offset记录的是:Master在发送数据到Slave时Master已经发送的数据位置
- Slave的offset记录的是:Slave实际接收到的数据位置
这样利于在网络抖动情况下,主从节点之间还可以继续接着上一次同步的位置进行同步,而不必要进行全量同步。
Tips:Master与Slave的offset不一致一般情况下是Master发送了指令但由于网络抖动等原因Slave没有接收到;
假设在命令传播时,Master已经传输到了"4"这个字节(offset为7),但Slave实际直接收到"t"这个字节(offset为5),由于Slave与Master一直保持着ping/pong机制,因此每秒Slave都会将自己所保存的offset发送到Master与之对比,那么下次Master则会从"t"这个字节开始发送数据到Slave;
1.2.3.2 运行id(runid)
在上一小结说了Master与Slave之间都维护着一个offset偏移量,让我们可以根据offset的偏移量进行增量同步,避免网络抖动情况下进行全量同步。
但是如果在同步的过程中切换了Master节点则会出现问题(哨兵切换等问题),因为新的Master节点并没有维护着offset偏移量,并且Slave中的数据应该与新Master节点数据保持一致。那么redis是如何做到这一点的呢?
其实在Master与Slave服务器启动时都保存有一个由40位随机的16进制字符串组成的运行id(runid),用于标识一台唯一的redis,每次启动都不一样。在info的server组下可以查看到:
info server
在Slave首次连接Master时,Master会将自己的runid发送给Slave,Slave会将此runid保存下来(我们查询不到),当出现网络抖动时,Slave会将此runid发送给Master,Master根据此runid判断进行全量同步还是增量同步。
- runid与现在Master的runid一致:说明网络是同一个Master节点,增量同步条件满足(具体是否执行还需要看复制积压缓冲区是否有溢出)。
- runid与现在Master的runid不一致:说明是新的Master节点,进行全量同步。
1.2.3.3 复制积压缓冲区
复制积压缓冲区(replication backlog buffer):复制积压缓冲区是Master节点创建的一个先进先出的队列,默认大小为1MB,用于备份主节点的传播命令,所有的Slave共享一个复制积压缓冲区。
Slave将offset发送给Master之后,Master会根据当前的offset值来决定是全量同步还是增量同步。
- 如果offset偏移量之后的数据仍在缓冲区中(意味着在执行bgsave时,缓冲区还未满,未溢出),则执行增量同步(runid要是同一个)。
- 如果offset偏移量之后的数据不在缓冲区中(意味着在执行bgsave时间过长,缓冲区的数据已经被挤出,数据溢出),此时执行全量同步
完整同步过程如下:
总结:在网络抖动情况下,增量同步的条件:
1)offset之后的数据仍在复制积压缓冲区中(在Master执行bgsave期间,复制积压缓冲区未发现溢出)
2)runid要是同一个
1.2.3.4 心跳机制
在命令传播阶段,Master与Slave之间通过心跳机制来保证Master与Slave双方正常连接在线。
- Master定时向Slave发送ping,查看对方是否在线,通过repl-ping-Slave-period参数维护,默认10s
127.0.0.1:6379> config get repl-ping-Slave-period 1) "repl-ping-Slave-period" 2) "10" 127.0.0.1:6379>
- Slave定时向Master发送 replcconf ack<偏移量>命令,频率为每秒发送一次。
作用1):用于检测Slave与Master的连接状态,可以在Master端使用info命令查看replication组的lag值,代表上一次接收到此Slave的replcconf ack命令间隔。
作用2):Slave发送当前的offset来到Master与之对比,如果出现网络丢包情况下那么Slave与Master之间的offset不一致,通常是Master发了多个字节,而Slave由于网络原因没有接到那么多,可通过offset判断出上一条指令是否丢失;如果丢失,主服务器可从复制积压缓冲区找出丢失的指令,重新命令传播。
2.8版本以前无法检测命令是否丢失,因此存在主从数据不一致的风险。
作用3):Slave每次发送replcconf ack命令给Master,Master用于确认有多少个Slave与之连接,并且还可以确定多个Slave与Master上一次发送心跳的时间(lag),如果Slave长时间未发送心跳来到Master,可以先将Master暂停写的操作。
- min-Slaves-to-write:当前Master的Slave数量小于此值则暂停写操作
min-Slaves-to-write 3
- min-Slaves-max-lag:当前Master的Slave的lag都等于此值则暂停写的操作
min-Slaves-max-lag 5