以前总结过redis的持久化机制:深度剖析Redis持久化机制,持久化
机制主要解决redis数据单机备份问题;redis的高可用须要考虑数据的多机备份,多机备份经过主从复制
来实现,这是redis高可用的基石。本文将详细介绍redis主从复制的实现原理,在使用过程当中应该注意的问题和相关配置。redis
CAP理论是分布式领域的牛顿定律,全部的分布式存储中间件都要使用它做为理论基石。以下图所示:bash
这个原理很简单,首先明确几个概念:服务器
分布式系统的节点每每分布在不一样的机器上,它们之间由网络进行隔离,当网络断开时就会产生网络分区
。网络分区不可避免,可是当网络分区发生时,对分布式系统中一个节点的修改操做没法同步给其它节点,数据一致性
也就没法知足;想要知足一致性
,除非牺牲可用性
,也就是暂停分布式节点服务,等到网络恢复,数据一致后,再对外提供服务。分布式系统中网络分区
不可避免,一致性
和可用性
水火不容。这就是cap理论:网络分区发生时,一致性和可用性两难全。网络
互联网圈常常谈“三高”架构:高并发、高性能、高可用。对于redis来讲,高并发、高性能能够保证,高可用须要架构的设计,单机redis因为存在系统崩溃、硬盘故障的风险,还有内存的限制,因此企业通常都会搭建主从,对系统有更高要求会搭建集群。架构
为了保持数据一致性,主节点(master)只写,从节点(slave)只读,数据由主节点复制给从节点,这个复制的过程就是主从复制。并发
redis的主从复制是异步的,分布式的redis系统并不知足一致性要求,可是在网络断开的状况下,主节点依然能够对外提供服务,知足可用性。redis保证最终一致性,从节点会努力追赶主节点,最终从节点的状态会和从节点保持一致。网络断开的状况下,主从节点数据会出现大量的不一致,但一旦网络恢复,从节点会继续追赶主节点,最终达到和主节点状态一致。负载均衡
为了减轻redis主节点的同步负担,redis 的后续版本还增长了从从同步,与此同时,数据一致性会变差。异步
主从复制在服务中起到了什么效果呢?socket
有三种配置实现redis的主从:分布式
slaveof <masterip> <masterport>
复制代码
redis-server -slaveof <masterip> <masterport>
复制代码
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>
复制代码
redis主从复制实现过程有三个阶段:
创建链接、数据同步、命令传播。创建链接阶段主从节点创建通讯的桥梁,彼此之间同步一些基础信息;数据同步阶段实现从节点全量同步主节点的数据;从节点同步完主节点数据以后,就进入了命令传播阶段,主节点接收写请求,数据不断发生变化,经过命令传播阶段主节点将数据源源不断的同步给从节点。下边咱们详细介绍主从复制这三个阶段的工做细节和注意事项。
创建slave到master的链接,使master能识别slave, 并保存slave的端口号;与此同时,slave也保存master的地址和端口号信息。
slaveof ip port
命令给master,master响应slave经过以上过程主从之间的链接就创建了。
数据同步阶段实现的功能是从节点从主节点同步全量的数据。这个过程又分为几个小阶段,最主要的就是数据的全量复制
和部分复制
, 对应的流程就是主节点发送rdb文件同步数据和发送缓冲区写命令(aof)同步数据给从节点。下图是实现细节:
首先slave节点先发起命令psync ? -1
,向master节点要全量数据。
master节点接收到指令之后,执行bgsave,将当前内存数据快照保存为rdb文件,这个过程为了避免影响主节点继续对外提供服务,采用了Copy On Write
技术。与此同时,master节点也会将bgsave保存快照期间接收到的写更新命令添加到复制挤压缓冲区
当中。master节点rdb文件生成完毕之后,会经过第一阶段创建的socket链接将它发送给slave节点,还会发送+FULLRESYNC runid offset
给slave节点,告诉slave节点本身的runid
和offset
。
什么是
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节点的runid
和offset
记录下来。
有了master节点的runid
和offset
,在加载完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数据。
到这里,主从复制的第二阶段:数据同步阶段工做就完成了。
命令传播阶段相似于数据同步阶段的部分复制
,当master节点数据被修改之后,就和slave节点的数据不一致了,这个时候master节点就会根据slave上报的offset开始传播数据(一主多从的架构中,master节点要记录每个slave的offset)。slave接收到数据之后,执行bgrewriteaof重放数据。在这个工做过程当中,若是由于网路问题致使offset溢出或者换主的状况,主从之间仍是会进行数据的全量同步的。
进入命令传播阶段之后,master节点与slave节点须要进行信息传递,使用心跳机制进行维护,实现双方保持在线。
master节点心跳使用指令PING
,由配置repl-ping-slave-period
决定,默认10秒,做用是判断slave是否在线,能够经过info replication
获取slave最后一次链接到如今的时间间隔,lag的值维护在0和1视为正常。
slave节点的心跳任务使用指令REPLCONF ACK {offset}
,周期是1秒,slave的心跳任务有两个做用:
在心跳阶段应该注意:当slave节点多数掉线,或者延迟太高时,master节点为了保证数据的稳定性,将拒绝全部信息的同步。有以下配置:
min-slaves-to-write 2
min-slaves-max-lag 8
复制代码
上述配置含义是:当slave数量小于2个,或者全部的slave的延迟都大于等于8秒时,强制关闭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-id
和repl-offset
加载到内存中。经过info 指令能够查看:
master_repl_id = repl
master_repl_offset = repl-offset
复制代码
做用是:master节点重启以后会保存原来的runid,重启后恢复该值,会让全部的slave节点认为仍是以前的master节点。
当复制积压缓冲区过小的时候,当master节点写并发很大,master节点和slave节点网络有抖动的时候,就会致使数据同步不及时,形成offset溢出,进而致使全量复制
。这个时候,咱们能够考虑修改复制积压缓冲区的大小,由配置repl-backlog-size
控制。设置多大比较合适呢,这要根据master的并发量和网络状况作具体的评估。
前边内容咱们提到slave节点每秒都会发送REPLCONF ACK指令到master节点,master节点调用复制函数relicationCron()同步数据给slave节点时,若是slave节点执行了keys *、hgetall等阻塞命令的时候,就会在很长一段时候得不到响应。这就会致使master的各类资源(输出缓冲区、带宽、链接)等被占用。master节点的CPU就会变高,slave频繁的断开链接。
解决方案是master节点经过配置:repl-timeout
设置合理的超时时间(默认60s),超过改值,master节点将释放slave节点。
master节点默认10s向slave节点发送一次ping指令,由于master节点不只要处理大量的写任务,还可能维护着多个master,因此ping设置的不太及时。可是当ping指令在网络中存在丢包时,master节点若是设置的超时时间过短,就会致使master节点与slave节点断开链接。
解决方案有:提升master节点ping的频度,超时时间repl-time设置为ping指令时间的5~10倍。
当主从同步中网络数据发送有延迟的时候,就会形成多个slave获取到的数据不一样步,解决方案是优化master节点和slave节点的网络环境,一般是放置在一个机房部署。另外要监控master和slave节点的延迟,若是延迟过大,能够暂时屏蔽对slave节点的访问。经过下面指令设置:
slave-serve-stale-data yes | no
复制代码
开启后,slave节点仅仅能响应info、slaveof等少数命令,除非对数据一致性要求很高,不然不要轻易这样使用。
本文主要总结了redis实现主从复制的实现细节和注意事项。redis的主从复制是实现高可用的重要基石,后边的文章将总结哨兵和集群的搭建。