Redis主从架构的核心原理
主从架构的核心原理
-
当启动一个slave node的时候,它会发送一个PSYNC命令给master node;
-
如果这是slave node重新连接master node,那么master node仅仅会复制给slave部分缺少的数据; 否则如果是slave node第一次连接master node,那么会触发一次full resynchronization;
-
开始full resynchronization的时候,master会启动一个后台线程,开始生成一份RDB快照文件,同时还会将从客户端收到的所有写命令缓存在内存中。RDB文件生成完毕之后,master会将这个RDB发送给slave,slave会先写入本地磁盘,然后再从本地磁盘加载到内存中。然后master会将内存中缓存的写命令发送给slave,slave也会同步这些数据;
-
slave node如果跟master node有网络故障,断开了连接,会自动重连。master如果发现有多个slave node都来重新连接,仅仅会启动一个rdb save操作,用一份数据服务所有slave node;
主从复制的断点续传
从redis 2.8开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份master node会在内存中常见一个backlog,master和slave都会保存一个replica offset还有一个master id,offset就是保存在backlog中的。如果master和slave网络连接断掉了,slave会让master从上次的replica offset开始继续复制但是如果没有找到对应的offset,那么就会执行一次resynchronization。
无磁盘化复制
repl-diskless-sync:master在内存中直接创建rdb,然后发送给slave,不会在自己本地落地磁盘了;
repl-diskless-sync-delay:等待一定时长再开始复制,因为要等更多slave重新连接过来;
过期key处理
slave不会过期key,只会等待master过期key。如果master过期了一个key,或者通过LRU淘汰了一个key,那么会模拟一条del命令发送给slave。
复制的完整流程
- slave node启动,仅仅保存master node的信息,包括master node的host和ip,但是复制流程没开始master host和ip是从哪儿来的,redis.conf里面的slaveof配置的;
- slave node内部有个定时任务,每秒检查是否有新的master node要连接和复制,如果发现,就跟master node建立socket网络连接;
- slave node发送ping命令给master node;
- 口令认证,如果master设置了requirepass,那么salve node必须发送masterauth的口令过去进行认证;
- master node第一次执行全量复制,将所有数据发给slave node;
- master node后续持续将写命令,异步复制给slave node;
数据同步相关的核心机制
指的就是第一次slave连接msater的时候,执行的全量复制,那个过程里面你的一些细节的机制
- master和slave都会维护一个offset
master会在自身不断累加offset,slave也会在自身不断累加offset,slave每秒都会上报自己的offset给master,同时master也会保存每个slave的offset, 这个倒不是说特定就用在全量复制的,主要是master和slave都要知道各自的数据的offset,才能知道互相之间的数据不一致的情况。
-
backlog:master node有一个backlog,默认是1MB大小,master node给slave node复制数据时,也会将数据在backlog中同步写一份,backlog主要是用来做全量复制中断候的增量复制的。
-
master run id:info server,可以看到master run id,如果根据host+ip定位master node,是不靠谱的,如果master node重启或者数据出现了变化,那么slave node应该根据不同的run id区分,run id不同就做全量复制,如果需要不更改run id重启redis,可以使用redis-cli debug reload命令。
-
psync:从节点使用psync从master node进行复制,psync runid offset,master node会根据自身的情况返回响应信息,可能是FULLRESYNC runid offset触发全量复制,可能是CONTINUE触发增量复制。
全量复制
- master执行bgsave,在本地生成一份rdb快照文件;
- master node将rdb快照文件发送给salve node,如果rdb复制时间超过60秒(repl-timeout),那么slave node就会认为复制失败,可以适当调节大这个参数;
- 对于千兆网卡的机器,一般每秒传输100MB,6G文件,很可能超过60s;
- master node在生成rdb时,会将所有新的写命令缓存在内存中,在salve node保存了rdb之后,再将新的写命令复制给salve node;
- client-output-buffer-limit slave 256MB 64MB 60,如果在复制期间,内存缓冲区持续消耗超过64MB,或者一次性超过256MB,那么停止复制,复制失败;
- slave node接收到rdb之后,清空自己的旧数据,然后重新加载rdb到自己的内存中,同时基于旧的数据版本对外提供服务;
- 如果slave node开启了AOF,那么会立即执行
BGREWRITEAOF
,重写AOF;
rdb生成、rdb通过网络拷贝、slave旧数据的清理、slave aof rewrite,很耗费时间,如果复制的数据量在4G~6G之间,那么很可能全量复制时间消耗到1分半到2分钟。
增量复制
- 如果全量复制过程中,master-slave网络连接断掉,那么salve重新连接master时,会触发增量复制;
- master直接从自己的backlog中获取部分丢失的数据,发送给slave node,默认backlog就是1MB;
- msater就是根据slave发送的psync中的offset来从backlog中获取数据的;
heartbeat
主从节点互相都会发送heartbeat信息,master默认每隔10秒发送一次heartbeat,salve node每隔1秒发送一个heartbeat;
异步复制
master每次接收到写命令之后,现在内部写入数据,然后异步发送给slave node;
Redis复制原理
Redis为了满足故障恢复和负载均衡的需求提供了复制功能,实现了相同数据的多个Redis副本,复制功能是高可用Redis的基础。
配置
建立复制
参与复制的Redis实例划分为主节点(master)和从节点(slave)。默认情况下,Redis都是主节点。每个从节点只能有一个主节点,而主节点可以同时具有多个从节点。
- 在配置文件中加入
slaveof{masterHost}{masterPort}
随Redis启动生效; - 在redis-server启动命令后加入
--slaveof{masterHost}{masterPort}
生效; - 直接使用命令:
slaveof{masterHost}{masterPort}
生效;
断开复制
slaveof
命令不但可以建立复制,还可以在从节点执行slaveof no one
来断开与主节点复制关系。
断开复制主要流程:
- 断开与主节点复制关系。
- 从节点晋升为主节点。
通过slaveof命令还可以实现切主操作,所谓切主是指把当前从节点对主节点的复制切换到另一个主节点。执行slaveof{newMasterIp}{newMasterPort}
命令即可。
切主操作流程如下:
- 断开与旧主节点复制关系;
- 与新主节点建立复制关系;
- 删除从节点当前所有数据;
- 对新主节点进行复制操作;
原理
复制过程
- 保存主节点(master)信息:执行slaveof后从节点只保存主节点的地址信息便直接返回,这时建立复制流程还没有开始。
- 主从建立socket连接:从节点(slave)内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接。发送socket到主节点(master)的24555端口建立连接。如果从节点无法建立连接,定时任务会无限重试直到连接成功或者执行slaveof no one取消复制。
- 发送ping命令:连接建立成功后从节点发送ping请求进行首次通信,ping请求主要目的
如下:
- 检测主从之间网络套接字是否可用;
- 检测主节点当前是否可接受处理命令;
- 权限验证:如果主节点设置了requirepass参数,则需要密码验证,从节点必须配置masterauth参数保证与主节点相同的密码才能通过验证;如果验证失败复制将终止,从节点重新发起复制流程。
- 同步数据集:主从复制连接正常通信后,对于首次建立复制的场景,主节点会把持有的数据全部发送给从节点,这部分操作是耗时最长的步骤。Redis在2.8版本以后采用新复制命令
psync
进行数据同步,原来的sync
命令依然支持。 - 命令持续复制:当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来主节点会持续地把写命令发送给从节点,保证主从数据一致性。
数据同步
Redis在2.8及以上版本使用psync
命令完成主从数据同步,同步过程分 为:全量复制和部分复制。
- 全量复制:一般用于初次复制场景,Redis早期支持的复制功能只有全量复制,它会把主节点全部数据一次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。
- 部分复制:用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销。
psync命令运行需要以下组件支持:
- 主从节点各自复制偏移量;
- 主节点复制积压缓冲区;
- 主节点运行id;
复制偏移量
参与复制的主从节点都会维护自身复制偏移量。主节点(master)在处理完写入命令后,会把命令的字节长度做累加记录,统计信息在info relication中的master_repl_offset指标中:
1
2
3
4
5
127.0.0.1:6379> info replication
# Replication
role:master
...
master_repl_offset:1055130
从节点(slave)每秒钟上报自身的复制偏移量给主节点,因此主节点 也会保存从节点的复制偏移量,统计指标如下:
1
2
3
4
127.0.0.1:6379> info replication
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=1055214,lag=1
...
从节点在接收到主节点发送的命令后,也会累加记录自身的偏移量。统 计信息在info relication中的slave_repl_offset指标中:
1
2
3
4
5
127.0.0.1:6380> info replication
# Replication
role:slave
...
slave_repl_offset:1055214
通过对比主从节点的复制偏移量,可以判断主从节点数据是否一致。
复制积压缓冲区
复制积压缓冲区是保存在主节点上的一个固定长度的队列,默认大小为 1MB,当主节点有连接的从节点(slave)时被创建,这时主节点(master) 响应写命令时,不但会把命令发送给从节点,还会写入复制积压缓冲区。 复制缓冲区相关统计信息保存在主节点的info replication中:
1
2
3
4
5
6
7
8
127.0.0.1:6379> info replication
# Replication
role:master
...
repl_backlog_active:1 // 开启复制缓冲区
repl_backlog_size:1048576 // 缓冲区最大长度
repl_backlog_first_byte_offset:7479 // 起始偏移量,计算当前缓冲区可用范围
repl_backlog_histlen:1048576 // 已保存数据的有效长度。
复制积压缓冲区内的可用偏移量范围: [repl_backlog_first_byte_offset, repl_backlog_first_byte_offset+repl_backlog_histlen]。
主节点运行ID
每个Redis节点启动后都会动态分配一个40位的十六进制字符串作为运 行ID。运行ID的主要作用是用来唯一识别Redis节点,比如从节点保存主节 点的运行ID识别自己正在复制的是哪个主节点。如果只使用ip+port的方式识 别主节点,那么主节点重启变更了整体数据集,从节点再基于偏移量复制数据将是不安全的,因此当运行ID变化后从节点将 做全量复制。
psync命令
从节点使用psync命令完成部分复制和全量复制功能,命令格式: psync{runId}{offset}
,参数含义如下:
- runId:从节点所复制主节点的运行id;
- offset:当前从节点已复制的数据偏移量;
- 从节点(slave)发送
psync
命令给主节点,参数runId是当前从节点保 存的主节点运行ID,如果没有则默认值为,参数offset是当前从节点保存的 复制偏移量,如果是第一次参与复制则默认值为-1; - 主节点(master)根据
psync
参数和自身数据情况决定响应结果:- 如果回复
+FULLRESYNC{runId}{offset}
,那么从节点将触发全量复制流程; - 如果回复
+CONTINUE
,从节点将触发部分复制流程; - 如果回复
+ERR
,说明主节点版本低于Redis2.8,无法识别psync命令, 从节点将发送旧版的sync命令触发全量复制流程;
- 如果回复
全量复制
全量复制是Redis最早支持的复制方式,也是主从第一次建立复制时必 须经历的阶段。触发全量复制的命令是sync
和psync
。redis版本低于2.8使用sync
命令;redis版本大于等于2.8使用psync
命令。
- 发送psync命令进行数据同步,由于是第一次进行复制,从节点没有复制偏移量和主节点的运行ID,所以发送psync-1;
- 主节点根据psync-1解析出当前为全量复制,回复+FULLRESYNC响应;
- 从节点接收主节点的响应数据保存运行ID和偏移量offset;
- 主节点执行bgsave保存RDB文件到本地;
- 主节点发送RDB文件给从节点,从节点把接收的RDB文件保存在本地并直接作为从节点的数据文件;
- 对于从节点开始接收RDB快照到接收完成期间,主节点仍然响应读写命令,因此主节点会把这期间写命令数据保存在复制客户端缓冲区内,当从节点加载完RDB文件后,主节点再把缓冲区内的数据发送给从节点,保证主从之间数据一致性;
- 从节点接收完主节点传送来的全部数据后会清空自身旧数据;
- 从节点清空数据后开始加载RDB文件;
- 从节点成功加载完RDB后,如果当前节点开启了AOF持久化功能, 它会立刻做bgrewriteaof操作,为了保证全量复制后AOF持久化文件立刻可用;
无盘复制:为了降低主节点磁盘开销,Redis支持无盘复制,生成的RDB文件不保存到硬盘而是直接通过网络发送给从节点,通过repl- diskless-sync
参数控制,默认关闭。无盘复制适用于主节点所在机器磁盘性能较差但网络带宽较充裕的场景。
时间开销主要包括:
- 主节点bgsave时间;
- RDB文件网络传输时间;
- 从节点清空数据时间;
- 从节点加载RDB的时间;
- 可能的AOF重写时间;
部分复制
部分复制主要是Redis针对全量复制的过高开销做出的一种优化措施, 使用psync{runId}{offset}
命令实现。
- 当主从节点之间网络出现中断时,如果超过repl-timeout时间,主节点会认为从节点故障并中断复制连接;
- 主从连接中断期间主节点依然响应命令,但因复制连接中断命令无 法发送给从节点,不过主节点内部存在的复制积压缓冲区,依然可以保存最近一段时间的写命令数据,默认最大缓存1MB;
- 当主从节点网络恢复后,从节点会再次连上主节点;
- 当主从连接恢复后,由于从节点之前保存了自身已复制的偏移量和主节点的运行ID。因此会把它们当作psync参数发送给主节点,要求进行部分复制操作;
- 主节点接到psync命令后首先核对参数runId是否与自身一致,如果一致,说明之前复制的是当前主节点;之后根据参数offset在自身复制积压缓冲区查找,如果偏移量之后的数据存在缓冲区中,则对从节点发送
+CONTINUE
响应,表示可以进行部分复制; - 主节点根据偏移量把复制积压缓冲区里的数据发送给从节点,保证主从复制进入正常状态;
心跳
主从节点在建立复制后,它们之间维护着长连接并彼此发送心跳命令。 主从心跳判断机制:
- 主从节点彼此都有心跳检测机制,各自模拟成对方的客户端进行通 信,通过client list命令查看复制相关客户端信息,主节点的连接状态为 flags=M,从节点连接状态为flags=S;
- 主节点默认每隔10秒对从节点发送ping命令,判断从节点的存活性 和连接状态。可通过参数
repl-ping-slave-period
控制发送频率; - 从节点在主线程中每隔1秒发送
replconf ack{offset}
命令,给主节点 上报自身当前的复制偏移量;replconf命令主要作用:- 实时监测主从节点网络状态;
- 上报自身复制偏移量,检查复制数据是否丢失,如果从节点数据丢失,再从主节点的复制缓冲区中拉取丢失数据;
- 实现保证从节点的数量和延迟性功能,通过
min-slaves-to-write
、min- slaves-max-lag
参数配置定义;
异步复制
主节点不但负责数据读写,还负责把写命令同步给从节点。写命令的发送过程是异步完成,也就是说主节点自身处理完写命令后直接返回给客户端,并不等待从节点复制完成。
主节点复制流程:
- 主节点接收处理命令;
- 命令处理完之后返回响应结果;
- 对于修改命令异步发送给从节点,从节点在主线程中执行复制的命令;
在主节点执行info replication命令查看延迟多少字节。
1
slave0:ip=127.0.0.1,port=6380,state=online,offset=841,lag=1 master_repl_offset:841
- offset表示当前从节点的复制偏移量;
- master_repl_offset表示当前主节点的复制偏移量,两者的差值就是当前从节点复制延迟量;