我们先来回顾一下github这次事故: 2018年10月21日,github 在更换网络设备时,引发了美国东海岸网络中心和东海岸数据中心的网络链接发生了40秒的中断,最终导致多个mysql的主集群由Orchestrator 自动选举切换到了美国西海岸数据中心对应的集群,由此引发了数据不一致,直接导致了超过24小时的服务降级,最后仍然还有少量数据需人工排查修复的悲剧。

事件的整个过程参考https://blog.github.com/2018-10-30-oct21-post-incident-analysis/ 这篇文章,在这里不详细说明,我们来直接看导致事件的根本原因。

根本原因:缺失异地数据中心之间数据一致性的保障机制

github数据库做的是本地主备+跨区域数据中心的容灾方案,主数据库集群和其他数据中心的集群是异步同步的,当物理网络发生中断后,对于github这样全球性的,大量用户访问的系统,时时刻刻都有数据变更,刚刚提交的数据还没有来得急同步过去,这时按照Orchestrator的机制,必然会重新选举出新的数据库写入主节点。在没有确定数据全局一致性的情况,就这么一切,两边数据就没法一致同步了,更悲剧的是等网络40s后恢复后,在Orchestrator没有将原有的数据库主节点置为从节点之前,东海岸本地的应用还在往本地数据库集群写数据,加剧了数据的不一致,发生了脑裂现象。除了数据不一致的问题外,还有个比较严重的后果,github 除了数据库主节点负责单点写外,其他区域数据中心的数据库节点负责读,把东海岸的应用数据库切都西海岸,会有60ms的网络消耗,就这点耗时,导致很多应用出现了服务超时不可用,对于很多web应用,一个前端请求会触发后端系统几十次的请求非常正常。由此可以看出,github在处理跨数据中心的高可用方案上是有较大漏洞,没有真正考虑一个数据中心不可用,和数据中心之间的网络故障时的两个重要场景。

没有引起重视的原因分析

对于这么重要的场景,为啥github这么牛的公司没有实施了?个人猜测的几个原因(仅个人观点):

1.github 发展太快也太顺利了,之前应当是没有出过什么严重问题,精力都聚焦到能带来效益的事情了,这样的事情没有引起足够的重视,技术战略上没有特别重视。

2.异地多活的方案非常复杂,不仅是数据库的跨地域数据中心同步这么简单,需要结合公司的实际业务进行全局整体性的方案设计。

3.投入大,成本高,就拿异地机房之间的网络线路来说,至少需要保障两个物理上独立的线路,而这些线路对数据中心来说,至少需要100G 光纤专线。此外其应用系统为了适应异地多活带来也需要进行架构升级,灰度测试等等。

实际来看这是很多公司都面临的普遍问题,在业务高速发展和系统可靠性建设上如何找到平衡点的问题。对于很多业务导向的公司,系统出现过严重问题,才会重视起来。

异地多活方案如何来做?

计算机的分层思想非常有利于分析一个复杂系统,异地多活涉及到数据,应用系统,基础软件,网络,运维系统等等,非常复杂。采用分层思想能极大的简化问题,解耦各层,然后全局看。下面是一个典型分布式是数据中心的分层结构:

让我们来看每个分层的异地多活方案如何做:

DNS层:本身就是一个全球性的分布式系统,天然就是异地多活的,在这一层做自己的异地多活,更多是依赖DNS机制,系统化的动态切换域名和服务器的映射。因其DNS变更生效需要几分钟到几十分钟,对实时性要求高的容灾,不能依赖这块。

CDN层:严格来说,和DNS 一样,也是天然异地多活的,因为CDN更多的是对静态资源访问的加速,需提前将静态资源分发到离消费者更近的节点,挂掉一个节点不会影响数据的不一致,会降低访问速度,影响用户体验。

