【Redis】主从复制
为了满足故障恢复和负载均衡等需求,Redis提供了复制功能,实现了相同数据的多个副本。复制功能是高可用Redis的基础,哨兵和集群也都是在复制的基础上实现高可用。
配置
建立复制
参与复制的Redis实例划分为主节点(master)和从节点(slave)。默认情况下,Redis都是主节点。每个从节点只能有一个主节点,而主节点可以同时具有多个从节点。复制的数据流是单向的,只能由主节点复制到从节点。
配置复制的方式有以下三种:
- 在配置文件中加入slaveof {masterHost} {masterPort} 随Redis启动生效
- 在redis-server启动命令后加入--slaveof
- 直接使用命令:slaveof
slaveof本身是异步命令,执行slaveof命令时,节点只保存主节点信息后返回,后续复制流程在节点内部异步执行。主从节点复制成功建立后,可以使用info replication命令查看复制相关状态。
断开复制
slaveof no one命令用来在从节点执行断开与主节点的复制关系。
断开复制主要流程:
- 断开与主节点复制关系
- 从节点晋升为主节点
通过slaveof {newMasterIp} {new MasterPort}命令还可以实现切主操作,把当前节点对主节点的复制切换到另一个主节点。
切主流程如下:
- 断开与旧主节点的复制关系
- 与新主节点建立复制关系
- 删除从节点当前所有数据
- 对新主节点进行复制操作
安全性
主节点设置requirepass参数进行密码验证时,从节点需要配置masterauth参数与主节点密码保持一致。
只读
默认情况下,从节点使用slave-read-only=yes配置成只读模式。由于复制关系只能从主节点到从节点,对于从节点的任何修改主节点都无法感知,修改从节点会造成主从数据不一致,因此不建议修改线上从节点的只读模式。
传输延迟
主从节点一般部署在不同机器上,考虑到复制时的网络延迟问题,Redis提供repl-disable-tcp-nodelay参数用于控制是否关闭TCP_NODELAY,默认关闭。
- 当关闭时,主节点产生的命令数据无论大小都会及时地发送给从节点,这样主从之间延时变小,但是增加了网络带宽的消耗。适用于主从之间网络环境良好的场景,如同机架或机房部署。
- 当开启时,主节点会合并较小的TCP数据包从而节省带宽。默认发送时间间隔取决于Linux内核,一般默认40ms。这样节省了带宽但是增大了主从之间的延迟,适用于主从网络环境复杂或带宽紧张的场景,如跨机房部署。
拓扑
Redis的复制拓扑结构可以支持单层或多层复制关系,根据拓扑复杂性可以分为三种:一主一从、一主多从、树状主从结构。
一主一从
一主一从结构是最简单的复制拓扑结构,用于主节点出现宕机时从节点提供故障转移支持。
当应用写命令并发量较高且需要持久化时,可以只在从节点上开启AOF,这样既保证数据安全性同时也避免了持久化对主节点的性能干扰。
需要注意的是,当主节点关闭持久化功能时,如果主节点脱机要避免自动重启操作。因为主节点之前没有开启持久化功能自动重启后数据集为空,这时从节点如果继续复制主节点会导致从节点数据也被清空。
一主多从
一主多从结构(又称为星形拓扑结构)使得应用端可以利用多个从节点实现读写分离。对于读占比较大的场景,可以把读命令发送到从节点来分担主节点压力。
树状主从
树状主从结构(又称为树状拓扑结构)使得从节点不但可以复制主节点数据,同时可以作为其他从节点的主节点继续向下层复制。通过引入复制中间层,可以有效降低主节点负载和需要传送给从节点的数据量。
原理
复制过程
复制过程大致分为6个过程:
- 保存主节点信息:执行slaveof后从节点保存主节点的地址信息后直接返回
- 主从建立socket连接:从节点(slave)内部通过每秒运行的定时任务维护复制相关逻辑,当发现存在新主节点会尝试与之建立网络连接
- 发送ping命令:连接建立成功后从节点发送ping请求进行首次通信,如果失败会定时重连。发送ping请求主要目的如下:
- 检测主从之间网络套接字是否可用
- 检测主节点当前是否可接受处理命令
- 权限验证:如果主节点设置了requirepass参数,需要进行密码验证(从节点masterauth参数)
- 同步数据集:主从复制连接正常通信后,对于首次建立复制的场景,主节点会把持有的数据全部发送给从节点
- 命令持续复制:数据集同步完成后,主节点会持续地把写命令发送到从节点,保证主从数据一致性
数据同步
Redis在2.8及以上版本使用psync命令完成主从数据同步,2.8以下使用sync命令,同步过程分为全量复制和部分复制。
全量复制一般用于初次复制场景,把主节点全部数据一次性发送到从节点,当数据量较大时,会对主从节点和网络造成很大的开销。
部分复制用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连接主节点,如果条件允许,主节点会补发丢失数据给从节点。部分复制有效避免了不必要的全量复制操作。
psync命令运行需要以下组件支持:
- 主从节点各自复制偏移量
- 主节点复制积压缓冲区
- 主节点运行id
复制偏移量
参与复制的主从节点都会维护自身复制偏移量。主节点在处理完写入命令后,会把命令的字节长度做累加记录,统计信息info replication中的master_repl_offset指标中。
从节点每秒钟上报自身的复制偏移量给从主节点,因此主节点也会保存从节点的复制偏移量。从节点在接收到主节点发送的命令后,也会累加记录自身的偏移量,统计信息在info replication中的slave_repl_offset指标中。
通过对比主从节点的复制偏移量,可以判断主从节点数据是否一致。
复制积压缓冲区
复制积压缓冲区是保存在主节点上的一个固定长度的队列,默认大小是1MB。当主节点有连接的从节点时被创建,这时主节点响应写命令时,不但会把命令发送给从节点,还会写到复制积压缓冲区。
缓冲区本质上是先进先出的定长队列,实现保存最近已复制数据的功能,用于部分复制和复制命令丢失数据的补救。
复制缓冲区相关信息保存在主节点info replication中:
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识别正在复制的是哪个主节点。使用info server命令可查看当前节点的运行ID。
psync命令
psync命令格式为psync {runId} {offset}
,runId为主节点运行id,offset为当前从节点已复制的数据偏移量。
psync命令运行流程如下:
- 从节点发送psync命令给主节点,第一次参与复制时offset为-1
- 主节点根据psync参数和自身数据决定响应结果:
- 回复+FULLRESYNC {runId} {offset},此时从节点将触发全量复制流程
- 回复+CONTINUE,从节点将触发部分复制流程
- 回复+ERR,说明主节点版本低于2.8无法识别psync命令,从节点将发送旧版的sync命令触发全量复制流程
全量复制
全量复制是主从第一次建立复制时必须要经历的阶段。触发全量复制的命令时sync (Redis<2.8)和psync(Redis>=2.8)。
psync全量复制流程与2.8前的sync全量复制机制基本一致,psync全量复制完整运行流程如图所示:
- 发送psync命令进行数据同步,由于是第一次进行复制,从节点没有复制偏移量和主节点运行ID,发送psync ? -1
- 主节点回复+FULLRESYNC {runId} {offset}响应
- 从节点接收主节点的响应数据保存运行ID和偏移量offset
- 主节点执行bgsave保存RDB文件到本地
- 主节点发送RDB文件给从节点,从节点接收RDB文件保存在本地直接作为从节点数据文件(Redis支持无盘复制,RDB文件不落盘,通过参数repl-diskless-sync参数控制,默认关闭)
- RDB传输期间,主节点响应读写命令,同时会把写命令写到复制积压缓冲区,当从节点加载完成RDB文件后,主节点在把缓冲区的数据发送到从节点,保证主从数据一致
- 从节点接收完主节点传输的所有数据后会清空自身旧数据
- 从节点清空旧数据后,开始加载RDB文件
- 从节点成功加载RDB后,如果当前节点开启了AOF持久化功能,会立即做bgrewriteaof操作,为了保证全量复制后AOF持久化功能立即可以使用
部分复制
部分复制使用psync {runId} {offset}命令实现。当从节点正在复制主节点时出现网络闪断或者命令丢失等异常情况时,从节点会向主节点要求补发丢失的命令数据,如果主节点的复制积压缓冲区内存在这部分数据则直接发送给从节点,由于补发的这部分数据一般远远小于全量数据从而做到节省开销。
部分复制的流程如下图所示:
- 当主从节点之间出现网络中断时,如果超过repl-timeout时间,主节点会认为从节点故障并中断复制连接
- 主从连接中断期间主节点依然响应命令同时写入复制积压缓冲区
- 从节点网络恢复再次连接主节点
- 从节点发送psync {runId} {offset}命令到主节点要求进行部分复制操作
- 主节点校验runId,根据offset在复制积压缓冲区查找,如果偏移量之后的数据在缓冲区中则向从节点回复+CONTINUE响应,表示可以进行部分复制
- 主节点根据偏移量把复制积压缓冲区数据发送给从节点,保证主从复制进入正常状态
心跳
主从节点建立复制后维护着长连接并彼此发送心跳命令。
主从心跳判断机制:
- 主从节点彼此都有心跳检测机制,各自模拟成对方的客户端进行通信,通过client list命令查看复制相关的客户端信息,主节点连接状态为flag=M,从节点连接状态为flag=S。
- 主节点默认每隔10s对从节点发送ping命令,判断从节点存活性和连接状态。通过参数repl-ping-slave-period控制发送频率
- 从节点在主线程中每个1s发送replconf ack {offset}命令给主节点上报自身当前的复制偏移量
异步复制
主节点进行数据读写的同时还会把写命令同步给从节点,发送过程是异步完成的,也就是说主节点自身处理完写命令后直接返回给客户端,并不等待从节点复制完成。
主节点复制流程如下:
- 主节点接收处理命令
- 命令处理完之后返回响应结果
- 对于修改命令异步发送给从节点,从节点在主线程中执行复制的命令
由于主从复制过程是异步的,就会造成从节点的数据相对主节点存在延迟。具体延迟多少字节,可以在主节点执行info replication命令查看相关指标获得。例如以下信息:
slave0:ip=127.0.0.1,port=6380,state=online,offset=841,lag=1
master_repl_offset:841
从节点slave0信息中offset表示当前从节点的复制偏移量,master_repl_offset表示当前主节点的复制偏移量,两者的差值就是当前从节点复制延迟量。