我正在参加「掘金·启航计划」
作为后端开发,对业务系统的稳定性保障是一个永恒的主题。在To B业务的视频会议领域中,稳定性尤为重要。但是,要做好系统的稳定性保障工作,涉及的东西非常琐碎。因此,以曾经主导过的一次对大会的后端业务系统稳定性保障经验,我总结出4个方面的保障措施,梳理异常情况、预防问题发生、如何发现问题、如何解决问题。希望对后续的工作有一定的借鉴经验。
为更好地结合例子说明,先大致介绍一下业务背景。大会是由国家体育总局主办的第十二届全国体育科学大会,活动共持续3天,每天的会议从早上8点半开始,分上午、下午、晚上三个时间段,直到晚上9点结束。期间参会总人数共32000多人,共召开了243场专题会议。
梳理异常情况
关键业务场景梳理
在得知这个活动由我们来承办线上会议时,距离大会真正召开还有大约一个月的时间。当时我们负责的业务系统有很多,要保证业务系统的稳定性并不是漫无目的的,应该是有针对性的。因此,要做的第一件事就是梳理关键的业务场景,明确用户在大会举办的过程中,会使用我们的什么功能。而这些功能,就是我们要重点保障的。当时联动技术支持团队,与客户梳理出活动期间会使用到的功能场景,形成相关的操作手册。下面列出对后端影响较大的、有代表性的部分场景如下:
场景 | 重要程度及原因 |
---|---|
加入、离开视频会议 | 视频会议的基本操作,而且加入会议是并发量最高的场景 |
会中操作-全体静音(会中以此为例) | 大会必备功能,由会议主持人控制。并发量不高,但是需要通知会中其他端点,是大会场景下发送消息量最大的场景 |
登录 | 使用视频会议软件的必备功能,由于与会人大多数不会提前下载视频会议软件,所以很有可能会在会议当天集中登录,导致登录请求并发量变大 |
要做好系统的稳定性保障,这是第一步,也是最关键的一步。做好了梳理,才知道后续的保障重心应该放在哪里。下面基于这些典型的例子来说明如何进行稳定性保障。
关键链路梳理
根据上述梳理的关键场景,梳理出从请求的接入层开始,到业务系统处理完成过程中,整个系统的运行情况。包括但不限于:
- 核心场景涉及的关键链路
只有知道了要保障哪些系统,才能有的放矢。为方便后续说明,以实际业务作为参考,我们假定保障的重点业务系统为rsc(Remote Session Control,远程会话控制),即负责提供视频会议业务能力、负责入会、离会及会中操作等关键能力的业务系统。
- 处于关键链路中的系统,对其他系统和基础设施的依赖关系
通过对依赖关系的梳理,可以明确除了关键系统外,还有哪些系统需要额外关注,以及即使这些被依赖的系统挂掉了,会不会对核心场景产生不可用的影响。如果存在这样的情况,那就需要对这些被依赖的功能做好降级处理。比如,在rsc中,在加入会议的过程中,会去会议权益系统查询这个会议当前使用的是什么权益,万一用户加入会议的流量过大,或者会议权益系统出现慢查询导致响应慢,此时就可以根据梳理出的依赖关系,对查询会议权益的功能进行降级,直接跳过会议权益的判断。
- 用户要完成一个核心场景的操作,从客户端开始,要经过哪些接口才能完成,以及这些接口的作用分别是什么。
这一步的目的是为了避免遗漏使用过程中涉及的接口。比如要完成一个业务功能,在客户端上要经历A、B、C三个接口才能完成,但是只从后端的角度去分析,会因为不清楚客户端实际的实现逻辑,而导致遗漏了部分会使用到的接口(比如后端以为客户端只用到了A、B接口,而忽略了C接口)。这样在并发量上来的时候,A、B接口可能因为提前识别到了所以做好了保障,没有发生异常,但是C接口被遗漏了,导致C接口在高并发的场景没有任何的保障措施,最终呈现给客户的现象就是这个核心场景不可用。所以对核心场景中用到的接口进行梳理非常重要,而且是需要从客户端开始进行梳理的。
预测可能出现的问题
对系统的稳定性保障要做到有的放矢,就要先梳理出可能出现的问题。导致系统稳定性出现异常的情况有很多。因此我们需要对异常进行分类,对于不同的异常,采取不同的保障措施。 按个人理解,异常通常可以归为两大类:
- 业务系统的功能性异常,是指日常功能迭代开发上线后,出现的线上问题,一般都是由于业务系统的变更引起的。
- 基础设施的异常,包括网络、容量、连接、磁盘、缓存、JVM等等中间件或者底层硬件设施产生的异常,一般出现在流量激增的场景。
而稳定性保障的工作,也可以归为两大类:
- 日常稳定性保障,通常指对业务功能上的缺陷的日常维护
- 大促稳定性保障,指要进行某次活动时导致流量成倍的激增场景。大促这个词是从电商领域来的,这里借用一下,指代由于某些活动导致流量激增的场景。
按照上述定义,列出这异常和保障措施之间的联系:
业务系统功能性异常 | 基础设施异常 | |
---|---|---|
日常稳定性保障 | 重点关注 | 通常不需要关注 |
大促稳定性保障 | 重点关注 | 重点关注 |
这个表格表示,当我们进行稳定性保障工作时,需要重点关注哪些异常。对于这次活动,属于大促稳定性保障要关注的场景,业务功能性异常和基础设施异常都要重点关注,即使基础设施是良好的,但在关键场景下,业务功能性异常在视频会议业务中往往是致命的,比如在视频会议中无法进行全体静音,这会导致视频会议的声音一片混乱,会议秩序失控。特别是在这样大型的会议中,这是十分致命的。所以,对于大型会议关键场景下的批量操作,我们也需要关注和测试。
在挖掘可能出现的问题时,也要考虑时间和成本之间的平衡。活动准备的时间很有限,不可能对整个系统做一次非常全面的测试,这样肯定来不及。这时候,在此前做的关键场景和关键链路梳理就至关重要。我们不需要对所有功能都进行保障,在有限的时间内,只对关键场景和链路预测问题,并结合梳理出来的关键链路,输出对应的补救措施即可。
预防问题发生
神医扁鹊曾说过,”上医治未病,防患于未然“。通过对异常情况的梳理,我们已经对当前要保障的场景和系统有了一定的认识。但稳定性保障工作更重要的在于预防,如果到活动进行期间出现了阻碍性的故障,影响是非常严重的。那应该怎样预防故障发生呢?可以从两个方面进行预防:人工预防、系统预防。
人工预防
像这种活动类的稳定性保障工作,本质上是时间与成本上的权衡。我们无法在短时间内投入大量的资源进行全面的测试,所以人工预防的目的,是为了避免系统在一些边缘场景被激增的流量打垮,可以让我们更加专注于核心场景、核心系统的稳定性保障。人工预防与业务场景、团队协作息息相关。 大型的会议活动都会有专门的技术支持团队到现场跟进和培训。从上面梳理的关键场景中,我们知道在视频会议前都会先进行登录,并且也知道了这次大会中的高频操作场景有哪些。而且,这次会议是由一所大学主办的,在会议准备阶段会召集一批志愿者来对每个分会场进行会控,技术支持团队也会在会前对志愿者进行培训。所以,会议准备阶段,会联动技术支持团队,让技术支持团队在培训时根据会议进行的流程,着重培训关键场景的操作,这样可以避免在使用其他场景时出现异常的概率。 另外,因为参加会议的人多数都是第一次安装我们的视频会议软件,所以很有可能在会前的一段时间集中登录,可能会导致登录功能异常。为此,我们也让技术支持团队在会议准备阶段分批引导参会人提前登录,以避免集中时段登录造成登录流量激增的问题。
系统预防
系统预防主要着眼于核心场景、核心系统的稳定性保障上。在明确关键场景和链路的前提下,对于保障哪些系统的稳定性已经比较清晰。功能上的正确性,在日常的迭代就已经验证过了,在系统的日常运行过程中不会出现什么问题。但是当流量激增的情况下,系统是否仍能保证功能能正常使用,则是当下需要关注的问题。因此,我们需要知道,在活动期间,系统需要支撑多大的流量才能让活动正常开展。理清流量需求之后,我们才能知道要分配多少资源来支持。
综上,为保证系统稳定性,我们在预防阶段进行了如下步骤:
确定性能指标
可以参考线上环境相关接口平时的TPS和RT,确定待测场景中接口期望达到的TPS和RT指标。注意,确定指标这一步不是自己闭门造车拍出来的,而是要和需求方、各方的技术经理共同决定的。线上环境的数据只是用作参考,并不是制定期望指标的标准。
比如说,大会举办期间,最多可以在同一时间开始60场会议,场均参会人数最多达200人。而通常用户在会议开始前1-2min内才陆续加入会议,这样也就意味着,系统需要支持,在1min内有1.2w用户加入视频会议的场景。那么意味着作为核心系统的rsc,要支持1w人在1min内完成入会,那么核心场景涉及的接口中,TPS均不能低于170。
输出压测方案
根据场景输出压测方案,压测方案中要体现出如何模拟真实场景,测试脚本如何设计、如何定位系统瓶颈、梯度压测如何实施等方面。还是资源与成本的权衡,视频会议的背后需要媒体引擎的支撑,要在压测过程中支持1w方同时加入会议,需要相应的机器人资源和音视频媒体服务资源都很多,而且还要持续压测两星期,这样的成本明显不太能接受。所以我们只需要在压测过程中验证,在生产环境中,增加多少的机器,可支持多少方用户入会即可。这样我们可以通过较少的资源,来推算出最终需要多少资源的机器才能支撑当下的需求。
压测环境搭建
压测环境要尽可能模拟线上数据和环境,应用版本、配置要同步到和线上版本一致。现阶段我们并不像阿里那样可以在产线上进行全链路的压测,产线上的全链路压测对我们来说风险也很大。所以我们的压测环境只能尽可能保持和产线一致,以减少由于环境问题带来的影响。
系统容量预估
由于此前很久一段时间没有进行过压测,所以在这次对于核心系统的性能情况并不清楚。所以要先对核心系统rsc进行一轮压测摸底,主要观察单实例能支持的TPS和99th 的RT,以及如果要维持一定的99th的RT,在增加机器的情况下,rsc能支持的TPS会按照怎样的比例发生变化。一般情况下会多次压测,然后取多次压测的均值作为摸底的性能数据。
在压测摸底的过程中,每次压测,都需要记录如下数据:
- 核心应用本身的CPU使用率、内存占用、实例数量、每个实例的tomcat线程数、数据库连接池的连接数、JVM内存配置
- 核心应用所依赖的系统的CPU使用率、内存占用、实例数量、每个实例的tomcat线程数、数据库连接池的连接数、需要承受核心应用请求的TPS大小、JVM内存配置
- 核心应用所依赖的中间件的配额
总的来说,就是要记录应用自身的配额,应用在核心链路中的下游系统的配额,以及应用所依赖的中间件的配额。这样在压测过后,我们才知道系统的最佳配置应该是怎样。
比如,假设rsc摸底测试得到在1个容器下的TPS只有30,那么由此推算,要满足TPS不低于170的要求,在同等cpu、内存配额下,需要至少6个实例才能满足需求。
定位系统瓶颈
压测通常由测试团队进行。在进行摸底测试时,需要针对梳理出来的链路,计算对链路经过的每个系统在单实例的情况下,接口TPS的峰值达到多少。链路中的某个接口TPS较低,都有可能导致整个链路的TPS变低。因此首先需要定位出链路瓶颈在哪个系统,再定位出瓶颈系统中的问题。
在时间较为紧迫的情况下,对于链路中的非核心系统,一般直接增加配置进行降级,即当发现链路上出现TPS降低的情况下,优先对非核心系统进行降级。而对于链路中的核心系统,无法通过降级处理,则需要去排查这些系统中影响TPS的原因并解决。
优化系统瓶颈
针对定位出来的系统瓶颈进行优化,并再次进行压测,如此循环,直到满足既定性能指标。记录下满足性能指标时,核心场景链路上的各个系统的配额分别是多少。注意不能只取某一次压测的数据,某次压测可能具备偶然性;也不能取应用刚刚发布后的第一次测试记录,因为应用没有预热。要经过多次测试,观察其TPS和RT指标,取其平均值。在压测时需要注意以下几点:
- 压测前要先进行预热,接着再执行3次压测,取其平均值。
- 对系统的优化可以通过优化代码的方式,也可以采用扩容的方式。如果时间充裕,建议从优化代码入手,如果时间紧张,则可通过压测得到多少的实例才能满足既定的TPS需求。
- 在系统优化的过程中,需要运用各种监控看板、命令、工具去分析,包括但不限于grafana、apm、top、jstack、arthas、火焰图、tcpdump、jprofile、gceasy.io、fastthread.io等等
- 在此过程中,测试需要对问题做记录,有些问题需要立即解决,有些问题由于时间原因只能暂时规避,后续再排期跟踪,这些问题都需要做记录,方便后续跟踪
- 记录限流的TPS数值,计算方法:在95th 响应时间都小于用户可接受的响应时间的情况下,应用集群的TPS峰值,作为sentinel的限流值。这也是压测的一个重要目标,它确保了我们的核心系统能在可控的流量范围内,不会被激增的流量打挂。
梯度压测
优化系统瓶颈后得到的是核心接口的TPS数据,是在单实例的情况下得到的。为了用较少的资源就可以推算出最终的资源配额,我们需要对核心场景取一定的梯度进行测试,为后续对不同规模的核心场景提供数据支撑。主要为了解决以下两个问题:
- 评估并发数和核心应用实例数量之间的关系,为后续对不同规模的核心场景提供数据支撑,以便能在短时间内支持并发量达到一定规模但又不是特别大的活动。
- 验证应用是否随着实例数增长,其接口TPS是否也随着增长,如果不是的话还需要解决这其中的瓶颈问题
比如,在机器的CPU、内存配额不变的情况下,我们用较少的资源压测得到rsc在2k、3k、4k个用户在1min内入会完毕的机器配额。接下来我们可以通过这样的测试数据,得到增加多少实例对应能支撑多少的用户数量。通过这样的梯度压测的方式,我们就算出要支持1w个用户在1min内入会,需要多少实例才能完成。
输出压测报告
在上述过程进行时,记录测试过程中的问题、过程数据、结果数据,输出测试报告。报告中包括但不限于:压测过程中发现的问题链接、梯度压测的结果(以入会为例,怎样的应用配额能支持多少人在1min内入会,TPS和95th、99th的RT分别是多少)、哪些系统做了哪些优化等等。
在这次压测过程中,我们总结出了压测报告应该关注哪些方面的数据,包括但不限于:
- 压测方案设计链接,目的是为了能通过报告评估出压测方案的合理性,结合压测方法和结果,可能评估出是否存在问题,又或者能让其他人按照这样的方式复现同样的压测结果。
- 按线上版本的配额执行压测时,链路中涉及的每个系统的接口的性能数据(TPS、95th RT、平均RT、限流的并发数),以及链路中涉及的每个系统的配额(应用版本、CPU、内存、JVM配置、实例数量、tomcat最大线程数、数据库连接池最大连接数等),目的是为了可以了解当时系统的现状,用来做对比。
- 满足期望的性能指标时,链路中涉及的每个系统的接口的性能数据(TPS、95th RT、平均RT、限流的并发数),以及链路中涉及的每个系统的配额(应用版本、CPU、内存、JVM配置、实例数量、tomcat最大线程数、数据库连接池最大连接数等)
- 系统优化项,说明性能的提升是由于我们做了哪些改变导致的,目的是为了方便回溯,以及形成相关的代码组件或规范
- 梯度压测的性能数据,需要解答在同等配额下,各个应用的多少实例能支持怎样的并发量的问题。
- 问题记录,发现了什么问题,解决了什么问题,哪些问题延后解决,排期如何,需要记录下来,方便后续追踪
实施扩容
通过压测,我们知道了核心系统的机器配额需要多少才能支撑活动的进行。在活动开始前,按照压测结果进行垂直扩容和水平扩容。
如何发现问题
在活动进行期间,不知道系统当前的运行情况是很危险的。 因此,如何及时发现问题至关重要。这主要包含两个方面:
- 怎么知道系统出现了问题?
- 怎么及时发现问题?
要做到以上两点,离不开对系统和基础设施的监控和告警机制。
监控
评判一个监控好坏的标准主要有正确性、覆盖率、直观性。
- 正确性保证监控的基本功能,能够正确的反应真实情况。没有正确性的监控毫无存在意义。
- 覆盖率是衡量一个监控系统成功与否的关键指标,覆盖率越高,监控系统就越能够完美的体现系统的实际运行情况。
- 直观的展示监控指标有助于快速发现异常、快速定位问题。
在实践中,监控主要分为业务大盘监控和基础设施监控。基础设施监控由运维负责,主要监控MySQL、RocketMQ、Redis等基础设施,当出现慢查询、消息积压、数据库CPU使用率升高等情况时,则主动告警出来。而对于业务大盘的监控,主要关注的是业务数据和系统运行数据。
- 业务大盘关注的业务数据,由在客户现场的技术支持团队和客户沟通后得到。比如属于当前正在进行活动的会议数有多少,参加的人数有多少,当前有几个用户的麦克风处于开启/关闭状态等等,都可以在业务大盘上展示出来,通常以表格的形式展示出来。为保证正确性,在监控面板建造出来之后,可以自己尝试创建一些视频会议,以验证统计的数据是否会跟着发生变化。
- 对于系统运行数据,则是通过Promethus+Grafana,以及ElasticApm来实现。我们主要关注的是一段时间内的TPS、CPU使用率、内存占用及JVM的GC情况等系统运行数据,以便从监控上可以较直观地知道系统当前的运行情况。
告警
只有监控仍然不够,我们不可能每时每刻都盯着大盘,需要通过告警机制及时通知已经发生了什么问题。个人认为衡量告警机制好坏的标准是:及时性、有效性、责任制。
- 及时性:告警出现一般都是有异常情况,可能涉及到故障、用户使用受影响或出现较大的流量,发现越及时,止血越及时,损失就越小。
- 实践中我们通过容量告警进行预警,在会议总人数达到媒体服务所能容纳的用户数的70%时开始告警,此时就需要考虑进行扩容,以防止用户数量进一步增长导致媒体服务资源被占满;
- 流量的增长有时不是一个缓慢增长的过程,而是激增。容量告警很可能并不能立即察觉流量激增,流量就超过了系统的容量。通过增加限流告警,当触发限流机制时,我们就可以知道此时某些接口的流量增长比较迅速,可能是真实用户变多,也可能是遭到了攻击,或者是由于程序bug。曾经有过这样一个案例,在灰发阶段,因为客户端的一些错误设计,导致客户端触发了重试后不会停止,造成的结果就是在一瞬间疯狂请求后端的登录接口,直接将后端的登录系统打挂了,最终导致用户反馈系统无法登录了,我们才知道出了问题。所以,增加限流告警,对问题处理的及时性非常重要。
- 告警最终都是人工处理,无效的告警会浪费人力成本,因此告警配置要注意过滤噪音,保障告警的有效性,保证报出的确实是问题。实践中我们通过配置日志告警和异常告警来保证有效性。
- 出现日志告警,通常是业务中的逻辑执行出问题了,导致进入了不可预料的情况,一般在开发阶段就会知道某段逻辑在正常情况下是不会进入的,一旦进入就会有比较严重的问题。如果出现了日志告警,就说明了代码逻辑,或数据上出现了问题。
- 异常告警,通常指的是系统http请求的5xx告警。出现5xx告警肯定是后端接口处理异常。
- 责任制则是强调告警必须要有人响应,每条告警最好分配到人。有响应的告警才是最终有效的。
- 在活动期间每个部分都安排值班人员,值班人员至少有两个,互为备份,主要负责处理活动期间出现的用户反馈的问题、系统告警发现的问题等
- 在日常迭代期间,我们有轮值的制度,每星期轮流当接盘侠,除了完成当周的开发任务之外,还要负责处理线上问题,包括但不限于告警问题、用户反馈工单、内部销售工单等。为提高处理效率,以及达到每个开发都可以自主处理问题的程度,我们整理了一份接盘侠操作手册,每个人在处理了一个新问题之后,都需要把问题、处理方式积累下来,形成操作手册。这样线上需要处理的问题在一段时间后就会收敛在一定的范围内。接着,对手册中经常需要人工处理的问题,接盘侠会在当值的那个星期,将其用程序实现自动化处理,人只对自动化工单做审核即可。通过这样的方式,提高接盘侠的处理效率,尽量降低当值期间线上问题对正常迭代的影响。
如何解决问题
预防措施不可能覆盖到方方面面。因此我们仍然对于可能出现的问题设定预案。但是可能出现的问题有很多,应该如何制定预案呢?还是得从需求出发。需求上决定了可能使用的关键场景有哪些,因此我们制定的预案重点仍然是这些场景。关键场景大概率不会有问题,但凡事总有个万一,我们应该设想这些关键场景如果真的无法使用了,该怎么办。对此,我们也制定了相关的机制:设定止损预案、建立WarRoom。无论如何,当影响用户使用的问题出现时,首要的目标就是降低影响面,并尽快恢复,也就是通常说的及时止血。
设定预案
对于非核心场景,一般不影响会议的正常运行,影响面较小。对于核心场景,如果出现问题则会影响会议的正常运行。通常情况下,对于核心场景,我们要留有后门接口或后门脚本,以便在业务运行异常时,可以快速止损。
比如,全体静音无法使用了,我们的补救措施是什么?从链路上分析,出现问题的地方可能在客户端,也可能在服务端。如果问题出在客户端,那这可能是个别客户端存在的问题,那我们可以通过服务端的后门接口帮客户执行全体静音。但如果问题出在服务端呢?这时候就很难做到业务无损了,我们的业务服务要实现静音,最终是要调用媒体服务提供的接口的。所以假设业务服务出现了问题,我们可以通过数据库找到当时会议中存在哪些端,找到对应的id,然后通过http请求调用媒体服务的静音接口,以实现全体静音。思路有了,接下来就是实现这样一个脚本即可。这么做会影响到客户端的静音状态没有随之改变,但至少能暂时规避全体静音的问题,降低了影响面。
建立WarRoom
WarRoom是作战室的意思,当某项功能出现较长时间的故障仍未恢复时,会有事件经理联动相关方进入视频会议(即WarRoom)中进行沟通,WarRoom中主要沟通这几件事:
- 安排技术支持团队在客户现场安抚客户情绪,并尽可能提供其他方案满足客户需求
- 指挥特定的人员进行分析故障和系统恢复
- 定期同步故障恢复的进展,定期同步的时间按分钟计算
- 在故障恢复后,针对问题原因,制定短期、长期的解决方案
总结
经过这一系列的稳定性保障工作,这次大型活动结束后无重大故障发生,最终得到了活动主办方的一致好评。活动会结束,但稳定性保障不会结束,业务功能的迭代一直在进行,稳定性保障工作也会逐渐转为日常的保障工作。稳定性保障工作本质上是一个对时间和资源的权衡,即如何在有限的时间内,以较低的成本保障系统尽可能稳定运行,除了系统运行需要的机器成本外,人力和时间也是很重要的成本因素。这需要持续积累稳定性保障相关的方法、工具和流程,并转化到日常的稳定性保障中,才能使得系统的稳定性不断迈向新的台阶。