在实际开发中,redis通常会搭建集群,来提高redis的整体的性能。但在客户端访问时有可能多次访问到不同的redis,因此造成多台redis数据不一致问题,为了解决这种多台redis中数据不同步问题,我们提出了主、从的概念;
Master负责写的操作,Slave负责读的操作,Master与Slave直接保证数据的同步。
注:一个Master可以对应有多个Slave,一个Slave只能有一个Master
准备好两个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启动日志:
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
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
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
redis的主从复制分为三个阶段:
1)Slave连接Master(建立连接阶段)
2)Master同步数据到Slave(数据同步阶段)
3)期间Master接到来自客户端"写"的命令之后需要将数据同步到Slave(命令传播阶段)
在主从配置的建立连接阶段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。
在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属于增量同步
从上图可以看出,在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
Master与Slave保持连接后,此后Master接到来自客户端"写"的命令之后,需要将数据同步各个Slave端,此阶段叫命令传播阶段。
在数据同步中,Master与Slave之间分别维护着一个offset偏移量
这样利于在网络抖动情况下,主从节点之间还可以继续接着上一次同步的位置进行同步,而不必要进行全量同步。
Tips:Master与Slave的offset不一致一般情况下是Master发送了指令但由于网络抖动等原因Slave没有接收到;
假设在命令传播时,Master已经传输到了"4"这个字节(offset为7),但Slave实际直接收到"t"这个字节(offset为5),由于Slave与Master一直保持着ping/pong机制,因此每秒Slave都会将自己所保存的offset发送到Master与之对比,那么下次Master则会从"t"这个字节开始发送数据到Slave;
在上一小结说了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判断进行全量同步还是增量同步。
复制积压缓冲区(replication backlog buffer):复制积压缓冲区是Master节点创建的一个先进先出的队列,默认大小为1MB,用于备份主节点的传播命令,所有的Slave共享一个复制积压缓冲区。
Slave将offset发送给Master之后,Master会根据当前的offset值来决定是全量同步还是增量同步。
完整同步过程如下:
总结:在网络抖动情况下,增量同步的条件:
1)offset之后的数据仍在复制积压缓冲区中(在Master执行bgsave期间,复制积压缓冲区未发现溢出)
2)runid要是同一个
在命令传播阶段,Master与Slave之间通过心跳机制来保证Master与Slave双方正常连接在线。
127.0.0.1:6379> config get repl-ping-Slave-period
1) "repl-ping-Slave-period"
2) "10"
127.0.0.1:6379>
作用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 3
min-Slaves-max-lag 5