微信号:infoqchina

介绍:有内容的技术社区媒体

解密云计算:持续的特性交付与海量数据迁移

2016-07-18 08:00 俞圆圆
千里之任:后台管理程序的分拆

UCloud的SDN网络服务的整个后台逻辑事实上是一个由20多个服务组成的大型的分布式系统。这些服务负责了SDN业务逻辑的方方面面,其中包括(只列举了主要的):

我们在可用区项目进行的过程中,遇到的第一个大型的全局性问题就是我们发现由于之前广泛遵循的模式(design pattern),所有的manager的前端(Frontend)和后端(Backend)的逻辑都是在一个模块里实现的(当然部署的时候也是这一个模块同时覆盖了Frontend和Backend的功能),如下图:

这个架构的主要问题在于服务的前后端功能是耦合的。在服务程序逻辑相对简单,升级变更还相对轻量级的情况下,这里的矛盾还不是特别突出,我们通过严密的监控、主动的运营(扩容或重启服务等)、及快速的回滚(升级验证失败的时候)等手段基本还是能控制整个系统的运行状态。

但其实之前我们已经逐渐发现这个不同关键路径程序逻辑间的耦合带来的问题了:一个十分典型的事例就是曾经发生过的一个现网事故——当时有用户反馈说从某个时间开始,控制台上的一些网络服务的页面经常有间歇性超时从而无法显示数据的情况。

如上图所示,我们的官网控制台是通过API Gateway来调用Manager上的接口的,因此我们的研发工程师自然而然地去排查了所有Frontend的运行日志,却发现在出现CPU飙高的时间点,Frontend的日志里没有显示任何异常的行为。然后我们逐个排查了整条控制台到Manager调用路径上所有服务(API Gateway,Access层,包括控制台本身),都没有发现任何的问题。

正当我们一筹莫展的时候,一个偶然的机会,另一位负责Manager中Backend功能的同学提到,由于Backend每秒收到的请求(query per second)较高,所产生的日志也通常比较大,容易占用过多的磁盘空间。因此他提交了一个优化的逻辑,会随机地将一些旧的日志打包并送到远端的一个存储空间里保存起来。

而进一步分析Backend的运行日志后,我们发现所有控制台发生超时情况的时间点都是和Backend上这个日志打包逻辑的运行时间是吻合的。这个变更对Backend的影响姑且不论,这里最大的问题在于:一个看似旁路的后端逻辑却直接影响了直接有损用户体验的前端服务,这是十分不应该的。

笔者之前在Amazon工作的时候,曾经听过一次内训的讲座,是当时Amazon的Global Payments部门的高级研发经理Thomas Vaughan所做的”Greatest Disaster in Amazon’s History”的演讲(这个是Amazon内训讲座中排名前三的一个演讲,可惜由于是内部资料,非Amazon的员工无法听讲)。Thomas在讲座中总结了Amazon历史上最有教育意义的6次重大现网事故并从中总结了一系列大型分布式系统开发的原则和规范,其中第二条就是:

Partition your critical use cases.

也就是说:要注意解耦关键路径上程序逻辑。很难想象这样一个简单的原则却一直在不断地被违反(我们可以指出很多的原因,客观的,主观的),但事实就是如此。

在可用区项目中,我们面临着一个相关的但更大的挑战,即:由于整个业务逻辑的复杂性,将一个用户从非可用区切换至可用区,整个操作流程需要经过许多个业务组的配合联动,若强行要求前后端的业务同时操作不发生同步上的问题,那是代价巨大且不现实的,因此我们最终的灰度方案是后端控制面和管理面的逻辑先进行切换,然后前端再将用户切换至新的可用区界面上(这样前后端的业务组不必一定要同时操作)。但如此我们必定要对Manager中耦合在一起的Frontend和Backend的逻辑进行拆分才行:

我们对大部分的后台Manager做了这样的重构。这样做也为我们今后打算进行的持续优化做了必要的铺垫。比如,之前整个Manager是用C++编写的,但拆分后,我们就可以为Frontend和Backend各自选择更适合它们特性的编程语言和框架 - 当前我们正在将整个Frontend用Go语言进行重构,对于编写一个基于REST风格的HTTP web service来说,用Go语言做开发,所能获得的工作效率上的提升是十分显著的(Go对RESTful风格的支持,对异步请求的处理等等,都要明显优于C++)。

分村之间:控制面程序的灰度

做完了Manager的Frontend和Backend的拆分后,我们分别对Frontend和Backend的代码做了大量的修改以支持可用区相关的特性。其中,Frontend部分主要负责的是和前端控制台以及API交互的逻辑,也就是所谓管理面management plane的逻辑,这部分逻辑一般是用户直接可见或可操作的。Frontend模块的灰度能力是由上图中的API Gateway来提供,可以支持按用户/按API/按控制台版本等各个维度的转发调度。

