Redis 学习笔记(五)高可用之主从模式
上一节提到了 Redis 的持久性,也就是在服务器实例宕机或故障时,拥有再恢复的能力。但是在这个服务器实例宕机恢复期间,是无法接受新的数据请求。对于整体服务而言这是无法容忍的,因此我们可以使用多个服务器实例,在一个实例宕机中断时,另外的服务器实例可以继续对外提供服务,从而不中断业务。Redis 是如何做的呢?Redis 做法是增加冗余副本,将一份数据同时保存在多个实例上。那么如何保存各个实例之间的数据一致性呢?Redis 采用主从库读写分离模式来保证数据副本的一致性。
一、主从复制介绍
在 Redis 中提供复制的服务器称为主服务器(master),被主服务器进行复制的服务器称为从服务器(slave )。但是在复制方式上有所区别,主从库之间采用的是读写分离的方式:
- 读操作:主库和从库都可接收
- 写操作:写操作先在主库中执行,然后再将操作同步给从库
为何要采用读写分离,因为如果在不同实例上执行修改操作,要保证实例之间的一致性就必须加锁、实例间的协商等操作,会带来巨额的开销。如果采用读写分离,数据的修改迁移到主库上进行,然后再同步到从库上,就可以达到不使用锁达到数据一致性的效果。
二、主从复制原理
主从复制是如何进行复制的,是一次性全部复制,还是分批一批一批的复制?而且如果复制中网络中断,数据还能保持一致性吗,其内部原理是怎样的?Redis 的复制功能主要有两个操作:
同步(sync):同步操作是将从库的数据状态更新至主库当前所处的状态,主要有全量复制和增量复制两种
命令传播(command propagate):命令传播是作用在主库的数据库状态被修改后,主从库之间的数据库状态出现不一致,让主从服务器的数据库状态重新回到一致性。
2.1 同步操作
2.1.1 全量复制
在同步操作中,我们首先来看看全量复制,顾名思义也就是主库将所有的数据传送给从库。因为主从库初始化需要传输全部数据,所以第一次同步其实就是一次全量复制。当启动多个 Redis 实例时,主从库间就可以通过 replicaof
(Redis 5.0 前使用的是 slaveof
)命令形成主从库的关系,在这期间从库就会全量复制当前主库的数据库状态。主要分为三个阶段:
主从库建立连接:主要是为全量复制做准备,在这一步,从库和主库建立起连接,告知主库进行数据同步,待主库确认回复后,主从库之间可以开始同步。主要的操作是从库给主库发送
psync
命令,主库根据这个命令参数来启动复制。而psync
命令包含了主库的runID
和 复制进度offset
两个参数:runID
: 是每个 Redis 实例启动时随机生成的 ID,它是用来唯一标记这个实例。而当主从库第一次复制时,从库不知道主库的runID
。因此将runID
设置为 “?”。offset
:第一次设置为 -1,表示第一次复制
主库在接收到 psync 命令后,会利用 FULLRESYNC 响应命令带上两个参数:主库
runID
和主库目前的复制进度offset
来返回给从库。而从库在收到响应后会记录下主库传递的这两个参数。主库将所有数据同步给从库:数据同步给从库后,从库接收到数据后,在本地完成数据加载。这个过程主要依赖于内存快照生成的 RDB 文件。
内部具体的流程是,主库先执行
bgsave
命令,执行持久化生成 RDB 文件,并将该文件发送给从库。从库在接收到 RDB 文件后,会先清空当前数据库,然后再加载 RDB 文件。在主库同步从库过程中,我们知道
bgsave
命令系统会 fork 一个子线程来创建 RDB 文件,另外的主线程可以继续处理命令请求。而在同步过程中的新增的写操作,主库会在内存中用专门的replication buffer
来记录这些写操作。主库将新接收的写操作再发送给从库:当主库完成 RDB 文件发送后,就会将
replication buffer
中的写操作发送给从库,从库再执行这些写操作。到此完成所有的操作数据同步。
结合一个实例来展示三个阶段:
现有两个实例,实例1(ip: 172.16.19.3)和实例 2(ip: 172.16.19.5)。在从实例2上执行以下命令后,实例2 就变成了实例 1 的从实例。执行以下命令后,实例2 就变成了实例1 的从库,并开始复制数据:
replicaof 172.16.19.3 6379
2.1.2 主从从模式复制
在一次全量复制中,对于主库而言需要完成两个消耗资源的操作:发送 RDB 文件和 repl buffer
文件。所以如果请求的从库数量较多时,虽然主库会 fork 子线程进行生成 RDB 文件,但是从库请求数量过多,也会导致主线程 fork 操作过多,最终也会阻塞主线程的其他正常请求。所以,为了解决这个问题,我们可以通过 “主-从-从”模式来将主库生成 RDB 和传输 RDB 的压力,以级联的方式分散到从库上。如下图:
具体操作是: 在部署主从集群的时候,手动选择一个从库(比如选择内存资源配置较高的从库)用于级联其他的从库。然后可以再选择一些从库(如三分之一的从库),在这些从库上执行下面命令,让它们和刚才所选的从库建立起主从关系:
replicaof 所选从库的IP 6379
这样,这些级联的从库不用和主库进行交互,而只需要和连接的从库进行写操作同步即可。这样就可以减轻主库的压力了。
2.1.3 增量复制
在主从库完成第一次的全量复制后,它们会形成一个网络连接,主库会通过这个连接将后续收到的命令操作再同步给从库。这个过程也称作为基于长连接的命令传播。这个长连接可以避免频繁的建立连接开销,后续我们会再提命令传播。
那么为什么需要增量复制呢,因为连接过程中存在着网络连接和阻塞,如果网络连接中断,主从库之间就无法实现命令传播。那如果再次进行全量复制,其开销就有点得不偿失。所以新设计出了增量复制,而与全量复制不相同,增量复制只会把主从库网络断联期间主库收到的命令同步给从库。其中重点就是利用repl_backlog_buffer
缓冲区,上面我们知道,在全量复制时,主库会把写操作命令写入 replication buffer
,与此同时,也会把这些操作命令写入 repl_backlog_buffer
缓冲区中(repl_backlog_buffer
是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置)。如下图:
主库在缓冲区的写位置偏移量就是 master_repl_offset
,从库的读位置偏移量是 slave_repl_offset
。正常情况下两个偏移量基本相等。
接下来我们看看网络连接断开时,主库有可能会收到新的写操作命令,一般而言,master_repl_offset
会大于 slave_repl_offset
。所以当主从库网络连接后,主库只需要将 master_repl_offset
和 slave_repl_offset
中间的命令操作同步给从库。
如上图中的 repl_backlog_buffer
示意图,主库和从库之间相差了put d e
和 put d f
两个操作,在增量复制时,主库只需要将它们同步给从库即可。
我们再来看一下增量复制的流程:
repl_backlog_buffer
是一个环形缓冲区,所以存在这样的情况:在缓冲区写满后,主库会继续写入,这样就会覆盖之前的写操作,那么这有可能就会导致主从库之间的数据不一致。此时,我们可以调整 repl_backlog_size
这个参数,实际情况下可以根据应用调整 repl_backlog_size
的大小。
2.2 命令传播
在上述初始化全量复制结束后,主从库两者的数据库状态达到一致完成了同步,后面两者则处于长连接状态。此时主库只需要一直将自己执行的写命令发送给从库,从库一直接收并执行主库发来的写命令即可保证主从库的数据一致性了。这个时候主从库会互相成为对方的客户端。
2.2.1 主库的命令传播
当完成了同步之后,主从库就会进入命令传播阶段,这时主库只要一直将自己执行的写命令发送给从库,而从库只要一 直接收并执行主库发来的写命令,就可以保证主从库保持数据操作一致性。
2.2.2 从库的心跳检测
在该阶段,从库默认会以每秒一次的频率向主库发送命令:
REPLCONF ACK <replication_offset>
replication_offset 是从库当前的复制偏移量
REPLCONF ACK 命令的作用有
检测主从库的网络连接状态:当主库超过一秒钟未收到从库发来的 REPLCONF ACK 命令,那么主库就会知道主从库之间连接出现问题。
辅助实现 min-slaves 选项:Redis的
min-slaves-to-write
和min-slaves-max-lag
两个选项可以防止主库在不安全的情况下执行写命令。举个例子,如果我们向主库提供以下设置:
min-slaves-to-write 3
min-slaves-max-lag 10
那么在从库的数量少于3个,或者三个从库的延迟(lag) 值都大于或等于10秒时,主库将拒绝执行写命令,这里的延迟值就是上面提到的INFO replication命令的lag值。
检测命令丢失: 如果因为网络故障,主库传播给从库的写命令在半路丢 失,那么当从库向主库发送REPLCONF ACK命令时,主库将发觉从库当前的复制偏移量少于自己的复制偏移量,然后主库就会根据从库提交的复制偏移量,在复制积压缓冲区(
repl_backlog
)里面找到从库缺少的数据,并将这些数据重新发送给从库。
总结就是在传播命令阶段,主库通过向从库传播命令来更新从库的状态,保持主从库一致。而从库则通过向主库发送命令来进行心跳检测,以及命令丢失检测。
三、主从复制的面试题
3.1 AOF 记录的操作命令更全,相比 RDB 丢失的数据更少,那么为什么主从库间的复制不使用 AOF ?
- RDB 文件是以压缩二进制的方式存储,文件小,所以在从库加载 RDB 文件时,速度会很快。而 AOF 需要依次重放每个写命令,过程相对 RDB 文件的方式要慢的多。
- 如果使用AOF 做全量复制,打开 AOF 功能需要选择文件刷盘的方式,选择不当会严重影响 Redis 的性能。而 RDB 持久化方式只需要定时备份和主从全量复制数据时才会触发生成一次快照。在大多数丢失数据不敏感的业务场景,其实是不需要开启 AOF 的。
3.2 如何处理读写分离中的问题
Redis 的读写分离可以实现 Redis 的读负载均衡,能够提高 Redis 服务器的并发量,但是在使用 Redis 读写分离时,也需要注意延迟不一致、数据过期问题。
3.2.1 延迟与不一致问题
对于命令传播阶段:因为命令传播阶段是异步操作,所以延迟与数据的不一致无法避免。有以下解决方式:
- 若应用对数据不一致的接受程度较低,可以优化中从节点之间的网络环境、使用集群同时扩展写负载和读负载、监控主从节点延迟(offset)判断,若从节点延迟过大,则通知应用不再通过该从节点读取数据
对于非命令传播的其他阶段,可以对 slave-serve-stale-date
设置为 no 。则从节点只能响应 info、slaveof 等少数命令,可以保证对数据的一致性。
3.2.2 数据过期问题
数据过期问题已经在Redis 的键管理 中提到过,在单机 Redis 中存在惰性删除和定期删除两种删除策略。而在主从复制场景下,从库不会主动删除数据,主要通过主库控制从库中过期数据的删除。而主库的删除策略都不能保证主库及时对过期数据执行删除操作,所以当客户端通过 Redis 从库读取数据时很容易读取到已经过期的数据。
3.2.3 故障切换问题
在没有使用哨兵的读写分离场景下,建议写监控程序进行切换读写分别连接的 Redis 节点。针对于手动进行切换的方式更复杂但是不容易出错。
总结
在使用读写分离前,可以考虑其他方法增加Redis的读负载能力:如尽量优化主节点(减少慢查询、减少持久化等其他情况带来的阻塞等)提高负载能力;使用Redis集群同时提高读负载能力和写负载能力等。如果使用读写分离,可以使用哨兵,使主从节点的故障切换尽可能自动化,并减少对应用程序的侵入。
参考资料
《Redis 设计与实现》
《Redis 开发与运维》
https://pdai.tech/md/db/nosql-redis/db-redis-x-copy.html
https://time.geekbang.org/column/article/272852
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59#/detail/pc?id=1782
Redis 学习笔记(五)高可用之主从模式的更多相关文章
- 大数据学习笔记——Hadoop高可用完全分布式模式完整部署教程(包含zookeeper)
高可用模式下的Hadoop集群搭建 本篇博客将会在之前写过的Linux的完整部署的基础上进行,暂时不会涉及到伪分布式或者完全分布式模式搭建,由于HA模式涉及到的配置文件较多,维护起来也较为复杂,相信学 ...
- spring cloud(学习笔记)高可用注册中心(Eureka)的实现(二)
绪论 前几天我用一种方式实现了spring cloud的高可用,达到两个注册中心,详情见spring cloud(学习笔记)高可用注册中心(Eureka)的实现(一),今天我意外发现,注册中心可以无限 ...
- Nginx知多少系列之(十四)Linux下.NET Core项目Nginx+Keepalived高可用(主从模式)
目录 1.前言 2.安装 3.配置文件详解 4.工作原理 5.Linux下托管.NET Core项目 6.Linux下.NET Core项目负载均衡 7.负载均衡策略 8.加权轮询(round rob ...
- 老司机带你玩转面试(3):Redis 高可用之主从模式
前文回顾 建议前面文章没看过的同学先看下前面的文章: 「老司机带你玩转面试(1):缓存中间件 Redis 基础知识以及数据持久化」 「老司机带你玩转面试(2):Redis 过期策略以及缓存雪崩.击穿. ...
- Redis系列3:高可用之主从架构
Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 1 主从复制介绍 上一篇<Redis系列2:数据持久化提高可用性>中,我们介绍了Redis中的数据 ...
- spring cloud(学习笔记)高可用注册中心(Eureka)的实现(一)
最近在学习的时候,发现微服务架构中,假如只有一个注册中心,那这个注册中心挂了可怎么办,这样的系统,既不安全,稳定性也不好,网上和书上找了一会,发现这个spring cloud早就想到了,并帮我们解决了 ...
- 大数据学习笔记——Hbase高可用+完全分布式完整部署教程
Hbase高可用+完全分布式完整部署教程 本篇博客承接上一篇sqoop的部署教程,将会详细介绍完全分布式并且是高可用模式下的Hbase的部署流程,废话不多说,我们直接开始! 1. 安装准备 部署Hba ...
- Redis学习笔记--五种数据类型的使用场景
String 1.String 常用命令: 除了get.set.incr.decr mget等操作外,Redis还提供了下面一些操作: 获取字符串长度 往字符串append内容 设置和获取字符串的某一 ...
- StackExchange.Redis学习笔记(五) 发布和订阅
Redis命令中的Pub/Sub Redis在 2.0之后的版本中 实现了 事件推送的 发布订阅命令 以下是Redis关于发布和订阅提供的相关命令 SUBSCRIBE channel [channe ...
随机推荐
- html 基础 vscode的常用快捷键
1.ctrl+/ //注释代码 2.文件内容查找替换:ctrl+f ctrl+h ,替换一个ctrl+shift+1,替换所有ctrl+alt+enter 3.移动当前行,向上alt+up(方向键↑) ...
- Python多线程、线程池及实际运用
我们在写python爬虫的过程中,对于大量数据的抓取总是希望能获得更高的速度和效率,但由于网络请求的延迟.IO的限制,单线程的运行总是不能让人满意.因此有了多线程.异步协程等技术. 下面介绍一下pyt ...
- python+selenium 定位元素的主要方法
selenium对web各元素的操作首先就要先定位元素,定位元素的方法主要有以下几种: 通过id定位元素:find_element_by_id("id_vaule") 通过name ...
- 安装JavaJDK没有jre环境的解决办法 错误: C:\Program Files\Java\jdk-11.0.7\jre
安装JDK11 发先没有jre解决办法 在安装目录下执行 bin\jlink.exe --module-path jmods --add-modules java.desktop --output j ...
- 【Java】二分法查找
二分法查找 前提:所要查找的数组必须有序 public class Dichotomy { public static void main(String[] args) { int[] array = ...
- 【刷题-LeetCode】211. Add and Search Word - Data structure design
Add and Search Word - Data structure design Design a data structure that supports the following two ...
- manjaro20夜灯夜间模式开关
- 使用 Swoole 加速你的 CMS 系统
项目介绍 MyCms是一款基于Laravel开发的开源免费的自媒体博客CMS系统,适用于个人网站及企业网站开发使用,助力个人开发者知识技术变现 Swoole介绍 Swoole: PHP的异步.并行.高 ...
- IoC容器-Bean管理XML方式(注入内部bean和级联赋值)
注入属性-内部bean和级联赋值 (1)一对多关系:部分和员工 一个部门有多个员工,一个员工属于一个部门 部门是一,员工是多 (2)在实体类之间表示一对多关系 (3)在spring配置文件中进行配置 ...
- java-异常概述及体系
1 package p1.exception; 2 3 4 /* 5 * 异常:是在运行时期发生的不正常情况. 6 * 7 * 8 * 在java中用类的形式对不正常情况进行了描述和封装对象. 9 * ...