由于数据都是存储在一台服务器上,如果出事就完犊子了,比如:
要避免这种单点故障,最好的办法是将数据备份到其他服务器上,让这些服务器也可以对外提供服务。
主从复制,是指将一台Redis
服务器的数据,复制到其他的Redis
服务器。前者称为主节点(master)
,后者称为从节点(slave)
;数据的复制是单向的,只能由主节点到从节点。默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点)
,但一个从节点只能有一个主节点
。(redis有主从同步,从从同步)。
主从复制的作用主要包括:
一.
1.复制一份redis.conf文件
2.修改部分配置
port 6380
pidfile /var/run/redis_6380.pid # 把pid进程号写入pidfile配置的文件
logfile "6380.log"
dir /usr/local/redis‐5.0.3/data/6380 # 指定数据存放目录
# 需要注释掉bind
# bind 127.0.0.1(bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,代表允许客户端通过机器的哪些网卡ip去访问,内网一般可以不配置bind,注释掉即可)
3、配置主从复制
replicaof 192.168.0.60 6379 # 从本机6379的redis实例复制数据
replica‐read‐only yes # 配置从节点只读
4、启动从节点
redis‐server redis.conf
5、连接从节点
redis‐cli ‐p 6380
6、测试在6379实例上写数据,6380实例是否能及时同步新修改数据
二.
1.复制一份redis.conf文件
2.修改部分配置
port 6380
pidfile /var/run/redis_6380.pid # 把pid进程号写入pidfile配置的文件
logfile "6380.log"
dir /usr/local/redis‐5.0.3/data/6380 # 指定数据存放目录
# 需要注释掉bind
# bind 127.0.0.1(bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,代表允许客户端通过机器的哪些网卡ip去访问,内网一般可以不配置bind,注释掉即可)
3、配置主从复制
replica‐read‐only yes # 配置从节点只读
4、启动从节点
redis‐server redis.conf
5.执行slaveof指令
slaveof 127.0.0.1 6379 #6380节点执行slaveof命令,使之变为6379端口服务的从节点
6、测试在6379实例上写数据,6380实例是否能及时同步新修改数据
主从复制过程大体可以分为3个阶段:连接建立阶段、数据同步阶段、命令传播阶段
;下面分别进行介绍。
该阶段的主要作用是在主从节点之间建立连接,为数据同步做好准备。
1.建立socket连接
从节点每秒1次调用复制定时函数replicationCron()
,发现有主节点的ip和port
,创建socket连接。如果连接成功,从节点:为该socket建立一个专门处理复制工作的文件事件处理器,负责后续的复制工作,如接收RDB
文件、接收命令传播
等。
2.发送ping命令
从节点向主节点发送ping命令进行首次请求,目的是:检查socket连接是否可用,以及主节点当前是否能够处理请求。
3.身份验证
如果从节点中设置了masterauth
选项,则从节点需要向主节点进行身份验证
;没有设置该选项,则不需要验证。
4.发送从节点端口信息
主节点会保存从节点的端口信息。
从服务器复制主服务器时发送货·psync·命令,从服务器先需要执行同步操作。也就是当前从服务器数据状态和主服务器当前主服务器数据状态一致。数据同步阶段是主从复制最核心的阶段,根据主从节点当前状态的不同,可以分为全量复制和部分复制。
主服务器会执行 bgsave
命令来生成 RDB
文件,然后把文件发送给从服务器。从服务器收到 RDB
文件后,会先清空当前的数据,然后载入RDB
文件。但是,这期间的写操作命令并没有记录到刚刚生成的 RDB 文件中,这时主从服务器间的数据就不一致了。那么为了保证主从服务器的数据一致性,主服务器在下面这三个时间间隙中将收到的写操作命令,写入到 replication buffer 缓冲区
里:
从服务器完成RDB
的载入后,会回复一个确认消息给主服务器,接着,主服务器将 replication buffer 缓冲区
里所记录的写操作命令发送给从服务器,从服务器执行来自主服务器 replication buffer 缓冲区
里发来的命令,这时主从服务器的数据就一致了。
数据同步阶段完成后。主从节点进入命令传播阶段;在这个阶段主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。
在Redis2.8以前,从节点向主节点发送sync命令请求同步数据,此时的同步方式是全量复制;在Redis2.8及以后,从节点可以发送psync命令请求同步数据,此时根据主从节点当前状态的不同,同步方式可能是全量复制或部分复制。
全量复制:用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。
部分复制:用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。需要注意的是,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制。
通过全量复制的过程可以看出,全量复制是非常重型的操作:
(1)主节点通过bgsave
命令fork
子进程进行RDB
持久化,该过程是非常消耗CPU、内存(页表复制)、硬盘IO
;
(2)主节点通过网络将RDB文件发送给从节点,对主从节点的带宽
都会带来很大的消耗,并对主节点响应命令
的时间有影响;
(3)从节点清空老数据、载入新RDB文件
的过程是阻塞的,无法响应
客户端的命令;全量复制完成之后,从节点执行bgrewriteaof
,也会带来额外的消耗。
主节点
和从节点
分别维护一个复制偏移量
(offset),代表的是主节点向从节点传递的字节数;主节点每次向从节点传播N个字节数据时,主节点的offset增加N;从节点每次收到主节点传来的N个字节数据时,从节点的offset增加N。通过对比偏移量,判断主从节点是不是一致。
复制积压缓冲区是由主节点维护的、固定长度的、先进先出(FIFO)队列,默认大小1MB;当主节点开始有从节点时创建,其作用是备份主节点最近发送给从节点的数据。注意,无论主节点有一个还是多个从节点,都只需要一个复制积压缓冲区。
在命令传播
阶段,主节点除了将写命令发送给从节点,还会发送一份给复制积压缓冲区
,作为写命令的备份;除了存储写命令,复制积压缓冲区中还存储了其中的每个字节对应的复制偏移量(offset
)。该缓冲区长度固定
且有限,因此可以备份的写命令也有限,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
从节点将offset
发送给主节点后,主节点根据offset和缓冲区大小决定能否执行部分复制:
注意:如果全量同步的时间过长或者复制 buffer 太小,都会导致同步期间的增量指令在复制 buffer 中被覆盖,这样就会导致快照同步完成后无法进行增量复制,然后会再次发起快照同步,如此极有可能会陷入快照同步的死循环。
主从Redis服务器,在启动时都会自动生成一个随机ID,由40个随机的十六进制字符组成;runid用来唯一识别一个Redis节点。主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来;当断线重连时,从节点会将这个runid发送给主节点;主节点根据runid判断能否进行部分复制:
如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。
在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING
和REPLCONF ACK
。心跳机制对于主从复制的超时判断、数据安全等有作用。
1.主->从:PING
每隔指定的时间,主节点会向从节点发送PING
命令,这个PING
命令的作用,主要是为了让从节点进行超时判断。
PING发送的频率由repl-ping-slave-period
参数控制,单位是秒,默认值是10s
。
2.从->主:REPLCONF ACK
在命令传播阶段,从节点会向主节点发送REPLCONF ACK
命令,频率是每秒1次;命令格式为:
REPLCONF ACK {offset},
其中offset
指从节点保存的复制偏移量。REPLCONF ACK
命令的作用包括:
info Replication
,可以看到其从节点的状态中的lag
值,代表的是主节点上次收到该REPLCONF ACK
命令的时间间隔,在正常情况下,该值应该是0
或1
。offset
,主节点会与自己的offset
对比,如果从节点数据缺失,主节点会推送缺失的数据(复制挤压区)。min-slaves-to-write
和min-slaves-max-lag
参数,来保证主节点在不安全的情况下不会执行写命令;所谓不安全,是指从节点数量太少,或延迟过高
。min-slaves-to-write 3
min-slaves-max-lag 10
# 含义是如果从节点数量小于3个,或所有从节点的延迟值都大于10s,则主节点拒绝执行写命令。
而这里从节点延迟值的获取,就是通过主节点接收到REPLCONF ACK
命令的时间来判断的,即前面所说的info Replication
中的lag
值。
replication buffer
、repl backlog buffer
区别如下:
出现的阶段不一样:
repl backlog buffer
是在增量复制阶段出现,一个主节点只分配一个 repl backlog buffer
;replication buffer
是在全量复制阶段和增量复制阶段都会出现,主节点会给每个新连接的从节点,分配一个 replication buffer
; Buffer
都有大小限制的,当缓冲区满了之后,发生的事情不一样:repl backlog buffer
满了,因为是环形结构,会直接覆盖起始位置数据;replication buffer
满了,会导致连接断开,删除缓存,从节点重新连接,重新开始全量复制,引发复制中断问题(下文)。主从复制的命令传播是异步的,延迟与数据的不一致不可避免。优化措施包括:优化主从节点之间的网络环境(如在同机房部署);监控主从节点延迟(通过offset)判断,如果从节点延迟过大,通知应用不再通过该从节点读取数据;使用集群同时扩展写负载和读负载等
在主从复制场景下,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过期数据的删除。由于主节点的定期删除策略,都不能保证主节点及时对过期数据执行删除操作,因此,当客户端通过Redis从节点读取数据时,很容易读取到已经过期的数据。
Redis 3.2中,从节点在读取数据时,增加了对数据是否过期的判断:如果该数据已过期,则不返回给客户端;将Redis升级到3.2可以解决数据过期问题。参考这个文章
在没有使用哨兵的读写分离场景下,应用针对读和写分别连接不同的Redis节点;当主节点或从节点出现问题而发生更改时,需要及时修改应用程序读写Redis数据的连接;连接的切换可以手动进行,或者自己写监控程序进行切换,但前者响应慢、容易出错,后者实现复杂,成本都不算低。
主从节点复制超时是导致复制中断的最重要的原因之一。
超时判断意义
在复制连接建立之后,主从节点都有机制判断连接是否超时,其意义在于:
(1)如果主节点判断连接超时,其会释放相应从节点的连接,从而释放各种资源,否则无效的从节点仍会占用主节点的各种资源(输出缓冲区、带宽、连接等);此外连接超时的判断可以让主节点更准确的知道当前有效从节点的个数,有助于保证数据安全。
(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文件时耗时过多,可能会导致从节点长时间收不到数据而触发超时;此时从节点会重连主节点,然后再次全量复制,再次超时,再次重连……这是个悲伤的循环。
(2)命令传播阶段:如前所述,在该阶段主节点会向从节点发送PING命令,频率由repl-ping-slave-period控制;该参数应明显小于repl-timeout值(后者至少是前者的几倍)。
(3)慢查询导致的阻塞:如果主节点或从节点执行了一些慢查询(如keys *或者对大数据的hgetall等),导致服务器阻塞;阻塞期间无法响应复制连接中对方节点的请求,可能导致复制超时。
复制缓冲区溢出
当主节点数据量较大,或者主从节点之间网络延迟较大时,可能导致该缓冲区的大小超过了限制,此时主节点会断开与从节点之间的连接;这种情况可能引起全量复制->复制缓冲区溢出导致连接中断->重连->全量复制->复制缓冲区溢出导致连接中断……的循环。
(1)第一次建立复制
全量复制不可避免,但仍有几点需要注意:
2)主节点重启
主节点重启可以分为两种情况来讨论,一种是故障导致宕机,另一种则是有计划的重启。
主节点宕机
主节点宕机重启后,runid
会发生变化,因此不能进行部分复制,只能全量复制。
安全重启:debug reload
在一些场景下,可能希望对主节点进行重启,例如主节点内存碎片率过高,或者希望调整一些只能在启动时调整的参数。如果使用普通的手段重启主节点,会使得runid
发生变化,可能导致不必要的全量复制。
为了解决这个问题,Redis
提供了debug reload
的重启方式:重启后,主节点的runid
和offset
都不受影响,避免了全量复制。但debug reload
是一柄双刃剑:它会清空当前内存中的数据,重新从RDB
文件中加载,这个过程会导致主节点的阻塞,因此也需要谨慎。
3)从节点重启
从节点宕机重启后,其保存的主节点的runid会丢失,因此即使再次执行slaveof,也无法进行部分复制。
(1)切主:当主节点宕机时,一种常见的容灾策略是将其中一个从节点提升为主节点,并将其他从节点挂载到新的主节点上,此时这些从节点只能进行全量复制;如果Redis单机内存达到10GB
,一个从节点的同步时间在几分钟的级别;如果从节点较多,恢复的速度会更慢。如果系统的读负载很高,而这段时间从节点无法提供服务,会对系统造成很大的压力。
(2)从库扩容:如果访问量突然增大,此时希望增加从节点分担读负载,如果数据量过大,从节点同步太慢,难以及时应对访问量的暴增。
(3)缓冲区溢出:(1)和(2)都是从节点可以正常同步的情形(虽然慢),但是如果数据量过大,导致全量复制阶段主节点的复制缓冲区溢出,从而导致复制中断,则主从节点的数据同步会全量复制->复制缓冲区溢出导致复制中断->重连->全量复制->复制缓冲区溢出导致复制中断……的循环。
(4)超时:如果数据量过大,全量复制阶段主节点fork
+保存RDB
文件耗时过大,从节点长时间接收不到数据触发超时,主从节点的数据同步同样可能陷入全量复制->超时导致复制中断->重连->全量复制->超时导致复制中断……的循环。
此外,主节点单机内存除了绝对量不能太大,其占用主机内存的比例也不应过大:最好只使用50%-65%的内存,留下30%-45%的内存用于执行bgsave命令和创建复制缓冲区等。
1、主从复制的作用:宏观的了解主从复制是为了解决什么样的问题,即数据冗余、故障恢复、读负载均衡等。
2、主从复制的原理:主从复制包括了连接建立阶段、数据同步阶段、命令传播阶段;其中数据同步阶段,有全量复制和部分复制两种数据同步方式;命令传播阶段,主从节点之间有PING和REPLCONF ACK命令互相进行心跳检测。
3、应用中的问题:包括读写分离的问题(数据不一致问题、数据过期问题、故障切换问题等)、复制超时问题、复制中断问题等,然后总结了主从复制相关的配置。
主从复制虽然解决或缓解了数据冗余、故障恢复、读负载均衡等问题,但其缺陷仍很明显:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。