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

栏目: 数据库 · 发布时间: 5年前

内容简介:前言

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

前言

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

正文

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

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

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

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

一、先简单谈谈主从分离(Master-Slave)

(注:由于目前个人工作中大多数情况应用的是Redis 3.x,以下若有特性关联,均是以此作为参照说明。)

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

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

我更想说的是,甚至任何涉及数据同步的环节,包括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 可以稍微高一些。另外,稍微注意,从节点内存尽量不要小于主节点的预算内存。

对于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),那么大概率存在频繁的阻塞,包括网络和自身脚本命令的阻塞。一般内部网络都是专线环境,并且都是独立部署,所以优先排查命令执行效率,和不必要的扫描问题(可参考上篇讨论)。但是无论如何,延迟或者说偏移过大的问题,总不可能完全规避,所以在开发里要么利用专业的监控服务,要么使用不同驱动库定时判断,这也无疑增加了编码复杂度,哪怕一些开源库已经尽力做了一些工作。

二、尝试谈谈相关的高可用(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.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Definitive Guide to HTML5 WebSocket

The Definitive Guide to HTML5 WebSocket

Vanessa Wang、Frank Salim、Peter Moskovits / Apress / 2013-3 / USD 26.30

The browser is, hands down, the most popular and ubiquitous deployment platform available to us today: virtually every computer, smartphone, tablet, and just about every other form factor imaginable c......一起来看看 《The Definitive Guide to HTML5 WebSocket》 这本书的介绍吧!

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具