⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/6844904098773352455 「绘你一世倾城」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

之前总结过redis的持久化机制:深度剖析Redis持久化机制,持久化机制主要解决redis数据单机备份问题;redis的高可用需要考虑数据的多机备份,多机备份通过主从复制来实现,这是redis高可用的基石。本文将详细介绍redis主从复制的实现原理,在使用过程中应该注意的问题和相关配置。

1. CAP理论

CAP理论是分布式领域的牛顿定律,所有的分布式存储中间件都要使用它作为理论基石。如下图所示:

这个原理很简单,首先明确几个概念:

  • C : Consistent, 一致性
  • A : Availability, 可用性
  • P : Partition tolerance, 分区容忍性

分布式系统的节点往往分布在不同的机器上,它们之间由网络进行隔离,当网络断开时就会产生网络分区。网络分区不可避免,但是当网络分区发生时,对分布式系统中一个节点的修改操作无法同步给其它节点,数据一致性也就无法满足;想要满足一致性,除非牺牲可用性,也就是暂停分布式节点服务,等到网络恢复,数据一致后,再对外提供服务。分布式系统中网络分区不可避免,一致性可用性水火不容。这就是cap理论:网络分区发生时,一致性和可用性两难全

2. redis主从复制

2.1 概述

互联网圈经常谈“三高”架构:高并发、高性能、高可用。对于redis来说,高并发、高性能可以保证,高可用需要架构的设计,单机redis由于存在系统崩溃、硬盘故障的风险,还有内存的限制,所以企业一般都会搭建主从,对系统有更高要求会搭建集群。

为了保持数据一致性,主节点(master)只写,从节点(slave)只读,数据由主节点复制给从节点,这个复制的过程就是主从复制

redis的主从复制是异步的,分布式的redis系统并不满足一致性要求,但是在网络断开的情况下,主节点依然可以对外提供服务,满足可用性。redis保证最终一致性,从节点会努力追赶主节点,最终从节点的状态会和从节点保持一致。网络断开的情况下,主从节点数据会出现大量的不一致,但一旦网络恢复,从节点会继续追赶主节点,最终达到和主节点状态一致。

为了减轻redis主节点的同步负担,redis 的后续版本还增加了从从同步,与此同时,数据一致性会变差。

2.2 主从复制的作用

主从复制在服务中起到了什么效果呢?

  • 读写分离:master写,slave读,提高服务器的读写负载能力。
  • 负载均衡:基于主从架构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高redis服务器并发量和数据吞吐量。
  • 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复。
  • 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式。
  • 高可用基石:基于主从复制,构建哨兵模式与集群,实现redis的高可用方案。

2.3 怎样配置实现主从复制?

有三种配置实现redis的主从:

  • 方式一:客户端发送命令

slaveof <masterip> <masterport>

  • 方式二:启动服务器时添加参数

redis-server -slaveof <masterip> <masterport>

  • 方式三:服务器配置文件中配置(通过redis.conf)

slaveof <masterip> <masterport>

比较主流的用法是通过配置文件的方式实现主从。还有其它的一些命令:

  • 主从断开连接,可以从客户端发送命令

slaveof no one

  • 服务端设置了授权访问

#master有两种方式设置

//-- 1.master配置文件中设置
requirepass <password>
//-- 2.master客户端发送命令设置密码
config set requirepass <password>
config get requirepass

#slave有三种方式实现认证
//-- 1.客户端发送命令设置密码
auth <password>
//-- 2.slave配置文件设置密码
masterauth <password>
//-- 3.启动客户端设置密码
redis-cli -a <password>

2.4 redis主从复制的工作流程

redis主从复制实现过程有三个阶段:

建立连接、数据同步、命令传播。建立连接阶段主从节点建立通信的桥梁,彼此之间同步一些基础信息;数据同步阶段实现从节点全量同步主节点的数据;从节点同步完主节点数据之后,就进入了命令传播阶段,主节点接收写请求,数据不断发生变化,通过命令传播阶段主节点将数据源源不断的同步给从节点。下边我们详细介绍主从复制这三个阶段的工作细节和注意事项。

2.4.1 建立连接阶段

建立slave到master的连接,使master能识别slave, 并保存slave的端口号;与此同时,slave也保存master的地址和端口号信息。

  • slave发送slaveof ip port命令给master,master响应slave
  • slave保存master的ip和端口号,建立socket连接
  • 在socket连接之上,主从节点实现了心跳机制,这部分内容也比较重要,后边会提到。
  • 如果有认证机制,从节点通过上边说到的认证指令,发送认证信息给master,实现认证。
  • 从节点将自己的端口信息发送发送给主节点,主节点保存。

