搞懂Redis复制原理
前言
与大多数db一样,Redis也提供了复制机制,以满足故障恢复和负载均衡等需求。复制也是Redis高可用的基础,哨兵和集群都是建立在复制基础上实现高可用的。复制不仅提高了整个系统的容错能力,还可以水平扩展,实现在一个重读取的应用中,通过增加多个Redis只读从实例来减轻主实例的压力。
本文主要介绍Redis复制机制
一.配置与实践
配置
Redis实例分为主节点(master)和从节点(slave),默认情况下都是主节点。每一个从节点只能有一个主节点,但是每一个主节点可以有多个从节点(注意数量,多个从节点会导致主节点写命令多次发送从而过度消耗网络带宽,可用树状结构降低主节点负载)。复制是单向的,只能从主节点复制到从节点。配置复制的方式由以下3种:
- 在redis-slave.conf配置文件中加入slaveof {masterHost} {masterPort}
- 在redis-server启动命令后加入 --slaveof {masterHost} {masterPort}
- 启动后直接使用命令slaveof {masterHost} {masterPort}
综上,Redis支持在启动之前配置,也支持运行中动态配置。
实践
我们用动态配置的方法来配置,先起一个端口为6379的Redis实例,作为主节点:
redis-server /usr/local/Cellar/redis/4.0.9/.bottle/etc/redis.conf
再起一个端口为6380的Redis实例,作为6379的从节点:
redis-server /usr/local/Cellar/redis/4.0.9/.bottle/etc/redis-slave.conf
用客户端连到从节点,使用slaveof命令,slaveof配置都是在从节点发起的。
127.0.0.1:6380> slaveof 127.0.0.1 6379 OK
从节点日志:
75585:S 06 May 16:27:50.389 * Connecting to MASTER 127.0.0.1:6379 75585:S 06 May 16:27:50.389 * MASTER <-> SLAVE sync started 75585:S 06 May 16:27:50.390 * Non blocking connect for SYNC fired the event. 75585:S 06 May 16:27:50.390 * Master replied to PING, replication can continue... 75585:S 06 May 16:27:50.390 * Trying a partial resynchronization (request 47770067272eb8101489fe7c00c8e838125c3aa3:1). 75585:S 06 May 16:27:50.392 * Full resync from master: e91e683b1e13332f97ecb9fa90ecdace460ab4ca:0 75585:S 06 May 16:27:50.392 * Discarding previously cached master state. 75585:S 06 May 16:27:50.491 * MASTER <-> SLAVE sync: receiving 215 bytes from master 75585:S 06 May 16:27:50.492 * MASTER <-> SLAVE sync: Flushing old data 75585:S 06 May 16:27:50.492 * MASTER <-> SLAVE sync: Loading DB in memory 75585:S 06 May 16:27:50.492 * MASTER <-> SLAVE sync: Finished with success
主节点日志:
75553:M 06 May 16:27:50.391 * Slave 127.0.0.1:6380 asks for synchronization 75553:M 06 May 16:27:50.391 * Partial resynchronization not accepted: Replication ID mismatch (Slave asked for '47770067272eb8101489fe7c00c8e838125c3aa3', my replication IDs are '160af1c75f86edc50186e3e4a4dc6ecb5e3fa586' and '0000000000000000000000000000000000000000') 75553:M 06 May 16:27:50.391 * Starting BGSAVE for SYNC with target: disk 75553:M 06 May 16:27:50.391 * Background saving started by pid 75675 75675:C 06 May 16:27:50.395 * DB saved on disk 75553:M 06 May 16:27:50.490 * Background saving terminated with success 75553:M 06 May 16:27:50.491 * Synchronization with slave 127.0.0.1:6380 succeeded
可以看到,第一次建立复制关系的时候,主节点和从节点进行了一次全量复制,见图:
当完成复制的建立之后,接下来主节点会持续的把写命令发送给从节点,保证主从数据一致。
在主实例上添加新的key:
127.0.0.1:6379> set Lin 112131 OK
在从实例查看刚刚添加的key:
127.0.0.1:6380> get Lin "112131"
只读
由于复制只能从主节点到从节点,对于从节点的数据修改主节点无法感知,为了避免主从实例之间的数据不一致。从节点默认配置为只读模式:
slave-read-only yes
二.工作原理
我们先讲3个比较关键的参数:master_replid、master_repl_offset和slave_repl_offset。我们分别在master6379和slave6380上执行info replication
127.0.0.1:6379> info replication # Replication role:master connected_slaves:1 slave0:ip=127.0.0.1,port=6380,state=online,offset=1093,lag=1 master_replid:e91e683b1e13332f97ecb9fa90ecdace460ab4ca master_replid2:0000000000000000000000000000000000000000 master_repl_offset:1093 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:1093 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:9 master_sync_in_progress:0 slave_repl_offset:1107 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:e91e683b1e13332f97ecb9fa90ecdace460ab4ca master_replid2:0000000000000000000000000000000000000000 master_repl_offset:1107 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:1107
- master_replid是master启动时生成的随机字符串,用来标识主实例
- master_repl_offset是复制流中的一个偏移量,master处理完写入命令后,会把命令的字节长度做累加记录,统计在该字段。该字段也是实现部分复制的关键字段。
- slave_repl_offset同样也是一个偏移量,从节点收到主节点发送的命令后,累加自身的偏移量,通过比较主从节点的复制偏移量可以判断主从节点数据是否一致。
当从实例连接到主实例时,从实例会发送master_replid和master_repl_offset(标识与主实例同步的最后一个快照)请求部分复制。如果主实例接收部分复制的话则从最后一个偏移量开始增量进行部分复制,否则将进行全量复制。如图:
三.数据同步
Redis在2.8之前使用sync命令完成主从数据同步,Redis在2.8及以上使用psync命令完成主从数据同步,同步过程分为:全量复制和部分复制
全量复制
全量复制是Redis最早支持的复制方式,也是主从第一次建立复制的时候必须经历的。它会把主节点全部数据一次性发送给从节点,当数据量较大的时候,会对主从节点和网络造成很大开销。主节点执行bgsave保存RDB文件,然后将这个文件发送给从节点,从节点收到RDB文件后,会先将内存中的所有数据清除,然后再将RDB文件中的数据导入。
主实例在复制过程中是完全异步的,因此不会阻塞主节点的请求。在这一期间内主节点的所有写入命令数据都保存在从客户端缓冲区(slave client buffer)内,在从节点加载完RDB文件后,主节点会将这个缓冲区的内容发送给从节点。
从客户端缓冲区默认大小限制为:
client-output-buffer-limit slave 256mb 64mb 60
意思是如果60秒内缓冲区消耗持续大于64MB或者直接超过256MB时,主节点将直接关闭复制客户端连接,造成全量同步失败。
部分复制
在高版本的Redis实现中,master_replid和offset存储在RDB文件中。当从实例在复制过程中,因网络闪断等原因造成的数据丢失场景,Redis能够从rdb文件中重新加载master_replid和offset,从而使部分重新同步成为可能。因为补发的数据远小于全量数据,所以可以有效的避免全量复制带来的负载和消耗。
之前说过,从节点连接主节点之后,会使用master_replid和master_repl_offset请求主节点,首先判断master_replid是否和自己的master_replid一致,然后检查请求中的master_repl_offset是否能从缓冲区(replication backlog)中获取,如果偏移量在backlog范围内,那么可以进行部分复制。如果在断开连接期间主节点收到的写入命令的数量超过了backlog缓冲区的容量,那么会进行全量复制。默认情况下backlog为1MB。
参考
基本和redis篇第一个帖子一样: