本文介绍一下我曾在jd工作时团队在大促保障中做的事情,这些事情更像一个模板。可能你的业务领域不存在大促的场景,在这里看看也是好的。
双11和618基本是年中和年尾,所以一般在过完年2月开始备战618,618结束后到了7月开始备战双11。
一切都是围绕着大流量下的性能和稳定性俩个核心目标,只有这俩个达到目标才能顺利度过大促。制定目标之前,我们需要跟业务人员根据过往大促经验和用户活跃度预估出本次大促流量是增是减。
同时也要回顾过往大促时期出现的问题,吸收经验,同样的问题不能出现第二次。
有人会说,既然曾经出过的问题,肯定当时就修复了,为啥还会出现第二次?不要忘记,系统是变化的,需求是不断变更的,变化就会引入新的问题或曾经出现过的问题。
有了上面的预先说明,我想你也应该理解了大促核心的问题是什么。即在极端流量下,产生了海量的用户数据,系统要稳定的运行(耗时不能增加、系统更不能崩溃)。这也是SLA服务水平协议的核心。
首先无论是从稳定性还是性能方面,我们都需要有一张完整的系统架构图,架构图划分出了有哪些模块,模块之间的依赖关系,边界是什么。这有助于我们从宏观上分析系统的薄弱环节,即哪些地方可能产生异常。而对于运维工程师(包括DBA)来说,一个物理部署架构图也有助于分析出流量和机房分配是否合理,哪些需要冗余。
薄弱环节一般具有如下特征(形式上不限于接口、消息、任务调度等):
- 没有足够的冗余和负载均衡的
- 业务耗时高,承载的QPS大或吞吐量低的
- 异步执行并没有持久化的
- 过往大促出现问题的
- 日常经常出现问题的
架构图能看到工程的整体面貌,为了保证可靠性,我们需要解决模块依赖(即外部调用)异常情况下模块自身如何保证可靠的问题。这里依赖要分为2种去思考:
- 我依赖哪些外部资源(接口),这些涉及到与其他部门的沟通协调
- 我依赖哪些内部资源,这包括自己系统内部的接口调用、数据库、缓存、消息队列等中间件
接下来,我们依照这个思想进入系统的微观看看。
我们需要梳理哪些是核心接口,哪些是非核心接口。这需要2个excel(一个核心接口,一个非核心接口),这里有个模板可以套用:
接口名称,接口地址,接口说明,响应耗时p99,负责人和联系方式
梳理核心和非核心的目的是在系统达到算力瓶颈或出现不稳定情况,对非核心进行降级,让出算力给核心接口。以及对核心接口进行独立部署,性能更高的服务器部署核心接口。
我们还需要依照接口的重要程度来确定是否可以降级,尤其是核心接口如果出现异常是否可以降级接口中部分功能,从而确保业务能继续执行下去。降级通常分为有损和无损,有人会感觉GET类型的接口是无损的,而PUT、POST、DELETE等接口是有损的。但不一定,总会有人在GET中去修改数据,用户也可能基于前一个GET接口返回的结果POST数据上来的,业务流程是连续的,要考虑清楚GET被降级POST能否正常执行。
上面表格中还有一个响应耗时p99一列,需要填写p99阈值,即100个请求中99个请求耗时低于此值,1个请求会高于此值。这是一个比较合理的指标,如果计算平均响应耗时的话,最慢的一条很可能就被拉低了,无法体现出系统出现了问题或达到瓶颈。所以这是个底线,如果系统出现大量的p99耗时增高,系统必定不再稳定。这个时候你需要考虑按照预案进行降级并排查问题。
可能你会感觉耗时高了扩容是不是就可以了?错,大促期间会提前封网,你一切线上操作都要经过严格的审批,包括扩容。所以能做的第一件事就是排查出问题然后迅速操作降级开关。
上面梳理出的接口模板,也可以用在梳理每个接口所依赖的外部资源、接口上。依赖如果出现服务不可用、耗时增高,需要第一时间联系负责人并进行降级。
依赖的内部资源也可能存在问题,也需要梳理,这里也可以套用如下模板:
资源类型(db、redis、mq…),连接配置(域名/ip+端口),资源说明,负责人和联系方式
内部依赖的资源,比如redis挂了,要第一时间联系DBA告知哪个集群ip和端口,说明这个redis用来干嘛。我们也可以对资源进行降级,这里通常会切换到备选的中间件,如redis切换到另外一个机房或切换成memcached。
通常资源的负责人也会提前与研发进行沟通,哪些资源是重要的,需要在大促期间重点保护的。他们会在大屏上投放这些重点保护对象的实时监控,搭建完善的高可用架构。
上面说了降级,降级本质上就是代码中编码写入一些可随时动态修改的开关,如果开关关闭则不走下面的业务逻辑,否则执行业务逻辑。然后通过一个统一的平台进行管理,可在页面中操作开关进行降级。降级是有损的,所以这里又有一个模板可供参考:
开关,业务功能说明,是否核心,负责人和联系方式,是否可降级,降级是否有损,有损说明
需要写清楚降级是否有损,损害了什么:用户的体验?是否会出现脏数据?业务流程能否继续执行? 因为通常是有损的,所以要写清楚是否是核心,核心结构通常是非必要不要降级。非核心可以必要时随时降级。在操作降级时要有操作记录,要周知相关负责人(产品、业务、研发、QA等)。
有一种需要格外注意的情况,一些GET请求流量会先请求cdn,cdn上没有数据会回源,如果原站降级,cdn缓存时间过了,原站恢复降级,流量全都回到原站会直接压垮原站。这种情况需要对cdn进行预热。
在梳理完降级成后就需要进入演练环节,这一步骤是为了确保降级开关的有效性。这一环节通常要产品、开发、QA、运维共同参与,运维负责集中操作降级开关,QA和产品负责回归业务逻辑,研发观测系统相关指标,确认是否达到预期目标。预期目标通常为:
- 通过自动化case和手动回归验证核心流程是否可用,被降级的非核心功能是否降级成功
- 非核心降级后,系统各项指标可能会有变动,如cpu负载和使用率降低,这意味着核心功能可得到更多的算力
演练完成后需要恢复开关,需要记录演练结果,即:降级是否达到目标,如果出现降级失败的情况需要检查并修复代码。
接下来需要对核心接口进行压测,压测出单机qps,施压流量要成阶梯式上升,直到压到瓶颈。为何阶梯上升?因为低流量下一些缓存可能并没有充分预热,而随着流量逐渐增加,缓存逐渐预热,同时真实流量大多情况下也是阶梯式上升的。压测时要确定的核心指标:
- 响应耗时,约束接口响应耗时,比如20ms,超出20ms都认为异常
- 异常率,随着流量的增加机器的负载升高,请求会进入队列排队,这时候耗时会增加,随着流量增加队列也会出现溢出丢弃请求,此时就会出现网关超时异常,整个业务请求链路中涉及到的DB、缓存等中间件也都会随着流量产生增高产生异常,甚至是代码产生了Bug,这时接口就会出现大量的状态码为5xx的错误。
- 系统的cpu使用率、load、内存使用率、磁盘IOPS、网卡过包量、丢包、重传,这些指标有助于发现业务之外的异常状况。比如在for循环中把数据库查出来的对象输出到磁盘日志,随着qps增加磁盘被日志打满。
- java应用也需要观测堆内存使用量和gc情况,确保不会出现频繁的fullgc、内存泄露等问题。
- 监控业务系统的异常日志。
压测很可能是一个有损行为,一些接口涉及到入库、调用外部接口等,产生脏数据。所以在压测之前要根据之前梳理出的依赖的资源和外部接口,来分析接口是否可进行压测。一个良好的压测系统应具有流量染色的能力,各依赖接口和资源可基于sdk识别染色的压测流量,进行影子分身(如数据库的影子表、缓存的分身key),这样可防止压测流量产生垃圾数据污染正常的业务流转。压测的时候也要周知相关负责人:依赖接口负责人、DBA、产品经理。
除了对单个接口压测,也需要进行全链路压测,这样可测试出业务链路中的问题和瓶颈。这通常要求压测系统具有采集线上真实流量并重放的能力。
压测完成后要收集压测报告,分析瓶颈产生的原因,进行优化和修复。在优化完成后需要重新进行压测,验证优化效果。这时会得出单机最优的QPS,可根据单机的QPS和预期大促的QPS计算出需要集群部署多少服务器。
以上基本了确保系统的稳定性和性能,但这还不够,大促还需要做很多事情。
- 处理常见的异常日志:消灭掉常见的异常日志,比如:npe异常在日常的流量下可能没什么问题,对业务产生的异常影响并不大,但在大促期间很可能随着流量翻倍,对业务产生的影响可能引起大面积的连锁反应,系统雪崩了。除了消灭异常日志,还要注意梳理核心业务逻辑的异常是否有正确捕获并处理。
- 找出存在单点和瓶颈的组件:我所在的jd团队内部曾在大促期间出现过分布式定时调度集群出现崩溃的情况,虽然锅是中间件团队的人背,但对业务产生的影响我们也要负次要责任。所以在后来的大促保障中会部署一套tbschedule(淘宝开源的分布式调度)。为何不把调度集群部署2套集群,一个挂了用另外一个集群继续工作?因为在大流量的冲击上,中间件出现了bug也可能被放大导致整体崩溃,所以部署一个异构的中间件有效杜绝了这个问题。
- 故障注入和演练:故障注入是来模拟各种线上故障的,通过故障注入系统产生cpu过载、内存满、jvm进程杀死等行为,来观察系统的表现。并通过一些自动化运维手段确保能在出现常见问题的时候,系统能自我恢复。本质上故障注入日常就应该周期性的进行演练,而不是为了大促。
- 准备修复问题的工具:对于日常经常出现的线上问题反馈,可以开发一些自动化工具解放双手。这些工具的使用方法需要记录在应急预案中,同时要考虑一些操作是否会涉及到重要且敏感的数据变更,是否需要严格审批才能使用工具修复这些核心数据。
- 调整jvm参数:这里所说的调整jvm参数除了调整堆大小、gc参数、关闭偏向锁等这种在日常就需要做的调整之外。在大促之前,需要把栈空间调小,即-Xss参数,如日常-Xss=256k,大促改为-Xss=128k。这样做的目的是确保业务应用一切正常的情况下,在有限的内存空间之内降低单个线程所占用的内存空间,来换取更多的可并行执行的线程。从而提升整体的QPS。
- 补充监控系统:随着业务系统不断的迭代,代码的变更和功能的增加,都有可能引入新的接口和依赖。至少要确保核心业务的监控、报警完整,接口的核心监控指标包括:QPS、P99、平均耗时、4xx/5xx异常率、2xx成功率。对于MQ、定时调度这种后台的异步行为,也需要监控和报警,监控指标包括:MQ生产者的写入量、消费者的消费耗时、消费者的拉取量、MQ集群的心跳状态等、分布式调度任务的执行耗时等。系统的cpu使用率、load、内存使用率、网卡、jvm等非业务性的监控也要有。报警需要分等级,不同等级报警通知方式不同,包括:电话、IM、邮件,核心业务在进行电话报警时如果负责人没有接听,电话报警需要向上级投递。
- 确定值班人和应急预案:值班人和应急预案通常都要录入系统,这样公司内部任何人员可以随时联系到值班人或相关负责人使用系统内的应急预案解决问题。值班人需要有主备,留下姓名、联系方式(电话、IM)。应急预案基本是降级和扩容,一般不会轻易扩容,扩容需要走严格的审批流程,降级是第一选择。所以预案要进行演练,确保各种降级开关一切正常。在大促当天,全员需要在线,值班人只是出现问题时第一个联络人。
- 申请机器资源:遵照运营、产品等预估的大促QPS,研发和运维要申请足够的机器,通常基于压测得出单机所能承载的最大QPS,然后根据预估的QPS*2计算出要申请的机器数量。为何需要2倍?防止出现突发流量或预估出错的情况。在申请完资源后,可以第一时间部署到线上,不要进行冷备,不要等到扛不住出了问题才扩容。可靠性远比资源成本重要。
- 依赖外部资源的重试:梳理核心接口依赖外部资源(包括DB、缓存、RPC接口等)的重试策略。需要时刻记住,重试可能产生副作用:重试耗时会增加,会大幅度降低用户体验,所以要耗时用户是否可接受。一些依赖本身不支持幂等,重试可能产生脏数据。重试通常在调用外部依赖出现问题的时候执行,这时候如果是网络抖动重试可能有一定价值,但如果是依赖已经出现故障,重试无疑是让对方雪上加霜,很可能彻底压垮对方,所以要控制重试策略:
a. 直接重试n次
b. 等待一段时间后重试n次
c. 等待一段时间后重试n次,每次间隔x秒
重试可以使用开源的GuavaRetryer库实现上述所说的策略。
以上就是大促保障中需要做的事情。不知我有没有讲清楚,你有没有看明白。其实没经历过那种场景无论怎么看怎么想都是比较浮于表面的,只有亲身经历过大促,感受那种氛围,一步一步深度参与这些保障才能有所收获。这样的收获对人成长很有帮助,这是对系统的整体把控。梳理系统的薄弱环节很考验人的观察力,应对突发状况需要冷静的思考(出现问题,你前后围满了人,一个比一个级别高)。每参与一次大促都有一次不同的收获,对个人的能力有很大的提升。大促除了紧张忙碌,还有就是快乐,大促1个月内有吃不完的零食,大促当天有领到手软的红包,各种小礼物,10几米长的桌子上摆满了饮料酒水、蛋糕、零食。