通过以上过程主从之间的连接就建立了。

2.4.2 数据同步阶段

数据同步阶段实现的功能是从节点从主节点同步全量的数据。这个过程又分为几个小阶段,最主要的就是数据的全量复制部分复制, 对应的流程就是主节点发送rdb文件同步数据和发送缓冲区写命令(aof)同步数据给从节点。下图是实现细节:

  • 首先slave节点先发起命令psync ? -1,向master节点要全量数据。
  • master节点接收到指令以后,执行bgsave,将当前内存数据快照保存为rdb文件,这个过程为了不影响主节点继续对外提供服务,采用了Copy On Write技术。与此同时,master节点也会将bgsave保存快照期间接收到的写更新命令添加到复制挤压缓冲区当中。master节点rdb文件生成完毕以后,会通过第一阶段建立的socket连接将它发送给slave节点,还会发送+FULLRESYNC runid offset给slave节点,告诉slave节点自己的runidoffset

什么是runid?

redis-server在每次启动的时候都会生成一个runid,因为redis-server是一个守护进程,所以在运行期间,runid不会发生变化,可以通过info server指令查看runid,它是一个40位字符长度的字符串。上文提到的psync有两个参数,和+FULLRESYNC一样:psync <runid> <offset>;runid的意义是什么呢?当master节点发生故障发生了变更后,在接到slave的指令以后,对比参数中的runid如果和自己的runid不一致,就会再次进行全量复制,因为换主了。

什么是复制挤压缓冲区offset?

复制挤压缓冲区是一个先进先出(FIFO)的环形队列,用于存储服务端执行过的命令,每次传播命令,master节点都会将传播的命令记录下来,保存在这里。

复制挤压缓冲区由两部分组成:偏移量和字节值。字节值是redis指令字节的存储(redis指令以一种Redis序列化文本协议的格式存储),偏移量offset就是当前字节值在环形队列中的偏移量。

  • slave节点接收完master节点同步的rdb文件之后,将rdb的内容加载到自己的内存,然后将master节点的runidoffset记录下来。
  • 有了master节点的runidoffset,在加载完rdb文件之后,就开始向master节点发送新的命令psync runid offset,向master节点要新数据。新数据是master节点在bgsave生成rdb文件时和向slave同步数据的这段时间产生的,所以这段时间的工作也称为部分复制
  • master节点收到slave节点发送的请求数据命令之后,会检查runid是否一致(是否换主),offset是否一致(因为复制挤压缓冲区是定长的,所有有可能会溢出),这两个条件只要有一个不满足,master就会向slave再次全量的同步数据(读者可能会发现,如果master节点写并发很高,复制挤压缓冲区又设置的比较小的话,可能会每次向slave同步完数据以后,每次复制挤压缓冲区都会溢出,造成主从之间循环的全量复制。这确实是应该规避的问题!我们后边会针对主从复制应该考虑的问题做一个总结)。在runid和offset都满足的情况下,master节点就会向slave节点发送指令+CONTINUE offset,接着从offset位置开始同步数据,数据都在主节点的复制挤压缓冲区中了,所以直接复制发送就可以了。
  • slave节点接收到master节点发送的+CONTINUE offset指令之后,更新自己保存的offset值,然后将从master节点同步过来的数据,使用bgrewriteaof,重放aof数据。

到这里,主从复制的第二阶段:数据同步阶段工作就完成了。

2.4.3 命令传播阶段

命令传播阶段类似于数据同步阶段的部分复制,当master节点数据被修改以后,就和slave节点的数据不一致了,这个时候master节点就会根据slave上报的offset开始传播数据(一主多从的架构中,master节点要记录每一个slave的offset)。slave接收到数据以后,执行bgrewriteaof重放数据。在这个工作过程中,如果因为网路问题导致offset溢出或者换主的情况,主从之间还是会进行数据的全量同步的。

2.5 心跳机制

进入命令传播阶段以后,master节点与slave节点需要进行信息传递,使用心跳机制进行维护,实现双方保持在线。

master节点心跳使用指令PING,由配置repl-ping-slave-period决定,默认10秒,作用是判断slave是否在线,可以通过info replication获取slave最后一次连接到现在的时间间隔,lag的值维护在0和1视为正常。

slave节点的心跳任务使用指令REPLCONF ACK {offset},周期是1秒,slave的心跳任务有两个作用:

  • 汇报自己的offset给master,这在数据传播起到了关键性作用,因为master节点向slave节点传播数据,offset是一项非常重要的指标。
  • 判断master是否在线

