本篇文章来自于 2018 年 12 月 22 日举办的《阿里云栖开发者沙龙—Java 技术专场》,梁笑专家是该专场第一位演讲的嘉宾,本篇文章是根据梁笑专家在《阿里云栖开发者沙龙—Java 技术专场》的演讲视频以及 PPT 整理而成。
摘要:2018 年双十一平稳度过,海量订单、零点流量高峰,阿里是如何实现供应链 99.9% 的下单成功率?本次分享中,阿里 2018 年双十一大促供应链服务保障平台负责人向大家详细阐述大促前 4 个月的全程经历。面对大促第一步需要做什么,流量峰值如何评估,性能优化从何处着手,一套有条不紊的供应链服务平台迎接大促的解决方案至关重要。
演讲嘉宾简介:
梁笑,阿里新零售供应链平台事业部,擅长 Java、Spring、OOP、分布式、架构设计,参与过供应链服务决策平台、双十一大促等项目,对业务开发、大促保障、架构设计等有所涉猎。
本次直播视频精彩回顾,戳这里!
PPT 下载地址:https://yq.aliyun.com/download/3183
以下内容根据演讲嘉宾视频分享以及 PPT 整理而成。
本文围绕双十一大促历程,从以下几方面介绍供应链服务平台如何迎接大促:
1. 概述
2. 梳理链路
3. 峰值评估
4. 容量评估
5. 性能优化
6. 依赖改造
7. 保障协同
8. 压测检验
9. 项目协同
10. 监控治理
11. 战前准备
12. 总结
一、概述
大家熟悉的供应链服务包括库存控制、计划调拨等,这里介绍的供应链服务更偏向于交易侧。例如在淘宝或天猫购买物品时,会有今日下单预计某日送达等物流提示,这种物流时效承诺即是由供应链服务平台提供。再如,购买电冰箱、空调等大件需要预约配送时,需要选择指定的配送商或者货到付款、拆单合单等物流透出,都需要供应链服务平台的支持。除了这些大家作为消费者可以直观感受到的服务外,商家的商品入库发货等操作也都离不开供应链服务平台。因为大促时物流服务需要实时透出,服务平台是需迎接双十一实时下单的高流量的,此外服务平台还需要承接物流发货的服务透出,例如发货的仓库、路线、发货时效及服务等。因此,供应链服务平台在阿里体系中扮演了一个上承交易、下接物流的腰部力量,主要包括物流服务、物流透出、商家订购、容量管控等功能。物流服务及物流透出上述已提及,商家订购包含仓订购和快递资源订购,容量管控是指物流干线的容量能力,例如从北京到上海,可以在当日送达的物流能力为一千单,超过一千单外的便无法达到这样的物流时效诉求。至此,大家应大致了解了供应链服务平台的概念。
供应链服务平台保障的过程如下图所示:
如何在业务容量峰值时保障系统,该采取哪些措施,这是一个不小的挑战。首先,需要分析出哪些接口需要重点保障,哪些薄弱点或性能低下的链路需要重点评估。接下来,需要对这些进行优化操作。然后,对优化后的接口通过自测或压测进行验证。验证过程中可能会发现,已有的问题解决了但是出现了新的问题,这就需要再次分析优化。这是个螺旋上升形的过程。上图右侧是保障过程的阶段描述,会在接下来进行详细阐释。
二、梳理链路
如果给你一个系统,在你不了解的情况下,让你在流量峰值下保障系统的稳定性,首先你需要明确要做的事情,清晰目标任务的范围。因此,第一步就是梳理系统业务的链路,对系统有宏观全面的认识,这是保障大促稳定性的前提。但是,以服务平台的现状来说,有以下几个通病:祖传代码、上古应用、文档缺失、无人可问。阿里的服务平台自 2011 年开始经历过三次较大的升级改造,每次升级大部分核心功能会下架升级,但仍有某些小功能无法改造。这就造成在 2018 年的大促保障时还需要阅读 2011 或 2012 年的代码,甚至某些应用都无法打印日志。文档缺失、无人可问也使系统保障更加困难。
梳理链路可以分为以下四步进行:
1. 梳理对外接口。在日常开发中,主要的业务逻辑代码大约包含四五个对外接口,但是通常来说实际会比这要多。在对服务平台的链路梳理中,新老三版系统包含大约 80 多个对外接口。但在实际工作中,只会涉及十个接口左右。可见,这些接口存在巨大的冗余。某些接口可能已不再使用,或某些接口有使用者,但使用者并没有意识到在调用该接口。因此,梳理对外接口就是梳理出接口是否在实际提供服务。
2. 移除无用接口。在剩下的 70 多个接口中,一定有无用的部分,移除这些接口也是移除了潜在的 bug 出现。这可以通过查看调用者、分析流量或者咨询使用者来进行移除。在这个阶段,阿里的供应链服务系统大约下架了近 40 个废接口,如此大幅缩小了需要保障的系统范围。
3. 梳理剩余接口。这个阶段中将各接口按照重要性进行保障力度的划分,例如某些接口是需要重点保障的,而某些接口无需重点保障,不应占用宝贵的机器资源、数据库资源等,例如一些查询功能的端页面,在大促时出现问题也不会产生重大影响。
4. 确认强弱依赖。对划分出的需要重点保障的接口需再次梳理出相互间的强弱依赖关系,这就需要理清系统的业务逻辑。例如在阿里的大促系统中,如果库存出现问题,那么紧接其后的订单、配送等接口也都会混乱,这便是一个强依赖过程。因此,这里应设置成如果库存出现问题,下单只能返回失败。弱依赖过程则不同,例如根据历史数据得知某消费者经常使用某一个自提站点,如果在大促中,自提站点的链路挂掉,分配的不是这个自提点,虽然这可能会导致消费者的愉悦感下降,但下单过程不会产生太大的问题。此外,还需对接口的调用量和调用比例进行统计梳理,例如调用一次 A 接口,可能会在下游调用一次或若干次 B 或 C 接口,通过这个比例可以根据 A 接口流量的增长幅度计算下游的接口流量。
如此梳理完成后便可以产出一份链路文档,大致掌握了需要重点保障的部分、可能的调用方、可能的消费者、消费的量级、涉及的数据库、缓存中间件、链路的强弱依赖等信息,这是后续工作的纲领性文件。
三、峰值评估
在有了这份纲领性文件之后,则需要明确任务目标。对这样实时下单的强依赖系统来说,最直观的的目标是要支撑住双十一零点的交易峰值。这就需要评估这个交易峰值是多少,毕竟每个系统被核心的交易系统调用的频率并不相同。峰值可以根据交易下单量、业务预测和历史经验来进行评估。交易下单量相对来说确定性较高,因为这在系统内部可以通过限流解决。当下单量超出了系统的承受力,则直接返回下单失败,让消费者再次下单即可。因此每秒的最高下单量是相对确定的值。业务预测是指对每个行业线的下单量进行预测。即使知道每秒的下单总量,却无法知道例如天猫超市或者某个行业线的细化下单量,那么便收集该行业线下的店铺信息,再根据一些专业知识进行评估。这样的业务预测值是缺少瞬值的,例如它可能可以预测出一天的单量,但无法预测 0 点那一秒的单量。因此,还是需要根据历史经验来弥补这个缺陷。例如根据前三年的大促流量分布、双十一前的三八大促九九大促流量分布等数据,进行流量预估得到流量漏斗。甚至在大促前,对某些链路进行了改造变更等操作的,也需要在流量预估的过程中详尽考虑。由上述可见,在这样输入有限的情况下,需要抽丝剥茧得出一份可能性较高的峰值评估。
这样的峰值评估主要为两方面,如下图所示。一部分是接口峰值,这会用于指导保障整个系统。例如若下单量为十万,那么商品详情接口大约会产生三十万流量,渲染下单接口十二万到十五万,真正下单扣减约七到八万,这些值会指导进行系统下单时的重点保障。另一部分是行业峰值。例如下单量为十万时,快消品牌和美妆产品各自的单量为多少,这会直接显示出系统各行业所需要的下单承受能力。
四、容量评估
确定了任务目标后,就需要评估现有的机器容量是否可以支撑预估的流量峰值。阿里的供应链服务平台是一个单元化的应用,可以理解为一个跨城市跨机房的异地部署过程。每个单元(机房)有不同的流量承受能力,例如可以在华北设立机房承受 30% 流量,华南机房承受 30% 流量,华东机房承受 40% 流量。接着通过压测等方法可以方便的得出单机性能,测试出健康情况下单机的 QPS 流量。例如,UNSZ(深圳单元)需要承担 6.52% 的流量,结合单机性能预估需要的机器数量,根据比例得出预估 QPS 值。接下来需要考虑,现有的机器能满足预估状况嘛?是否有缺口?如果存在缺口,是可以通过优化还是申请预算解决呢?最短的木板(压力最大的部分)是哪里?因为各个机房的实际性能存在差异,这部分差异也需要考虑在内。根据最短木板确定限流保护值,重点保障最危险的单元。此外,在容量评估时,最好预留一定的 buffer,保障可能超出的流量。
五、性能优化
基于已有的机器预算,和之前梳理出的接口链路,接下来需要一些手段优化性能来支持峰值流量。不仅包括对古老代码的优化,也需要对新产生的代码进行改进。下图展示了供应链服务平台的大促性能优化中采取的主要手段:
这些细节化手段这里不再赘述,大家可以参考一些 web 技术文档即可了解,这里重点为大家介绍如何梳理上述优化过程。没有一个整体的方法论,贸然入手消耗的精力暂且不谈,达到的效果可能也不尽人意。这些优化手段主要通过分析阿里的业务特点及双十一的玩法特点得出。例如,若某一地区的仓配产能不高,无法实现物流时效承诺,那么撤下物流时效承诺的同时,与其相关的链路也可以一同撤下,这便大大提升了这一块的性能。因此,提升性能不只是通过内存调优等,也可以根据业务逻辑来调整。优化的各个步骤如下:
1. 减少流量。在客户端或服务端屏蔽流量,降低不必要的调用,这样的性能优化效果最为可观。
2. 移除或降级 IO。根据业务需求尽可能的下线无用的 IO 逻辑或者根据业务特点降级部分 IO。
3. 减少 IO 调用。例如在货品查询 IO 中,可能会采用多个 for 循环来进行查询,那么便可以采用批量查询来代替单个查询。
4. 移除本地 IO。移除和检查没用的日志和滥用的日志。某些 debug 代码在平时产生的流量不会引起大家注意,但是在全链路压测验证时就可能产生非常多流量以致线程卡住。
5. 使用缓存。短时间内数据不变的情况下,进行缓存,避免多余的 IO。这种方式在大促时效果尤为明显。在电商行业,类似某个店铺与货品的绑定关系在大促时间内基本不会变化,这种不变的数据使用缓存效果最佳。
6. 缓存命中率。在平时缓存可能设定失效时间为几十分钟,但是大促时可以禁止该类影响稳定性的操作,将失效时间设置为几个小时甚至十几小时。拉长缓存失效时间,可以极大的提高缓存命中率,平时的命中率大约在 60%-70% 左右,在大促时这个数据提升到了 99.99%。此外,还需要充分预热。
7. 缓存吞吐。除了本地缓存外,大家可能都会使用远程缓存,例如 redis 等。阿里巴巴使用 tair 缓存,对其序列化方式进行了修改。java 自带的序列化方式性能较差,得到的序列化对象也字节较大,因此将其改成了 Hessian 方式(没有选择 kryo 或 protobuf 是因为当时转换成 Hessian 方式最为简便)。同时精简字段,假设某个返回对象有十个字段,但对外接口只需使用两三个。大家知道,带宽一定的情况下,单个数据量越大,同时能通行的数据就越少。流量峰值时,带宽通常都会比较紧张,那么便无需将剩下的废字段也存入缓存。进行了上述改进,缓存的吞吐有了很大程度的好转。
8. 本地缓存。尽量使用本地缓存在 tair 前挡一层,避免网络 IO。本地缓存不可能存太多,但是在某些极限情况,例如卡券类或红包等,当天调用极其频繁并且不会变化,这样的数据可以缓存在堆外内存中。
9. 数据库。在解决了数据 IO 的大部分问题后,便可以采取一些更细致的优化手段。例如检查慢的 SQL,索引使用是否正确,有没有离线拖库任务,是否可以清理一些碎片等。
10. 本地代码。去掉高频率低效的本地代码。但根据经验,这种手段得到的优化效果有限。
因此,纵观整个优化思路,减少 IO 和使用缓存是优化性能的效果较为明显的方法。
六、依赖改造
在性能优化后,大致不会在峰值时出现宕机的情况,但仍需要进行强弱依赖的改造,即强依赖改为弱依赖,弱依赖改为强依赖。强依赖是指如果对兄弟系统、数据库或缓存的调用出现问题,那么流程直接中断。弱依赖指即使这部分宕掉,可以直接设置超时或者熔断,并不会影响流程的后续。强弱依赖的改造其实是一个兜底的过程。将某些错误的强依赖改为弱依赖,使其不会影响核心流程,防止雪崩。其次,将一些弱依赖改为强依赖。第一种是确定性的依赖更改。例如如果下单成功需要将扣减库存数量,如果库存数量没有扣减成功,会导致后续各种问题,如果之前采用的措施是将这个异常 catch 住继续后面的流程,那么这里就需要更改为抛出异常阻断流程。第二种是下游重依赖的数据。第三种是可能会造成资损的数据。强弱依赖更改实际上就是对业务负责和保障的过程。
七、保障协同
作为系统的负责人,做到上述性能优化等工作对本系统来说足够了,但是其他兄弟系统的保障协同也是重要的一环。在阿里,每次中间件大规模要求升级时,大促也就近了。供应链服务平台重度依赖的其他系统,例如商品中心、库存中心、订购中心和容量中心等也各自针对大促做了不同程度的优化。例如商品中心预热手段发生了变化,重点流量做出分组操作,并专门留出了部分机器给服务平台系统做流量分割。库存中心升级了新模型,改进了缓存等,那么服务平台系统也需要针对这部分进行相应的协同。和订购中心的协同主要在预热和防雪崩,假如宕掉时间过长,就避免调用数据库,而去调用缓存。容量中心之前只支持单个扣减,现在可以支持批量扣减,或者修改了表结构。这些兄弟系统诸如此类的优化也从侧面使得服务平台更加健康。
八、压测检验
完成优化和协同保障后,当然需要压测检验。在双十一期间总共经历 12 次全链路压测,因为无法再造一个全链路系统,所以只能使用真实系统压测。为了不影响真实系统的运行和消费者的感观,压测通常在半夜进行。在压测过程中,发现了不少问题,这里举出以下三个例子:
1. 本地线程池满。jstack 查看线程堆栈时,发现很多线程卡在 logback 输出日志的地方。查看源码后发现是使用 log.error 打印了一个很大的 JSON 对象,且每次调用都会打印,这就造成本地线程池立刻满了。因为系统较老,依靠个人的代码 review 无法发现此类问题,这就需要压测这种真实的流量手段来发现。
2. 超时请求。鹰眼(阿里全链路监控系统)发现有一个长达 3s 的请求,一般请求为毫秒级别,检查后发现是该部分数据缓存未预热,查询数据库超时引起。因为该部分数据在大促时不会调用,因此在压测时是可以提前将其撤下的。但在压测时出现这个问题可能会影响到其他业务的稳定。
3. Jmap 超时。阿里的服务平台通过手工执行 jmap 命令触发 GC 来释放内存,发现在执行 jmap 时(会先下线对应服务),对应的机器仍然对外服务,并且大多超时。查看了对应的下线脚本后发现,由于镜像升级,导致 Linux 命令输出与老脚本预期不一致。这也影响了线上服务的稳定。
上述问题只有压测验证才会检查出来,因此,压测的意义不容置否。每一次压测都是对上一轮优化结果的验证,同时,每次压测发现的问题,也是下一次优化的主要方向。
九、项目协同
服务平台内部的工作同事便有 10 人左右,更要包括对外的例如中间件的部门、依赖和被依赖的兄弟系统部门。人员众多导致各种协同问题的产生,这就需要一个协同机制。需要明确每个项目的负责人,有固定的联系方式。任务拆解要细致,每个部分的任务交给指定的人解决。另外需要文档沉淀,得出的文档不仅是这次大促的回顾总结,也是下次大促的一个参考。2018 年的大促能够完成,也是依靠了之前的多次大促经验文档。验证考核也必不可少,验证此次的优化是否达到了设定的目标。某些在测试时没有显现出的问题会在真实的流量下被发现,因此事后的验证考核会帮助发现更潜在的 bug。最后,风险识别,某些问题可能因为历史原因无法修复,那么这种问题要识别出来,和业务人员或产品人员共同找一个可以弥补的方法。例如可以将由于这个 bug 产生的问题订单使用工具单独抛出,再行安排。这些都是作为一个项目 PM 需要注意的重点。当然,如果能有一个项目间供大家 face to face 探讨各种问题是最好了。
十、监控治理
传统意义上的监控治理有两个诉求:看图和告警。看图主要关注各类曲线是否有波动是否稳定等,告警是在产生问题时可以通过短信、邮件等方式告知。除去传统的系统监控指标,例如 GC 次数、内存使用率、QPS 高低、系统反应时间,还需要业务监控,这需要通过日志或离线采集进行统计,如此才能对系统和业务都有直观的认识。当然,“会出错的事总会出错”,过程中难免会出现问题。如果出现问题,则需要及时统计产生影响的商家数量、单量、消费者数量,以便后续紧急处理,采取拉单、扩容或下线服务等手段防止过大损失,这便是消防处理。
十一、战前准备
所有优化和监控措施完成后,大促开始前 1 - 2 天,按部就班执行各类计划动作。首先是执行前置预案,例如下线了某些业务链路要执行观察效果。其次是预热,在大促前一天将一切准备就绪,准备迎接峰值流量。接着进行一致性检查,单元化的核心应用涉及到成千的机器,可以通过计算 MD5 校验值,来确保所有的机器上的 zip、jar、war 包等是一致的。然后资源检查,对于大促当天的数据库、机器、中间件、内存等资源做一个检查,确保资源到位。清理数据库,如压测使用后废弃的表可以删除。最后,jmap 释放内存,将可能会触发 GC 的内存全部释放。
十二、总结
回顾整个大促过程,今年的供应链服务平台比去年更加稳定,核心下单链路成功率接近 99.9%,没有发生限流等严重影响用户体验的情况。从 8 月开始思考双十一的筹备工作,整个大促保障期间,涉及到众多的系统、人员和业务,每个系统和业务分别由不同的团队负责,需要做链路分析、模型分析、容量评估、目标评估,要协调和明确各节点间的职责划分、要做各类优化、压测、监控和反馈等等,持续了长达三四个月。
“这个过程是一次人与人、人与对象间的碰撞,精密的仪器产生完美的作品。”
本文作者:李博 bluemind 阅读原文
本文为云栖社区原创内容,未经允许不得转载。