写点什么

多活 / 多机房的几种实现方式与重点

用户头像
Justfly
关注
发布于: 2020 年 12 月 08 日



还可以在 https://mdhs.io/posts/ways-to-multi-dc/ 读到这篇文章。




当业务规模提升到一定程度之后,很多公司会考虑「多活」,也就是将自己的业务部署在多个机房,不管是同城还是异地,其中的原因包括但不限于:



  • 从高可用角度,公司已经不再能承担单机房故障导致的服务中断

  • 从业务的地域属性角度,部署需要距离用户更近,来获得更迅速的用户体验

  • 或者更直接的,单个机房已经承担不了全部的业务流量



等等。无论哪种情况,将要建设的机房都要求具有独立性,尤其是网络环境,机房之间通过专线来进行连接。典型的拓扑会如下图所示:





在这种情况下,技术人员有多种多机房实现方式可以选择。



初始的多机房实现



首先是技术人员遇到多机房部署任务时最先想到和最容易能够做到的方案,将无状态的计算应用,也就业务方开发的网络应用程序,直接部署在新机房,将新机房视为本机房的简单拓展:





这种方案,优点是接到多机房部署任务后,无需任何准备就可以把新机房利用起来并对外提供服务。但同时也会有一个致命缺陷——业务应用对于专线具有强依赖:不管是对于其他服务的API调用还是对于存储的访问都是实时通过专线传输。



而专线的稳定性恰恰是不可控的



专线问题常常表现为,延迟抖动,丢包,甚至在一段时间内完全不可用。而且,更致命的是,以上时间在发生之前通常不会得到预警。强如国内的某些独角兽企业也会遇到因为光纤被挖断而丧失部分服务能力的情况。对于刚刚进行双机房的部署的企业来说,业务总会时不时因为专线问题收到报警或者错误。长此以往,技术人员应对这些问题会十分疲惫。



💡 针对这个问题,可能会有人选择将一个机房,完全作为一个冗余或热备 —— 有一份实时备份的数据,同时也部署服务,但不让其对外提供服务,仅仅作为灾备。为了让备用主机房真正挂掉的时候能顶上去,通常还要为其配置和主机房相当的计算能力以及冗余带宽,这在平时是较大的浪费,称之为「多活」也有些牵强,在此不论。



如果机房专线的稳定性已经对你产生了足够的困扰,则需要解除业务应用对于专线的实时依赖。下面我们将探究多机房部署下,完全避免专线实时影响的可能方案。

理想的多机房实现





图上的实现方式能够实现以下效果:



  • 两个机房之间看起来像两个完全独立的系统,各自提供全量服务,实时调用被限制在机房内部,对专线无强依赖

  • 用户可能任意访问到两个机房,并可以随时在一个机房不可用时访问另一个机房

  • 两个机房的数据存储(比如MySQL,MongoDB,Redis 等)通过某种强大的逻辑能够进行实时同步,从而各自拥有一份全量且一致的数据。



这是一种理想的实现,无论是从架构上设计上还是在对于业务开发友好性上都很好,但在现实中极难落地。问题在于 CAP [1],也不能忍受两个机房数据库均写入成功再返回给客户端,否则延迟增大且可用性会受专线影响,只能通过后台异步的方式将数据源源不断从一个机房同步到另一个机房。而异步复制数据总是需要需要时间的,而且也会受到专线可用性影响。



这种数据复制上延迟带来两个问题:



  1. 用户在机房 A 修改数据后,又立刻切换到机房B来读,数据可能因为复制延迟而不能被用户读到(且专线故障时延迟相当于无限大,可能放大这种问题)

  2. 用户在机房 A 修改数据后,数据尚未同步到B机房时刻,用户立刻切换到机房 B 修改同一行数据,数据可能会发生冲突



第一种情况容易理解。第二种情况可以类比 A B 两位开发人员要修改同一份代码, A 修改后提交,如果 B 在 A 提交之后先拉取,再修改提交,不会产生任何冲突。但如果 A 修改提交后,B 未拉取(类比复制延迟),此时修改提交会产生冲突。代码冲突,要么商量解决,要么干脆强制覆盖掉 A 的提交(牺牲了代码逻辑正确性),但对于线上的实时业务数据冲突,想要挂起业务由人工来修复是不现实的,只能选择强制覆盖,数据可能出错。



没有任何办法了吗?倒也不至于。每个公司业务形态不同,可以根据自身的业务特点来在此基础上做改进,提供两种方法:



基于用户分区的多机房实现



这种方案基于用户仅会修改自己的数据,而不会修改其他用户的数据,可能是数据库每一行都会有个用户id字段,也可能是用户拥有地域或者其他属性,两地用户虽在使用同样的服务,但一般见不到彼此,数据上是隔离的。



💡 比如订餐应用或者约车服务,某城市的商户或者司机,只会为本城市的用户提供服务。





如果用户集和地域相关,可以享受到DNS可按地域配置优势,绝大多数用户可以直接通过DNS直达指定的机房。各机房网关还需要实现逻辑,将DNS出错,或在地域交界处的偶尔「迷路」走到错误机房的用户重定向回正确机房。



如果你的业务用户集没有地域属性,可能需要在各机房网关实现一套类似 redis-cluster 对 key 的分区策略,并让路由结果在客户端缓存,从而一个用户在访问过一次机房之后就能拿到正确的机房地址,涉及到联动客户端,整套实现下来更为复杂。



