【深刻学习Redis】主从复制(下)

 

(续上文)node


 

6、应用中的问题git

1. 读写分离及其中的问题github

在主从复制基础上实现的读写分离,能够实现Redis的读负载均衡:由主节点提供写服务,由一个或多个从节点提供读服务(多个从节点既能够提升数据冗余程度,也能够最大化读负载能力);在读负载较大的应用场景下,能够大大提升Redis服务器的并发量。下面介绍在使用Redis读写分离时,须要注意的问题。redis

 

一、延迟与不一致问题安全

前面已经讲到,因为主从复制的命令传播是异步的,延迟与数据的不一致不可避免。若是应用对数据不一致的接受程度程度较低,可能的优化措施包括:优化主从节点之间的网络环境(如在同机房部署);监控主从节点延迟(经过offset)判断,若是从节点延迟过大,通知应用再也不经过该从节点读取数据;使用集群同时扩展写负载和读负载等。服务器

 

在命令传播阶段之外的其余状况下,从节点的数据不一致可能更加严重,例如链接在数据同步阶段,或从节点失去与主节点的链接时等。从节点的slave-serve-stale-data参数便与此有关:它控制这种状况下从节点的表现;若是为yes(默认值),则从节点仍可以响应客户端的命令,若是为no,则从节点只能响应info、slaveof等少数命令。该参数的设置与应用对数据一致性的要求有关;若是对数据一致性要求很高,则应设置为no。网络

 

二、数据过时问题并发

在单机版Redis中,存在两种删除策略:负载均衡

  • 惰性删除:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过时,若是过时则删除。less

  • 按期删除:服务器执行定时任务删除过时数据,可是考虑到内存和CPU的折中(删除会释放内存,可是频繁的删除操做对CPU不友好),该删除的频率和执行时间都受到了限制。

在主从复制场景下,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过时数据的删除。因为主节点的惰性删除和按期删除策略,都不能保证主节点及时对过时数据执行删除操做,所以,当客户端经过Redis从节点读取数据时,很容易读取到已通过期的数据。

 

Redis 3.2中,从节点在读取数据时,增长了对数据是否过时的判断:若是该数据已过时,则不返回给客户端;将Redis升级到3.2能够解决数据过时问题。

 

三、故障切换问题

在没有使用哨兵的读写分离场景下,应用针对读和写分别链接不一样的Redis节点;当主节点或从节点出现问题而发生更改时,须要及时修改应用程序读写Redis数据的链接;链接的切换能够手动进行,或者本身写监控程序进行切换,但前者响应慢、容易出错,后者实现复杂,成本都不算低。

 

四、总结

在使用读写分离以前,能够考虑其余方法增长Redis的读负载能力:如尽可能优化主节点(减小慢查询、减小持久化等其余状况带来的阻塞等)提升负载能力;使用Redis集群同时提升读负载能力和写负载能力等。若是使用读写分离,可使用哨兵,使主从节点的故障切换尽量自动化,并减小对应用程序的侵入。

 

2. 复制超时问题

主从节点复制超时是致使复制中断的最重要的缘由之一,本小节单独说明超时问题,下一小节说明其余会致使复制中断的问题。

 

超时判断意义

  1. 在复制链接创建过程当中及以后,主从节点都有机制判断链接是否超时,其意义在于:

  2. 若是主节点判断链接超时,其会释放相应从节点的链接,从而释放各类资源,不然无效的从节点仍会占用主节点的各类资源(输出缓冲区、带宽、链接等);此外链接超时的判断可让主节点更准确的知道当前有效从节点的个数,有助于保证数据安全(配合前面讲到的min-slaves-to-write等参数)。

  3. 若是从节点判断链接超时,则能够及时从新创建链接,避免与主节点数据长期的不一致。

 

判断机制

主从复制超时判断的核心,在于repl-timeout参数,该参数规定了超时时间的阈值(默认60s),对于主节点和从节点同时有效;主从节点触发超时的条件分别以下:

主节点:每秒1次调用复制定时函数replicationCron(),在其中判断当前时间距离上次收到各个从节点REPLCONF ACK的时间,是否超过了repl-timeout值,若是超过了则释放相应从节点的链接。

从节点:从节点对超时的判断一样是在复制定时函数中判断,基本逻辑是:

  • 若是当前处于链接创建阶段,且距离上次收到主节点的信息的时间已超过repl-timeout,则释放与主节点的链接;

  • 若是当前处于数据同步阶段,且收到主节点的RDB文件的时间超时,则中止数据同步,释放链接;

  • 若是当前处于命令传播阶段,且距离上次收到主节点的PING命令或数据的时间已超过repl-timeout值,则释放与主节点的链接。

 

