一、主从复制了解
定义:主从复制是指将一个redis服务器(又叫主节点master)的数据复制到其他的redis服务器(又叫从节点slave)上。其主要是为了解决数据的多机备份(数据副本)以及性能的扩展(扩展读性能),为高可用、负债均衡等做基础。想想如果是单机应用,当服务器宕机则无法提供服务,且一台机器容量十分有限。有了主从复制则当一个节点损坏(指不可恢复的硬件损坏)时,数据因为有备份,可以方便恢复。
主从复制过程中有如下规则:
- 一个master节点可有多个slave节点;
- 一个slave 只能有一个master;
- 数据流只能单向的从msater复制到slave。
- 一般情况下master节点可以进行读和写,slave节点只能进行读操作,写操作被禁止
- slave节点挂了不影响其他slave节点的读和master节点的读和写,重新启动后会将数据从master节点同步过来
- master节点宕机,不影响slave节点的读,且不会从slave节点重新选一个master,即redis将不再提供写服务。
- 让slave节点支持写操作没有意义,因为写入的数据不会被同步到其他节点;且当master节点修改同一条数据后,slave节点的数据会被覆盖掉。
作用:数据冗余备份、故障恢复(主节点故障可由从节点提供服务)、负债均衡(配合读写分离,主节点写从节点读,分担服务器负载来提高并发处理量)、实现高可用及集群的基础。
二、实现主从复制的方式
开启主从复制完全由从节点发起,主节点不做任何操作,有两种方式:slaveof 命令、配置
- slaveof 命令:客户端执行 slaveof ip port (ip、port为master节点对应的ip和redis服务端口)。
- 配置:在 redis.conf 配置文件添加 slaveof ip port (ip、port同上),另外为实现slave的只读,多加 slave-read-only yes 配置。
另外,当从节点不想做从节点,即要与主节点脱离关系,可以执行命令 slaveof no one 。此时从节点将断开复制,但是已存在的数据不会删除,只是不会再根据以前的主节点修改数据。当redis成为从节点时,会自动把其原来的数据清空。
三、主从信息查看
我们可以在节点执行 info replication 命令来查看节点的主从信息。
- 主节点执行:可看到节点角色(master)、偏移量、从节点连接个数、从节点的ip和端口 等信息
- 从节点执行:可看到节点角色(slave)、偏移量、连接的主节点ip及端口 等信息
可以在节点执行 info server 命令来查看节点的启动信息。
- 主从节点执行:可看到节点启动时使用的配置文件路径、redis版本号、进程id、run_id 等信息
上面查看到的信息中,run_id以及偏移量是两个比较重要的概念,其对下面全量复制和部分复制等有重要作用。
- run_id:每个redis服务启动都会生成的一个唯一标识,重启后该id会发生变化。从节点根据该id查找主节点,同时根据主节点该id是否发生变化来判断主节点是否改变以及重启,从而触发全量或部分复制。
- 偏移量:主节点在每次执行写操作都会修改该偏移量进行增加,从节点通过自己的偏移量与主节点偏移量的差距来判断自己还有多少信息没有同步,根据两个偏移量差距判断是使用全量复制还是部分复制。
四、全量复制与部分复制
全量复制流程:
- 从节点在开始连接主节点时,并不知道主节点偏移量以及runid,所以从节点发 psync ? -1 命令给主节点表示其第一次连接。
- 主节点收到信息后知道从节点需要全量复制,于是给从节点发此时主节点的偏移量以及runid
- 从节点将主节点的这两个信息进行保存
- 主节点发送完信息后会执行bgsave命令进行rdb持久化(上节讲到的一个触发情景),生成对应的rdb文件(可能会因为耗时太长,从节点收不到数据而超时导致复制失败,可增加repl-timeout值处理)。
- 在主节点进行持久化时,不避免会有新命令执行,如果不作处理从节点执行完rdb文件后也无法与主节点数据一致。于是主节点会将bgsave之后的操作记录在一个buffer(复制缓冲区)。
- 当bgsave完成后主节点会将rdb文件发送给从节点,接着将复制缓冲区中的写命令也发给从节点
- 从节点先将旧数据清空,然后执行rdb文件以及buffer发送的数据,从而实现主从数据一致,同时从节点也记录了主节点此刻偏移量等信息。
全量复制需要耗费的时间:
- 主节点执行bgsave耗费的时间
- 主节点将rdb文件通过网络传输给从节点的时间
- 从节点清空遗留数据耗费的时间
- 从节点加载rdb文件花费的时间
全量复制发生场景及避免(因为全量复制耗费时长和资源):
- 主从第一次建立连接一定会进行全量复制(不可避免)。优化:可以在夜间数据库操作较少时进行
- 主节点runid发生变化,如重启主节点。优化:可通过哨兵和集群进行故障转移
- 在进行部分复制然而发现复制积压缓冲区(repl_back_buffer)不足,如网络断开时间较长,从节点的偏移量不在复制积压缓冲区内,无法使用部分复制。优化:增大复制积压缓冲区的大小(rel_backlog_size),默认为1M。
部分复制流程:
场景:在redis主从结构中,主从一般部署在不同的机器,即主从之间必须通过网络进行数据传输。当网络异常时,master节点将有部分数据没有同步到slave,一段时间后从节点重新连上主节点,这是就会出现不少数据未同步。当数据量差距不是特别大时,将进行部分复制来减少消耗。流程如下:
- 主节点在执行写命令的同时会把命令发送给复制积压缓冲区(repl_back_buffer),复制积压缓冲区将保存写命令及对应的偏移量。注意该缓冲区有大小,当写入的数量超过则会删除最先写进的命令。
- 从节点因为网络原因断开一段时间后重新连上主节点并发送 pysnc {offset} {runId} 命令给主节点,其中offset为从节点断开连接前的偏移量,runid为主节点runid。
- 主节点判断从节点的偏移量之后的操作还在复制积压缓冲区内,会将缓冲区中的写命令发送给从节点,从节点记录到数据库,此过程为部分复制。
- 当从节点的偏移量之后的部分数据不在复制积压缓冲区内(数据被挤出删除),则会在从节点进行全量复制。
五、复制缓冲区与复制积压缓冲区
(1)复制缓冲区
全量复制时使用到的一个缓冲区,每个从节点都会有一个该缓冲区,其主要存放全量复制过程中主节点开始执行bgsave到从节点载入rdb文件这个时间段中主节点的写命令。但是当主节点数据量太大或者网络延迟太大导致上面的时间段太长,即要写入的数据超过缓冲区的大小,导致主节点断开与从节点的连接。可能还会引起全量复制->复制缓冲区溢出导致连接中断->重连->全量复制->复制缓冲区溢出导致连接中断的循环。
复制缓冲区的大小由client-output-buffer-limit slave {hard limit} {soft limit} {soft seconds}配置,默认值为client-output-buffer-limit slave 256MB 64MB 60,其含义是:如果buffer大于256MB,或者连续60s大于64MB,则主节点会断开与该从节点的连接。
(2)复制积压缓冲区
主节点维护的一个缓冲区,在主节点开始有从节点时创建(不管几个从节点都只有一个),其是长度固定且为先进先出的队列,默认大小为1MB。主要用来备份主节点最近发送给从节点的写命令,同时还会存储该写命令对应的偏移量(offset),当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。由于该缓冲区长度有限,因此备份的命令也有限,又因为其为先进先出的队列,所以先进入缓冲区的命令会随着命令的增加被挤出缓冲区。因为部分复制效率高,所以为了能够提高部分复制的几率,可以适当增加积压缓冲区的大小,即设置配置 repl-backlog-size 的大小。
(3)总结
- 复制缓冲区是客户端输出缓冲区的一种,主节点会为每一个从节点都创建一个复制缓冲区,主要用于全量复制使用。
- 复制积压缓冲区则是一个主节点只有一个,与从节点数量无关。
六、其他
(1)主从redis故障影响:
- slave宕机:失去一个读节点,增加读操作压力到别的机器,影响不大
- master宕机:失去写操作能力,影响很大,生产环境一般不允许,所以如果没有实现Sentinel,则需要手动将其中一个slave节点设置为主节点,然后让其他节点修改主节点。但是这种手动操作不友好,也会导致服务一段时间的瘫痪,一般还是通过Sentinel处理该问题。
(2)读写分离作用
主从中的读写分离是为了将读操作分摊到多个服务,减轻master的压力,但是读写分离有可能有导致复制数据延迟、读到过期数据
(3)复制风暴
当只有一个主节点且主节点重启,则runid会发生变化,如果有很多个从节点,则会一起对主节点发起全量复制,这将对主节点的性能有一个很大的损耗。所以做主从时要合理规划网络结构。
(4)master宕机导致redis无法提供写服务问题可通过sentinel进行处理
主从模式中,master节点挂了以后,redis就不能对外提供写服务了,其他slave不能自动升级为master,所以一般主从模式还会添加sentinel。当sentinel发现master节点挂了以后,sentinel就会从slave中重新选举一个master。sentinel会在slave中选择一个做为master,并修改它们的配置文件,其他slave的配置文件也会被修改,比如slaveof属性会指向新的master。当旧的master节点重启后将不再是master,而是做为slave接收新的master节点的同步数据。当使用sentinel模式时,客户端连接的是sentinel的ip和端口,由sentinel来提供具体的功能实现,这样当master节点挂掉以后,sentinel就会感知并将新的master节点提供给使用者。
(5)主从模式中会读取到过期数据的原因
因为redis有两种删除策略:
- 惰性删除:服务器不会主动删除某个过期数据,当客户端查询该数据时服务器才判断该数据是否过期并进行删除。
- 定期删除:服务器执行定时任务删除过期数据,考虑服务器性能,所以删除的频率和执行时间都有限制。
结合上面两种删除策略,因为从节点不会主动对数据进行修改删除操作(由主节点控制),而主节点又没有及时将过期数据删除,所以客户端很容易就获取到从节点中过期的数据。好在redis3.2处理了这个问题,在从节点读数据时会判断是否过期,过期将不返回。
(6)主从数据不一致
主节点在给从节点发送写命令时是异步的,即不会等待从节点的回复,所以由于网络、性能、命令执行频率等原因导致主从节点很难保持实时的一致性,延迟在所难免。这里我们讲讲主节点中repl-disable-tcp-nodelay配置对延迟的影响。
repl-disable-tcp-nodelay no:该配置默认为no,主要用来控制主节点是否禁止与从节点的TCP_NODELAY。设置为no则不禁止TCP_NODELAY,此时TCP会立马将主节点的数据发送给从节点,带宽增加但延迟变小。设置为yes时,TCP会对包进行合并从而减少带宽,但是发送的频率会降低导致从节点数据延迟增加,一致性变差;具体发送频率与操作系统有关,默认配置为40ms。所以一般还是使用默认的no。
七、主从复制涉及的配置
slaveof <masterip> <masterport>:在从节点设置,设置其连接的主节点的ip和端口,无配置则为主节点。
repl-disable-tcp-nodelay no:同命令传播阶段的延迟有关
masterauth <master-password>:在从节点连接主节点时身份验证
slave-read-only yes:从节点设置为只读;默认为yes。
repl-backlog-size 1mb:复制积压缓冲区的大小设置,默认1MB
client-output-buffer-limit slave 256MB 64MB 60:全量复制过程中主节点复制缓冲区大小设置
repl-timeout 60:与各个阶段主从节点连接超时判断有关
repl-ping-slave-period 10:与命令传播阶段主从节点的超时判断有关
repl-backlog-ttl 3600:当主节点没有从节点时,复制积压缓冲区保留的时间,这样当断开的从节点重新连进来时,可以进行全量复制;默认3600s。如果设置为0,则永远不会释放复制积压缓冲区。
repl-diskless-sync no:作用于全量复制阶段,控制主节点是否使用diskless复制(无盘复制)。所谓diskless复制,是指在全量复制时,主节点不再先把数据写入RDB文件,而是直接写入slave的socket中,整个过程中不涉及硬盘;diskless复制在磁盘IO很慢而网速很快时更有优势。需要注意的是,截至Redis3.0,diskless复制处于实验阶段,默认是关闭的。
repl-diskless-sync-delay 5:该配置作用于全量复制阶段,当主节点使用diskless复制时,该配置决定主节点向从节点发送之前停顿的时间,单位是秒;只有当diskless复制打开时有效,默认5s。之所以设置停顿时间,是基于以下两个考虑:(1)向slave的socket的传输一旦开始,新连接的slave只能等待当前数据传输结束,才能开始新的数据传输 (2)多个从节点有较大的概率在短时间内建立主从
八、命令传播阶段的心跳机制(参考 https://www.cnblogs.com/kismetv/p/9236731.html)
主从节点在命令传播时维持着心跳机制:PING和REPLCONF ACK。心跳机制对于主从复制的超时判断、数据安全等有作用。
每隔指定的时间,主节点会向从节点发送PING命令,这个PING命令的作用,主要是为了让从节点进行超时判断。
PING发送的频率由repl-ping-slave-period参数控制,单位是秒,默认值是10s。
关于该PING命令究竟是由主节点发给从节点,还是相反,有一些争议;因为在Redis的官方文档中,对该参数的注释中说明是从节点向主节点发送PING命令,如下图所示:
但是根据该参数的名称(含有ping-slave),以及代码实现,我认为该PING命令是主节点发给从节点的。相关代码如下:
在命令传播阶段,从节点会向主节点发送REPLCONF ACK命令,频率是每秒1次;命令格式为:REPLCONF ACK {offset},其中offset指从节点保存的复制偏移量。REPLCONF ACK命令的作用包括:
(1)实时监测主从节点网络状态:该命令会被主节点用于复制超时的判断。此外,在主节点中使用info Replication,可以看到其从节点的状态中的lag值,代表的是主节点上次收到该REPLCONF ACK命令的时间间隔,在正常情况下,该值应该是0或1,如下图所示:
(2)检测命令丢失:从节点发送了自身的offset,主节点会与自己的offset对比,如果从节点数据缺失(如网络丢包),主节点会推送缺失的数据(这里也会利用复制积压缓冲区)。注意,offset和复制积压缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情形;区别在于前者是在断线重连后进行的,而后者是在主从节点没有断线的情况下进行的。
(3)辅助保证从节点的数量和延迟:Redis主节点中使用min-slaves-to-write和min-slaves-max-lag参数,来保证主节点在不安全的情况下不会执行写命令;所谓不安全,是指从节点数量太少,或延迟过高。例如min-slaves-to-write和min-slaves-max-lag分别是3和10,含义是如果从节点数量小于3个,或所有从节点的延迟值都大于10s,则主节点拒绝执行写命令。而这里从节点延迟值的获取,就是通过主节点接收到REPLCONF ACK命令的时间来判断的,即前面所说的info Replication中的lag值。