2017年 09 月 23 日,又拍云 Open Talk NO.36 期活动在北京举行,豆瓣首席架构师彭宇作了主题为《微服务自动伸缩落地实践》,以下是分享的实录:

背景

今天主要分享下微服务中的Auto Scale,豆瓣2005年3月上线,是一家历史比较悠久的互联网公司,主要覆盖文化综合领域的Web、APP等各种产品,现在有豆瓣读书、豆瓣电影、豆瓣音乐等等。

image.png
豆瓣简介

在技术方面,豆瓣主要的开发语言是Python和Golang,豆瓣拥有自研私有云平台Douban App Engine(以下简称“DAE”),上面托管豆瓣网所有应用使用配置来描述应用:应用依赖MQ、Daemon,以及Cron,这样开发者使用一个配置文件就可以描述对资源的所有需求,平台拿到描述文件后可以在上面做需要的资源管配。

image.png
Douban App Engine

在DAE上会统一调度所有资源,产品开发人员不必关心具体的机器设备,比如无需关心某个业务线需要配备多少台物理机器,所有资源由平台统一调配,所有开发流程,从提交PR到测试,上线是一套统一流程。

image.png
现状

豆瓣从2012年开始做服务化,主要因为随着产品线越来越多,单体应用已经无法满足豆瓣对开发效率的需求,因此,2012年起逐步开始拆分整个网站,截止2016年底,90%以上的产品已经完成产品服务化,由于前期准备充分,这个过程中整个网站性能没有发生变化,没有因为做了服务化导致性能上的下降,全栈可用性得到提升,不会因为新入职同事做了一个不太成功的提交,而导致全栈挂掉,发布也由每日发布变为按需随时发布,服务化后,所有上线流程由各个业务线自行控制。

回归今天的主题,为什么豆瓣要做Auto Scale,做了微服务的公司内部各个系统之间会形成非常多的依赖,微服务化之后,整个系统结构很难用层次化的模型来描述,呈现出来的是一个复杂的网状依赖关系,大量应用相互之间有交错的依赖。

目标

image.png
这里有个问题,单体应用时,运维人员部署应用其实非常简单,在一台机器上将应用部署完毕,按照机器的性能部署相应的进程,当请求过来处理能力不足时,直接加服务器。

微服务化之后,运维人员手里有大量的微服务,每个服务的请求量和性能都不同,运维很难手工维持每个具体的应用需要多少计算资源,这就带来一个问题:怎样能够让应用既能跑得好又跑的省,如果每个应用都给予足够多的资源,这显然不是一个经济的方式,迫切需要一个既能提高服务器利用率,又能提高自动化的方式。

image.png
服务化之前的方式

豆瓣微服务的时间起始于2013年12月,因为过早,现在流行的一些开源技术当时还没有用到,2014年2月,开始进行简单的场内线上应用,3月,已经可以接管部署在平台上的140多个应用。公司处在服务化进程中,当时只有一小部分应用部署了微服务享受到Auto Scale带来的好处。

14年3月,Docker还没出来,豆瓣此时是一家纯Python的公司,用Python做应用打包,2014年下半年,开始发现Docker的巨大优势,于是做了整个平台的Docker改造,2015年Q2完成整体Docker化改造。

整个AutoScale的发展是从前面提到的140个应用开始,随着服务化推荐,持续优化性能,提高稳定性,目前整个平台上500个托管应用都在Auto Scale之下。

image.png
DAE Scale模型

一个Git Repo映射到DAE平台上的资源分配、调度、计费和逻辑集合,每个应用全局唯一且相互独立,通过thrift/pidl/http RPC相互调用,对于Web和Service服务,豆瓣支持Multi-instances,比如一个应用既需要有Web页面,也需要Thrift服务,可以用一份代码起两个不同服务,其中Web服务主要服务每天来自外部用户的访问,Thrift Service是内部调用,量级比外部访问大很得多,Auto Scale提供一个机制,通过配置把两个Instance分拆开,Austo Scale提供一直机制,通过配置将两个Instance分拆开,分别做Scale。

image.png
DAE Scale是多进程模型,每个请求由一个进程来处理,整个系统可以理解为Worker的总数和应用服务处理能力直接相关,Node是物理机,有些Node会部署一个应用,有的部署若干个应用,从平台资源调度来看,所有无状态节点都是均等的,差别主要在CPU和Mem方面,Node和APP之间是多对多的关系,每个APP在每个Node里面有多少Worker(工作进程),是Auto Scale的关键,会放到数据库里。

image.png
从理论上来说,所有应用都是平等的,但有些应用相比其他应用重要度更低,需要物理层面的硬隔离,Pool就是一组节点,在被分隔开的若干个Pool中,有的Pool是公司内部的小应用,只需做有限支持,计算资源非常受限,对外较重要的应用分成两级,Production Pool和Stable Pool以及一些非常重要的应用,是公司的核心产品,它们会单独用自己的资源,另外,有些部门有自己的计算资源,希望用到平台调动的机制,这种情况部门可以将自己的计算资源贡献出来单独有一个Pool。

image.png
上图是整个Auto Scale结构,最下面的节点上有Monitor,会采集借点上面各种性能指标,包括Memory、CPU、Load、当前进程的繁忙程度,采集之后发到若干地方,主要用来给监控人员查看,在Auto Scale的应用中,其实它的性能还不够好,所以一般会存一份数据Redis里面,到了Redis之后,整合的数据称作Bridge,在Bridge节点上面采集的数据进一步加工封装出一些API,包括关于APP、Node、以及Pool的API。所有应用Scale各种策略的配置都在这个应用里做。

真正做Auto Scale策略的应用是DAE Scale,Scale这一块会有若干个Cron Job,做相应的事情,比如APP的Auto Scale,Web Auto Scale、真正去Scale他们的Worker,里面会配置一系列的策略,对于Pool,豆瓣核心的几个应用,计算资源也是共享的,Movie、Group和SNS是豆瓣核心的三个业务,会独占一个Pool,但每天需要Pool中的多少节点,是一个动态调整的过程,会有专门的Pool做弹性伸缩。

在Scale里做完运算,下一步要么Scale UP到某一个APP,要么Scale Down某个APP,或把一个APP从一个节点挪到另一个节点,或者把一个APP在某个节点上放上去或拿掉,Scale产生这些行为,最后落地又回到节点上,节点上面有一些工具暴露API,让Scale回到节点上面来,这样将整个流程串起来。

外部应用更新完毕后,会更改Nginx的配置,不同节点同一应用的触点不同,配置反应不同的权重,处理能力强的节点拿到的请求更多,能力弱的节点拿到的请求少一些,这就是Scale整体的结构。

image.png
采集数据

关于采集数据,豆瓣是多进程模型,怎么去判断这个进程在工作没有呢?是去数,每个进程在运行之前会做一个标记,完成后这个标记会删掉,Monitor会到这个机器上数,这台机器上有多少个Worker,多少标记,标书就是正在忙的节点,正在忙的进程,这里有一个小技巧,标记放在内存盘上,它的系统上就看不到了。

image.png

Scale应用策略

整个APP Scale有两种策略,一是每个应用Instance现在有多少个进程在忙,需要有一个策略来计算,到底需要多少进程,豆瓣尝试过若干策略,比如Noop,有的应用不需要Scale,配几个进程就够了,不需要Scale,不需要干涉,但busy_ratio_mark_cnt是需要干涉的,rps_ratio可以根据应用的具体请求量和响应时间计算出来,当前应该还需多少个Worker。

还有一个Ondemand_rps进程,Auto Scale基于的假设是,所有请求都是一个逐步增长和减少过程,但有特例,比如APP做一次大规模推送,或上一个广告,这时有预期推送后或广告放出去后,会有大量的回放上来,此时Auto Scale的曲线型就会比较明显,因为它没办法与指导RPS陡然的上涨,豆瓣处理方式是给Scale提供一个API,将主动权交回给应用开发者,让他按照推送的规模,估计大概的规模,平均回访率,反馈到Scale后,Scale会做一个快速拉起动作,马上将需要的进程涨上去维持一段时间,等待请求过来,然后随着请求过去再慢慢Down下去。

Ondemand_rps还有一点经验,就是需要有一个大概预估的时间,Scale上去后,如果请求还没来,那么Auto Scale会很快介入,如果还是没有足够请求它会开始往下Down,比如保持半个小时,这是一个产品经验值,如果半小时请求还没来,可能这次就不会有那么强的请求了。

image.png
关于哪些节点加进程,有两个策略:一个是完全equal,比如某个应用在5个节点上需要20个进程,每个节点做4个,绝对均衡,有的应用要求每个节点处理能力是相当的,但是,更多的节点允许不同的节点上处理能力不一样,这个前提下会稍微保证节点之间不要差的太多,某些处理能力强的节点也不会拿到太多进程,比如将80%的请求都放在一个节点,如果那个节点有问题,就会都有问题,而应该是当某个节点宕掉,当期那的冗余度能够Cover宕掉的问题。

image.png

每个应用会做一个配置,这个应用最小需要多少节点,各个应用不一样,有大的需求得更高,比如至少10个节点,有的应用两个节点就可以了,这也是通过Scale确保上面做这些操作不会破坏掉最低的要求。

所以整个Scale核心诉求是,调整总Worker以及到每个节点上面去调进程,在工程上有时会将VIP和普通分开,如果某一次Scale卡住,比如早晚高峰,会有相应的报警,如果那个时候没有把处理能力加上去,很可能会出大面积的问题。

Scale一些基本的策略,在加Worker时一定要快,请求来的时候要非常快的响应,但减的时候需要慢,如果很快减掉了,可能系统会颠簸比较厉害,另外,节点之间分配杜绝过于不均,后期简单算一下各个节点之间的标准差距,看是不是差的太远。

还有一个维度,从节点的角度看,需要同一个Pool里面平衡各个节点的负载,在同一个Pool的节点之间做均衡,平衡主要看CPU、存储、可以优先把Worker较小的应用拿掉,留下一些比较大的Worker,过一段时间Scale趋于稳定后,会发现有些访问量很大,一些应用慢慢会独占一些节点,这也是IT人员希望看到的情况。

对于VIP的Pool,它们会有一个弹性池子的概念,也有一个Freed Pool,我们目前闲置的资源都是Free Pool里,当VIP Pool有资源的需求的话它会从Free Pool里面拿,Load下去之后它会还回去,在这几个大的VIPPool之间,其实是共享整个计算资源的,主要的考虑也是各个产品用户访问Pattern不太有一样,有的产品白天访问量比较高,晚上比较低,有的则反之,同一组机器其实通过机制调度能够用的更充分一些。

image.png
监控

Auto Scale是一个完整自动化的工具,豆瓣围绕它做了很多监控,网站所有的资源调配都交给Auto Scale来管理,一旦Auto Scale出现任何卡顿,或其他情况,会产生非常大地问题,所以会有大量报警去看相关的事情,另外,Scale应用是5分钟做一个,频率更高意义并不大,会导致整个系统颠簸的太厉害,还需要提供足够的手工工具,当Auto Scale本身出现状况时,需要给运维人员提供充分的脚本,如果自动系统出现状况而不能人工干预是一件非常要命的事情。

有一些典型的情况,比如网站被DDOS,整个网站访问量直接Down到底,所有Worker都闲下来,这时Auto Scale开始介入工作,会逐渐把Worker往下调,DDOS过去之后这些Worker是不够的,所以当出现类似状况时,用手工的开关把它停掉,同时也需要强劲的LOG,这样在后续查找问题可以快速定位。

image.png

推而广之

做完App Auto Scale后推而广之发现,豆瓣有大量离线MQ Consumer,之前是产品开发人员来调配,应该调到多少是比较难以把控的,MQ的Auto Scale做下来发现和外部请求类不太一样,外部请求来了之后,无论响应与否,它很快就走掉了,但MQ来了之后就不会走,它会一直在那里,MQ可能会越来越长,还有一个之前做APP没有关注到的点——排队,队列在这时非常重要,需要看看队列是不是在增长,如果增长需要快速把Consumer加上来。