[原创]分布式系统之缓存的微观应用经验谈(二) 【主从和主备高可用篇】

分布式系统之缓存的微观应用经验谈(二) 【主从和主备高可用篇】
html

 

前言node


  近几个月一直在忙些杂事,几乎年后都没怎么闲过。忙忙碌碌中就进入了2018年的秋天了,不得不感叹时间老是如白驹过隙,也不知道收获了什么和失去了什么。最近稍微休息,买了两本与技术无关的书,其一是 Yann Martel 写的《The High Mountains of Portugal》(葡萄牙的高山),发现阅读此书是须要一些耐心的,对人生暗喻很深,也有足够的留白,有兴趣的朋友能够细品下。好了,下面回归正题,尝试写写工做中缓存技术相关的一些实战经验和思考。
缓存

 

正文
服务器


  在分布式Web程序设计中,解决高并发以及内部解耦的关键技术离不开缓存和队列,而缓存角色相似计算机硬件中CPU的各级缓存。现在的业务规模稍大的互联网项目,即便在最初beta版的开发上,都会进行预留设计。可是在诸多应用场景里,也带来了某些高成本的技术问题,须要细致权衡。本系列主要围绕分布式系统中服务端缓存相关技术,也会结合朋友间的探讨说起本身的思考细节。文中如有不妥之处,恳请指正。

  为了方便独立成文,原谅在内容排版上的一点点我的强迫症。网络

 

  第二篇这里尝试聊聊缓存的主从(Master-Slave),以及相关的高可用实现(High-Availability)(具体应用依然以Redis 举例)架构

  另见:分布式系统之缓存的微观应用经验谈(一) 【基础细节篇】(https://www.cnblogs.com/bsfz/p/9568591.html 并发


  1、先简单谈谈主从分离(Master-Slave)运维

    (注:因为目前我的工做中大多数状况应用的是Redis 3.x,如下如有特性关联,均是以此做为参照说明。)异步

 

    1.1 关于主从分离的取舍观点tcp

 

      是否采用主从分离(这里特指读写分离),我的目前的观点是,它在不少场景里,并非一个很好的方案。

 

      我更想说的是,甚至任何涉及数据同步的环节,包括DB读写分离、缓存数据复制等等,若是没有特殊场景强制要求,那么尽可能规避。

 

      虽然在互联网的一些应用场景里,读远大于写,也演变了一套看似完整的读写实践方案,大致归为“主写从读”、“一写多读”、“多读多写”。但本质上,在大多数环境下,均存在必定缺陷,不管是基于服务底层的数据同步延迟,仍是开发中的逻辑切换,都带来了一些可能本末倒置的隐患和生硬。同时,在集群模式下读写分离成本实在太高,不只仅是一致性问题,必须慎重考虑和权衡。

 

      若是没明白读写分离方案基于什么本质什么条件、包含哪些细节问题,那你的设计可能就很勉强,甚至出现某些关键问题时,反而很难去分析和解决。去年跟一前辈朋友取经,他们一个业务服务兜兜转转实际测出的结果是读QPS约2000,写QPS不到500,这些的分离啼笑皆非,程序性能也没获得优化,反而增长了彻底浪费的技术成本,以及由于读写分离带来的程序本不应处理的例外问题,也是折腾。

    1.2 主从分离的一些细节

      以 Redis为例,每一个Redis实例(Instance)都是做为一个节点(node),而且默认主节点(master node),它们都可以手动转换为从节点(slave node)。每一个slave node只能隶属于一个 master node, 而每一个master node能够拥有n个slave node。任何主从同步都离不开复制的概念,在Redis中主要命令是 slaveof host port (指定一个master便可),这样master node的数据将自动异步的同步到各个slave中,这算是最基本的形态了。

      

      在进行相关软性配置时,我的推荐最好保持配置文件(config-file)的一致,这里指多个salve node。对于slave node的操做默认是只读(read-only),也建议保持这个设置。若是更改成可写权限,那么对slave node的修改是不会反向同步至master node中的,并且就算经过其余方式实现了反向同步,中间将大量存在相似传统RDBMS里的幻读问题,这里并发不大但足够繁琐,追踪到具体缘由也是得不偿失。(而对应程序开发中,每每写操做也是都直接进master node执行。)

 

      另外顺便提下,主从的硬件配置能够一致,可是我依然推荐slave node 能够稍微高一些。稍微注意,slave node的内存尽可能不要小于 master node 的预算内存。

      对于node之间的数据延迟问题,外在因素通常都是网络I/O影响为主,CPU影响为次,换句话说,每每CPU的负载都足够(详见上一篇中提到的一些关联处),而网络I/O则会比较明显。在部署时候,没有特殊场景,都是同机房内联而对外隔离,但即便这样,也须要额外注意延迟的接收程度,每次同步复制的TCP数据包,并不是是真正的实时处理,这个相似于以前提到的 AOF 和 RDB 的设计思想,分为实时复制和间隔性复制,前者更及时但带宽消耗大,然后者正好相反。在Redis中主要以 repl-disable-tcp-nodelay切换,默认使用前者,我的也较为推荐,可是在单次数据量较频较大,业务场景的时效要求不高,彻底能够设置为后者,从而节省很多性能,也连锁提高了必定稳定性。

 

      对于拓扑结构的设计,应用最多的就是单层拓扑,针对大量相似 keys全表扫描的操做,slave node会作到分担性能压力的做用。若是还想极致一些,把总体阻塞降到最低,能够将拓扑结构转换为树状,最简单的作法,将某个slave node直接转移到最底部,但会带来更多时效上的牺牲,因此须要考虑场景的接受程度了。同时,这里可能在具体架构落地的环节里,会比较分身乏术,须要开始考虑交由专业的运维来参与部署了,涉及上下节点间的通讯、带宽监控、级联之间的复制问题,以及一些更独立的高频率统计和管理。ps下,这点一直做为备用,但在截止到目前的工做生涯里尚未找到必要的机会去采用。

 

      对于复制/同步数据自己,不管是全量、仍是增量,因为异步性(我的认为不可能设计为同步)和必定的时效损耗,一定存在一个偏移值(offset)。以Redis举例,master node和slave node中,各自对本身的当前复制时的offset作记录,若是两个角色的偏移值存在较大差别(可参考查询对应repl_offset),那么大几率存在频繁的阻塞,包括网络和自身脚本命令的阻塞。通常内部网络都是专线环境,而且都是独立部署,因此优先排查命令执行效率,和没必要要的扫描问题(可参考上篇讨论:http://www.javashuo.com/article/p-muuglegt-p.html)。可是不管如何,延迟或者说偏移过大的问题,总不可能彻底规避,因此在开发里要么利用专业的监控服务,要么使用不一样驱动库定时判断,这也无疑增长了编码复杂度,哪怕一些开源库已经尽力作了一些工做。

 

  2、尝试谈谈相关的高可用(High-Availability)

 

    缓存既然做为一种通用的中间件(固然,某些场景里也多是最后一层,见第一篇),决定了在诸多场景里其交互频率(包括QPS)大多远远高于其余服务,这就须要其具有极高的稳定性、可用性。我的在前面阐述了一些关于主从分离的细节,下面尝试谈谈相关的HA方案和一些思考。

 

    2.1 关于高可用说明

      这里声明下,我认为主从分离和高可用本质上是没有任何一丝关系的,只是有些刚恰好的做用使之结合造成了一些HA方案。

 

      前面提到过,单个相对可靠的缓存服务,除了自己所在服务器自身的内存负载,设计时更须要充分考虑网络I/O、CPU的负载,以及某些场景下的磁盘I/O的代价。而这些条件所有都会拥有瓶颈,除此,你永远没法避免的问题还有服务器形成的直接宕机、服务自身的缺陷形成的某些时候的不可用(单点问题)等等。一套相对可以落地的高可用缓存方案,必然还须要拥有足够健壮的承载和相对完善的内部故障转移机制,从而达到对外提供的是整套程序化的高可用的缓存服务。

 

    2.2 实现HA的原始步骤

      以Redis为例,其自己的设计已经足够优秀和成熟,但在负载过大致使延迟太高、甚至崩溃的过程里,比较原始的方式是这样去操做:将一个关联的备用 slave node升级为 master node,能够一个 slaveof no one 基本处理。而后分析是否还作了业务层面上的主从分离,若是存在,那么还须要手工修改其余slave node 里的旧 master node指向,映射为当前 master node。 最后,当master node 从新上线时,修改自身角色并从新加入集群。

    2.3 谈谈程序化HA方案的部分实践

 

      上面提到的主干思路看起来并不复杂,但实际以人工去操做每个环节所须要解决的问题每每不止这些,好比对于node的不可用的断定、确认后的选举逻辑、程序客户端的事件通知处理、服务的同步处理细节等。在Redis中比较成熟的 HA方案,目前主要包括依赖独立 node的 Sentinel 和自身基于 Gossip 传播的 Cluster 方案。

    

      从宏观角度来谈, Sentinel 和 Cluster 对于HA的设计均有互相借鉴,但后者 Cluster 更可能是偏向提供一套可行性集群分片方案,与这里主题关联性不是很大(后续我会尝试单独写一篇,这里不延伸),围绕 Sentinel 的HA实践更直接。

      Sentinel 的本质逻辑就是对全部node做按期巡视,当发现存在共同投票认为不可达的 master node, 会对其作下线标识,同时进行必要的 master选举升级,并将事件状态返回给信号方及客户端。Sentinel 的故障转移是针对 master node的,一般是把 slave node做为master的一个热备。

 

      这里依然以Redis 3.x 为主,在 Redis Sentinel方案里,一般指 n个 Sentinel node来自动监听Redis普通node。准确的说,每一个Sentinel node 其实会监放任何一个node,包括其余Sentinel node。

 

      对于选举和审定的控制,能够调整配置 monitor的quorum 来确认严格性,好比, 在大多数场景里,设置为 (n / 2 + 1) 个,这样表明过半的票数认同,即认为指定node当前宕机。同时,当须要选举新的领导者master,这样也至少是趋势性客观判断。是否能够设置更小?固然能够,只是要注意的一个问题是,这样对失败的认定流程更短更快,可是偏差也相对越大了,须要看看场景是否适配。我的在权衡时会尽可能优先设置为前者。

 

      对于内部故障转移天然能够获得相应的事件通知,通常还能够写入到对应执行脚本,理论上会适合自动化这块,但我的目前还没有应用,这个偏向纯运维了,我的这里依然保持针对架构和开发来作一些讨论。

 

      对于监听通讯,能够适度调整 failover-timeout等相关配置,这里并无相应的计算方式,在大多数状况没太多讲究,可是也须要关注不能过分调整。我的目前采起的方式是,优先设置一个较大值,好比审定时间30秒,五个实例,那么同步转移超时时间则不低于150秒。

 

      对于选举完成后,发起新的数据复制流程,因为master node会面向多个 slave node 进行瞬间同步, 默认并发复制,而不少时候服务器环境有限,没有很足够的配置,甚至常常同一服务器上存在几个理想上本应该独立的服务, 这里则须要重点考虑下网络IO和磁盘IO的问题,根据实际状况临时调整,除此以外,在高峰时这也起到了限流做用。

 

      额外再强调一下,基于主从的HA方案,依然存在 master node同步到 slave node 的延迟问题,这个基本是任何相似热备方案均存在的问题,系统交互越是密集,或者 slave node 的不断增长,都会明显增大这个延迟,因此在权衡的时候,须要考虑业务的初衷,到底可以接受到什么程度。

 

      任何服务里的应用,都不是看起来越多越好。假如你打算手动实现一套自定义的HA方案,或者相关的热备思路,你甚至能够考虑在业务程序里,具体点能够直接是在相关的驱动库(好比JAVA的Jedis、和.Net的 StackExchange.Redis)修改,插入数据的同时,插入到另外一个备用库中。这在一些非缓存场景里,也有相似设计,并非必定不被采用的,毕竟架构设计的初衷必定是考虑总体可行性和利弊权衡。

 


结语



  本篇先写到这里,下一篇会围绕相关主题尝试扩展阐述。
  PS:因为我的能力和经验均有限,本身也在持续学习和实践,文中如有不妥之处,恳请指正。


【预留占位:分布式系统之缓存的微观应用经验谈(三)【集群场景篇】https://www.cnblogs.com/bsfz/

 

 



End.

相关文章
相关标签/搜索