主从节点判断链接超时的相关源代码以下:

/* Replication cron function, called 1 time per second. */
void replicationCron(void) {
   static long long replication_cron_loops = 0;

   /* Non blocking connection timeout? */
   if (server.masterhost &&
       (server.repl_state == REDIS_REPL_CONNECTING ||
        slaveIsInHandshakeState()) &&
        (time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)
   {
       redisLog(REDIS_WARNING,"Timeout connecting to the MASTER...");
       undoConnectWithMaster();
   }

   /* Bulk transfer I/O timeout? */
   if (server.masterhost && server.repl_state == REDIS_REPL_TRANSFER &&
       (time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)
   {
       redisLog(REDIS_WARNING,"Timeout receiving bulk data from MASTER... If the problem persists try to set the 'repl-timeout' parameter in redis.conf to a larger value.");
       replicationAbortSyncTransfer();
   }

   /* Timed out master when we are an already connected slave? */
   if (server.masterhost && server.repl_state == REDIS_REPL_CONNECTED &&
       (time(NULL)-server.master->lastinteraction) > server.repl_timeout)
   {
       redisLog(REDIS_WARNING,"MASTER timeout: no data nor PING received...");
       freeClient(server.master);
   }

   //此处省略无关代码……

   /* Disconnect timedout slaves. */
   if (listLength(server.slaves)) {
       listIter li;
       listNode *ln;
       listRewind(server.slaves,&li);
       while((ln = listNext(&li))) {
           redisClient *slave = ln->value;
           if (slave->replstate != REDIS_REPL_ONLINE) continue;
           if (slave->flags & REDIS_PRE_PSYNC) continue;
           if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout)
           {
               redisLog(REDIS_WARNING, "Disconnecting timedout slave: %s",
                   replicationGetSlaveName(slave));
               freeClient(slave);
           }
       }
   }

   //此处省略无关代码……

}

 

须要注意的坑

下面介绍与复制阶段链接超时有关的一些实际问题:

一、数据同步阶段:在主从节点进行全量复制bgsave时,主节点须要首先fork子进程将当前数据保存到RDB文件中,而后再将RDB文件经过网络传输到从节点。若是RDB文件过大,主节点在fork子进程+保存RDB文件时耗时过多,可能会致使从节点长时间收不到数据而触发超时;此时从节点会重连主节点,而后再次全量复制,再次超时,再次重连……这是个悲伤的循环。为了不这种状况的发生,除了注意Redis单机数据量不要过大,另外一方面就是适当增大repl-timeout值,具体的大小能够根据bgsave耗时来调整。

二、命令传播阶段:如前所述,在该阶段主节点会向从节点发送PING命令,频率由repl-ping-slave-period控制;该参数应明显小于repl-timeout值(后者至少是前者的几倍)。不然,若是两个参数相等或接近,网络抖动致使个别PING命令丢失,此时恰巧主节点也没有向从节点发送数据,则从节点很容易判断超时。

三、慢查询致使的阻塞:若是主节点或从节点执行了一些慢查询(如keys *或者对大数据的hgetall等),致使服务器阻塞;阻塞期间没法响应复制链接中对方节点的请求,可能致使复制超时。

 

3. 复制中断问题

主从节点超时是复制中断的缘由之一,除此以外,还有其余状况可能致使复制中断,其中最主要的是复制缓冲区溢出问题。

 

复制缓冲区溢出

前面曾提到过,在全量复制阶段,主节点会将执行的写命令放到复制缓冲区中,该缓冲区存放的数据包括了如下几个时间段内主节点执行的写命令: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也能够生效)。

 

当复制缓冲区溢出时,主节点打印日志以下所示:

须要注意的是,复制缓冲区是客户端输出缓冲区的一种,主节点会为每个从节点分别分配复制缓冲区;而复制积压缓冲区则是一个主节点只有一个,不管它有多少个从节点。

 

4. 各场景下复制的选择及优化技巧

在介绍了Redis复制的种种细节以后,如今咱们能够来总结一下,在下面常见的场景中,什么时候使用部分复制,以及须要注意哪些问题。

 

一、第一次创建复制

此时全量复制不可避免,但仍有几点须要注意:若是主节点的数据量较大,应该尽可能避开流量的高峰期,避免形成阻塞;若是有多个从节点须要创建对主节点的复制,能够考虑将几个从节点错开,避免主节点带宽占用过大。此外,若是从节点过多,也能够调整主从复制的拓扑结构,由一主多从结构变为树状结构(中间的节点既是其主节点的从节点,也是其从节点的主节点);但使用树状结构应该谨慎:虽然主节点的直接从节点减小,下降了主节点的负担,可是多层从节点的延迟增大,数据一致性变差;且结构复杂,维护至关困难。

 