另一方面,Backend模块主要负责的是与controller交互下发规则的逻辑,也就是所谓控制面control plane的逻辑(或者说Backend模块 + controller共同组成了control plane,直接决定了整个底层SDN网络在数据转发面的行为逻辑)。此时对于Backend模块,我们还是缺乏一个灵活的灰度能力。在此之前,Manager的发布流程是这样灰度的:

也就是说,对于后端控制面,我们是按照宿主机的维度来灰度的:通过修改一台宿主机上controller的指向来决定它所运行的控制面逻辑。这个方式也许在之前的场景下都还可以接受,但在面对可用区这样深入彻底的全局特性变更的时候,一台宿主机粒度的灰度能力还是不够精细。

这是因为对于基于虚拟化的云计算平台而言,一台宿主机上一般都是多租户(multi-tenant)的(除非是像主机私有专区之类的特殊主机产品),我们要求的灰度能力是可以按用户的维度,一个用户一个用户地将他们切换到可用区的服务上。 

正如David Wheeler所言:

All problems in computer science can be solved by another level of indirection.

我们也为controller和各个Manager的Backend之间添加了一层转发代理(这里以Route Manager为例):

在Controller Proxy里我们实现了如下能力:

  • SDN层面的ID到用户账户ID之间的映射(因为数据转发面上的overlay协议是不关心用户的账户信息的,因此也不可见);

  • 高并发的处理能力(因为数据转发面和控制面之间的交互通信是海量的);

  • 快速水平扩容的能力(其实就是通过ZooKeeper来添加节点)。

如此,我们对整个后台服务灰度能力的改造就基本完成了。最后,我们还是想提一句,对于模块间耦合的重构一定要持续主动地进行。这次在可用区项目的过程中对如此大量的服务程序做一次性的重构(可以说是“被迫的”),代价是很大的,整个研发团队顶着巨大的压力加班加点;从项目管理的角度来看,这不是一个很理想的状态。而且在时间期限的压力下,很可能还会做出妥协而非最合理的架构决定,要知道,对于David Wheeler上面那句著名的引语,Kevlin Henney还给出了一句著名的推论:

All problems in computer science can be solved by another level of indirection, except for the problem of too many layers of indirection.

在下一节中,我们就来讨论一下,既然我们做了如此之多的变更和重构,我们又是如何保证这些新的逻辑既能实现新的功能,又不会破坏向前兼容性的?靠巨细靡遗的测试来保障吗?

移山之术:管理面程序的灰度和亿万用户数据迁移

UCloud的整个后台服务程序在升级到可用区逻辑的过程中经历了大量的改造,尤其是在SDN网络服务的部分,大量的overlay互联互通(内网虚拟IP之间的,外网EIP和内网UHost之间的)的逻辑都发生了改变。

更为重要的是,为了支持可用区的逻辑,后台业务信息数据库表的Schema都要经过改造,我们面临的挑战是将海量的用户数据平行迁移到新的Schema下与新的后台程序的读写逻辑兼容并在这期间不中断客户的现网业务。

首先,我们清醒地认识到:即使我们设计再复杂的测试用例,也无法真正覆盖到生产环境中错综复杂的用户场景;并且,如果我们单纯地依赖自身的测试,整个项目的实施时间也会变得无法忍受之长。在飞速奔驰的生产环境的跑车上换引擎的问题我们已经在前面讨论过了,但在换之前,如何保证这个引擎和车的其他部分是兼容的而不会一换上就将整辆车crash掉,这个是我们要解决的问题。

我们必须在行进中的跑车上加一组轮胎让新的引擎去驱动,然后看看那组轮胎是不是运行正常,新的引擎和轮胎会接受同样的驾驶指令——方向盘、油门、排挡等等——并做出它们的响应,但不会直接影响原车的行驶,我们只需观察它们响应驾驶指令时的行为是否与平行的原车的行为是一致的,如果有问题,我们可以做停机修正然后再启动。

这个思路的灵感是来自于一篇Twitch.tv工程师分享的技术文章《How we migrated over half a billion records without downtime》(注:Twitch.tv是北美第一大的游戏直播门户,2014年Amazon耗资10亿美元收购了它),我们首先来看一下总体的迁移思路:

  1. 对当前的数据库做一次全量的dump;

  2. 开启“双读双写”模式将全量的用户读写操作导到新的可用区程序逻辑上执行一遍;

  3. 将原数据库的全量dump导入新的数据库内;

  4. 将第1步和第2步之间“遗漏”的所有操作重新写入(replay)新的数据库中;

  5. 开始验证新的逻辑和新数据库里的信息适合和原数据库保持一致;

  6. 对于任何产生不一致的情况排查并修复根因;

  7. 当数据一致性达到100%并保持一定时间的时候,确信新的pipeline可以投入使用了;

  8. 按用户维度将流量切换到新的pipeline上。

下图完整地呈现了我们整体思路的架构:

当非可用区的API接到一个请求时,它会首先执行该请求,然后将一个message添加到我们后台的消息队列(AMQP协议)中去,这个message会包含下列信息:

 
           
 
           
 
           
 
           
 
           
  1. Message: [TimeStamp] [API Operation] [Input Parameter] [Result of the API Operation]

其中“时间戳”的信息是必要的,因为我们在新的逻辑中执行这些操作的时候必须保证其时序是不变的。另外对于“读”操作,我们会在message中带入原操作的结果,这样在新的逻辑上执行了同一操作后,我们可以实时地在线校验两个操作的返回结果是否一致。

如果不一致的话,DCVE会立刻抛出一个异常告警,然后研发团队就会介入排查原因了。对于“写”操作,由于一般不会直接返回操作后的新数据内容(有些API可能会),所以我们更多的是依赖离线对账,但原理也是一样的。

图中的DCVE是Data Consistency Validation Engine的缩写,它主要的任务就是从消息队列中读取包含API操作信息的消息,然后通过一张API映射表将同样的操作在新的可用区逻辑上执行一遍并比对执行的结果。新的操作在执行后,我们通过在线校验或者离线对账的手段来验证两边的数据是否在语义上保持一致的,如果不是就会抛出异常告警。

这样的机制使我们能实时利用现网真实的用户操作来验证我们新编写的程序代码,不但省去了我们自己穷举测试用例的时间,也利用了“众包”的手段更好地复现了用户真实的操作行为,帮助我们更快地定位到那些真正会对用户使用造成影响的程序bug:

这样截图中显示了我们对其中一个Manager上两边的读写操作所做的校验结果。绿色的是不一致的结果,红色的是一致的结果。可以看到,前期还是有一定量不一致的结果的,一般都会对应一个或数个程序逻辑里的bug。在经过一段时间的修复后,我们基本能达到一个稳定的状态了。我们发现的bug里比较有代表性的包括:

  • 异步操作执行顺序不一致导致数据不一致;

  • 批量返回数据的时候由于数据是不排序的导致不一致;

  • 操作失败后的重试逻辑在新的服务里没有正确实现导致的不一致。

下图是我们对每一个用户灰度过程中会遵循的一个标准化流程:

简单解释一下每一个阶段我们后台逻辑工作的模式:

大家可能注意到,在Stage 3中,我们还是将切换后的用户在新可用区下所做的操作同样“双写”回了老的非可用区的数据库里(通过调用非可用区对应的API)。这样做的原因在于如果我们一旦发现可用区的逻辑有重大缺陷的时候,老的数据库中的信息还是和新的数据库保持了一致的,如此我们如果需要做紧急回退的话,也能够有条件完成。

结语

在本篇技术分享中,我们详细介绍了我们是如何验证并灰度发布可用区这个全局性的产品的。实现一个生产环境下的大型分布式系统,如果面对的问题数量级很小,通常很多矛盾都不会暴露出来。

如果所有的新功能都能重起炉灶,也只是在规模达到一定量级前一个美好的理想状态。真正的困难往往就是在运营海量数据和保证现网服务不回归这两个前提下才会集中爆发出来,而在这两个前提条件下稳定地迭代新的特性和功能,就犹如是给高速飞驰中的跑车更换引擎,是对一个系统和它背后的研发运营团队的真正挑战。

仅仅有用户至上的情怀恐怕还是不够的,要能拿出切实可行的技术方案和运营手段来。而这里的方法论、支撑系统、团队协同都需要经过大量的实战考验才能形成。

UCloud的团队在运营一个超大型的IaaS公有云平台上已经探索了4年时间,我们还是在不断地发现可以改进的地方,也在不断地总结和推广经验和方法论,同时希望在这方面有实践的朋友们一起来讨论和切磋。感谢大家对这个“可用去特性技术内幕全面解析”系列的关注,期待听到您的批评和指正。

老司机简介

Y3(俞圆圆)UCloud基础云计算研发中心总监,负责超大规模的虚拟网络及下一代NFV产品的架构设计和研发。在大规模、企业级分布式系统、面向服务架构、TCP/IP协议栈、数据中心网络、云计算平台的研发方面积累了大量的实战经验。曾经分别供职于Microsoft Windows Azure和Amazon AWS EC2,历任研发工程师,高级研发主管,首席软件开发经理,组建和带领过实战能力极强的研发团队。

本文首发于InfoQ垂直号:细说云计算更多关于「云计算太祖长拳」系列之“可用区技术”文章,请关注「细说云计算」(ID:CloudNote)并回复“太祖长拳”。

 
InfoQ 更多文章 覃超:Facebook iOS App技术演化十年之路 专访蘑菇街七公:25倍增长远非极限,优化需要偏执狂 我是怎么从Instagram、谷歌和微软那里偷钱的 支撑滴滴高速发展的引擎:滴滴的组件化实践与优化 技术不应成为业务的工具,一个资深架构师的思辨
猜您喜欢 我的祝福还差最后一个你。 用实例理解Storm的Stream概念(一) Android手机游戏测试要点 这很谷歌!让现在的黑科技成为未来的稀松平常 工作前 VS 工作后的区别,躺着也中枪的请举手