负载均衡层:通过故障转移策略,可以迅速的将挂掉的服务器流量切到同城或者异地的数据中心,非常便捷高效,但这个地方一定要确保底层的数据已经同步过去之后,才能切,否则会出现数据不一致的情况。

应用层:需要跨数据中心弹性集群部署,就单个应用本身而言,没有什么难度,难度主要集中在依赖应用上,对于很多业务系统,特别是微服务架构成为主流后,一个业务系统至少会依赖调用好几个其他应用,对于复杂的电商系统来说,一个系统依赖几十个其他系统的服务很正常。应用层的异地双活,就不是简单的单应用的跨数据中心部署,而是需要按照业务单元整体性考虑,单元化的思想是阿里异地双活的基础。这块需要重点考虑的是,业务单元内的系统之间相互调用要本地化调用,避免跨单元,但对于一些没法单元化的业务,需要有中心单元化来统一支撑。此外应用系统本身的架构也需要考虑异地多活,特别是要考虑跨地域访问带来的时延影响,数据重复消费等问题。

中间件,缓存层:需要中间件本身要支持异地多活,就拿分布式缓存redis来说,put 数据,需要支持在本地数据中心,和异地数据中心同时生效,失效也是一样。这就需要对redis 进行修改。消息中间件,以及其他中间件也是类似,对于不支持异地数据中心的版本,都需自行修改源码进行支持,对于一般的小公司来说,这个难度非常大。只有top的公司才具备这样的技术和环境去做这个事情。

数据库/存储:数据库每个厂商对异地数据中心的支持方案有差异,就mysql来说,mysql 目前的版本还不支持异地同步的方案,只支持本地同步副本,对于mysql的同步,只能是利用实时的数据同步工具drc 来做,因为不是异步同步,仍然存在几ms-几百ms的延迟。将流量从故障的数据中心切换到其他数据中心时,一定要先将故障集群的数据库设置成只读集群,其次确定数据库已经完全同步数据到对应异地数据中心的数据库。最后将异地数据库集群设置为写集群。

而整个过程会有几分钟的中断,服务不可用,遇上罕见的这种故障,几分钟的故障恢复时间是可以接受的,支付宝的异地多活也是分钟级别的。这个地方最怕的就是物理线路故障,如挖掘机挖断光纤的事情,对于大型金融级的数据中心,物理上有两条独立的光纤线路是必须的。遇上物理线路故障,肯定会存在少量数据没有同步完的情况,这时候,原本到故障dc的写流量是不能贸然转移到其他dc的,这时必然要牺牲部分服务的可用性;除非你能接受数据不一致的情况。

看完每层的情况,再来看全局,当一个DC发生故障时,如何在保证数据完整的情况下,故障转移了。发生故障不可怕,怕的是丢数据,数据错乱。所有的策略都是围绕数据的完整和正确性。全局设计也是一样,因此我认为较为合理故障处理策略应当按照下面的步骤来:

我们的原则就是将一切造成数据混乱的可能性降到最低,必然就是先关闭数据库的写,这会造成应用层写数据库等异常,但这也是我们期望的。

发生整个DC出现大面积故障的事件是比较少的,最常见的故障还是比如某个核心系统库,部分网络故障. 而这些故障会根据异地多活的方案有不同的一些恢复策略,以数据库发生异常为例,通常的恢复策略为:

1.切换本地备库为主库;从已有备份重新还原做备库.

2.切换数据库的代理到异地/同城的对应的数据库集群,一定要确保数据是完全同步过去之后才能切,如果用Orchestrator之类的故障自动转移,需要做对应的数据检查机制。

总结

对于异地多活,没有一刀切完全通用的方案,需要根据自己的业务特点,在成本,服务可用性和数据完整性,技术复杂度之间做平衡,找到最适合自己公司的方案;但是最重要的原则就是不管那种方案,确保数据的完整性和一致性是首要考虑的。

联系邮箱:public@space-explore.com

(未经同意,请勿转载)