微信搜索公众号“码路印记”,感谢关注!
在分布式环境中,数据副本 (Replica) 和复制 (Replication) 作为提升系统可用性和读写性能的有效手段被大量应用在各种分布式系统中,Redis 也不例外。Redis主从复制,以一主多从的模式建立的分布式系统,是Redis搭建高可用集群(哨兵模式、Cluster模式)的基础,为容错、故障转移提供强有力的支撑。本文将介绍以下内容:
Redis设置主从结构有三种方式:
replicaof <masterip> <masterport>
,如replicaof 192.168.1.2 6379
将建立与实例192.168.1.2:6379
的主从关系;这种方式等同于在Redis启动命令中增加参数replicaof
参数。replicaof <masterip> <masterport>
命令。注意:如果Redis重启,主从关系将无法重新建立。注意:Redis 5.0之后,replicaof已经替换slaveof,由于一些种族歧视的奇葩要求,Redis已经对指令进行了兼容升级,目前slaveof还是可以使用的。
本文示例采用docker compose搭建一主两从的主从结构。
docker-compose.yml
添加以下配置信息。version: "3"
services:
redis-master:
image: redis:6.0
ports:
- "6379:6379"
container_name: "redis-master"
command: redis-server
networks:
- redis-master-slave
redis-slave-1:
image: redis:6.0
ports:
- "6380:6379"
container_name: "redis-slave-1"
command: redis-server --slaveof redis-master 6379
depends_on:
- redis-master
networks:
- redis-master-slave
redis-slave-2:
image: redis:6.0
ports:
- "6381:6379"
container_name: "redis-slave-2"
command: redis-server --slaveof redis-master 6379
depends_on:
- redis-master
networks:
- redis-master-slave
networks:
redis-master-slave:
docker-compose up
启动docker,效果如下:info replication
查看主从关系,可以看到有两个从节点。set name abcd
,然后到slave节点客户端命令行执行get name
,可以看到数据同步成功,如下图所示:del name
,然后到slave客户端再执行get name
,可以看到数据已经删除。由上述过程,我们可以看到,redis-master
、redis-slave-1
、redis-slave-2
三个节点建立起了一主二从的关系;而且,master节点写入的数据成功同步至至两个slave节点。主从关系示例先介绍到这里,接下来我们了解一下Redis主从节点之间数据同步的原理与过程。
Redis Replication是一种简单、易用的主从模式(master-slave)的复制机制,它能够使得slave节点成为与master节点完全相同的副本。每次与master节点连接中断后slave节点会自动重联,并且无论master节点发生什么,slave节点总是尝试达到与master节点一致的状态。Redis采取了一系列的辅助措施来保证数据安全。
Redis主从复制技术有两个版本:在2.8版本之前,每次slave节点断线重联后,只能进行全量同步。在2.8版本之后进行了重新设计,引入了部分同步的概念。本文将以Redis 6.0版本为基础对主从复制原理进行介绍。
旧版主从复制采用的是“全量同步+命令传播”机制完成主从数据同步,这里的硬伤是从机重连后,哪怕主从之间只有少量的数据不一致,也要执行一个耗时、耗资源的全量同步操作来达到数据一致。为此,Redis团队引入了若干机制确保在少量数据不一致时,采用代价较低的部分同步来完成主从复制。所以,当前的主从复制机制包含三个部分:全量同步、部分同步、命令传播。
为保证内容完整性,还是先介绍一下全量同步、命令传播两个历史的概念。
在主从复制模式下,Redis使用一对Replicaion ID, offset
来唯一识别Master节点数据集的版本,要理解这个“版本“的概念需要认识Redis的以下三个概念:
概念可能比较抽象,通过下图直观看下它们之间的关系。
图示说明:
图示Redis角色为Master,其复制ID(replid)为xxxx,当前的复制偏移量(offset)为1010; 它有一个复制积压缓冲区(backlog),容量(backlog_size)为100,backlog起点相对于offset的偏移量(backlog_off)为1000,当前backlog存储的命令字节数(backlog_hislen)为11个,对应了backlog中[1000,1010]偏移量范围内的字节; offset始终与backlog中最后一个字节的偏移量相同。
以上内容是master节点存储维护的,在slave中也有复制ID、复制偏移量的概念。
server.master
内;如果slave与master连接断开,Redis会把它存储在server.cache_master
。
server.master
代表的是正在进行主从复制且正常工作的主节点信息;server.cache_master
是曾经正常进行主从复制工作的主节点信息,它是为部分同步做准备的。
扯了这么多,这些东西有什么用呢?前面也说了,旧版本每次从节点连接到主节点都要进行全量同步,效率很低,所以引入了部分同步。上面这些组件就是为了在一定条件下可以使用部分同步,提高效率。那它们是如何工作的呢?
PSYNC <replid> <offset>
向主节点发起同步请求,从节点会从server.cache_master
中取出两个参数构建命令,然后发给主节点。注意:如果该从节点是全新的,从未与任何主节点进行主从复制,那么会使用特殊的命令:PSYNC ? -1
。便于理解,接下来看几个例子,如下图所示。有四个slave节点要与主节点发起主从复制,主节点、从节点的状态已经在图中标示,我们来分析下。
总结一下:部分同步其实是以全量同步为基础(得到复制ID),用复制积压缓冲区中的缓存命令做命令重放的增量同步逻辑,不过受制于复制积压缓冲区的容量,它可容忍的范围是有限的。这与持久化机制的AOF混合持久化如出一辙,也与mysql中主从复制的Binlog思路不谋而合。
当完成同步操作之后,master-slave便会进入命令传播阶段,此时master-slave的数据是一致的。
当maste执行完新的写命令后,会通过传播程序把该命令追加至复制积压缓冲区,然后异步地发送给slave。slave接收命令并执行,同时更新slave维护的复制偏移量offset。命令传播如下图所示:如果slave可以收到每条传播指令,并执行成功,便可以保持与master的数据一致状态。但是master并不等待slave节点的返回,master与slave是通过网络通信,由于网络抖动等因素,命令传播过程不保证slave真正接收到,那如何在传播阶段确保主从数据一致呢?
在命令传播阶段,每隔一秒,slave节点向master节点发送一次心跳信息,命令格式为REPLCONF ACK <offset>
。
命令中的offset是就是slave最新的复制偏移量,master接收后便会与自己的offset对比。如果从节点数据缺失,主节点会推送缺失的数据(这句话写的很虚,我在源码中没有找到相关逻辑,但是参考的所有文章都提到了这个点。如果哪位同学了解,烦请告知)。
按照我的理解,我把主从复制的流程分为三个阶段:准备阶段、同步阶段、命令传播阶段,先上图再依次解释。下图左侧为主从复制的整体流程,右侧为新增加一台slave节点后slave的主从复制日志输出。
准备阶段完成与master的连接,主从之间通过命令一问一答处理状态检查及身份认证工作,为接下来的数据同步打好基础。
REPL_STATE_CONNECT
。serverCron
中触发的,如果发现服务状态为REPL_STATE_CONNECT
,会调用函数connectWithMaster
创建与master的网络连接。连接成功后,修改复制状态为REPL_STATE_CONNECTING
,同时触发回调函数syncWithMaster
开始与master节点的命令交互。连接失败,则等待下次重试。client list
查看。同步是主从复制最复杂的阶段,基于上面的工作原理介绍,我们知道可能为部分同步,也可能为全量同步。所以,接下来的流程就是先确定同步方式,然后再分别按照不同的方式执行同步流程。
该阶段首先要做的就是master-slave共同协作确定同步的方式,简单来说就是两步:slave发送PSYNC命令、master判断决定同步方式进行回复。
slave发送PSYNC指令。PSYNC命令要为参数replid和offset赋值,这里需要区分两种情况:
replid=?; offset=-1
;怎么判断slave是第一次进行主从复制呢?Redis核心结构server的
cached_master
保存了master节点的信息,只有进行过主从复制才会赋值,否则为空。以下代码片段来自函数slaveTryPartialResynchronization
:
// 代码文件:replication.c 函数:slaveTryPartialResynchronization
// 是否有缓存的master节点信息
// 有:执行if
if (server.cached_master) {
// 这里的replid,复制id就是master的replid
psync_replid = server.cached_master->replid;
// 获取复制偏移量
snprintf(psync_offset,sizeof(psync_offset),"%lld", server.cached_master->reploff+1);
serverLog(LL_NOTICE,"Trying a partial resynchronization (request %s:%s).", psync_replid, psync_offset);
} else {
// 没有:走else
serverLog(LL_NOTICE,"Partial resynchronization not possible (no cached master)");
// 设置默认值:? -1
psync_replid = "?";
memcpy(psync_offset,"-1",3);
}
/* 向master发送PSYNC命令 */
reply = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PSYNC",psync_replid,psync_offset,NULL);
master判断决定同步方式。 master接收命令后由syncCommand
函数处理,然后调用masterTryPartialResynchronization
函数判断同步的方式,从代码分析可知,有以下几种情况:
接下来,从master和slave两个角度详细看下全量同步和部分同步的执行流程。
先看master。 确认需要执行全量同步后,master直接进入处理流程。这里提一下,全量同步过程中Redis是依靠slave的状态来驱动整个流程的,我先通过一张图描述下全量同步过程及slave的状态流转,再做说明:
SLAVE_STATE_WAIT_BGSAVE_START
(等待bgsave操作开始)并把它加入从机列表;SLAVE_STATE_WAIT_BGSAVE_END
,需要考虑以下三种情况:backgroundSaveDoneHandler
,进而调用updateSlavesWaitingBgsave
,它做的事情就是依次向所有复制状态为SLAVE_STATE_WAIT_BGSAVE_END
的slave传输rdb文件。rdb传输之前修改slave复制状态为SLAVE_STATE_SEND_BULK
,传输完成后修改状态为SLAVE_STATE_ONLINE
。在看slave侧。 在准备阶段,slave发送psync指令后,就等待master的回复,当收到全量同步的回复后,开始执行全量同步流程。过程如下:
相对于全量同步,部分同步要简单的多。
master判定可以使用部分同步方式,执行以下流程:
SLAVE_STATE_ONLINE
,并把slave加入从机列表;slave接收到部分同步的回复后,执行以下流程:
与工作原理部分一致,不再重复写了。
默认情况下,从机工作在只读模式下,即无法对从机执行写指令。如下图,对从机执行写指令将会返回错误。若要更改此模式,可在配置文件修改如下选项:
# 默认是yes-只读,no-可写
slave-read-only yes/no
如果slave也配置了自己的从服务器(sub-slave),那么sub-slave只会同步从master服务器同步到slave的数据,而不会同步我们直接写入slave服务器的数据。如:A--->B--->C
,如果B关闭了只读模式,C只会同步来自A的命令且与A保持一致。
Redis可以通过设置key的过期时间来限制key的生存时间,Redis处理key过期有惰性删除和定期删除两种机制,这一机制依赖Redis实例的计时能力。如果主机、从机同时启用key过期的处理机制,可能会导致一些问题。为此,Redis采取了三个技术手段来解决key过期的问题:
DEL
指令的方式向所有从机传播指令,从而保证从机移除过期的key。Redis主从复制不仅仅是解决主机、从机之间数据同步的问题,它还需要保证数据的安全性。这里的安全性主要是指主从之间数据同步达到一致的效率,以及主从结构下读写分离场景中分布式系统的可靠性。
Redis采用异步复制机制,它无法真正保证每个从机都能准确的收到传播的指令,所以主从之间必然会存在命令丢失的时间窗口。
为此,Redis引入了min-replicas
选项,该机制在redis.conf中有两个配置项:
通过info replication
可以查看从机数量(connected_slaves)、每个从机的延迟值(lat)。这一机制是通过从机与主机之间心跳来实现的,如上文所讲,从机每隔一秒向主机发送一次心跳数据,基于心跳,主节点可以:
当master关闭了持久化时,如果发生故障后自动重启时,由本地没有保存持久化的数据,重启的Redis内存数据为空,而slave会自动同步master的数据,就会导致slave的数据也会被清空。
所以,我们应该尽可能为master节点开启持久化,这样可以防止Redis故障重启时数据丢失,进而导致slave数据被清除。如果确实无法开启持久化机制,那应该配置master节点无法自动重启,确保从机可以成为新的master节点,防止数据被清除。
本文通过示例介绍主从结构搭建的方式及数据同步效果,然后详细介绍了Redis主从复制的工作原理,即“全量复制+部分复制+命令传播”,然后结合源码详细介绍主从复制的执行流程,最后介绍了一些主从复制相关的其他问题。通过这些问题,大体是介绍清楚了Redis的主从同步原理,希望能给大家带来帮助。
这篇文章差不多写了4天,中间推翻了三次,主要是想找一个比较清晰的思路来把这个问题讲述清楚。能力太差,还是需要多练。
如果觉得有用,请转发给更多需要的朋友,感谢您的阅读!码字不易,感谢支持!
推荐阅读: