Redis 面试热点之工程架构篇(二)

前言

前面用了3篇文章介绍了一些底层实现和工程架构相关的问题,鉴于Redis的热点问题还是比较多的,因此今天继续来看工程架构相关的问题,感兴趣的可以先回顾一下之前的3篇文章,如下:

【决战西二旗】|Redis面试热点之底层实现篇

【决战西二旗】|Redis面试热点之底层实现篇(续)

【决战西二旗】|Redis面试热点之工程架构篇

通过本文你将了解到以下内容:

  • Redis的数据同步机制

    持久化和数据同步的关系、Redis分布式存储的CAP选择、Redis数据同步复制和异步复制、全量复制和增量复制的原理、无盘复制等

各位亲爱的读者系好安全带 坐稳扶好 戴好耳机 时速300的 五道口大白号 发车了!

Q:谈谈你对Redis数据同步(复制)的理解吧!

持久化和数据同步的关系

理解持久化和数据同步的关系,需要从 单点故障高可用 两个角度来分析:

单点宕机故障

假如我们现在只有一台作为缓存的Redis机器,通过持久化将热点数据写到磁盘,某时刻该Redis单点机器发生 故障宕机 ,此期间缓存失效, 主存储服务 将承受所有的请求 压力倍增 ,监控程序将宕机Redis 机器拉起

重启之后,该机器可以Load磁盘RDB数据进行 快速恢复 ,恢复的时间取决于数据量的多少,一般秒级到分钟级不等,恢复完成保证之前的热点数据还在,这样存储系统的CacheMiss就会降低,有效降低了 缓存击穿 的影响。

在单点Redis中持久化机制非常有用, 只写文字容易让大家睡着 ,我 画了张图

高可用的Redis系统

作为一个 高可用 的缓存系统单点宕机是不允许的,因此就出现了 主从架构 ,对主节点的数据进行 多个备份 ,如果主节点挂点,可以立刻切换 状态最好 的从节点为主节点,对外提供写服务,并且其他从节点向新主节点同步数据,确保整个Redis缓存系统的高可用。

如图展示了一个 一主两从读写分离的Redis系统 主节点 故障迁移 的过程,整个过程并没有停止 正常工作 ,大大提高了系统的高可用:

从上面的两点分析可以得出个小结论【 划重点 】:

持久化让单点故障不再可怕,数据同步为高可用插上翅膀。

我们理解了数据同步对Redis的重要作用,接下来继续看数据同步的实现原理和过程、重难点等细节问题吧!

Redis系统中的CAP理论

对分布式存储有了解的读者一定知道 CAP理论 ,说来惭愧笔者在2018年3月份换工作的时候,去Face++旷视科技面后端开发岗位时就遇到了CAP理论,除了CAP理论问题之外其他问题都在射程内,所以最终还是拿了Offer。

但是印象很深T大毕业的面试官说前面的问题答得都不错,为啥CAP问题答得这么菜…其实我当时只知道CAP理论就像高富帅一样,不那么容易达到…细节不清楚…

各位吃瓜读者,笔者前面说这个小事情的目的是想表达: CAP理论对于理解分布式存储非常重要 ,下回你们面试被问到CAP别怪我没提醒。

在理论计算机科学中,CAP定理又被称作 布鲁尔定理Brewer’s theorem ,这个定理起源于加州大学 伯克利分校 的计算机科学家埃里克·布鲁尔在2000年的分布式计算原理研讨会 PODC 上提出的一个 猜想

在2002年 麻省理工学院 的赛斯·吉尔伯特和南希·林奇发表了 布鲁尔猜想的证明 ,使之成为一个 定理 。它指出对于一个分布式计算系统来说,不可能同时满足以下三点:

  • C Consistent 一致性 连贯性

  • A Availability 可用性

  • P Partition Tolerance 分区容忍性

来看一张 阮一峰大佬画的图

举个简单的例子,说明一下CP和AP的兼容性:

CP和AP问题

