精耕细作

三园区容灾

2013.7.22 微信发生了有史以来最大规模的故障,消息收发和朋友圈等服务出现长达5个小时的故障,故障期间消息量跌了一半。故障的起因是上海数据中心一个园区的主光纤被挖断,近2千台服务器不可用,引发整个上海数据中心(当时国内只有这一个数据中心)的服务瘫痪。

故障时,我们曾尝试把接入到故障园区的用户切走,但收效甚微。虽然数百个在线模块都做了容灾和冗余设计,单个服务模块看起来没有单点故障问题;但整体上看,无数个服务实例散布在数据中心各个机房的8千多台服务器内,各服务RPC调用复杂,呈网状结构,再加上缺乏系统级的规划和容灾验证,最终导致故障无法主动恢复。在此之前,我们知道单个服务出现单机故障不影响系统,但没人知道2千台服务器同时不可用时,整个系统会出现什么不可控的状况。

其实在这个故障发生之前3个月,我们已经在着手解决这个问题。当时上海数据中心内网交换机异常,导致微信出现一个出乎意料的故障,在13分钟的时间里,微信消息收发几乎完全不可用。在对故障进行分析时,我们发现一个消息系统里一个核心模块三个互备的服务实例都部署在同一机房。该机房的交换机故障导致这个服务整体不可用,进而消息跌零。这个服务模块是最早期(那个时候微信后台规模小,大部分后台服务都部署在一个数据园区里)的核心模块,服务基于3机冗余设计,年复一年可靠地运行着,以至于大家都完全忽视了这个问题。

为解决类似问题,三园区容灾应运而生,目标是将上海数据中心的服务均匀部署到3个物理上隔离的数据园区,在任意单一园区整体故障时,微信仍能提供无损服务。

同时服务

传统的数据中心级灾备方案是“两地三中心”,即同城有两个互备的数据中心,异地再建设一个灾备中心,这三个数据中心平时很可能只有一个在提供在线服务,故障时再将业务流量切换到其他数据中心。这里的主要问题是灾备数据中心无实际业务流量,在主数据中心故障时未必能正常切换到灾备中心,并且在平时大量的备份资源不提供服务,也会造成大量的资源浪费。

三园区容灾的核心是三个数据园区同时提供服务,因此即便某个园区整体故障,那另外两个园区的业务流量也只会各增加50%。反过来说,只需让每个园区的服务器资源跑在容量上限的2/3,保留1/3的容量即可提供无损的容灾能力,而传统“两地三中心”则有多得多的服务器资源被闲置。此外,在平时三个园区同时对外服务,因此我们在故障时,需要解决的问题是“怎样把业务流量切到其他数据园区?”,而不是“能不能把业务流量切到其他数据园区?”,前者显然是更容易解决的一个问题。

数据强一致

三园区容灾的关键是存储模块需要把数据均匀分布在3个数据园区,同一份数据要在不同园区有2个以上的一致的副本,这样才能保证任意单一园区出灾后,可以不中断地提供无损服务。由于后台大部分存储模块都使用KVSvr,这样解决方案也相对简单高效——将KVSvr的每1组机器都均匀部署在3个园区里。

故障时自动切换

三园区容灾的另一个难点是对故障服务的自动屏蔽和自动切换。即要让业务逻辑服务模块能准确识别出某些下游服务实例已经无法访问,然后迅速自动切到其他服务实例,避免被拖死。我们希望每个业务逻辑服务可以在不借助外部辅助信息(如建设中心节点,由中心节点下发各个业务逻辑服务的健康状态)的情况下,能自行决策迅速屏蔽掉有问题的服务实例,自动把业务流量分散切到其他服务实例上。另外,我们还建设了一套手工操作的全局屏蔽系统,可以在大型网络故障时,由人工介入屏蔽掉某个园区所有的机器,迅速将业务流量分散到其他两个数据园区。

容灾效果检验

三园区容灾是否能正常发挥作用还需要进行实际的检验,我们在上海数据中心和海外的香港数据中心完成三园区建设后,进行了数次实战演习,屏蔽单一园区上千台服务,检验容灾效果是否符合预期。特别地,为了避免随着时间的推移某个核心服务模块因为某次更新就不再支持三园区容灾了,我们还搭建了一套容灾拨测系统,每天对所有服务模块选取某个园区的服务主动屏蔽掉,自动检查服务整体失败量是否发生变化,实现对三园区容灾效果的持续检验。

性能优化

之前我们在业务迅速发展之时,优先支撑业务功能快速迭代,性能问题无暇兼顾,比较粗放的贯彻了“先扛住再优化”的海量之道。2014年开始大幅缩减运营成本,性能优化就被提上了日程。

我们基本上对大部分服务模块的设计和实现都进行了重新review,并进行了有针对性的优化,这还是可以节约出不少机器资源的。但更有效的优化措施是对基础设施的优化,具体的说是对Svrkit框架的优化。Svrkit框架被广泛应用到几乎所有服务模块,如果框架层面能把机器资源使用到极致,那肯定是事半功倍的。

结果还真的可以,我们在基础设施里加入了对协程的支持,重点是这个协程组件可以不破坏原来的业务逻辑代码结构,让我们原有代码中使用同步RPC调用的代码不做任何修改,就可以直接通过协程异步化。Svrkit框架直接集成了这个协程组件,然后美好的事情发生了,原来单实例最多提供上百并发请求处理能力的服务,在重编上线后,转眼间就能提供上千并发请求处理能力。Svrkit框架的底层实现在这一时期也做了全新的实现,服务的处理能力大幅提高。

防雪崩

我们一直以来都不太担心某个服务实例出现故障,导致这个实例完全无法提供服务的问题,这个在后台服务的容灾体系里可以被处理得很好。最担心的是雪崩:某个服务因为某些原因出现过载,导致请求处理时间被大大拉长。于是服务吞吐量下降,大量请求积压在服务的请求队列太长时间了,导致访问这个服务的上游服务出现超时。更倒霉的是上游服务还经常会重试,然后这个过载的服务仅有的一点处理能力都在做无用功(即处理完毕返回结果时,调用端都已超时放弃),终于这个过载的服务彻底雪崩了。最糟糕的情况是上游服务每个请求都耗时那么久,雪崩顺着RPC调用链一级级往上传播,最终单个服务模块的过载会引发大批服务模块的雪崩。

我们在一番勒紧裤腰带节省机器资源、消灭低负载机器后,所有机器的负载都上来了,服务过载变得经常发生了。解决这一问题的有力武器是Svrkit框架里的具有QoS保障的FastReject机制,可以快速拒绝掉超过服务自身处理能力的请求,即使在过载时,也能稳定地提供有效输出。

安全加固

近年,互联网安全事件时有发生,各种拖库层出不穷。为保护用户的隐私数据,我们建设了一套数据保护系统——全程票据系统。其核心方案是,用户登录后,后台会下发一个票据给客户端,客户端每次请求带上票据,请求在后台服务的整个处理链条中,所有对核心数据服务的访问,都会被校验票据是否合法,非法请求会被拒绝,从而保障用户隐私数据只能用户通过自己的客户端发起操作来访问。