二、主节点重启

主节点重启能够分为两种状况来讨论,一种是故障致使宕机,另外一种则是有计划的重启。

 

主节点宕机

主节点宕机重启后,runid会发生变化,所以不能进行部分复制,只能全量复制。

 

实际上在主节点宕机的状况下,应进行故障转移处理,将其中的一个从节点升级为主节点,其余从节点重新的主节点进行复制;且故障转移应尽可能的自动化,后面文章将要介绍的哨兵即可以进行自动的故障转移。

 

安全重启:debug reload

在一些场景下,可能但愿对主节点进行重启,例如主节点内存碎片率太高,或者但愿调整一些只能在启动时调整的参数。若是使用普通的手段重启主节点,会使得runid发生变化,可能致使没必要要的全量复制。

 

为了解决这个问题,Redis提供了debug reload的重启方式:重启后,主节点的runid和offset都不受影响,避免了全量复制。

 

以下图所示,debug reload重启后runid和offset都未受影响:

但debug reload是一柄双刃剑:它会清空当前内存中的数据,从新从RDB文件中加载,这个过程会致使主节点的阻塞,所以也须要谨慎。

 

三、从节点重启

从节点宕机重启后,其保存的主节点的runid会丢失,所以即便再次执行slaveof,也没法进行部分复制。

 

四、网络中断

若是主从节点之间出现网络问题,形成短期内网络中断,能够分为多种状况讨论。

第一种状况:网络问题时间极为短暂,只形成了短暂的丢包,主从节点都没有断定超时(未触发repl-timeout);此时只须要经过REPLCONF ACK来补充丢失的数据便可。

第二种状况:网络问题时间很长,主从节点判断超时(触发了repl-timeout),且丢失的数据过多,超过了复制积压缓冲区所能存储的范围;此时主从节点没法进行部分复制,只能进行全量复制。为了尽量避免这种状况的发生,应该根据实际状况适当调整复制积压缓冲区的大小;此外及时发现并修复网络中断,也能够减小全量复制。

第三种状况:介于前述两种状况之间,主从节点判断超时,且丢失的数据仍然都在复制积压缓冲区中;此时主从节点能够进行部分复制。

 

5. 复制相关的配置

这一节总结一下与复制有关的配置,说明这些配置的做用、起做用的阶段,以及配置方法等;经过了解这些配置,一方面加深对Redis复制的了解,另外一方面掌握这些配置的方法,能够优化Redis的使用,少走坑。

 

配置大体能够分为主节点相关配置、从节点相关配置以及与主从节点都有关的配置,下面分别说明。

 

一、与主从节点都有关的配置

首先介绍最特殊的配置,它决定了该节点是主节点仍是从节点:

  1. slaveof <masterip> <masterport>:Redis启动时起做用;做用是创建复制关系,开启了该配置的Redis服务器在启动后成为从节点。该注释默认注释掉,即Redis服务器默认都是主节点。

  2. repl-timeout 60:与各个阶段主从节点链接超时判断有关,见前面的介绍。

 

二、主节点相关配置

  1. repl-diskless-sync no:做用于全量复制阶段,控制主节点是否使用diskless复制(无盘复制)。所谓diskless复制,是指在全量复制时,主节点再也不先把数据写入RDB文件,而是直接写入slave的socket中,整个过程当中不涉及硬盘;diskless复制在磁盘IO很慢而网速很快时更有优点。须要注意的是,截至Redis3.0,diskless复制处于实验阶段,默认是关闭的。

  2. repl-diskless-sync-delay 5:该配置做用于全量复制阶段,当主节点使用diskless复制时,该配置决定主节点向从节点发送以前停顿的时间,单位是秒;只有当diskless复制打开时有效,默认5s。之因此设置停顿时间,是基于如下两个考虑:(1)向slave的socket的传输一旦开始,新链接的slave只能等待当前数据传输结束,才能开始新的数据传输 (2)多个从节点有较大的几率在短期内创建主从复制。

  3. client-output-buffer-limit slave 256MB 64MB 60:与全量复制阶段主节点的缓冲区大小有关,见前面的介绍。

  4. repl-disable-tcp-nodelay no:与命令传播阶段的延迟有关,见前面的介绍。

  5. masterauth <master-password>:与链接创建阶段的身份验证有关,见前面的介绍。

  6. repl-ping-slave-period 10:与命令传播阶段主从节点的超时判断有关,见前面的介绍。

  7. repl-backlog-size 1mb:复制积压缓冲区的大小,见前面的介绍。

  8. repl-backlog-ttl 3600:当主节点没有从节点时,复制积压缓冲区保留的时间,这样当断开的从节点从新连进来时,能够进行全量复制;默认3600s。若是设置为0,则永远不会释放复制积压缓冲区。

  9. min-slaves-to-write 3与min-slaves-max-lag 10:规定了主节点的最小从节点数目,及对应的最大延迟,见前面的介绍。

 