理解CP和AP的关键在于分区容忍性P,网络分区在分布式存储中再平常不过了,即使机器在一个机房,也不可能全都在一个机架或一台交换机。

这样在局域网就会出现 网络抖动 ,笔者做过1年多DPI对于网络传输中最深刻的三个名词: 丢包、乱序、重传 所以 我们看来风平浪静的网络,在服务器来说可能是风大浪急 ,一不小心就不通了,所以当网络出现断开时,这时就出现了网络分区问题。

对于Redis数据同步而言,假设从结点和主结点在两个机架上,某时刻发生网络断开,如果此时Redis读写分离,那么从结点的数据必然无法与主继续同步数据。 在这种情况下,如果 继续在从结点读取数据就造成数据不一致问题 ,如果 强制保证数据一致从结点就无法提供服务造成不可用问题 ,从而看出在 P的影响下C和A无法兼顾

其他几种情况就不深入了,从上面我们可以得出结论: 当Redis多台机器分布在不同的网络中,如果出现网络故障,那么数据一致性和服务可用性无法兼顾,Redis系统对此必须做出选择,事实上Redis选择了可用性,或者说Redis选择了另外一种最终一致性。

最终一致性

Redis选择了
最终一致性
,也就是
不保证
主从数据在
任何时刻
都是
一致
的,并且Redis主从同步默认是
异步
的,亲爱的盆友们

不要晕!不要蒙圈!

我来一下解释 同步复制和异步复制 (注意: 考虑读者的感受 我并没有写成同步同步和异步同步 哈哈 ):

一图胜千言,看红色的数字就知道 同步复制和异步复制 的区别了:

  • 异步复制 :当客户端向主结点写了hello world,主节点写成功之后就向客户端回复OK,这样主节点和客户端的交互就完成了,之后主节点向从结点同步hello world,从结点完成之后向主节点回复OK,整个过程客户端不需要等待从结点同步完成,因此整个过程是异步实现的。

  • 同步复制 :当客户端向主结点写了hello world,主节点向从结点同步hello world,从结点完成之后向主节点回复OK,之后主节点向客户端回复OK,整个过程客户端需要等待从结点同步完成,因此整个过程是同步实现的。

Redis选择异步复制可以避免客户端的等待,更符合现实要求,不过这个复制方式可以修改,根据自己需求而定吧。

从从复制 假如Redis高可用系统中有 一主四从 ,如果四个从同时向主节点进行数据同步,主节点的压力会比较大,考虑到Redis的最终一致性,因此Redis后续推出了 从从复制 ,从而将 单层复制结构演进为多层复制结构 ,笔者画了个图看下:

全量复制和增量复制

全量复制 是从结点因为故障恢复或者新添加从结点时出现的 初始化阶段 的数据复制,这种复制是将主节点的数据全部同步到从结点来完成的,所以 成本大但又不可避免

增量复制 是主从结点正常工作之后的每个时刻进行的数据复制方式, 涓涓细流同步数据 ,这种同步方式又轻又快,优点确实不少,不过如果没有全量复制打下基础增量复制也没戏,所以二者不是矛盾存在而是 相互依存 的。

全量复制过程分析

Redis的全量复制过程主要分 三个阶段

  • 快照阶段 :从结点向主结点发起SYNC全量复制命令,主节点执行bgsave将内存中全部数据生成快照并发送给从结点,从结点释放旧内存载入并解析新快照,主节点同时将此阶段所产生的新的写命令存储到缓冲区。

  • 缓冲阶段 :主节点向从节点同步存储在缓冲区的操作命令,这部分命令主节点是bgsave之后到从结点载入快照这个时间段内的新增命令,需要记录要不然就出现数据丢失。

  • 增量阶段 :缓冲区同步完成之后,主节点正常向从结点同步增量操作命令,至此主从保持基本一致的步调。

借鉴参考1的一张图表,写的很好,我就不再重复画图了:

考虑一个 多从并发全量复制问题

如果此时有多个从结点同时向主结点发起全量同步请求会怎样?