同时底层的数据同步中间件,应该是实时的双向复制,因为两机房各自修改的行不同,理论上不应该存在冲突,但是这一层最好依然实现一定的冲突检测和修复机制,防止上层的方案失效或者误操作,导致数据被污染。冲突的检测逻辑可以参考 Amazon Dynamo 中向量钟(vector clock)的实现 [2]。



💡 前文说「理想的多机房实现」面临切换机房延迟问题以及冲突问题。延迟问题有多大影响取决于用户在两个机房切换的频率。而对于冲突问题,在vector clock 发现冲突基础上我们如果再给其增加一个时间戳,实现 LWW (Last Write Win),从而实时解决冲突,则可以在底层直接维护数据的一致性。两个机房的时间戳无法完全一致,尽量减少误差,可以更好地保证冲突解决的正确性。 实现完善后应当具有一些使用场景。



用户集在机房迁移时要注意,应先等待该用户集产生的数据完全同步到目标机房后,再将此用户集指向目标机房,防止同步数据和用户在新机房的写入操作产生竞争。



该方案完成后,专线完全切断,即使人工不介入,也不会对用户访问产生任何影响。若机房整体宕机,有一部分用户的访问完全不受影响,另一部分用户在人工介入前完全无法访问。



基于用户分区的多机房实现的一种妥协



如果,你认为实现底层的双向同步中间件并防止冲突的逻辑过于复杂,同时可以妥协掉一些写操作的稳定性(基于一般业务读远大于写),那么你也可以让DB在一个机房只读,这样可以仅做单向同步即可。





基于微服务调度的多机房实现



这种方案基于企业已经将业务进行了足够细粒度的微服务化。微服务主要属性之一就是,服务内的数据存储是内聚的,服务间低耦合,服务之间通过RPC(实时)或者消息队列(异步)进行通信。



核心思想是,对某个微服务而言是热备结构 —— 该服务在两个机房均部署,且都仅连接各自机房内数据库,但只给一个机房输入流量。但是对于所有微服务构成的业务整体来看,却利用了两个机房的算力和带宽。



💡 类似 TiKV 等分布式存储中的 multi raft [3]





因为一个微服务或者一组微服务对外可以拥有独立的域名,同样可以利用DNS实现直达目标机房。



底层的数据可以是单向同步,可以不再考虑冲突问题,但是为了防止可能出现的上层分流逻辑失败或者操作失误,底层最好加一层开关,让该服务的备份机房的数据库不可写入。



这种方案依赖微服务,有以下几个难点:



首先,服务之间可能会有实时的RPC调用,而实时调用不应该跨越机房,机房之间应当只有异步的消息队列,以避免对专线的强依赖。所以拥有实时RPC调用的多个服务应该划分为一个「微服务组」,他们才是跨机房调度的最小单元。这个单元应该尽量小,这个比较考验公司的服务治理和架构把控能力。



另外,方案的难点是需要一个合理的联动机制。因为演练或者其他需求要在机房之间的迁移服务时,需要找到该服务下所有的存储,让其数据复制反向,而且同样在业务切换之前先保证数据已经同步完毕,同时还要正确配置各存储读写开关。各个流程要紧密配合,还要有切换时出错的回滚机制。这要求服务治理拥有完善的分布式追踪,手动维护对应关系麻烦且易出错。



该方案完成后,专线完全切断,即使人工不介入,也不会对用户访问产生任何影响。若机房整体宕机,所有用户的某个微服务在人工介入前处于不可用状态。但因为该服务在另一个机房是随时 stand by 的状态,及时切过去即可。

一些妥协



上面的方案是以完全避免专线的影响为目标所做的努力,可能实际落地成本会比较高。比如,微服务组的设计,对于已经有业务包袱的企业来说,将现有微服务划分成组,规避组间实时调用已然不易,如果业务还在高速发展中,新业务开发中要做到不破坏「微服务组」还需要很多努力和试错。



这时候,可以根据自身的业务形态,在专线故障的容忍能力上适当做一些妥协。



每个企业,总有一些核心业务在决定实时收益,其他业务是负责用户粘性,为将来带来隐形效益。这两种业务又大概符合二八定律。前者业务变动相对较少,而从收益和损失角度来讲,后者对专线故障的容忍性更强。我们可以根据这个特点,把主要经历放在维护影响实时收益的「核心业务组」的高内聚上,负责用户粘性的业务放任其跨机房实时RPC,专线故障时做好降级。

小结



文章分析了企业在做多机房或者「多活」时可能遇到的问题,并给了两种免受专线可用性影响的实现方案:基于用户分区基于微服务调度。这里只给出了大体方向,实践中还有无数的细节需要考虑。多机房部署,还要考虑自身的业务形态,选择合适的落地方案。



💡 不受专线影响要求额外付出一些努力,如果你觉得专线对你们的影响尚不值得付出这些额外努力,也不必强求架构上的纯粹性。实践以上方案的过程中找到一个让自己舒服的点即可。



无论何种方案,对于公司的技术储备和架构设计能力都是一个考验,从上层的调度,到底层的同步可能都需要自研中间件实现,底层的复制中间件又要面对公司各种数据库选型,比如 MySQL Redis Mongo 等,还有对于消息队列正确调度。除此之外,需要还要留有一定的试错时间。




[1] CAP定理

[2] Dynamo: Amazon's Highly Available Key-value Store

[3] multi-raft



发布于: 2020 年 12 月 08 日阅读数: 32
用户头像

Justfly

关注

还未添加个人签名 2018.01.20 加入

基础平台研发,架构。https://mdhs.io/about/

评论

发布
暂无评论
多活/多机房的几种实现方式与重点