三、从节点相关配置

  1. slave-serve-stale-data yes:与从节点数据陈旧时是否响应客户端命令有关,见前面的介绍。

  2. slave-read-only yes:从节点是否只读;默认是只读的。因为从节点开启写操做容易致使主从节点的数据不一致,所以该配置尽可能不要修改。

 

6. 单机内存大小限制

在 深刻学习Redis(2):持久化 一文中,讲到了fork操做对Redis单机内存大小的限制。实际上在Redis的使用中,限制单机内存大小的因素很是之多,下面总结一下在主从复制中,单机内存过大可能形成的影响:

  1. 切主:当主节点宕机时,一种常见的容灾策略是将其中一个从节点提高为主节点,并将其余从节点挂载到新的主节点上,此时这些从节点只能进行全量复制;若是Redis单机内存达到10GB,一个从节点的同步时间在几分钟的级别;若是从节点较多,恢复的速度会更慢。若是系统的读负载很高,而这段时间从节点没法提供服务,会对系统形成很大的压力。

  2. 从库扩容:若是访问量忽然增大,此时但愿增长从节点分担读负载,若是数据量过大,从节点同步太慢,难以及时应对访问量的暴增。

  3. 缓冲区溢出:(1)和(2)都是从节点能够正常同步的情形(虽然慢),可是若是数据量过大,致使全量复制阶段主节点的复制缓冲区溢出,从而致使复制中断,则主从节点的数据同步会全量复制->复制缓冲区溢出致使复制中断->重连->全量复制->复制缓冲区溢出致使复制中断……的循环。

  4. 超时:若是数据量过大,全量复制阶段主节点fork+保存RDB文件耗时过大,从节点长时间接收不到数据触发超时,主从节点的数据同步一样可能陷入全量复制->超时致使复制中断->重连->全量复制->超时致使复制中断……的循环。

 

此外,主节点单机内存除了绝对量不能太大,其占用主机内存的比例也不该过大:最好只使用50%-65%的内存,留下30%-45%的内存用于执行bgsave命令和建立复制缓冲区等。

 

7. info Replication

在Redis客户端经过info Replication能够查看与复制相关的状态,对于了解主从节点的当前状态,以及解决出现的问题都会有帮助。

 

主节点:

从节点:

对于从节点,上半部分展现的是其做为从节点的状态,从connectd_slaves开始,展现的是其做为潜在的主节点的状态。

 

info Replication中展现的大部份内容在文章中都已经讲述,这里再也不详述。

 

7、总结

下面回顾一下本文的主要内容:

  1. 主从复制的做用:宏观的了解主从复制是为了解决什么样的问题,即数据冗余、故障恢复、读负载均衡等。

  2. 主从复制的操做:即slaveof命令。

  3. 主从复制的原理:主从复制包括了链接创建阶段、数据同步阶段、命令传播阶段;其中数据同步阶段,有全量复制和部分复制两种数据同步方式;命令传播阶段,主从节点之间有PING和REPLCONF ACK命令互相进行心跳检测。

  4. 应用中的问题:包括读写分离的问题(数据不一致问题、数据过时问题、故障切换问题等)、复制超时问题、复制中断问题等,而后总结了主从复制相关的配置,其中repl-timeout、client-output-buffer-limit slave等对解决Redis主从复制中出现的问题可能会有帮助。

 

主从复制虽然解决或缓解了数据冗余、故障恢复、读负载均衡等问题,但其缺陷仍很明显:故障恢复没法自动化;写操做没法负载均衡;存储能力受到单机的限制;这些问题的解决,须要哨兵和集群的帮助,我将在后面的文章中介绍,欢迎关注。

 

参考文献

  • 《Redis开发与运维》

  • 《Redis设计与实现》

  • 《Redis实战》

  • http://mdba.cn/2015/03/16/redis复制中断问题-慢查询/

  • https://redislabs.com/blog/top-redis-headaches-for-devops-replication-buffer/

  • http://mdba.cn/2015/03/17/redis主从复制(2)-replication-buffer与replication-backlog/

  • https://github.com/antirez/redis/issues/918

  • https://blog.csdn.net/qbw2010/article/details/50496982

  • https://mp.weixin.qq.com/s/fpupqLp-wjR8fQvYSQhVLg