2017 年 5 月 13 日,又拍云旗下技术主题分享沙龙—— Open Talk 重回杭州,举办了 Open Talk 美丽联合集团专场,这是 Open Talk 的第 31 期。来自美丽联合集团的无相作了题为《蘑菇街的稳定性实践》的分享,以下是演讲实录:
促销情况下的稳定性保障流程
蘑菇街是一个电商平台,每年会做四次大促,3.21、6.18、双11、双12。大促保障涉及到流量评估、依赖梳理、单链路压测、全链路压测等。蘑菇街大促的基本流程,基本是按照系统峰值评估、依赖关系梳理、单链路压测、系统扩容、全链路压测等几个环节展开的。
在保障大促稳定性工作的时候,稳定性团队会有一个KPI:这次大促不能出某个级别以上的故障,如果出了这个级别以上的故障这个KPI就挂掉了。第一次接到稳定性工作,我也是很迷茫——很多事情是其他团队小伙伴做的事情,万一他们的疏忽出了一个故障,这个也要我一起背吗?后来逐渐摸索出一套应对大促带来大流量,行之有效的大促稳定性保障方案。
评估系统峰值
机器和成本总是有限的,因此在每次大促之前必须要估出一个合理的峰值,根据峰值合理地扩容。每次大促开始前,必须找到运营方沟通大促的业务目标:大促GMV,包括预估的PV、UV、客单价、转化率。根据运营给出的数据,推算出所有系统的峰值,其中重要是下单系统的峰值。
倒金字塔梳理系统峰值
下单峰值的评估,有点像我们做应用题一样。已知运营给出大促的目标,包括PV、UV、GMV,要求推算出下单交易系统峰值。这里分享一个经验公式,“预估大促GMV/历史大促GMV×历史客单价/预估客单价”,一般就等于历史大促峰值。通过这个可以算出这次下单大概峰值是多少。
算出了下单峰值之后,再套入一个倒金字塔的模型。电商场景都是有一定特点的:最前面流量会从会场、首页、外部分享页进来,然后进店铺页面、图墙页面、搜索页面。到了这些页面之后会进入详情页;到了这里用户可以加入购物车,做购物车的操作;然后就可以下单、支付了。浏览了详情页后,有多少人基本会下单,这个比例我们称之为“转化率”,转化率可以预估出来了。
根据一定的比例关系,一旦确定好下单数额之后,就可以倒推出每个系统在本次大促是要承担多少QPS,把每个系统的峰值算出来。推算出每个系统QBS之后,再根据以往经验进行对比,比如对比3.21大促的预估峰值跟实际峰值,假如当时估值偏低,那在下次大促时稍微调高一点,这样就会确定本次大促各自系统峰值是多少。
关注运营玩法和架构的变化
电商平台的运营玩法层出不穷,一定要关注运营玩法和架构上的新变动,这对预估峰值来说很重要!蘑菇街吃过这方面的亏。
比如今年3.21大促的时候,运营在晚上10点时候会上线有一波游戏,App会有信息推送,首页会会弹出直达这个游戏的提示框,有好几种方法可以把流量迅速导到游戏会场去。在游戏开始之前我们没有太关注这个游戏的玩法,只是按照以往的经验简单估了一下这个值。但是游戏的流量比预估值高上一倍!那次大促有一半的KBS直接被限掉了,这对客户体验非常不好。
后来总结经验,对于这些玩法的变动,一定要做大促稳定性保障,一定要了解的细致,不能只是很泛泛地去问一下:玩法有没有新变动?是不是跟以前一样的?要具体到运营的每一个步骤,不问清楚很有可能对流量预估产生影响。
架构上有新变动带来的影响更不要说了,比如你以前应用是部署在物理机上的,现在部署在虚拟机上了;或者之前用A中间件,现在用是B中间件。架构上的变动一定要重点关注。
梳理依赖关系
一旦业务变大或服务化细致之后,依赖关系会变得非常复杂,梳理起来很麻烦。有时候某一个应用挂了,可能是某一个依赖方挂了;你负责的应用挂了之后又会导致所有调用此应用的应用也挂了。
在全链路监控系统上线之前,做依赖关系的梳理其实是一个非常麻烦的事情,假如开发人员出现了流动,新人接手这个系统,只有看代码才能知道依赖关系,对代码不太熟悉的话还会出现漏判的情况。有些代码因为业务逻辑关系非常复杂,很难判断和梳理,用比例无法精确计算。
依赖关系不明确,万一带大促的时候这个依赖发生了状况,但没有做预案处理,可能对系统本身的稳定性会产生影响。
现在蘑菇街是通过全链路监控系统来梳理依赖关系,因为全链路监控系统可以知道一条链路下来的每次调用,通过计算可以知道调用者和被调用者之间的调用比例关系,甚至可以详细到接口。
通过全链路监控系统梳理出来的全站应用拓扑,最前端的应用是Web Trode Order,调用了很多的应用。只要把这个关系出来就一目了然,再也不用看代码,而且直接知道它们之间的关系。
系统压测和扩容
蘑菇街的步骤是,先做单链路压测,再做扩容,最后做全链路压测。
在没有统一的全链路压测平台之前,业务方都是自己组织压力测试的,各种各样的压测工具都有。这里有几个注意点,一个是当你需要压到很高的QPS的时候,本身有限制,QPS不是很稳定。另外如果压测数值很高,业务方手里没有机器,他要跑到PE去申请,PE一般也不会给机器,这怎么办?基于这个问题,蘑菇街开发了全链路压测平台,把机器当成一种资源:只要告诉链路定义、场景定义和任务定义,简单定义之后就可以压测了。
单链路压测
在单链路压测阶段,各业务方把各自需求应用全部做完100%流量的压测,并且要验证所有的流量、限流策略是否正确。
单链路压测的时候有一个“局部集中”的概念,在做单链路压测的时候,电商基础相关的业务压测要尽量放在一起压,比如详情、促销、优惠、交易、下单,之前都是各自压测,但这些应用互相都是有交互情况的,最好在同一天单链路压测的时候就一起进行调。如果有问题可以提前发现解决掉,而不会把这些问题推到全链路压测的时候去解决。
系统扩容靠单机压测
单链路压测完了之后就开始做系统扩容。蘑菇街做系统扩容,必须要由单机压测系统提供水位报告,有数据说话,拒绝拍脑袋,才能够减少机器、降低成本。
比如有些应用压测出来要扛5千QPS,但是我只能抗3千,这就需要找PE扩容机器。扩容要尽量节约成本。业务方为了自己的应用更安全一点,通常会多要几台机器,能扛多一点,心里安全一点。如果对每一个业务方都心慈手软的话,你就放出去太多机器了,成本没法控制,必须要求业务方要拿着单机水位压测包来扩容,不能拍脑袋说“再给我5台机器,让我更安全一点”。
为此蘑菇街做了单机压测系统,它有两种方法,一种可以把线上真实流量全部会聚到其中某一台机器上去,这样可以测这台机器的真实水位到底是多少。另外一种情况,可以利用全链路压测平台,打模拟流量到一台机器上去,这样也可以测出这台机器真实水位是多少。
打流量的时候也不是一次打,假如你预测这台机器单机水位大概能达到1千KPS,我可能从100开始打,100打完了打200,直到测出你这个系统单机水位。这里的判断标准和预值通常是让业务方自己测,比如他测试CUO到了一定值之后,比如到了80%,就可以认为到极限了。业务方完成单机压测后,方可拿到机器进行扩容。
全链路压测要尽量真实
扩容之后要进行全链路压测。各自做单链路压测是不行的,因为在所有流量会聚到一起的时候,对整个系统某些平行点可能会发生意想不到的情况。所以在单链路压测做完之后,一定要把所有系统会聚在一起做全链路压测,一般会做两到三次。
一般认为全链路压测知道压过就OK,其实不是,全链路压测都够了,后来大促发现了问题,问题在哪里呢?因为全链路压测是模拟流量,平日的真实流量没有到大促那一天这么大,没有那么多的数据用于模拟流量数据库;比如线上商品有10万,业务方为了简单化,后来在测试数据库里只有2万的商品,所以在做全链路压测的时候没有发现这个问题,测试时的网卡流量虽然比较大,但是没有把网卡打爆。但是在真正大促当天,10万商品的流量一下子下来之后,就把网卡打爆了。
进行反思后,蘑菇街要求全链路压测数据要尽量真实,尽量模拟现场的情况。但是全链路压测的成本也要考虑好,这之间的度要把握好。
全链路压测是一个苦活,通常在晚上12点之后,持续四到五个小时,一直到凌晨。这里要提高人效、降低成本,蘑菇街尽量减少晚上压测的成本,如果白天压测50%的量,晚上可以从75%开始压了,这样就可以节约了晚上的压测时间,就不用到凌晨4点、5点,一般凌晨2点就可以手工了。
2017年蘑菇街也在想一些办法,可不可以在白天也做一些全链路压缩;白天做全链路压测的方向是有的,就是隔离出两个动态环境,一部分环境是线上流量,另一部分环境专门用于压测,这样互相不影响。这个方案还在可行性验证当中。
以上工作准备充分以后,还要准备一份预案评审和作战手册。
预案是为了大促如果真发生了事情到底怎么办,包括前面做依赖梳理的时候,一个系统如果有三个依赖的话,需要对每个依赖都要做好一个预案:这个依赖如果挂了怎么办,降级还是别的处理方案?
作战手册就比较简单明了,出现什么问题,团队成员能够在作战手册里第一时间找到应对方案和措施,及时应对,解决突发情况。
lurker:蘑菇街全链路监控平台
为了加强稳定性工作,蘑菇街开发了一个全链路监控平台工具——lurker。取这个名字的初衷,是希望它埋伏在地底下,在设计和使用的时候,尽量地减少业务方对这个东西的感知,但实际上lurker又帮助他们解决了实际问题。
在开发lurker的时候,蘑菇街95%代码以上还是PHP,部分代码做了PHP服务化,服务可以互相去调用的。比如想监控PHP服务里面的每一个函数,到底是不是每一个函数时间过长。所以我们当时想了一个办法,Facebook有一个xhprof的工具,可以监控每个函数,通过PHP扩展实现。我们在xhprof的基础上改了一下,就变成蘑菇街的lurker,它的原理是什么?PHP有一个绽裂引擎,有一个函数执行指针,把这个指针替换成蘑菇街的hook函数,然后做了一些处理,可以让业务方判定哪些函数是需要监控的。此外还修改了curl以及php-curl的实现。
全链路监控的原理早期是由Google Dappen提出的,是一个分布式的监控系统。当本身的应用逐步服务化的时候,服务拆分会越来越细,现在蘑菇街有大几百个应用,应用之间互相有很复杂的调用。某一条复杂的链路可能要涉及到几百次调用,如果一次调用IP超时的话,怎么知道这么复杂的链路里面到底哪个超时了?
它的原理,每次调用都有一个全局唯一的Trace lD,它里面包括了一些业务含义,比如当时请求进来的机器IP、请求发生时间、进程ID等等,这些信息的加入,本身是为了保证全局唯一的特性。
另外一个是Span lD,是为了区分调用中是一个顺序关系或者嵌套层次的关系。Trace Context是在哪里产生的?因为所有的请求进来都是在nginx,所以蘑菇街直接在nginx端做了插件,插件里面会产生Trace Context,它就会从请求进来的最前方开始一直传到最后面;它通过Servlet Filter去实现在前端加入外部应用。
全链路监控系统架构
蘑菇街全链路监控系统的架构如图所示。通过插件放在应用集群,不管是PHP还是Java,日志都会在本地落款。本地落款相对通过网络上传会更安全一点,出问题概率会小一点。
日志在本地落款之后再实时收集到Kafka,进入Kafka之后数据分三份:
· 一份数据进入Storm,然后进入ES Index里;
· 一部分原始日志直接放在Infobright;
· 一部分数据进入到HPS
这个系统要支持多维查询,数据一开始放到支持全文检索的ES里,但ES数据不做压缩,存储成本很高。当时数据一天有好几T,存不了多久。后来用了Infobright,节约了很多成本,很好用。但Infobright是开源版,有一个人为设置,写数据的时候不能读。另外它只能单线,速度很慢,读一条日志需要6、7秒的情况。今年Q2,蘑菇街模仿Infobright做了一个类似存储的东西,现在已经差不多完成了,测试的结果性能还是相当不错的,压测率和Infobright差不多,速度很快。
Kafka另外一部分数据进入到HPS,主要是适应不适合用Storm做实时计算的情况。
全链路监控系统的作用
在蘑菇街 lurker 示意图里,左边可以看到整个链路呈竖状的结构,1是1.1、1.2、1.3、1.4和1.5,1.4里面又有1.41,1.5调了1.51,每次调用服务、方法都写在这里,时间后面有Timeline,整个调用关系一目了然。
除了调用关系之外, lurker 还能统计应用信息。每次调用和被调用者都有一条固定的认证关系,可以算出每个应用的直接应用来源、去向应用的数量,蘑菇街称之为“法拉力应用”,每个时间点上QPS到底是多少,有没有出错,都可以在应用信息统计上看到。假如调用了一个前端应用,还可以看到URL是多少。假如调用了一个后端服务,还能看到最最前面的应用,就是最早进来的URL,或者来源于哪几个应用。
利用监控系统,可以画出所有应用之间互相依赖的关系,并且把互相调用的比例也算出来。
lurker 带有一个多维查询的功能,有些业务方可能想根据某些特殊条件,比如应用名、服务名、方法名、业务响应码或者是IP,都可以通过多维查询搜出来。多维查询的要求还是很高的,因为后续查询、后续数据存储都是需要解决的问题。
lurker 尚需解决的问题
在开发 lurker 的过程中,蘑菇街也碰到了一些问题:
· 数据存储
· 跨线程传递trace context
· 前端应用接入不全,导致后端应用QPS不准确
· 周期性任务,链路路过⻓长,展示有问题
数据存储问题如上文所说,历经几版,希望我们最近研发的产品能够撑住平台上产生的巨大数据量。
第二是跨线程传递,因为trace context从前端要往最后端传,为了给业务方不产生影响,或者让它们尽量没有感知,我们就把trace context放在这里面。但会出现一个问题,假如中间的一些工具有自己的现成词句,那就传不过去,信息就丢失了,链路到这一步就断掉了。我们想了几个办法,第一把trace context做的东西放进去,但是业务方不知道去用,他们不愿意去用,觉得比较麻烦。第二去修改现成词,直接把这些数据弄进去,但是修改以后业务方觉得修改后的这些东西到底靠不靠谱?所以目前碰到这个问题的时候,基本上建议业务方手工传一下,要不然在里面看不到。
第三个问题:前端应用接入不全。在推这个产品的时候,没有强制去接入,监控系统强大了之后,所有的前端用户都接入了,到后面的调用就没有被记录下来,所以后端应用QPS不准确。
最后是一些周期性任务。有一些任务链路比较长,比如一次跑一个小时,可能调个1万字也有可能,这个内容展示会有些问题。
全面分析全链路压测系统
全链路压测的意义在前文已经讲过了,在于验证瓶颈是否存在,以及找出可能的瓶颈,具体来说有以下几点:
· 对线上真实环境进行大流量演练
· 验证链路在超大流量系统里,容量和资源分配是否合理
· 找出链路中可能存在的瓶颈
· 验证网络设备、集群容量和预案、限流策略
建立压测模型
为了找到这个瓶颈,使蘑菇街的系统更加灵活,在设计压测模型的时候会有一些考虑:压测链路、压测场景、流量模型、压测任务、压测脚本。
所谓链路,一个URL就是一个链路,场景是链路上一层的概念,一个场景可以有单个或者多个链路组合而成。在电商环境下,某些场景是可以复用的。
压测场景以后,加上流量模型就可以构成一个压测任务,流量模型就是压测的时候流量怎么来,是一条直线比值压,还是要阶梯爬坡?
压测任务又会转化成压测脚本,这是一一对应的。
以支付里面的一个场景为例。支付收单是一个链路,流量从虚拟根节点进来之后,100%流量会去到支付收单节点,然后100%流量又会进入到收银台渲染节点,5%的流量会去A节点,40%去B节点,55%去C节点,最后100%去D节点。通过鼠标拽一下,一旦规定了入口接点是多少,就可以把整个场景规定下来。
电商场景是可以嵌套,比如3.21交易之后的全链路,里面包括了促销压测模型、交易模型、支付模型、支付成功页,这些全部可以复用。假如说业务场景的逻辑不变,下次大促时这一个场景直接可以附在上面。
压测脚本做了二次改造,能够支持这四种不同的协议。第一会做脚本的确认,第二是数据确认,可以上传一些压测数据。第三是开始之后状态的检控以及结果的展示。
全链路压测系统架构解析
蘑菇街的全链路压测系统基于master slave开发,搭建速度很快。
这里有一个比较坑的地方,系统仅仅是支持单master,一套系统部署起来只支持一个master,我们要尽量避免单点。当时全链路压测系统刚完成1.0版,第二周就要压测,当时我们担心这个master会不会贵?结果果真贵了。贵的原因是什么?它和slave有交互,全链路压缩大概有100台压测机,特别是当压力压的比较大的时候,容易出错。
之后做了一些改造,包括实时回传、发起压测命令、上传压测数据、观察压测进度、收集压测结果。改造之后master好了很多。生成完了之后有一个数据工厂,它会自动生成要压测的那些数据,比如商品流数据、交易数据、促销数据都是从这个数据工厂里面来。
全链路压测系统还有一个OSS Storage,用于存储压测脚本和压测数据。OSS Storage有好几个备份,而且能够保障数据文件的安全,不需要再次关注。
全链路压测系统也碰到一些问题。蘑菇街的场景非常大,场景里面有很多内容定义,脚本会编译成JVM方法,大小不能超过65535。解决方法是——拆场景,比如把交易支付拆成一个场景,把搜索拆成一个场景。假如有100台机器,每一台机器跑一个脚本,这样就绕开脚本大小的限制了。
整个全链路压测系统要表面单点,它没有办法加载太大的压测数据文件。有一些业务方会要求回访现场数据,一拿回来就是几个G,这会需要非常长的时间,所以不推荐。蘑菇街现在的应对做法是把数据分割开。