目录
五、主从复制
单机有什么问题?
单机即在一台机器上部署一个redis节点,主要会存在以下问题:
1. 机器故障
如果发生机器故障,例如磁盘损坏,主板损坏等,未能在短时间内修复好,客户端将无法连接redis。
当然如果仅仅是redis节点挂掉了,可以进行问题排查然后重启,姑且不考虑这段时间对外服务的可用性,那还是可以接受的。
而发生机器故障,基本是无济于事。除非把redis迁移到另一台机器上,并且还要考虑数据同步的问题。
2. 容量瓶颈
假如一台机器是16G内存,redis使用了12G内存,而其他应用还需要使用内存,假设我们总共需要60G内存要如何去做呢,是否有必要购买64G内存的机器?
3. QPS瓶颈
redis官方数据显示可以达到10w的QPS,如果业务需要100w的QPS怎么去做呢?
关于容量瓶颈和QPS瓶颈是redis分布式需要解决的问题,而机器故障就是高可用的问题了。
主从复制的作用
一主一从
如图所示左边是Master节点,右边是slave节点,即主节点和从节点。从节点也是可以对外提供服务的,主节点是有数据的,从节点可以通过复制操作将主节点的数据同步过来,并且随着主节点数据不断写入,从节点数据也会做同步的更新。
整体起到的就是数据备份的效果。
一主多从
除了一主一从模型之外,redis还提供了一主多从的模型,也就是一个master可以有多个slave,也就相当于有了多份的数据副本。
这样可以做一个更加高可用的选择,例如一个master和一个slave挂掉了,还能有其他的slave数据备份。
读写分离
除了作为数据备份,主从模型还能做另外一个功能,就是读写分离。
让master节点负责提供写服务,而将数据读取的压力进行分流和负载,分摊给所有的从节点。
主从复制的作用
- 数据副本(备份)
- 扩展读性能(读写分离)
简单总结
- 一个master可以有多个slave
- 一个slave只能有一个master
- 数据流向是单向的,master到slave
主从复制的配置
1. slaveof命令
如图,想让6380节点成为6379的从节点,只需要执行 slaveof
命令即可,此复制命令是异步进行的,redis会自动进行后续数据复制的操作。
注:一般生产环境不允许主从节点都在一台机器上,因为没有任何的价值。
取消复制
如果6380节点不希望成为6379的从节点,可以执行 slave of on one
命令,取消后6380节点的数据不会被清除,只是说后续6379节点新写入的数据不会再同步到该节点了。
注意:如果取消复制后想slave一个新的主节点,新的主节点在同步给slave节点数据时,会先将从节点的数据全部清除
2. 修改配置
# 配置主节点的IP和端口号
slaveof ip port
# 从节点只做读的操作,保证主从数据的一致性
slave-read-only yes
对比
runid和复制偏移量
redis每次启动的时候都会有一个随机的ID,作为一个标识,这个ID就是runid
,当然重启之后值就改变了。
查看runid:redis-cli -p 6379 info | grep run
假如端口为6380的redis去复制6379,知道runid
后,在6380上做一个标识,如果runid
改变了,说明主可能重启了或者发生了其它变化,这时候就可以做一个全量复制把数据同步过来。或者第一次启动时根本不知道6379的runid
,也会进行全量复制
偏移量:数据写入量的字节
比如主执行set hello world,就会有一个偏移量,然后从同步数据,也会记录一个偏移量
当两个偏移量达到一致时候,实际上数据就是完全同步的状态。
启动主从redis,并在写入命令执行前后查看主偏移量:redis-cli -p 6379 info replication | grep master_repl
从节点会向主节点做一个上报,把从节点的状态同步给主节点,这样主节点就知道了从节点的偏移量
生产环境我们一般不关心这个值,有时候做监控的时候会比对一下这两个值的差,如果差的太多,说明主从是有问题的。
全量复制
全量复制主节点会将RDB文件也就是当前状态去同步给slave,在此期间主新写入的命令会单独记录起来,然后当RDB文件加载完毕之后,会通过偏移量对比将这个期间产生的写入值同步给slave,这样就能达到数据完全同步的效果
全量复制过程
- 在其内部有一条命令
psync
,是做同步的命令,它可以完成全量复制和部分复制的功能,当启动slave节点时,它会发送psync
命令给主节点,需要传递两个参数,runid
和offset
(偏移量),也就是从向主传递主节点的runid以及自己的偏移量,对于第一次复制而言,就直接传递?和 -1,当然这个参数是由slave内部传的。 - master接收到命令后知道从希望做全量复制,主就会将自己的runid和offset传递给从
- slave节点保存master的基本信息
- master执行
bgsave
生成RDB文件,并且在此期间新产生的写入命令会被记录到repl_back_buffer
(复制缓冲区) - 主向从传输RDB文件
- 主向从发送复制缓冲区内容
- 清空从节点旧的数据
- 从节点加载RDB文件到内存中,同时加载缓冲区数据
全量复制的开销
实际上全量复制的开销是非常大的,主要体现在如下方面
- bgsave时间(对cpu、 内存、硬盘都会有一定的开销)
- RDB文件网络传输时间(网络带宽)
- 从节点清空数据时间(根据从节点的数据规模)
- 从节点加载RDB的时间
- 可能的AOF重写时间(在最后从加载完RDB之后如果开启了AOF,会做AOF重写)
全量复制除了上述开销之外,还会有个问题:
假如master和slave网络发生了抖动,那一段时间内这些数据就会丢失,对于slave来说这段时间master更新的数据是不知道的。最简单的方式就是再做一次全量复制,从而获取到最新的数据,在redis2.8之前是这么做的。
部分复制
部分复制,redis2.8之后提供。如果发生类似抖动时候,可以有一种机制将这种损失降低到最低,如何实现的?
- 如果发生了抖动,相当于连接断开了
- 主会将写命令记录到缓冲区,repl_back_buffer
- 当slave再次去连接master时候,就是说网络抖动结束之后,会触发增量复制
- 从会执行pysnc命令,将当前自己的offset和主的runid传递给master
- 如果发现传输的offset偏移量是在buffer内的,不在期间内就证明你已经错过了很多数据,buffer也是有限的,默认是1M,会将offset开始到队列结束的数据同步给从。这样master和slave就达到了一致。
通过部分复制(增量复制)有效的降低了全量复制的开销。
复制的相关配置
无磁盘化复制
# 开启无磁盘化复制
repl-diskless-sync yes
# 当收到第一个复制请求时,等待 5s 后再开始复制,因为要等更多 slave 一起连接过来
repl-diskless-sync-delay 5
复制的限制
如果在复制期间,rdb复制时间超过60秒,内存缓冲区持续消耗超过64MB,或者一次性超过256MB,那么将会停止复制(失败)
配置项:client-output-buffer-limit slave 256MB 64MB 60
主从复制概述
在Redis客户端通过info replication可以查看与复制相关的状态,对于了解主从节点的当前状态,以及解决出现的问题都会有帮助。
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
主从复制的作用主要包括:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
如何使用主从复制
开启主从复制
主从复制的开启,完全是在从节点发起的;不需要我们在主节点做任何事情。
从节点开启主从复制,有3种方式:
- 配置文件:在从服务器的配置文件中加入:slaveof <masterip> <masterport>
- 启动命令:redis-server启动命令后加入 --slaveof <masterip> <masterport>
- 客户端命令:Redis服务器启动后,直接通过客户端执行命令:slaveof <masterip> <masterport>,则该Redis实例成为从节点。
断开主从复制
通过slaveof <masterip> <masterport>命令建立主从复制关系以后,可以通过slaveof no one断开。
从节点断开复制后,不会删除已有的数据,只是不再接受主节点新的数据变化。
主从复制的实现原理
主从复制过程大体可以分为3个阶段:连接建立阶段(即准备阶段)、数据同步阶段、命令传播阶段;
连接建立阶段
step1:保存主节点信息
从节点服务器内部维护了两个字段,即masterhost和masterport字段,用于存储主节点的ip和port信息。
slaveof是异步命令,从节点完成主节点ip和port的保存后,向发送slaveof命令的客户端直接返回OK,实际的复制操作在这之后才开始进行。
step2:建立socket连接
从节点每秒1次调用复制定时函数replicationCron(),如果发现了有主节点可以连接,便会根据主节点的ip和port,创建socket连接。
如果连接成功:
从节点:为该socket建立一个专门处理复制工作的文件事件处理器,负责后续的复制工作,如接收RDB文件、接收命令传播等。
主节点:接收到从节点的socket连接后(即accept之后),为该socket创建相应的客户端状态,并将从节点看做是连接到主节点的一个客户端,后面的步骤会以从节点向主节点发送命令请求的形式来进行。
step3:发送ping命令
从节点成为主节点的客户端之后,发送ping命令进行首次请求,目的是:检查socket连接是否可用,以及主节点当前是否能够处理请求。
从节点发送ping命令后,可能出现3种情况:
(1)返回pong:说明socket连接正常,且主节点当前可以处理请求,复制过程继续。
(2)超时:一定时间后从节点仍未收到主节点的回复,说明socket连接不可用,则从节点断开socket连接,并重连。
(3)返回pong以外的结果:如果主节点返回其他结果,如正在处理超时运行的脚本,说明主节点当前无法处理命令,则从节点断开socket连接,并重连。
step4:身份验证
如果从节点中设置了masterauth选项,则从节点需要向主节点进行身份验证;没有设置该选项,则不需要验证。
从节点进行身份验证是通过向主节点发送auth命令进行的,auth命令的参数即为配置文件中的masterauth的值。
如果主节点设置密码的状态,与从节点masterauth的状态一致(一致是指都存在,且密码相同,或者都不存在),则身份验证通过,复制过程继续;如果不一致,则从节点断开socket连接,并重连。
step5:发送从节点端口信息
身份验证之后,从节点会向主节点发送其监听的端口号,主节点将该信息保存到该从节点对应的客户端的slave_listening_port字段中;该端口信息除了在主节点中执行info Replication时显示以外,没有其他作用。
数据同步阶段
主从节点之间的连接建立以后,便可以开始进行数据同步,该阶段可以理解为从节点数据的初始化。
具体执行的方式是:从节点向主节点发送psync命令,开始同步。
数据同步阶段是主从复制最核心的阶段,根据主从节点当前状态的不同,可以分为全量复制和部分复制。
在数据同步阶段之前,从节点是主节点的客户端,主节点不是从节点的客户端;而到了这一阶段及以后,主从节点互为客户端。原因在于:在此之前,主节点只需要响应从节点的请求即可,不需要主动发请求,而在数据同步阶段和后面的命令传播阶段,主节点需要主动向从节点发送请求(如推送缓冲区中的写命令),才能完成复制。
命令传播阶段
数据同步阶段完成后,主从节点进入命令传播阶段;在这个阶段主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。
在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK。
PS:
延迟与不一致:命令传播是异步的过程,即主节点发送写命令后并不会等待从节点的回复;因此实际上主从节点之间很难保持实时的一致性,延迟在所难免。数据不一致的程度,与主从节点之间的网络状况、主节点写命令的执行频率、以及主节点中的repl-disable-tcp-nodelay配置等有关。
repl-disable-tcp-nodelay no:该配置作用于命令传播阶段,控制主节点是否禁止与从节点的TCP_NODELAY;默认no,即不禁止TCP_NODELAY。当设置为yes时,TCP会对包进行合并从而减少带宽,但是发送的频率会降低,从节点数据延迟增加,一致性变差;具体发送频率与Linux内核的配置有关,默认配置为40ms。当设置为no时,TCP会立马将主节点的数据发送给从节点,带宽增加但延迟变小。一般来说,只有当应用对Redis数据不一致的容忍度较高,且主从节点之间网络状况不好时,才会设置为yes;多数情况使用默认值no。
【数据同步阶段】全量复制和部分复制
在Redis2.8以前,从节点向主节点发送sync命令请求同步数据,此时的同步方式是全量复制;
在Redis2.8以后,从节点可以发送psync命令请求同步数据,此时根据主从节点当前状态的不同,同步方式可能是全量复制或部分复制。
- 全量复制:用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。
- 部分复制:用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。需要注意的是,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制。
全量复制
Redis通过psync命令进行全量复制的过程如下:
(1)从节点判断无法进行部分复制,向主节点发送全量复制的请求;或从节点发送部分复制的请求,但主节点判断无法进行全量复制;
(2)主节点收到全量复制的命令后,执行bgsave,在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令
(3)主节点的bgsave执行完成后,将RDB文件发送给从节点;从节点首先清除自己的旧数据,然后载入接收的RDB文件,将数据库状态更新至主节点执行bgsave时的数据库状态
(4)主节点将前述复制缓冲区中的所有写命令发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态
(5)如果从节点开启了AOF,则会触发bgrewriteaof的执行,从而保证AOF文件更新至主节点的最新状态
通过全量复制的过程可以看出,全量复制是非常重型的操作:
(1)主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬盘IO的;
(2)主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗
(3)从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行bgrewriteaof,也会带来额外的消耗
部分复制
由于全量复制在主节点数据量较大时效率太低,因此Redis2.8开始提供部分复制,用于处理网络中断时的数据同步。
部分复制的实现,依赖于三个重要的概念:复制偏移量,复制积压缓冲区,服务器运行ID
复制偏移量
主节点和从节点分别维护一个复制偏移量(offset),代表的是主节点向从节点传递的字节数;主节点每次向从节点传播N个字节数据时,主节点的offset增加N;从节点每次收到主节点传来的N个字节数据时,从节点的offset增加N。
offset用于判断主从节点的数据库状态是否一致:如果二者offset相同,则一致;如果offset不同,则不一致,此时可以根据两个offset找出从节点缺少的那部分数据。例如,如果主节点的offset是1000,而从节点的offset是500,那么部分复制就需要将offset为501-1000的数据传递给从节点。而offset为501-1000的数据存储的位置,就是下面要介绍的复制积压缓冲区。
复制积压缓冲区
复制积压缓冲区是由主节点维护的、固定长度的、先进先出(FIFO)队列,默认大小1MB;当主节点开始有从节点时创建,其作用是备份主节点最近发送给从节点的数据。注意,无论主节点有一个还是多个从节点,都只需要一个复制积压缓冲区。
在命令传播阶段,主节点除了将写命令发送给从节点,还会发送一份给复制积压缓冲区,作为写命令的备份;除了存储写命令,复制积压缓冲区中还存储了其中的每个字节对应的复制偏移量(offset)。由于复制积压缓冲区定长且是先进先出,所以它保存的是主节点最近执行的写命令;时间较早的写命令会被挤出缓冲区。
由于该缓冲区长度固定且有限,因此可以备份的写命令也有限,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。反过来说,为了提高网络中断时部分复制执行的概率,可以根据需要增大复制积压缓冲区的大小(通过配置repl-backlog-size);例如如果网络中断的平均时间是60s,而主节点平均每秒产生的写命令(特定协议格式)所占的字节数为100KB,则复制积压缓冲区的平均需求为6MB,保险起见,可以设置为12MB,来保证绝大多数断线情况都可以使用部分复制。
从节点将offset发送给主节点后,主节点根据offset和缓冲区大小决定能否执行部分复制:
- 如果offset偏移量之后的数据,仍然都在复制积压缓冲区里,则执行部分复制;
- 如果offset偏移量之后的数据已不在复制积压缓冲区中(数据已被挤出),则执行全量复制。
服务器运行ID(runid)
每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),由40个随机的十六进制字符组成;
runid用来唯一识别一个Redis节点。通过info Server命令,可以查看节点的runid。
主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来;当断线重连时,从节点会将这个runid发送给主节点;主节点根据runid判断能否进行部分复制:
- 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
- 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。
psync命令的执行
(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,从节点保存这两个值,以备使用。
【命令传播阶段】心跳机制
在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK。心跳机制对于主从复制的超时判断、数据安全等有作用。
主->从:PING
每隔指定的时间,主节点会向从节点发送PING命令,这个PING命令的作用,主要是为了让从节点进行超时判断。
PING发送的频率由 repl-ping-slave-period 参数控制,单位是秒,默认值是10s。
从->主:REPLCONF ACK
在命令传播阶段,从节点会向主节点发送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值。
应用中的问题
读写分离及其中的问题
在主从复制基础上实现的读写分离,可以实现Redis的读负载均衡:由主节点提供写服务,由一个或多个从节点提供读服务(多个从节点既可以提高数据冗余程度,也可以最大化读负载能力);
在使用Redis读写分离时需要注意的问题。
延迟与不一致问题
由于主从复制的命令传播是异步的,延迟与数据的不一致不可避免。如果应用对数据不一致的接受程度程度较低,可能的优化措施包括:
优化主从节点之间的网络环境(如在同机房部署);
监控主从节点延迟(通过offset)判断,如果从节点延迟过大,通知应用不再通过该从节点读取数据;
使用集群同时扩展写负载和读负载等。
在命令传播阶段以外的其他情况下,从节点的数据不一致可能更加严重,例如连接在数据同步阶段,或从节点失去与主节点的连接时等。从节点的slave-serve-stale-data参数便与此有关:它控制这种情况下从节点的表现;如果为yes(默认值),则从节点仍能够响应客户端的命令,如果为no,则从节点只能响应info、slaveof等少数命令。该参数的设置与应用对数据一致性的要求有关;如果对数据一致性要求很高,则应设置为no。
数据过期问题
在单机版Redis中,存在两种删除策略:
- 惰性删除:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除。
- 定期删除:服务器执行定时任务删除过期数据,但是考虑到内存和CPU的折中(删除会释放内存,但是频繁的删除操作对CPU不友好),该删除的频率和执行时间都受到了限制。
在主从复制场景下,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过期数据的删除。由于主节点的惰性删除和定期删除策略,都不能保证主节点及时对过期数据执行删除操作,因此,当客户端通过Redis从节点读取数据时,很容易读取到已经过期的数据。Redis 3.2中,从节点在读取数据时,增加了对数据是否过期的判断:如果该数据已过期,则不返回给客户端;将Redis升级到3.2可以解决数据过期问题。
故障切换问题
在没有使用哨兵的读写分离场景下,应用针对读和写分别连接不同的Redis节点;当主节点或从节点出现问题而发生更改时,需要及时修改应用程序读写Redis数据的连接;连接的切换可以手动进行,或者自己写监控程序进行切换,但前者响应慢、容易出错,后者实现复杂,成本都不算低。
复制超时问题
主从节点复制超时是导致复制中断的最重要的原因之一。
超时判断意义:在复制连接建立过程中及之后,主从节点都有机制判断连接是否超时,其意义在于:
(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等),导致服务器阻塞;阻塞期间无法响应复制连接中对方节点的请求,可能导致复制超时。
复制缓冲区溢出
在全量复制阶段,主节点会将执行的写命令放到复制缓冲区中,该缓冲区存放的数据包括了以下几个时间段内主节点执行的写命令: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也可以生效)。
需要注意的是,复制缓冲区是客户端输出缓冲区的一种,主节点会为每一个从节点分别分配复制缓冲区;而复制积压缓冲区则是一个主节点只有一个,无论它有多少个从节点。
各场景下复制的选择及优化技巧
第一次建立复制
此时全量复制不可避免,但仍有几点需要注意:
如果主节点的数据量较大,应该尽量避开流量的高峰期,避免造成阻塞;
如果有多个从节点需要建立对主节点的复制,可以考虑将几个从节点错开,避免主节点带宽占用过大。
如果从节点过多,也可以调整主从复制的拓扑结构,由一主多从结构变为树状结构(中间的节点既是其主节点的从节点,也是其从节点的主节点);
但使用树状结构应该谨慎:虽然主节点的直接从节点减少,降低了主节点的负担,但是多层从节点的延迟增大,数据一致性变差;且结构复杂,维护相当困难。
主节点重启
主节点重启可以分为两种情况来讨论,一种是故障导致宕机,另一种则是有计划的重启。
主节点宕机
主节点宕机重启后,runid会发生变化,因此不能进行部分复制,只能全量复制。
实际上在主节点宕机的情况下,应进行故障转移处理,将其中的一个从节点升级为主节点,其他从节点从新的主节点进行复制;且故障转移应尽量的自动化。
安全重启:debug reload
在一些场景下,可能希望对主节点进行重启,例如主节点内存碎片率过高,或者希望调整一些只能在启动时调整的参数。如果使用普通的手段重启主节点,会使得runid发生变化,可能导致不必要的全量复制。
为了解决这个问题,Redis提供了debug reload的重启方式:重启后,主节点的runid和offset都不受影响,避免了全量复制。
但debug reload是一柄双刃剑:它会清空当前内存中的数据,重新从RDB文件中加载,这个过程会导致主节点的阻塞,因此也需要谨慎。
从节点重启
从节点宕机重启后,其保存的主节点的runid会丢失,因此即使再次执行slaveof,也无法进行部分复制。
网络中断
如果主从节点之间出现网络问题,造成短时间内网络中断,可以分为多种情况讨论。
第一种情况:网络问题时间极为短暂,只造成了短暂的丢包,主从节点都没有判定超时(未触发repl-timeout);此时只需要通过REPLCONF ACK来补充丢失的数据即可。
第二种情况:网络问题时间很长,主从节点判断超时(触发了repl-timeout),且丢失的数据过多,超过了复制积压缓冲区所能存储的范围;此时主从节点无法进行部分复制,只能进行全量复制。为了尽可能避免这种情况的发生,应该根据实际情况适当调整复制积压缓冲区的大小;此外及时发现并修复网络中断,也可以减少全量复制。
第三种情况:介于前述两种情况之间,主从节点判断超时,且丢失的数据仍然都在复制积压缓冲区中;此时主从节点可以进行部分复制。
复制相关的配置
slaveof <masterip> <masterport>:Redis启动时起作用;作用是建立复制关系,开启了该配置的Redis服务器在启动后成为从节点。该注释默认注释掉,即Redis服务器默认都是主节点。
repl-timeout 60:与各个阶段主从节点连接超时判断有关。
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)多个从节点有较大的概率在短时间内建立主从复制。
client-output-buffer-limit slave 256MB 64MB 60:与全量复制阶段主节点的缓冲区大小有关,见前面的介绍。
repl-disable-tcp-nodelay no:与命令传播阶段的延迟有关。
masterauth <master-password>:与连接建立阶段的身份验证有关。
repl-ping-slave-period 10:与命令传播阶段主从节点的超时判断有关。
repl-backlog-size 1mb:复制积压缓冲区的大小。
repl-backlog-ttl 3600:当主节点没有从节点时,复制积压缓冲区保留的时间,这样当断开的从节点重新连进来时,可以进行全量复制;默认3600s。如果设置为0,则永远不会释放复制积压缓冲区。
min-slaves-to-write 3与min-slaves-max-lag 10:规定了主节点的最小从节点数目,及对应的最大延迟,见前面的介绍。
slave-serve-stale-data yes:与从节点数据陈旧时是否响应客户端命令有关,见前面的介绍。
slave-read-only yes:从节点是否只读;默认是只读的。由于从节点开启写操作容易导致主从节点的数据不一致,因此该配置尽量不要修改。
单机内存大小限制
fork操作对Redis单机内存大小的限制。实际上在Redis的使用中,限制单机内存大小的因素非常之多,单机内存过大可能造成的影响:
(1)切主:当主节点宕机时,一种常见的容灾策略是将其中一个从节点提升为主节点,并将其他从节点挂载到新的主节点上,此时这些从节点只能进行全量复制;如果Redis单机内存达到10GB,一个从节点的同步时间在几分钟的级别;如果从节点较多,恢复的速度会更慢。如果系统的读负载很高,而这段时间从节点无法提供服务,会对系统造成很大的压力。
(2)从库扩容:如果访问量突然增大,此时希望增加从节点分担读负载,如果数据量过大,从节点同步太慢,难以及时应对访问量的暴增。
(3)缓冲区溢出:(1)和(2)都是从节点可以正常同步的情形(虽然慢),但是如果数据量过大,导致全量复制阶段主节点的复制缓冲区溢出,从而导致复制中断,则主从节点的数据同步会全量复制->复制缓冲区溢出导致复制中断->重连->全量复制->复制缓冲区溢出导致复制中断……的循环。
(4)超时:如果数据量过大,全量复制阶段主节点fork+保存RDB文件耗时过大,从节点长时间接收不到数据触发超时,主从节点的数据同步同样可能陷入全量复制->超时导致复制中断->重连->全量复制->超时导致复制中断……的循环。
此外,主节点单机内存除了绝对量不能太大,其占用主机内存的比例也不应过大:最好只使用50%-65%的内存,留下30%-45%的内存用于执行bgsave命令和创建复制缓冲区等。
六、哨兵机制
一、作用和架构
1.作用
在介绍哨兵之前,首先从宏观角度回顾一下Redis实现高可用相关的技术。它们包括:持久化、复制、哨兵和集群,其主要作用和解决的问题是:
-
持久化:持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。
-
复制:复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷是故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
-
哨兵:在复制的基础上,哨兵实现了自动化的故障恢复。缺陷是写操作无法负载均衡;存储能力受到单机的限制。
-
集群:通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。
详细内容可回顾:
下面说回哨兵。
Redis Sentinel,即Redis哨兵,在Redis 2.8版本开始引入。哨兵的核心功能是主节点的自动故障转移。下面是Redis官方文档对于哨兵功能的描述:
-
监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。
-
自动故障转移(Automatic Failover):当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
-
配置提供者(Configuration Provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。
-
通知(Notification):哨兵可以将故障转移的结果发送给客户端。
其中,监控和自动故障转移功能,使得哨兵可以及时发现主节点故障并完成转移;而配置提供者和通知功能,则需要在与客户端的交互中才能体现。
这里对“客户端”一词在文章中的用法做一个说明:在前面的文章中,只要通过API访问Redis服务器,都会称作客户端,包括Redis-cli、Java客户端Jedis等。为了便于区分说明,本文中的客户端并不包括Redis-cli,而是比Redis-cli更加复杂:Redis-cli使用的是Redis提供的底层接口,而客户端则对这些接口、功能进行了封装,以便充分利用哨兵的配置提供者和通知功能。
2.架构
典型的哨兵架构图如下所示:
它由两部分组成:
-
哨兵节点:哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的Redis节点,不存储数据。
-
数据节点:主节点和从节点都是数据节点。
二、部署
这一部分将部署一个简单的哨兵系统,包含1个主节点、2个从节点和3个哨兵节点。方便起见,所有这些节点都部署在一台机器上(局域网IP:192.168.92.128),使用端口号区分;且节点的配置尽可能简化。
1.部署主从节点
哨兵系统中的主从节点,与普通的主从节点配置是一样的,并不需要做任何额外配置。下面分别是主节点(port=6379)和2个从节点(port=6380/6381)的配置文件,配置都比较简单,不再详述:
#redis-6379.conf
port 6379
daemonize yes
logfile "6379.log"
dbfilename "dump-6379.rdb"
#redis-6380.conf
port 6380
daemonize yes
logfile "6380.log"
dbfilename "dump-6380.rdb"
slaveof 192.168.92.128 6379
#redis-6381.conf
port 6381
daemonize yes
logfile "6381.log"
dbfilename "dump-6381.rdb"
slaveof 192.168.92.128 6379
配置完成后,依次启动主节点和从节点:
redis-server redis-6379.conf
redis-server redis-6380.conf
redis-server redis-6381.conf
节点启动后,连接主节点查看主从状态是否正常,如下图所示:
2.部署哨兵节点
哨兵节点本质上是特殊的Redis节点。
3个哨兵节点的配置几乎是完全一样的,主要区别在于端口号的不同(26379 / 26380 / 263 81),下面以26379节点为例介绍节点的配置和启动方式;配置部分尽量简化,更多配置会在后面介绍:
#sentinel-26379.conf
port 26379
daemonize yes
logfile "26379.log"
sentinel monitor mymaster 192.168.92.128 6379 2
其中,sentinel monitor mymaster 192.168. 92.128 6379 2配置的含义是:该哨兵节点监控192.168.92.128:6379这个主节点,该主节点的名称是mymaster,最后的2的含义与主节点的故障判定有关:至少需要2个哨兵节点同意,才能判定主节点故障并进行故障转移。
哨兵节点的启动有两种方式,二者作用是完全相同的:
redis-sentinel sentinel-26379.conf
redis-server sentinel-26379.conf --sentinel
按照上述方式配置和启动之后,整个哨兵系统就启动完毕了。可以通过Redis-cli连接哨兵节点进行验证,如下图所示:可以看出26379哨兵节点已经在监控mymaster主节点(即192.168.92.128:6379),并发现了其2个从节点和另外2个哨兵节点。
此时如果查看哨兵节点的配置文件,会发现一些变化,以26379为例:
其中,dir只是显式声明了数据和日志所在的目录(在哨兵语境下只有日志);known-slave和known-sentinel显示哨兵已经发现了从节点和其他哨兵;带有epoch的参数与配置纪元有关(配置纪元是一个从0开始的计数器,每进行一次领导者哨兵选举,都会+1;领导者哨兵选举是故障转移阶段的一个操作,在后文原理部分会介绍)。
3.演示故障转移
哨兵的4个作用中,配置提供者和通知需要客户端的配合,本文将在下一章介绍客户端访问哨兵系统的方法时详细介绍。这一小节将演示当主节点发生故障时,哨兵的监控和自动故障转移功能。
Step1:首先,使用kill命令杀掉主节点:
Step2:如果此时立即在哨兵节点中使用info Sentinel命令查看,会发现主节点还没有切换过来,因为哨兵发现主节点故障并转移,需要一段时间。
Step3:一段时间以后,再次在哨兵节点中执行info Sentinel查看,发现主节点已经切换成6380节点。
但是同时可以发现,哨兵节点认为新的主节点仍然有2个从节点,这是因为哨兵在将6380切换成主节点的同时,将6379节点置为其从节点;虽然6379从节点已经挂掉,但是由于哨兵并不会对从节点进行客观下线(其含义将在原理部分介绍),因此认为该从节点一直存在。当6379节点重新启动后,会自动变成6380节点的从节点。下面验证一下。
Step4:重启6379节点,可以看到6379节点成为了6380节点的从节点。
Step5:在故障转移阶段,哨兵和主从节点的配置文件都会被改写。
对于主从节点,主要是slaveof配置的变化:新的主节点没有了slaveof配置,其从节点则slaveof新的主节点。
对于哨兵节点,除了主从节点信息的变化,纪元(epoch)也会变化,下图中可以看到纪元相关的参数都+1了。
4.总结
哨兵系统的搭建过程,有几点需要注意:
-
哨兵系统中的主从节点,与普通的主从节点并没有什么区别,故障发现和转移是由哨兵来控制和完成的。
-
哨兵节点本质上是Redis节点。
-
每个哨兵节点,只需要配置监控主节点,便可以自动发现其他的哨兵节点和从节点。
-
在哨兵节点启动和故障转移阶段,各个节点的配置文件会被重写(Config Rewrite)。
-
本章的例子中,一个哨兵只监控了一个主节点;实际上,一个哨兵可以监控多个主节点,通过配置多条sentinel monitor即可实现。
三、客户端访问哨兵系统
上一小节演示了哨兵的两大作用:监控和自动故障转移,本小节则结合客户端演示哨兵的另外两个作用:配置提供者和通知。
1.代码示例
在介绍客户端的原理之前,先以Java客户端Jedis为例,演示一下使用方法:下面代码可以连接我们刚刚搭建的哨兵系统,并进行各种读写操作:
public static void testSentinel() throws Exception {
String masterName = "mymaster";
Set<String> sentinels = new HashSet<>();
sentinels.add("192.168.92.128:26379");
sentinels.add("192.168.92.128:26380");
sentinels.add("192.168.92.128:26381");
JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels); //初始化过程做了很多工作
Jedis jedis = pool.getResource();
jedis.set("key1", "value1");
pool.close();
}
(注:代码中只演示如何连接哨兵,异常处理、资源关闭等未考虑)
2.客户端原理
Jedis客户端对哨兵提供了很好的支持。如上述代码所示,我们只需要向Jedis提供哨兵节点集合和masterName,构造Jedis SentinelPool对象;然后便可以像使用普通Redis连接池一样来使用了:通过pool.getResource()获取连接,执行具体的命令。
在整个过程中,我们的代码不需要显式的指定主节点的地址,就可以连接到主节点;代码中对故障转移没有任何体现,就可以在哨兵完成故障转移后自动的切换主节点。之所以可以做到这一点,是因为在JedisSentinelPool的构造器中,进行了相关的工作,主要包括以下两点:
遍历哨兵节点,获取主节点信息:遍历哨兵节点,通过其中一个哨兵节点+masterName获得主节点的信息;该功能是通过调用哨兵节点的sentinel get-master-addr-by-name命令实现,该命令示例如下:
一旦获得主节点信息,停止遍历(因此一般来说遍历到第一个哨兵节点,循环就停止了)。
增加对哨兵的监听:这样当发生故障转移时,客户端便可以收到哨兵的通知,从而完成主节点的切换。具体做法是:利用Redis提供的发布订阅功能,为每一个哨兵节点开启一个单独的线程,订阅哨兵节点的+switch-master频道,当收到消息时,重新初始化连接池。
3.总结
通过客户端原理的介绍,可以加深对哨兵功能的理解,如下:
配置提供者:客户端可以通过哨兵节点+masterName获取主节点信息,在这里哨兵起到的作用就是配置提供者。
需要注意的是,哨兵只是配置提供者,而不是代理。二者的区别在于:
-
如果是配置提供者,客户端在通过哨兵获得主节点信息后,会直接建立到主节点的连接,后续的请求(如set/get)会直接发向主节点;
-
如果是代理,客户端的每一次请求都会发向哨兵,哨兵再通过主节点处理请求。
举一个例子可以很好的理解哨兵的作用是配置提供者,而不是代理。在前面部署的哨兵系统中,将哨兵节点的配置文件进行如下修改:
sentinel monitor mymaster 192.168.92.128 6379 2
改为
sentinel monitor mymaster 127.0.0.1 6379 2
然后,将前述客户端代码在局域网的另外一台机器上运行,会发现客户端无法连接主节点;这是因为哨兵作为配置提供者,客户端通过它查询到主节点的地址为127.0.0.1:6379,客户端会向127.0.0.1:6379建立Redis连接,自然无法连接。如果哨兵是代理,这个问题就不会出现了。
通知:哨兵节点在故障转移完成后,会将新的主节点信息发送给客户端,以便客户端及时切换主节点。
四、基本原理
前面介绍了哨兵部署、使用的基本方法,本部分介绍哨兵实现的基本原理。
1.哨兵节点支持的命令
哨兵节点作为运行在特殊模式下的Redis节点,其支持的命令与普通的Redis节点不同。在运维中,我们可以通过这些命令查询或修改哨兵系统;不过更重要的是,哨兵系统要实现故障发现、故障转移等各种功能,离不开哨兵节点之间的通信,而通信的很大一部分是通过哨兵节点支持的命令来实现的。下面介绍哨兵节点支持的主要命令:
基础查询:
通过这些命令,可以查询哨兵系统的拓扑结构、节点信息、配置信息等。
-
info sentinel:获取监控的所有主节点的基本信息。
-
sentinel masters:获取监控的所有主节点的详细信息。
-
sentinel master mymaster:获取监控的主节点mymaster的详细信息。
-
sentinel slaves mymaster:获取监控的主节点mymaster的从节点的详细信息。
-
sentinel sentinels mymaster:获取监控的主节点mymaster的哨兵节点的详细信息。
-
sentinel get - master - addr - by- name mymaster:获取监控的主节点mymaster的地址信息,前文已有介绍。
-
sentinel is-master-down-by-addr:哨兵节点之间可以通过该命令询问主节点是否下线,从而对是否客观下线做出判断。
增加/移除对主节点的监控:
sentinel monitor mymaster2 192.168.92.128 16379 2:与部署哨兵节点时配置文件中的sentinel monitor功能完全一样,不再详述。
sentinel remove mymaster2:取消当前哨兵节点对主节点mymaster2的监控。
强制故障转移:
sentinel failover mymaster:该命令可以强制对mymaster执行故障转移,即便当前的主节点运行完好;例如,如果当前主节点所在机器即将报废,便可以提前通过failover命令进行故障转移。
2.基本原理
关于哨兵的原理,关键是了解以下几个概念:
定时任务:每个哨兵节点维护了3个定时任务。定时任务的功能分别如下:通过向主从节点发送info命令获取最新的主从结构;通过发布订阅功能获取其他哨兵节点的信息;通过向其他节点发送ping命令进行心跳检测,判断是否下线。
主观下线:在心跳检测的定时任务中,如果其他节点超过一定时间没有回复,哨兵节点就会将其进行主观下线。顾名思义,主观下线的意思是一个哨兵节点“主观地”判断下线;与主观下线相对应的是客观下线。
客观下线:哨兵节点在对主节点进行主观下线后,会通过sentinel is-master-down-by-addr命令询问其他哨兵节点该主节点的状态;如果判断主节点下线的哨兵数量达到一定数值,则对该主节点进行客观下线。
需要特别注意的是,客观下线是主节点才有的概念;如果从节点和哨兵节点发生故障,被哨兵主观下线后,不会再有后续的客观下线和故障转移操作。
选举领导者哨兵节点:当主节点被判断客观下线以后,各个哨兵节点会进行协商,选举出一个领导者哨兵节点,并由该领导者节点对其进行故障转移操作。
监视该主节点的所有哨兵都有可能被选为领导者,选举使用的算法是Raft算法;Raft算法的基本思路是先到先得:即在一轮选举中,哨兵A向B发送成为领导者的申请,如果B没有同意过其他哨兵,则会同意A成为领导者。选举的具体过程这里不做详细描述,一般来说,哨兵选择的过程很快,谁先完成客观下线,一般就能成为领导者。
故障转移:选举出的领导者哨兵,开始进行故障转移操作,该操作大体可以分为3个步骤:
-
在从节点中选择新的主节点:选择的原则是,首先过滤掉不健康的从节点;然后选择优先级最高的从节点(由slave-priority指定);如果优先级无法区分,则选择复制偏移量最大的从节点;如果仍无法区分,则选择runid最小的从节点。
-
更新主从状态:通过slaveof no one命令,让选出来的从节点成为主节点;并通过slaveof命令让其他节点成为其从节点。
-
将已经下线的主节点(即6379)设置为新的主节点的从节点,当6379重新上线后,它会成为新的主节点的从节点。
通过上述几个关键概念,可以基本了解哨兵的工作原理。为了更形象的说明,下图展示了领导者哨兵节点的日志,包括从节点启动到完成故障转移。
五、配置与实践建议
1.配置
下面介绍与哨兵相关的几个配置。
配置1:sentinel monitor {masterName} {masterIp} {masterPort} {quorum}
sentinel monitor是哨兵最核心的配置,在前文讲述部署哨兵节点时已说明,其中:masterName指定了主节点名称,masterIp和masterPort指定了主节点地址,quorum是判断主节点客观下线的哨兵数量阈值:当判定主节点下线的哨兵数量达到quorum时,对主节点进行客观下线。建议取值为哨兵数量的一半加1。
配置2:sentinel down-after-milliseconds {masterName} {time}
sentinel down-after-milliseconds与主观下线的判断有关:哨兵使用ping命令对其他节点进行心跳检测,如果其他节点超过down-after-milliseconds配置的时间没有回复,哨兵就会将其进行主观下线。该配置对主节点、从节点和哨兵节点的主观下线判定都有效。
down-after-milliseconds的默认值是30000,即30s;可以根据不同的网络环境和应用要求来调整:值越大,对主观下线的判定会越宽松,好处是误判的可能性小,坏处是故障发现和故障转移的时间变长,客户端等待的时间也会变长。例如,如果应用对可用性要求较高,则可以将值适当调小,当故障发生时尽快完成转移;如果网络环境相对较差,可以适当提高该阈值,避免频繁误判。
配置3:sentinel parallel - syncs {masterName} {number}
sentinel parallel-syncs与故障转移之后从节点的复制有关:它规定了每次向新的主节点发起复制操作的从节点个数。例如,假设主节点切换完成之后,有3个从节点要向新的主节点发起复制;如果parallel-syncs=1,则从节点会一个一个开始复制;如果parallel-syncs=3,则3个从节点会一起开始复制。
parallel-syncs取值越大,从节点完成复制的时间越快,但是对主节点的网络负载、硬盘负载造成的压力也越大;应根据实际情况设置。例如,如果主节点的负载较低,而从节点对服务可用的要求较高,可以适量增加parallel-syncs取值。parallel-syncs的默认值是1。
配置4:sentinel failover - timeout {masterName} {time}
sentinel failover-timeout与故障转移超时的判断有关,但是该参数不是用来判断整个故障转移阶段的超时,而是其几个子阶段的超时,例如如果主节点晋升从节点时间超过timeout,或从节点向新的主节点发起复制操作的时间(不包括复制数据的时间)超过timeout,都会导致故障转移超时失败。
failover-timeout的默认值是180000,即180s;如果超时,则下一次该值会变为原来的2倍。
配置5:除上述几个参数外,还有一些其他参数,如安全验证相关的参数,这里不做介绍。
2.实践建议
-
哨兵节点的数量应不止一个。一方面增加哨兵节点的冗余,避免哨兵本身成为高可用的瓶颈;另一方面减少对下线的误判。此外,这些不同的哨兵节点应部署在不同的物理机上。
-
哨兵节点的数量应该是奇数,便于哨兵通过投票做出“决策”:领导者选举的决策、客观下线的决策等。
-
各个哨兵节点的配置应一致,包括硬件、参数等;此外,所有节点都应该使用ntp或类似服务,保证时间准确、一致。
-
哨兵的配置提供者和通知客户端功能,需要客户端的支持才能实现,如前文所说的Jedis;如果开发者使用的库未提供相应支持,则可能需要开发者自己实现。
-
当哨兵系统中的节点在Docker(或其他可能进行端口映射的软件)中部署时,应特别注意端口映射可能会导致哨兵系统无法正常工作,因为哨兵的工作基于与其他节点的通信,而Docker的端口映射可能导致哨兵无法连接到其他节点。例如,哨兵之间互相发现,依赖于它们对外宣称的IP和port,如果某个哨兵A部署在做了端口映射的Docker中,那么其他哨兵使用A宣称的port无法连接到A。
六、总结
本文首先介绍了哨兵的作用:监控、故障转移、配置提供者和通知;然后讲述了哨兵系统的部署方法,以及通过客户端访问哨兵系统的方法;再然后简要说明了哨兵实现的基本原理;最后给出了关于哨兵实践的一些建议。
在主从复制的基础上,哨兵引入了主节点的自动故障转移,进一步提高了Redis的高可用性;但是哨兵的缺陷同样很明显:哨兵无法对从节点进行自动故障转移,在读写分离场景下,从节点故障会导致读服务不可用,需要我们对从节点做额外的监控、切换操作。
此外,哨兵仍然没有解决写操作无法负载均衡、及存储能力受到单机限制的问题;这些问题的解决需要使用集群,欢迎关注社群后续内容。
七、集群
1. Redis集群方案
Redis Cluster 集群模式通常具有 高可用、可扩展性、分布式、容错 等特性。Redis 分布式方案一般有两种:
1.1 客户端分区方案
客户端 就已经决定数据会被 存储 到哪个 redis 节点或者从哪个 redis 节点 读取数据。其主要思想是采用 哈希算法 将 Redis 数据的 key 进行散列,通过 hash 函数,特定的 key会 映射 到特定的 Redis 节点上。
客户端分区方案 的代表为 Redis Sharding,Redis Sharding 是 Redis Cluster 出来之前,业界普遍使用的 Redis 多实例集群 方法。Java 的 Redis 客户端驱动库 Jedis,支持 Redis Sharding 功能,即 ShardedJedis 以及 结合缓存池 的 ShardedJedisPool。
优点
不使用 第三方中间件,分区逻辑 可控,配置 简单,节点之间无关联,容易 线性扩展,灵活性强。
缺点
客户端 无法 动态增删 服务节点,客户端需要自行维护 分发逻辑,客户端之间 无连接共享,会造成 连接浪费。
1.2. 代理分区方案
客户端 发送请求到一个 代理组件,代理 解析 客户端 的数据,并将请求转发至正确的节点,最后将结果回复给客户端。
优点:简化 客户端 的分布式逻辑,客户端 透明接入,切换成本低,代理的 转发 和 存储 分离。
缺点:多了一层 代理层,加重了 架构部署复杂度 和 性能损耗。
代理分区 主流实现的有方案有 Twemproxy 和 Codis。
1.2.1. Twemproxy
Twemproxy 也叫 nutcraker,是 twitter 开源的一个 redis 和 memcache 的 中间代理服务器 程序。Twemproxy 作为 代理,可接受来自多个程序的访问,按照 路由规则,转发给后台的各个 Redis 服务器,再原路返回。Twemproxy 存在 单点故障 问题,需要结合 Lvs 和 Keepalived 做 高可用方案。
优点:应用范围广,稳定性较高,中间代理层 高可用。
缺点:无法平滑地 水平扩容/缩容,无 可视化管理界面,运维不友好,出现故障,不能 自动转移。
1.2.2. Codis
Codis 是一个 分布式 Redis 解决方案,对于上层应用来说,连接 Codis-Proxy 和直接连接 原生的 Redis-Server 没有的区别。Codis 底层会 处理请求的转发,不停机的进行 数据迁移 等工作。Codis 采用了无状态的 代理层,对于 客户端 来说,一切都是透明的。
优点
实现了上层 Proxy 和底层 Redis 的 高可用,数据分片 和 自动平衡,提供 命令行接口 和 RESTful API,提供 监控 和 管理 界面,可以动态 添加 和 删除 Redis 节点。
缺点
部署架构 和 配置 复杂,不支持 跨机房 和 多租户,不支持 鉴权管理。
1.3. 查询路由方案
客户端随机地 请求任意一个 Redis 实例,然后由 Redis 将请求 转发 给 正确 的 Redis 节点。Redis Cluster 实现了一种 混合形式 的 查询路由,但并不是 直接 将请求从一个 Redis 节点 转发 到另一个 Redis 节点,而是在 客户端 的帮助下直接 重定向( redirected)到正确的 Redis 节点。
优点
无中心节点,数据按照 槽 存储分布在多个 Redis 实例上,可以平滑的进行节点 扩容/缩容,支持 高可用 和 自动故障转移,运维成本低。
缺点
严重依赖 Redis-trib 工具,缺乏 监控管理,需要依赖 Smart Client (维护连接,缓存路由表,MultiOp 和 Pipeline 支持)。Failover 节点的 检测过慢,不如 中心节点 ZooKeeper 及时。Gossip 消息具有一定开销。无法根据统计区分 冷热数据。
2. 数据分布
2.1. 数据分布理论
分布式数据库 首先要解决把 整个数据集 按照 分区规则 映射到 多个节点 的问题,即把 数据集 划分到 多个节点 上,每个节点负责 整体数据 的一个 子集。
数据分布通常有 哈希分区 和 顺序分区 两种方式,对比如下:
分区方式 特点 相关产品 哈希分区 离散程度好,数据分布与业务无关,无法顺序访问 Redis Cluster,Cassandra,Dynamo 顺序分区 离散程度易倾斜,数据分布与业务相关,可以顺序访问 BigTable,HBase,Hypertable 由于 Redis Cluster 采用 哈希分区规则,这里重点讨论 哈希分区。常见的 哈希分区 规则有几种,下面分别介绍:
2.1.1. 节点取余分区
使用特定的数据,如 Redis 的 键 或 用户 ID,再根据 节点数量 N 使用公式:hash(key)% N 计算出 哈希值,用来决定数据 映射 到哪一个节点上。
优点
这种方式的突出优点是 简单性,常用于 数据库 的 分库分表规则。一般采用 预分区 的方式,提前根据 数据量 规划好 分区数,比如划分为 512 或 1024 张表,保证可支撑未来一段时间的 数据容量,再根据 负载情况 将 表 迁移到其他 数据库 中。扩容时通常采用 翻倍扩容,避免 数据映射 全部被 打乱,导致 全量迁移 的情况。
缺点
当 节点数量 变化时,如 扩容 或 收缩 节点,数据节点 映射关系 需要重新计算,会导致数据的 重新迁移。
2.1.2. 一致性哈希分区
一致性哈希 可以很好的解决 稳定性问题,可以将所有的 存储节点 排列在 收尾相接 的 Hash 环上,每个 key 在计算 Hash 后会 顺时针 找到 临接 的 存储节点 存放。而当有节点 加入 或 退出 时,仅影响该节点在 Hash 环上 顺时针相邻 的 后续节点。
优点
加入 和 删除 节点只影响 哈希环 中 顺时针方向 的 相邻的节点,对其他节点无影响。
缺点
加减节点 会造成 哈希环 中部分数据 无法命中。当使用 少量节点 时,节点变化 将大范围影响 哈希环 中 数据映射,不适合 少量数据节点 的分布式方案。普通 的 一致性哈希分区 在增减节点时需要 增加一倍 或 减去一半 节点才能保证 数据 和 负载的均衡。
注意:因为 一致性哈希分区 的这些缺点,一些分布式系统采用 虚拟槽 对 一致性哈希 进行改进,比如 Dynamo 系统。
2.1.3. 虚拟槽分区
虚拟槽分区 巧妙地使用了 哈希空间,使用 分散度良好 的 哈希函数 把所有数据 映射 到一个 固定范围 的 整数集合 中,整数定义为 槽(slot)。这个范围一般 远远大于 节点数,比如 Redis Cluster 槽范围是 0 ~ 16383。槽 是集群内 数据管理 和 迁移 的 基本单位。采用 大范围槽 的主要目的是为了方便 数据拆分 和 集群扩展。每个节点会负责 一定数量的槽,如图所示:
当前集群有 5 个节点,每个节点平均大约负责 3276 个 槽。由于采用 高质量 的 哈希算法,每个槽所映射的数据通常比较 均匀,将数据平均划分到 5 个节点进行 数据分区。Redis Cluster 就是采用 虚拟槽分区。
节点1: 包含 0 到 3276 号哈希槽。
节点2:包含 3277 到 6553 号哈希槽。
节点3:包含 6554 到 9830 号哈希槽。
节点4:包含 9831 到 13107 号哈希槽。
节点5:包含 13108 到 16383 号哈希槽。
这种结构很容易 添加 或者 删除 节点。如果 增加 一个节点 6,就需要从节点 1 ~ 5 获得部分 槽 分配到节点 6 上。如果想 移除 节点 1,需要将节点 1 中的 槽 移到节点 2 ~ 5 上,然后将 没有任何槽 的节点 1 从集群中 移除 即可。
由于从一个节点将 哈希槽 移动到另一个节点并不会 停止服务,所以无论 添加删除或者 改变 某个节点的 哈希槽的数量 都不会造成 集群不可用 的状态.
2.2. Redis的数据分区
Redis Cluster 采用 虚拟槽分区,所有的 键 根据 哈希函数 映射到 0~16383 整数槽内,计算公式:slot = CRC16(key)& 16383。每个节点负责维护一部分槽以及槽所映射的 键值数据,如图所示:
2.2.1. Redis虚拟槽分区的特点
解耦 数据 和 节点 之间的关系,简化了节点 扩容 和 收缩 难度。
节点自身 维护槽的 映射关系,不需要 客户端 或者 代理服务 维护 槽分区元数据。
支持 节点、槽、键 之间的 映射查询,用于 数据路由、在线伸缩 等场景。
2.3. Redis集群的功能限制
Redis 集群相对 单机 在功能上存在一些限制,需要 开发人员 提前了解,在使用时做好规避。
key 批量操作 支持有限。
类似 mset、mget 操作,目前只支持对具有相同 slot 值的 key 执行 批量操作。对于 映射为不同 slot 值的 key 由于执行 mget、mget 等操作可能存在于多个节点上,因此不被支持。
key 事务操作 支持有限。
只支持 多 key 在 同一节点上 的 事务操作,当多个 key 分布在 不同 的节点上时 无法 使用事务功能。
key 作为 数据分区 的最小粒度
不能将一个 大的键值 对象如 hash、list 等映射到 不同的节点。
不支持 多数据库空间
单机 下的 Redis 可以支持 16 个数据库(db0 ~ db15),集群模式 下只能使用 一个 数据库空间,即 db0。
复制结构 只支持一层
从节点 只能复制 主节点,不支持 嵌套树状复制 结构。
3. Redis集群搭建
Redis-Cluster 是 Redis 官方的一个 高可用 解决方案,Cluster 中的 Redis 共有 2^14(16384) 个 slot 槽。创建 Cluster 后,槽 会 平均分配 到每个 Redis 节点上。
下面介绍一下本机启动 6 个 Redis 的 集群服务,并使用 redis-trib.rb 创建 3主3从 的 集群。搭建集群工作需要以下三个步骤:
3.1. 准备节点
Redis 集群一般由 多个节点 组成,节点数量至少为 6 个,才能保证组成 完整高可用 的集群。每个节点需要 开启配置 cluster-enabled yes,让 Redis 运行在 集群模式 下。
Redis 集群的节点规划如下:
节点名称 端口号 是主是从 所属主节点 redis-6379 6379 主节点 --- redis-6389 6389 从节点 redis-6379 redis-6380 6380 主节点 --- redis-6390 6390 从节点 redis-6380 redis-6381 6381 主节点 --- redis-6391 6391 从节点 redis-6381 注意:建议为集群内 所有节点 统一目录,一般划分三个目录:conf、data、log,分别存放 配置、数据和 日志 相关文件。把 6 个节点配置统一放在 conf 目录下。
3.1.1. 创建redis各实例目录
$sudo mkdir -p /usr/local/redis-cluster$cd/usr/local/redis-cluster$sudo mkdir conf datalog$ sudo mkdir -p data/redis-6379 data/redis-6389 data/redis-6380 data/redis-6390 data/redis-6381 data/redis-6391复制代码
3.1.2. redis配置文件管理
根据以下 模板 配置各个实例的 redis.conf,以下只是搭建集群需要的 基本配置,可能需要根据实际情况做修改。
# redis后台运行daemonizeyes# 绑定的主机端口bind 127.0.0.1# 数据存放目录dir /usr/local/redis-cluster/data/redis-6379# 进程文件pidfile /var/run/redis-cluster/${自定义}.pid# 日志文件logfile /usr/local/redis-cluster/log/${自定义}.log# 端口号port 6379# 开启集群模式,把注释#去掉cluster-enabledyes# 集群的配置,配置文件首次启动自动生成cluster-config-file /usr/local/redis-cluster/conf/${自定义}.conf# 请求超时,设置10秒cluster-node-timeout 10000# aof日志开启,有需要就开启,它会每次写操作都记录一条日志appendonlyyes复制代码
redis-6379.conf
daemonize yesbind 127.0.0.1dir/usr/local/redis-cluster/data/redis-6379pidfile /var/run/redis-cluster/redis-6379.pidlogfile /usr/local/redis-cluster/log/redis-6379.logport 6379cluster-enabled yescluster-config-file/usr/local/redis-cluster/conf/node-6379.confcluster-node-timeout 10000appendonly yes复制代码
redis-6389.conf
daemonize yesbind 127.0.0.1dir/usr/local/redis-cluster/data/redis-6389pidfile /var/run/redis-cluster/redis-6389.pidlogfile /usr/local/redis-cluster/log/redis-6389.logport 6389cluster-enabled yescluster-config-file/usr/local/redis-cluster/conf/node-6389.confcluster-node-timeout 10000appendonly yes复制代码
redis-6380.conf
daemonize yesbind 127.0.0.1dir/usr/local/redis-cluster/data/redis-6380pidfile /var/run/redis-cluster/redis-6380.pidlogfile /usr/local/redis-cluster/log/redis-6380.logport 6380cluster-enabled yescluster-config-file/usr/local/redis-cluster/conf/node-6380.confcluster-node-timeout 10000appendonly yes复制代码
redis-6390.conf
daemonize yesbind 127.0.0.1dir/usr/local/redis-cluster/data/redis-6390pidfile /var/run/redis-cluster/redis-6390.pidlogfile /usr/local/redis-cluster/log/redis-6390.logport 6390cluster-enabled yescluster-config-file/usr/local/redis-cluster/conf/node-6390.confcluster-node-timeout 10000appendonly yes复制代码
redis-6381.conf
daemonize yesbind 127.0.0.1dir/usr/local/redis-cluster/data/redis-6381pidfile /var/run/redis-cluster/redis-6381.pidlogfile /usr/local/redis-cluster/log/redis-6381.logport 6381cluster-enabled yescluster-config-file/usr/local/redis-cluster/conf/node-6381.confcluster-node-timeout 10000appendonly yes复制代码
redis-6391.conf
daemonize yesbind 127.0.0.1dir/usr/local/redis-cluster/data/redis-6391pidfile /var/run/redis-cluster/redis-6391.pidlogfile /usr/local/redis-cluster/log/redis-6391.logport 6391cluster-enabled yescluster-config-file/usr/local/redis-cluster/conf/node-6391.confcluster-node-timeout 10000appendonly yes复制代码
3.2. 环境准备
3.2.1. 安装Ruby环境
$ sudobrew install ruby复制代码
3.2.2. 准备rubygem redis依赖
$ sudo geminstallredisPassword:Fetching: redis-4.0.2.gem (100%)Successfully installed redis-4.0.2Parsing documentationforredis-4.0.2Installing ri documentationforredis-4.0.2Done installing documentationforredisafter1seconds1gem installed复制代码
3.2.3. 拷贝redis-trib.rb到集群根目录
redis-trib.rb 是 redis 官方推出的管理 redis 集群 的工具,集成在 redis 的源码 src 目录下,将基于 redis 提供的 集群命令 封装成 简单、便捷、实用 的 操作工具。
$ sudo cp/usr/local/redis-4.0.11/src/redis-trib.rb /usr/local/redis-cluster复制代码
查看 redis-trib.rb 命令环境是否正确,输出如下:
$ ./redis-trib.rb Usage:redis-trib create host1:port1 ...hostN:portN--replicascheck host:portinfo host:portfix host:port--timeoutreshard host:port--from--to--slots--yes--timeout--pipelinerebalance host:port--weight--auto-weights--use-empty-masters--timeout--simulate--pipeline--thresholdadd-node new_host:new_port existing_host:existing_port--slave--master-iddel-node host:port node_idset-timeouthost:port millisecondscall host:port command arg arg ..argimport host:port--from--copy--replacehelp (show this help)For check,fix,reshard,del-node,set-timeoutyou can specify the host and port of any working node in the cluster.复制代码
redis-trib.rb 是 redis 作者用 ruby 完成的。redis-trib.rb 命令行工具 的具体功能如下:
命令 作用 create 创建集群 check 检查集群 info 查看集群信息 fix 修复集群 reshard 在线迁移slot rebalance 平衡集群节点slot数量 add-node 将新节点加入集群 del-node 从集群中删除节点 set-timeout 设置集群节点间心跳连接的超时时间 call 在集群全部节点上执行命令 import 将外部redis数据导入集群 3.3. 安装集群
3.3.1. 启动redis服务节点
运行如下命令启动 6 台 redis 节点:
sudo redis-serverconf/redis-6379.confsudo redis-serverconf/redis-6389.confsudo redis-serverconf/redis-6380.confsudo redis-serverconf/redis-6390.confsudo redis-serverconf/redis-6381.confsudo redis-serverconf/redis-6391.conf复制代码
启动完成后,redis 以集群模式启动,查看各个 redis 节点的进程状态:
$ ps -ef | grep redis-server 0 1908 1 0 4:59下午 ?? 0:00.01 redis-server *:6379 [cluster] 0 1911 1 0 4:59下午 ?? 0:00.01 redis-server *:6389 [cluster] 0 1914 1 0 4:59下午 ?? 0:00.01 redis-server *:6380 [cluster] 0 1917 1 0 4:59下午 ?? 0:00.01 redis-server *:6390 [cluster] 0 1920 1 0 4:59下午 ?? 0:00.01 redis-server *:6381 [cluster] 0 1923 1 0 4:59下午 ?? 0:00.01 redis-server *:6391 [cluster] 复制代码
在每个 redis 节点的 redis.conf 文件中,我们都配置了 cluster-config-file 的文件路径,集群启动时,conf 目录会新生成 集群 节点配置文件。查看文件列表如下:
$ tree -L3..├── appendonly.aof├── conf│ ├── node-6379.conf│ ├── node-6380.conf│ ├── node-6381.conf│ ├── node-6389.conf│ ├── node-6390.conf│ ├── node-6391.conf│ ├── redis-6379.conf│ ├── redis-6380.conf│ ├── redis-6381.conf│ ├── redis-6389.conf│ ├── redis-6390.conf│ └── redis-6391.conf├── data│ ├── redis-6379│ ├── redis-6380│ ├── redis-6381│ ├── redis-6389│ ├── redis-6390│ └── redis-6391├── log│ ├── redis-6379.log│ ├── redis-6380.log│ ├── redis-6381.log│ ├── redis-6389.log│ ├── redis-6390.log│ └── redis-6391.log└── redis-trib.rb9directories,20files复制代码
3.3.2. redis-trib关联集群节点
按照 从主到从 的方式 从左到右 依次排列 6 个 redis 节点。
$ sudo ./redis-trib.rb create --replicas1127.0.0.1:6379127.0.0.1:6380127.0.0.1:6381127.0.0.1:6389127.0.0.1:6390127.0.0.1:6391复制代码
集群创建后,redis-trib 会先将 16384 个 哈希槽 分配到 3 个 主节点,即 redis-6379,redis-6380 和 redis-6381。然后将各个 从节点 指向 主节点,进行 数据同步。
>>>Creatingcluster>>>Performinghashslotsallocationon6nodes...Using3masters:127.0.0.1:6379127.0.0.1:6380127.0.0.1:6381Addingreplica127.0.0.1:6390to127.0.0.1:6379Addingreplica127.0.0.1:6391to127.0.0.1:6380Addingreplica127.0.0.1:6389to127.0.0.1:6381>>>Tryingtooptimizeslavesallocationforanti-affinity[WARNING]SomeslavesareinthesamehostastheirmasterM:ad4b9ffceba062492ed67ab336657426f55874b7127.0.0.1:6379slots:0-5460(5461slots)masterM:df23c6cad0654ba83f0422e352a81ecee822702e127.0.0.1:6380slots:5461-10922(5462slots)masterM:ab9da92d37125f24fe60f1f33688b4f8644612ee127.0.0.1:6381slots:10923-16383(5461slots)masterS: 25cfa11a2b4666021da5380ff332b80dbda97208127.0.0.1:6389replicatesad4b9ffceba062492ed67ab336657426f55874b7S: 48e0a4b539867e01c66172415d94d748933be173127.0.0.1:6390replicatesdf23c6cad0654ba83f0422e352a81ecee822702eS:d881142a8307f89ba51835734b27cb309a0fe855127.0.0.1:6391replicatesab9da92d37125f24fe60f1f33688b4f8644612ee复制代码
然后输入 yes,redis-trib.rb 开始执行 节点握手 和 槽分配 操作,输出如下:
CanIsettheaboveconfiguration? (type'yes'to accept):yes>>>Nodesconfigurationupdated>>>Assignadifferentconfigepochtoeachnode>>>SendingCLUSTERMEETmessagestojointheclusterWaitingfortheclustertojoin....>>>PerformingClusterCheck(using node127.0.0.1:6379)M:ad4b9ffceba062492ed67ab336657426f55874b7127.0.0.1:6379slots:0-5460(5461slots)master1additionalreplica(s)M:ab9da92d37125f24fe60f1f33688b4f8644612ee127.0.0.1:6381slots:10923-16383(5461slots)master1additionalreplica(s)S:48e0a4b539867e01c66172415d94d748933be173127.0.0.1:6390slots: (0slots)slavereplicatesdf23c6cad0654ba83f0422e352a81ecee822702eS:d881142a8307f89ba51835734b27cb309a0fe855127.0.0.1:6391slots: (0slots)slavereplicatesab9da92d37125f24fe60f1f33688b4f8644612eeM:df23c6cad0654ba83f0422e352a81ecee822702e127.0.0.1:6380slots:5461-10922(5462slots)master1additionalreplica(s)S:25cfa11a2b4666021da5380ff332b80dbda97208127.0.0.1:6389slots: (0slots)slavereplicatesad4b9ffceba062492ed67ab336657426f55874b7[OK]Allnodesagreeaboutslotsconfiguration.>>>Checkforopenslots...>>>Checkslotscoverage...[OK]All16384slotscovered.复制代码
执行 集群检查,检查各个 redis 节点占用的 哈希槽(slot)的个数以及 slot 覆盖率。16384 个槽位中,主节点 redis-6379、redis-6380 和 redis-6381 分别占用了 5461、5461 和 5462 个槽位。
3.3.3. redis主节点的日志
可以发现,通过 BGSAVE 命令,从节点 redis-6389 在 后台 异步地从 主节点 redis-6379 同步数据。
$ catlog/redis-6379.log1907:C05Sep16:59:52.960# oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo1907:C05Sep16:59:52.961# Redis version=4.0.11, bits=64, commit=00000000, modified=0, pid=1907, just started1907:C05Sep16:59:52.961# Configuration loaded1908:M05Sep16:59:52.964* Increased maximumnumberofopen filesto10032(itwas originallysetto256).1908:M05Sep16:59:52.965* No cluster configuration found, I'm ad4b9ffceba062492ed67ab336657426f55874b71908:M05Sep16:59:52.967* Running mode=cluster, port=6379.1908:M05Sep16:59:52.967# Server initialized1908:M05Sep16:59:52.967* Readytoaccept connections1908:M05Sep17:01:17.782# configEpoch set to 1 via CLUSTER SET-CONFIG-EPOCH1908:M05Sep17:01:17.812# IP address for this node updated to 127.0.0.11908:M05Sep17:01:22.740# Cluster state changed: ok1908:M05Sep17:01:23.681* Slave127.0.0.1:6389asksforsynchronization1908:M05Sep17:01:23.681* Partial resynchronizationnotaccepted: Replication ID mismatch (Slave askedfor'4c5afe96cac51cde56039f96383ea7217ef2af41',myreplication IDs are '037b661bf48c80c577d1fa937ba55367a3692921'and'0000000000000000000000000000000000000000')1908:M05Sep17:01:23.681* Starting BGSAVEforSYNCwithtarget: disk1908:M05Sep17:01:23.682* Background saving startedbypid19521952:C05Sep17:01:23.683* DB savedondisk1908:M05Sep17:01:23.749* Background saving terminatedwithsuccess1908:M05Sep17:01:23.752* Synchronizationwithslave127.0.0.1:6389succeeded复制代码
参考文件:
https://www.jianshu.com/p/ba3cc187da9c
https://www.cnblogs.com/wade-luffy/p/9639986.html
http://www.redis.cn/articles/20181020001.html
https://www.jianshu.com/p/84dbb25cc8dc