在心跳阶段应该注意:当slave节点多数掉线,或者延迟过高时,master节点为了保证数据的稳定性,将拒绝所有信息的同步。有如下配置:

min-slaves-to-write 2
min-slaves-max-lag 8

上述配置含义是:当slave数量小于2个,或者所有的slave的延迟都大于等于8秒时,强制关闭master写功能,停止数据同步。

3.主从复制常见问题

上边介绍的主从复制是建立在主从节点间的网络和服务都正常的情况下,业务场景中要考虑更多的实际情况。

3.1 master重启

伴随着系统的运行,master节点的内存数据量变得很大的情况下,一旦master节点重启,runid将发生变化,会导致slave的全量复制操作。

这里有一个优化方案:在master节点内部创建master_replid变量,使用runid相同的策略生成,长度41位,发送给所有的slave节点。在master节点关闭时,执行命令shutdown save,进行RDB的数据持久化,将runid与offset保存在RDB文件中。在RDB文件中有了repl-id和repl-offset信息以后,通过指令redis-check-rdb命令可以查看这些信息。在master节点重启后,将RDB文件加载到内存中以后,也会将repl-idrepl-offset加载到内存中。通过info 指令可以查看:

master_repl_id = repl
master_repl_offset = repl-offset

作用是:master节点重启之后会保存原来的runid,重启后恢复该值,会让所有的slave节点认为还是之前的master节点。

3.2 复制积压缓冲区太小

当复制积压缓冲区太小的时候,当master节点写并发很大,master节点和slave节点网络有抖动的时候,就会导致数据同步不及时,造成offset溢出,进而导致全量复制。这个时候,我们可以考虑修改复制积压缓冲区的大小,由配置repl-backlog-size控制。设置多大比较合适呢,这要根据master的并发量和网络情况做具体的评估。

3.3 slave执行了keys * 、hgetall等命令

前边内容我们提到slave节点每秒都会发送REPLCONF ACK指令到master节点,master节点调用复制函数relicationCron()同步数据给slave节点时,如果slave节点执行了keys *、hgetall等阻塞命令的时候,就会在很长一段时候得不到响应。这就会导致master的各种资源(输出缓冲区、带宽、连接)等被占用。master节点的CPU就会变高,slave频繁的断开连接。

解决方案是master节点通过配置:repl-timeout设置合理的超时时间(默认60s),超过改值,master节点将释放slave节点。

3.4 master节点发送ping指令频度低,网络存在丢包

master节点默认10s向slave节点发送一次ping指令,因为master节点不仅要处理大量的写任务,还可能维护着多个master,所以ping设置的不太及时。但是当ping指令在网络中存在丢包时,master节点如果设置的超时时间太短,就会导致master节点与slave节点断开连接。

解决方案有:提高master节点ping的频度,超时时间repl-time设置为ping指令时间的5~10倍。

3.5 网络信息不同步,数据发送有延迟

当主从同步中网络数据发送有延迟的时候,就会造成多个slave获取到的数据不同步,解决方案是优化master节点和slave节点的网络环境,通常是放置在一个机房部署。另外要监控master和slave节点的延迟,如果延迟过大,可以暂时屏蔽对slave节点的访问。通过下面指令设置:

slave-serve-stale-data yes | no

开启后,slave节点仅仅能响应info、slaveof等少数命令,除非对数据一致性要求很高,否则不要轻易这样使用。

4.总结

本文主要总结了redis实现主从复制的实现细节和注意事项。redis的主从复制是实现高可用的重要基石,后边的文章将总结哨兵和集群的搭建。

文章目录
  1. 1. 1. CAP理论
  2. 2. 2. redis主从复制
    1. 2.1. 2.1 概述
    2. 2.2. 2.2 主从复制的作用
    3. 2.3. 2.3 怎样配置实现主从复制?
    4. 2.4. 2.4 redis主从复制的工作流程
      1. 2.4.1. 2.4.1 建立连接阶段
      2. 2.4.2. 2.4.2 数据同步阶段
      3. 2.4.3. 2.4.3 命令传播阶段
    5. 2.5. 2.5 心跳机制
  3. 3. 3.主从复制常见问题
    1. 3.1. 3.1 master重启
    2. 3.2. 3.2 复制积压缓冲区太小
    3. 3.3. 3.3 slave执行了keys * 、hgetall等命令
    4. 3.4. 3.4 master节点发送ping指令频度低,网络存在丢包
    5. 3.5. 3.5 网络信息不同步,数据发送有延迟
  4. 4. 4.总结