导读:本文分享了作者学习稳定性工作、构建思路、落实方案,面对问题不断反思再推进的经验总结。
一、背景
2024年初,我接手了内容风控团队的稳定性负责人工作。从接手时的生疏和不熟悉,到开始学习稳定性工作、构建思路、落实方案,面对问题不断反思再推进,现在也积累了一些经验,借此机会总结一下。
二、稳定性定义
为了做好系统稳定性建设,首先需要明确什么是系统稳定性。通俗来说,系统稳定性指的是在经历外部干扰导致偏离原有平衡状态后,系统仍能在干扰消失后具备自主恢复到原来平衡状态的能力。这种能力体现了系统的韧性和恢复力。
明确了稳定性的定义,下面将详细说明建设之初我所遇到的问题及挑战。
三、问题、挑战及期望目标
1、问题
1)问题现状及隐患
在今年3月初,我刚接手内容风控稳定性负责人一职时,系统面临一系列稳定性问题及隐患:
2)问题分析
上述一系列问题仍主要呈现为单独的点状问题,涉及到事前的引擎架构治理升级和对外SLA保障建设等,以及事中的监控告警体系和定位能力的建设等。经过归纳分析,我们可以将这些问题做如下分类:
仅对当前列举的问题归纳分类,并不能确保系统的健康和稳定,单纯应对当下问题难免会留下不完善之处。经过进一步咨询、资料查阅与学习后,结合内容风控系统的特点,我将可能存在的隐患进行了进一步归纳总结,抽象形成了一套稳定性建设思路:
结合上图抽象的稳定性建设思路再对内容风控域稳定性建设问题做思考:
这套稳定性建设思路模板涵盖了三个主要关注事项:事前降低发生频率、事中减少影响以及事后优化改进,同时包括30+个细化项目。结合现有的问题和模板的补充,通过系统治理这些问题,相信能够有效提升稳定性,达到一个较为理想的水平。
2、挑战
将问题转化为稳定性建设的具体事项,其中重要紧急亟需治理的部分用红色×标示、仍需提升的用黄色感叹号标示、以及较为完善需持续保持的用绿色对钩标示,如下图所示:
针对上图中需治理的事项,面临以下挑战:
在面对上游日调用量级大、且请求内容类型大小不均的情况下,如何将SLA提升至健康水平?
如何合理设立应急小组和监控预警机制,以确保全面覆盖的同时,尽量降低噪音,并提高响应时效,激发团队的积极性?
如何抽象并定义历史故障应急定位的手段,并将这一流程自动化,以避免人工操作,提高定位时效。
如何在内容风控这样多业务形态、大流量、长链路、多异步处理环节的复杂系统,在发生异常时仍然能100%按时正常返回业务结果?
如何实现架构升级过程中的无感知体验?
3、期望目标
基于上述亟需治理、仍需提升的事项以及挑战,定义出以下几点期望目标:
1)上游请求成功率提升一个数量级;
2)事中应急时效提升一个数量级;
3)解决链路中任何异常点导致上游卡单的问题,卡单量级周维度统计下降一个数量级;
4)系统实现单元化架构,消除资源和容灾能力瓶颈。
接下来将详细阐述为了解决上述问题和实现目标所采取的具体措施。
四、稳定性保障三件事
1、事前降发生
降发生,即降低故障发生的概率,从方案设计阶段即采用面向失败的理念来设计系统架构。具体可以再拆分三项:高可用、高质量及勤自查。
1)高可用
高可用定义:系统能够在长时间内持续提供服务的能力,即使在遇到故障或异常情况时,也能快速自动检测、恢复、转移或替代,从而保证系统的连续性和可用性,减少停机时间和数据损失。结合内容风控系统特点主要有以下几项:
①理上游请求失败率高的问题,消除风险隐患
进入CY24,广告、短视频、大模型应用、游戏、审核标注等多项业务迎来了弹射式发展,对内容风控引擎从可用性提出更严苛的要求。
下面简要介绍下内容风控引擎执行原理:简要说明核心是动态编排的决策流DAG执行,DAG中包含N个决策节点,每个节点又对应1-N颗策略树,树上的叶子节点对应1-N条策略,策略依赖模型、能力及特征左值及相应的规则进行计算、合并,最终产出风控结果,如图:
内容风控将引擎划分为同步计算引擎和异步计算引擎。同步计算引擎主要负责处理那些计算耗时稳定且风险较高的策略,以便及时给出结果;而对于那些耗时较长且适合异步执行的策略,则采用异步分析,并通过消息通知上游。如图:
目前,我们的重点是解决上游调用同步引擎分析所面临的两个主要问题:
日常失败率较高;
经常出现失败率飙升的尖刺。
A、日常失败率较高
针对第一个表象问题进行根因分析,最初考虑了引擎中的配置是否存在不合理之处,特别是超时时间设置与上游请求超时时间之间的匹配问题。经过排查和分析,确实发现某些场景中存在这一问题。为了解决这一问题,主要进行了以下几点优化:
引擎超时模式产品化
上游业务类型不同,对于耗时的要求也不一样,再加上引擎超时逻辑缺乏明确的产品化定义,所以存在业务场景接入时上游超时时间与引擎超时时间不匹配的问题。因此我们针对上游业务形态,抽象出了文本及图片两种引擎服务SLA保障模式,根据模式配置会动态调整上游请求在引擎中的超时规则来进行适配。
策略预计算&异步化
策略执行的耗时大头在于规则左值的计算,例如依赖的模型、宽表等,目前引擎已支持对部分左值进行预计算。但还有部分长耗时且不支持大批量预计算的特征仍然保持串行执行,而依赖这样特征的场景其实绝大多数并不需要一定在同步引擎执行,因此在产品上支持了勾选这部分特征进行异步化计算。
经过上述工程优化成功率有所提升,但离目标成功率提升一个数量级还差些,进一步分析剩余超时case发现,这些超时情况往往与机器GC过程密切相关,因此下一步重点分析为什么GC耗时高及如何优化GC耗时:
定位大对象进行优化
想到GC耗时高首先想到的就是看下是否有不合理的大对象创建,JVM参数中有一个配置-XX:ArrayAllocationWarningSize=xxx,用来将堆中分配大对象的大小超过该值的时候,标准输出中打印大数组的分配堆栈,用来排查大数组分配导致的GC问题。经过排查发现部分日志将事件上下文转json打印,而内容事件转成字符串部分很占内存,因此第一个优化点就是调整不规范的日志打印问题。
优化调整JVM参数
经过大对象优化后GC时间有所下降,超时笔数也在降低,但离目标水位还有差距。进一步分析JVM参数发现适当调整:InitiatingHeapOccupancyPercent
可以平衡GC频率及回收时机来达到降低耗时的目的,经过一系列变量控制对照实验验证,寻找到一个适合内容风控引擎的配置后进行了调整,成功率效果较为理想。
升级JDK21
尽管成功率提升较为理想,但仍未达到我们的预期目标。通过进一步对于垃圾收集器的分析,发现JDK11之后推出的ZGC在提升GC吞吐及提效回收时间上有极大增益,主要原理如下:
ZGC使用了染色指针技术,搭配上读屏障和转发表,因此可以在用户线程继续运行的同时进行内存整理(包含了存活对象转移和垃圾回收),这是CMS和G1都没有办到的。CMS可以与用户线程并发执行内存回收但没有进行内存整理;G1使用标记整理算法但回收时就要STW。
更深层次的原理分析请大家自行查询ZGC官方文档:
https://openjdk.org/jeps/439
经过上面一系列GC优化后,GC时间平均减少70%以上,上游请求成功率也提升一个数量级。
B、经常出现失败率飙升的尖刺
引擎经常在流量平稳的情况下出现短暂的超时量暴增的尖刺现象,即在几分钟内超时失败量暴增5倍以上后又迅速回落。经排查,发现问题是由于当时机器频繁进行GC导致的。因此,一开始是和第一个日常失败率高的问题当做一个问题,针对优化GC耗时来解决,但效果并不佳。
进一步分析问题时,我们注意到了两个线索:一是这种现象在周末几乎不出现,二是出现时所有机器几乎同时在频繁进行GC。根据这两个线索,分析问题可能与系统发布有关。最终排查发现,问题的根源是由于关键词的全量发布导致运行时构造仓储占用过多内存,从而引发频繁的GC导致。因此进行了关键词全量构建调整为增量构建的优化,优化后尖刺问题得到明显缓解。
②限流蓄洪功能产品化便于配置运营
预期外的上游业务流量的突增,会导致系统及下游模型等水位升高可能造成故障,之前有过类似的应急案例。内容风控引擎现已支持通过单机令牌和定时拉取及上报至分布式缓存的限流模块,实现了基于业务场景维度的集群限流功能。但对于一些高风险场景,简单的限流返回默认通过结果会造成风险遗漏的隐患,因此引擎还具备在触发限流后,利用数据库分库分表存储事件流水,通过定时调度机制实现蓄洪泄洪的能力。
现在主要的问题是这一套限流蓄洪功能使用需要通过一个复杂度很高的配置推送才能生效,这导致除了少数技术外,大多数策略同学难以独立完成配置。为此,我们将此功能产品化,降低使用难度。
③通过引擎事件高保能力建设,解决链路中任何异常点导致上游卡单的问题
现在内容风控系统是一个业务形态多、流量大、链路长、多异步处理环节的复杂系统,在发生异常时不能承诺100%按时正常返回业务结果。因此需要建设事件高保机制,通过异常感知重试来解决该问题。
④系统实现单元化架构,消除资源和容灾能力瓶颈
公司这几年开始发力内容化业务,经过一段时间的创新和孵化,各类泛内容业务逐渐兴起,且部分内容业务已经进入规模化发展阶段,对容量水平扩展的诉求也日益强烈,当前我们所面临是之前没有实现单元化架构导致机器资源出现限制,触达内容容量的天花板等问题,需要进行架构演进升级。
2)高质量
高质量是一种软件开发优秀实践经验的沉淀,通过设立开发规范可有效减少人为故障的发生,通过合理的系分理念,提升系统的可维护、可演进能力:
通过高质量规范建设可以提升团队技术系统品质,减少故障发生隐患。
目前,我们已经建立了统一的编码规范、系统分规范,以及评审、自测和发布前集中代码CR的机制,这些方面做得比较好。当前亟待解决的问题是预发MR占比较高的问题。通过与质量团队合作,构建了预发MR的离线报表进行分析后,我们发现大家做预发MR主要有以下三个原因:
线下环境不方便测试;
对于正在进行的预发MR缺乏足够的关注,例如serverless模块的发布;
统计方式不合理,例如在多分支联合发布时,预发阶段的合并冲突解决也被视作一次预发MR。
针对上述三个原因,我们联合质量团队共同研究解决方案。目前,关于第一个不便于测试的问题仍在积极处理之中,其他方面已达成共识,包括提升大家对多线下自主测试的认识,以减少直接进入预发测试和MR的情况,以及优化不合理的统计方式。
3)勤自查
勤自查主要是需要各应用owner对自己负责的应用上下游依赖、中间件、内部资源及机器水位保持日常关注:
应用的上下游依赖关系、中间件、内部资源和机器状态时刻都在发生变化。因此,定期进行review是非常必要的,这可以帮助我们提前识别并解决潜在的故障隐患。基于这一目的我们定义了应用owner需关注的系统指标,并收集了相关的离线数据,制作了DI报表以进行定期推送。同时,通过每月review异常指标,激励大家积极主动地修复系统应用中的潜在问题。
2、事中降影响
降影响,即降低故障发生后的影响范围。具体可以再拆分三项:早感知、快定位、急止损。
1)早感知
早感知:即通过各种系统保障手段(例如监控预警)来尽早发现甚至预测系统故障。结合内容风控系统特点主要有以下几项:
①建设应急值班机制
稳定性建设不是某一个人或少数几个人能够独立完成的,仅靠少数人容易造成故障发现响应时效慢的问题,因此需要团队每一个成员都具备相应的意识并积极参与。我们基于内容风控团队的业务特色将团队拆分为五个应急小组,每个小组设立组负责人:
业务维度分为两个小组:直播音视频组、大模型安全组。
平台基座维度分为三个小组:识别引擎组、能力中心组以及二三道防线组(包括传播追溯和回溯)。
同时建立了一些配套的机制:
A、告警触达策略建设
各小组将根据各自的关注点建立相应的监控与告警,并将告警结果汇总至同一个群组,以实现统一管理。为确保应急告警能够及时传达,我们制定了一套值班触达策略,具体逻辑如下:值班以天为轮换周期,每7天更换一组,每组由2名同学值班,分别担任主值班和备值班。当监控系统发出告警时,会在1分钟内通过应用DING通知主值班;如果主值班未及时响应,则在2分钟时通过电话通知。如在10分钟内仍无人响应,将启动通知升级策略,及时通知备值班人员及相关小组的负责人,以减少触达丢失的概率。
B、应急指标建设
为了增强大家的风险意识,提高应急响应的积极性,并持续优化监控告警配置,我们对值班期间的离线数据进行了统计分析,提取了关键指标,如:分钟级响应率、小时级完结率、告警完结率、有效告警比例以及应急噪音比例等,制作了DI报表进行定期review。其中,分钟级响应率、小时级完结率和告警完结率能够反映同学们的应急响应效率,而有效告警比例和应急噪音比例则通过定期review指导我们持续降低监控告警中的噪音,并进行相关优化,从而在减少告警干扰的同时提升告警质量。
②持续完善、降噪监控告警,提升感知时效
A、监控告警建设
监控告警建设主要分两个维度:
基于系统指标的通用监控告警:error量级、service成功率、rpc成功率、db成功率、cal成功率、消息发送指标、消息消费指标、端口可用情况、系统CPU水位及系统FGC总量等;
基于业务特性的监控告警:游事件请求成功率、下游能力成功率、关键业务跌0及对外给出的拦截处置量级等。
B、降噪
我们认为,与监控告警的配置相比,更为重要的是如何平衡告警与噪音的比例,尽可能降低噪音。以下是我们常用的几种降噪措施:
减少重复告警:大多数告警的配置并不是由单独个人完成的,往往需要多个人长时间的协作。在这个过程中,难免会出现一些重复或相似的告警。通过合并和统一这些告警,我们可以有效降低噪音。
分级告警:采用不同的告警级别,以确保高优先级告警能够及时处理。例如,对于一些上游请求耗时升高及系统高保重试异常的告警,我们可以在夜间进行降噪,只保留关键的P0级告警。
合理的阈值设定:之前在内容风控系统中,有一个关于外部拦截量上涨的告警,配置条件是数量。然而,随着业务量的增加,当请求与拦截的同比上涨时,实际上可能是一个合理的现象。因此,我们将拦截告警配置分为两类:对于调用量较小的请求,仍然按照数量设置预警条件;而对于调用量较大的请求,则采用拦截比例作为预警条件,以达到降噪的目的。
通过应急值班机制及监控预警机制的建设及实施,应急发现时效基本达到提升一个数量级的目标。
2)快定位
定位故障原因的时效在事中尤为关键,快速的定位能力能极大减少故障带来的影响。
为了提高故障发生时的定位效率,我们回顾了历史上内容风控团队的应急案例,发现主要问题集中在两个方面:
上游请求超时量激增。
异步引擎消息大量积压。
针对这些应急案例,之前的定位手法通常需要人工打开监控筛选排查具体原因,比如是哪个业务的调用量增加导致的问题?还是某个下游服务响应时间变长所致?这种逐个场景分析的方法效率较低。
因此,我们特别针对上述两点设计了综合监控方案。当检测到上游请求处理时间增长或引擎消息积压触发警报时,系统将自动检查相关业务场景下的调用次数、失败率以及响应时间是否也有所上升,并同步监测下游服务是否存在响应延迟情况。通过这种方式,可以更加快速准确地识别出故障根源所在。
这样的改进不仅提高了故障诊断的速度,还减少了人工干预的需求,从而使得整个定位流程变得更加高效可靠。
通过联合告警定位能力建设,应急定位时效基本达到提升一个数量级的目标。
3)急止损
急止损:“止血”大于“修复”,故障发生后第一反应永远是“优先止损”。止损的时效提升能极大减少故障带来的影响。
预案在止血过程中是至关重要的。我们在预案的建设方面已经有了较为完善的基础,而核心问题在于让更多的同学参与到预案演练中、以及将核心预案自动化,确保在关键时刻有更多的人可以执行预案、以及核心预案可自动化,提升止血效率。
①定期演练,提升止血时效
与高可用团队合作,每季度进行一次预案演练。
②核心预案自动化
在预案中,最关键的是因为策略或引擎故障所导致内容拦截量级暴涨的降级预案。在许多情况下,当故障发生时,技术团队需要依靠经验来判断是否可以执行相关操作。通常我们必须先与场景owner确认情况,才可以进行推送,而这种时间差本身就潜藏着重大的使故障升级的风险。因此,我们决定将该功能产品化自动化,供场景策略同学进行运营。
通过预案演练及自动化,应急止血时效基本达到提升一个数量级的目标。
3、事后优改进
优改进:及时进行复盘,以实现故障闭环,并不断提升稳定性建设水平。
之前,我们在组织复盘机制的建设方面还有待完善。因此,除了故障后的AAR复盘会外,我们还通过每月的月会,对与稳定性相关的事项和数据进行内部分析与总结。会议将重点关注各应用的健康状态、各组的应急数据指标以及对进一步完善稳定性建设的思考与讨论,以推动稳定性建设的持续优化和进步。
五、成果
1. 上游请求成功率提升一个数量级;
2. 事中应急时效提升一个数量级;
3. 解决链路中任何异常点导致上游卡单的问题,卡单量级周维度统计下降一个数量级;
4. 系统实现单元化架构,消除资源和容灾能力瓶颈。
六、总结展望
在进行系统稳定性学习建设的过程中,提升了自己的“系统化思考问题”及“透过现象看本质”的能力。稳定性建设过程中会遇到许多问题,而这些问题往往只是一层表象。如果我们没有进行深入的根因分析,便急于寻求解决方案,就很容易陷入思维误区,误以为在努力解决实际问题,甚至因此获得成就感。实际上,我们可能只是创造了本不存在的问题。举个例子,我在处理风控引擎超时尖刺时就经历过这样的情况:
引擎经常在流量平稳的情况下出现短暂的超时量暴增的尖刺现象,即在几分钟内超时失败量暴增5倍以上后又迅速回落。经排查,发现问题是由于当时机器频繁进行垃圾回收(GC)导致的。因此,我们提出了通过调整JVM参数来降低GC时间和频率的解决方案,但经过一周的调整观察,发现最终效果不佳。
进一步分析问题时,我们注意到了两个线索:一是这种现象在周末几乎不出现,二是出现时所有机器几乎同时在频繁进行GC。根据这两个线索,分析问题可能与系统发布有关。最终排查发现,问题的根源是由于关键词的全量发布导致运行时构造仓储占用过多内存,从而引发频繁的GC导致。
经过团队小伙伴的齐心协力及高可用、SRE、质量、策略团队的鼎力支持,使内容风控域的系统慢慢达到了比较健康的水位。但随着接入业务逐渐增多,靠一个大而全的引擎来支撑风险逐步增高,未来需要推进架构进一步升级,提升可扩展性,建设面向泛内容的、面向OU业务可实现安全运营模块灵活组装的识别决策引擎。