《Redis开发与运维》学习笔记--复制
本文是《Redis开发与运维》第六章--复制的学习笔记。
分布式系统中为了解决单点问题,会把数据复制多个副本部署到其他机器,实现故障恢复和负载均衡等需求。Redis也是如此,复制功能是Redis高可用的基础。
一、配置
1.1 建立复制
建立复制的配置方式有三种。
- 在redis.conf文件中配置slaveof <master ip> <master port>选项,然后指定该配置文件启动Redis生效。
- 在redis-server启动命令后加上--slaveof <master ip> <master port>启动生效。
- 直接使用 slaveof <master ip> <master port>命令在从节点执行生效。
1.2 断开复制
复制断开也是在从节点执行命令slaveof on one来断开于主节点的复制关系。
断开复制的过程:
①断开与主节点的复制关系
②从节点晋升为主节点
此时不会删除原有数据。
slaveof还可以实现切主操作。切主操作过程:
①断开与旧主的复制关系
②与新主建立复制关系
③删除从节点当前所有数据
④对新主进行复制操作
1.3 拓扑
Redis的复制拓扑结构有三种
①一主一从
优点:简单
②一主多从
优点:适合读写分离,适用于读请求多的场景
③树型主从结构
优点:挂载多个从节点的同时尽量减少主节点复制负载。
二、原理
我准备从宏观再到微观一步步介绍。也即复制过程->数据同步。(数据同步是复制过程的一部分,因为还有建立复制连接等等)
首先看复制过程:
①保存主节点信息:执行slaveof后从节点只保存主节点的地址信息便直接返回,这是建立复制的流程还没有开始
②主从建立socket,专门用于接受主节点的复制命令。
③发送ping命令:连接建立成功后从节点发送ping请求进行首次同行,ping请求主要的目的有:检测主从之间网络套接字是否可用,检测主节点当前是否可接受处理命令。
④验证权限
如果主节点设置了requirepass参数,则需要密码验证,此时必须在从节点配置masterauth参数保证与主节点相同的密码才能通过。
⑤同步数据集
使用psync命令进行数据同步,后面展开讲。
⑥命令持续复制
接下来我们看复制过程中的第⑤步:同步数据集
在Redis 2.8及以上版本使用psync命令完成主从数据同步,同步过程分为:全量复制和部分复制。
- 全量复制:一般用于初次复制场景,主节点全部数据一次性发给从节点
- 增量复制:slave和master断开了、当slave和master重新连接上之后如果需要全量复制,开销太大了。所以Redis2.8开始提供了增量复制的机制,用于补发丢失数据给从节点。
(1)首先,从节点根据当前状态,决定如何调用psync命令:
如果从节点之前未执行过slaveof或最近执行了slaveof no one,则从节点发送命令为psync ? -1,向主节点请求全量复制;
如果从节点之前执行了slaveof,则发送命令为psync<runid><offset>,其中runid为上次复制的主节点的runid,offset为上次复制截止时从节点保存的复制偏移量。
(2)主节点根据收到的psync命令及当前服务器状态来决定执行全量复制还是部分复制:
如果主节点版本低于Redis2.8,则返回-ERR回复,此时从节点重新发送sync命令执行全量复制;
如果主节点版本够新、runid与从节点发送的runid相同,且从节点发送的offset之后的数据在复制积压缓冲区中都存在,则回复+CONTINUE,表示将进行部分复制,从节点等待主节点发送其缺少的数据即可;
如果主节点版本够新,但runid与从节点发送的runid不同,或从节点发送的offset之后的数据已不在复制积压缓冲区中(在队列中被挤出了),则回复+FULLRESYNC<runid><offset>,表示要进行全量复制。其中runid表示主节点当前的runid,offset表示主节点当前的offset,从节点保存这两个值,以备使用。
在具体介绍全量、增量复制的过程之前,我们先介绍一下psync命令需要的组件支持(这里的组件相当于数据结构或者变量):
- 主从节点各自的复制偏移量
- 主节点复制积压缓冲区
- 主节点运行ID
复制偏移量
主从节点都会保存复制偏移量,这个东西说白了就是达到这个目的:主节点知道自己发送了多少数据,从节点知道自己接收了多少数据,主节点知道从节点已经接收了多少数据。因此通过对比主从节点的复制偏移量,可以判断主从节点数据是否一致。
复制积压缓冲区
这是一个在主节点上的固定长度的队列,默认为1M大小。当主节点有连接的从节点时这个结构被创建。主节点响应命令时,不但会发送给从节点,同时也会写入复制积压缓冲区。这个数据结构的作用是保存最近已复制数据的功能,实现部分复制的数据补救。
**主节点运行ID **
Redis节点启动后会动态分配一个40位的十六进制字符串作为运行ID,运行ID作用是唯一识别Redis节点。
好了,接下来进入全量复制和增量复制的过程讲解。
2.1 全量复制
全量复制流程如下:
-
slave内部首先会发送一个psync的命令给master 这个命令第一个参数是runId,第二个参数是偏移量,而由于是第一次复制,slave不知道master的runId,也不知道自己偏移量,这时候会传一个问号和-1,告诉master节点是第一次同步。
-
当master接受到psync ? -1 时,就知道slave是要全量复制,就会将自己的runID和offset告知slave
3.slave会将master信息保存
4.master这时会做一个RDB的生成(bgsave)
5.将RDB发送给slave
6.将复制缓冲区记录的操作也发送给slave
7.slave清空自己的所有老数据
8.slave这时就会加载RDB文件以及复制缓冲区数据,完成同步。
全量复制的开销:
1.bgsave时间
2.RDB文件网络传输时间
3.从节点数据清空时间
4.从节点加载RDB时间
5.可能的AOF重写时间(从节点可能开启了AOF配置)
2.2 增量复制
当master和slave断开连接时,master会将期间所做的操作记录到复制缓存区当中(可以看成是一个队列,其大小默认1M)。待slave重连后,slave会向master发送psync命令并传入offset和runId,这时候,如果master发现slave传输的偏移量的值,在缓存区队列范围中,就会将从offset开始到队列结束的数据传给slave,从而达到同步,降低了使用全量复制的开销。
部分复制流程如下:
至此全量复制和部分复制的过程就讲完了。
三、需要注意的点
3.1 心跳
在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK。心跳机制对于主从复制的超时判断、数据安全等有作用。
主节点默认每隔10S对从节点发送ping命令,判断从节点的存活性和连接状态。
从节点在主线程每隔1秒发送REPLCONF ACK <replication_offset>命令,给主节点上报自己当前的复制偏移量。主要功能有三点:
1.检测主从服务的网络连接状态
2.辅助实现min-slaves选项
两个配置项:
min-slaves-to-write
min-slaves-max-lag
提供以下配置项:
min-slaves-to-write 3
min-slaves-max-lag 10
在从服务器的数量小于3个,或者三个从服务器的延迟(lag)值都大于10S时,主服务器拒绝执行写命令
3.检测复制数据丢失。
3.2读写分离及其中的问题
在主从复制基础上实现的读写分离,可以实现Redis的读负载均衡:由主节点提供写服务,由一个或多个从节点提供读服务(多个从节点既可以提高数据冗余程度,也可以最大化读负载能力);在读负载较大的应用场景下,可以大大提高Redis服务器的并发量。下面介绍在使用Redis读写分离时,需要注意的问题。
(1)延迟与不一致问题
前面已经讲到,由于主从复制的命令传播是异步的,延迟与数据的不一致不可避免。如果应用对数据不一致的接受程度程度较低,可能的优化措施包括:优化主从节点之间的网络环境(如在同机房部署);监控主从节点延迟(通过offset)判断,如果从节点延迟过大,通知应用不再通过该从节点读取数据;使用集群同时扩展写负载和读负载等。
在命令传播阶段以外的其他情况下,从节点的数据不一致可能更加严重,例如连接在数据同步阶段,或从节点失去与主节点的连接时等。从节点的slave-serve-stale-data参数便与此有关:它控制这种情况下从节点的表现;如果为yes(默认值),则从节点仍能够响应客户端的命令,如果为no,则从节点只能响应info、slaveof等少数命令。该参数的设置与应用对数据一致性的要求有关;如果对数据一致性要求很高,则应设置为no。
(2)数据过期问题
在单机版Redis中,存在两种删除策略:
- 惰性删除:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除。
- 定期删除:服务器执行定时任务删除过期数据,但是考虑到内存和CPU的折中(删除会释放内存,但是频繁的删除操作对CPU不友好),该删除的频率和执行时间都受到了限制。
在主从复制场景下,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过期数据的删除。由于主节点的惰性删除和定期删除策略,都不能保证主节点及时对过期数据执行删除操作,因此,当客户端通过Redis从节点读取数据时,很容易读取到已经过期的数据。
Redis 3.2中,从节点在读取数据时,增加了对数据是否过期的判断:如果该数据已过期,则不返回给客户端;将Redis升级到3.2可以解决数据过期问题。
(3)故障切换问题
在没有使用哨兵的读写分离场景下,应用针对读和写分别连接不同的Redis节点;当主节点或从节点出现问题而发生更改时,需要及时修改应用程序读写Redis数据的连接;连接的切换可以手动进行,或者自己写监控程序进行切换,但前者响应慢、容易出错,后者实现复杂,成本都不算低。
(4)总结
在使用读写分离之前,可以考虑其他方法增加Redis的读负载能力:如尽量优化主节点(减少慢查询、减少持久化等其他情况带来的阻塞等)提高负载能力;使用Redis集群同时提高读负载能力和写负载能力等。如果使用读写分离,可以使用哨兵,使主从节点的故障切换尽可能自动化,并减少对应用程序的侵入。
3.3 复制超时问题
主从节点复制超时是导致复制中断的最重要的原因之一,本小节单独说明超时问题,下一小节说明其他会导致复制中断的问题。
超时判断意义
在复制连接建立过程中及之后,主从节点都有机制判断连接是否超时,其意义在于:
(1)如果主节点判断连接超时,其会释放相应从节点的连接,从而释放各种资源,否则无效的从节点仍会占用主节点的各种资源(输出缓冲区、带宽、连接等);此外连接超时的判断可以让主节点更准确的知道当前有效从节点的个数,有助于保证数据安全(配合前面讲到的min-slaves-to-write等参数)。
(2)如果从节点判断连接超时,则可以及时重新建立连接,避免与主节点数据长期的不一致。
判断机制
主从复制超时判断的核心,在于repl-timeout参数,该参数规定了超时时间的阈值(默认60s),对于主节点和从节点同时有效;主从节点触发超时的条件分别如下:
(1)主节点:每秒1次调用复制定时函数replicationCron(),在其中判断当前时间距离上次收到各个从节点REPLCONF ACK的时间,是否超过了repl-timeout值,如果超过了则释放相应从节点的连接。
(2)从节点:从节点对超时的判断同样是在复制定时函数中判断,基本逻辑是:
- 如果当前处于连接建立阶段,且距离上次收到主节点的信息的时间已超过repl-timeout,则释放与主节点的连接;
- 如果当前处于数据同步阶段,且收到主节点的RDB文件的时间超时,则停止数据同步,释放连接;
- 如果当前处于命令传播阶段,且距离上次收到主节点的PING命令或数据的时间已超过repl-timeout值,则释放与主节点的连接。
与连接超时有关的实际问题
(1)数据同步阶段:在主从节点进行全量复制bgsave时,主节点需要首先fork子进程将当前数据保存到RDB文件中,然后再将RDB文件通过网络传输到从节点。如果RDB文件过大,主节点在fork子进程+保存RDB文件时耗时过多,可能会导致从节点长时间收不到数据而触发超时;此时从节点会重连主节点,然后再次全量复制,再次超时,再次重连……这是个悲伤的循环。为了避免这种情况的发生,除了注意Redis单机数据量不要过大,另一方面就是适当增大repl-timeout值,具体的大小可以根据bgsave耗时来调整。(2)命令传播阶段:如前所述,在该阶段主节点会向从节点发送PING命令,频率由repl-ping-slave-period控制;该参数应明显小于repl-timeout值(后者至少是前者的几倍)。否则,如果两个参数相等或接近,网络抖动导致个别PING命令丢失,此时恰巧主节点也没有向从节点发送数据,则从节点很容易判断超时。
(3)慢查询导致的阻塞:如果主节点或从节点执行了一些慢查询(如keys *或者对大数据的hgetall等),导致服务器阻塞;阻塞期间无法响应复制连接中对方节点的请求,可能导致复制超时。
3.4复制中断问题
主从节点超时是复制中断的原因之一,除此之外,还有其他情况可能导致复制中断,其中最主要的是复制缓冲区溢出问题。
复制缓冲区溢出
前面曾提到过,在全量复制阶段,主节点会将执行的写命令放到复制缓冲区中,该缓冲区存放的数据包括了以下几个时间段内主节点执行的写命令:bgsave生成RDB文件、RDB文件由主节点发往从节点、从节点清空老数据并载入RDB文件中的数据。当主节点数据量较大,或者主从节点之间网络延迟较大时,可能导致该缓冲区的大小超过了限制,此时主节点会断开与从节点之间的连接;这种情况可能引起全量复制->复制缓冲区溢出导致连接中断->重连->全量复制->复制缓冲区溢出导致连接中断……的循环。
复制缓冲区的大小由client-output-buffer-limit slave {hard limit} {soft limit} {soft seconds}配置,默认值为client-output-buffer-limit slave 256MB 64MB 60,其含义是:如果buffer大于256MB,或者连续60s大于64MB,则主节点会断开与该从节点的连接。该参数是可以通过config set命令动态配置的(即不重启Redis也可以生效)。
需要注意的是,复制缓冲区是客户端输出缓冲区的一种,主节点会为每一个从节点分别分配复制缓冲区;而复制积压缓冲区则是一个主节点只有一个,无论它有多少个从节点。
参考资料
《Redis开发与运维》