Redis主结点是个 聪明又诚实 的家伙,比如现在有3个从结点A/B/C陆续向主节点发起SYNC全量同步请求。

  • 主节点在对A进行bgsave的同时,B和C的SYNC命令到来了,那么主节点就一锅烩,把针对A的快照数据和缓冲区数据同时同步给ABC,这样提高了效率又保证了正确性。

  • 主节点对A的快照已经完成并且现在正在进行缓冲区同步,那么只能等A完成之后,再对B和C进行和A一样的操作过程,来实现新节点的全量同步,所以主节点并没有偷懒而是重复了这个过程,虽然繁琐但是保证了正确性。

再考虑一个 快照复制循环问题

主节点执行bgsave是
比较耗时且耗内存的操作 ,期间从结点也经历
装载旧数据->释放内存->装载新数据 的过程,
内存先升后降再升的动态过程

,从而知道无论主节点执行快照还是从结点装载数据都是需要

时间和资源

的。

抛开对性能的影响,试想如果主节点快照时间是1分钟,在期间有 1w条新命令 到来,这些新命令都将写到缓冲区,如果 缓冲区比较小只有8k ,那么在快照完成之后, 主节点缓冲区也只有8k命令丢失了2k命令 ,那么此时从结点进行全量同步就缺失了数据,是一次错误的全量同步。

无奈之下, 从结点会再次发起SYNC命令 ,从而 陷入循环 ,因此 缓冲区大小 的设置很重要,二话不说再来一张图:

增量复制过程分析

增量复制过程稍微简单一些,但是非常有用,试想 复杂的网络环境下,并不是每次断开都无法恢复,如果每次断开恢复后就要进行全量复制,那岂不是要把主节点搞死 ,所以增量复制算是 对复杂网络环境下数据复制过程的一个优化 ,允许一段时间的落后,最终追上就行。

增量复制是个 典型的生产者-消费者模型 ,使用定长环形数组(队列)来实现,如果buffer满了那么新数据将覆盖老数据,因此从结点在复制数据的同时向主节点反馈自己的偏移量,从而确保数据不缺失。

这个过程非常好理解 ,kakfa这种MQ也是这样的 ,所以在合理设置buffer大小的前提下,理论上从的消费能力是大于主的生产能力的,大部分只有在网络断开时间过长时会出现buffer被覆盖,从结点消费滞后的情况,此时只能进行全量复制了。

无盘复制

理解无盘复制之前先看下什么是 有盘复制 呢?

所谓盘是指磁盘,可能是机械磁盘或者SSD,但是无论哪一种相比内存都更慢,我们都知道IO操作在服务端的耗时是占大头的,因此对于全量复制这种高IO耗时的操作来说,尤其当服务并发比较大且还在进行其他操作时对Redis服务本身的影响是比较大大,之前的模式时这样的:

在Redis2.8.18版本之后,开发了 无盘复制 ,也就是 避免了生成的RDB文件落盘再加载再网络传输的过程,而是流式的遍历发送过程,主节点一边遍历内存数据,一边将数据序列化发送给从结点 ,从结点没有变化,仍然将数据依次存储到本地磁盘,完成传输之后进行内存加载,可见 无盘复制是对IO更友好

小结

时间原因只能写这么多了, 和大家一起学习不是把桶填满而是把火点燃

回顾一下:本文主要讲述了持久化和数据同步的关系、Redis分布式存储的CAP选择、Redis数据同步复制和异步复制、全量复制和增量复制的原理、无盘复制等,相信耐心的读者一定会有所收获的。

最后可以思考一个问题:

Redis的

数据同步仍然会出现数据丢失的情况

,比如主节点往缓冲区写了10k条操作命令,此时主挂掉了,从结点只消费了9k操作命令,那么切主之后从结点的数据就丢失了1k,即使旧主节点恢复也只能作为从节点向新主节点发起全量复制,那么我们该如何优化这种情况呢?

巨人的肩膀

  1. https://segmentfault.com/a/1190000012390817

  2. 钱文品-《Redis深度历险核心原理和应用实践》