想了想,还是把过往一段时间里,我们在稳定性建设中的实践记录下来,包含一些思路和方法,也算是一部大型踩坑记录,也只是一些实践过的野路子、野方法。
团队背景
我们整个团队是基础服务团队,包含云上运维、大数据、devops
、中台基础服务(用户、文件存储等),还有一个大的测试团队(其中也有业务测试),手上维护的都是一些基础设施、存储、中间件和支撑型服务。
由此可见,团队手上服务的稳定性如果出现故障,对于上层业务的影响范围是非常广的。
所以在业务心目中的印象,应该算是“存在感”越弱越好,说明故障的次数少、严重程度低,大家不用找到我们。
治理目标
为了降低“存在感”,理想的情况当然是不发生故障,团队最早也是按这个方向去努力的,但是从实际情况来看比较难。
当前我们云上800节点,应用服务4000+,有大体量的C端产品,也有复杂业务模型的B端产品,规模和业务复杂度总会带来各种各样的问题,想没有一点故障基本是不太可能的。
既然如此,稳定性目标就不应该是没有故障,而是控制并减少故障对业务的影响,当故障发生时,将影响范围降到我们可以接受的范围内。
那么这时,新的问题就来了,怎样的影响范围是可接受的?
故障分级
可接受的故障应该是影响程度、范围、时间可控的,所以可以根据这几个维度来做故障分级。
- 影响程度:业务流程影响度,是否造成损失,数据是否异常或泄露等等
- 影响范围:影响的用户/客户比例
- 其他:一些特殊因素,比如关键业务活动、重点客户等
举个例子,
等级 | 影响程度 | 影响范围 |
---|---|---|
致命 | 主流程无法正常工作、敏感流程无法执行、敏感数据外泄等 | 超过30%的活跃用户 |
严重 | 主流程无法流畅、高效工作并带来客诉、非核心功能无法使用等 | 超过20%的活跃用户 |
一般 | 功能无法流畅、高效工作带来用户体验下降等 | 超过5%的活跃用户 |
程度/时间 | 持续5分钟内 | 持续10分钟内 | 持续30分钟内 | 持续30分钟内以上 |
---|---|---|---|---|
致命 | P2 | P1 | P0 | P0 |
严重 | P3 | P2 | P1 | P0 |
一般 | - | P3 | P2 | P1 |
稳定性目标
从上述的故障分级来看,我们最不希望发生的是P0级故障(最严重的等级),这个等级的故障往往对品牌、产品口碑、用户信心都会带来很大影响。
因此,团队的目标也从原来的==🔴没有故障==,变为==🔴没有P0级故障==。
中间其实我们也试过定义SLA、MTTR等目标,最终发现这些指标都指向P0级故障的发生,且使用这个目标,给到非技术相关方是一个比较好理解的目标,所以延用到现在。
治理思路
定了目标,有了OKR中的O,那么KR该怎么定呢?
这里的比较好的方法是做演绎,故障达到P0等级,要么持续时间长,要么影响程度大,要么影响范围广,那么治理思路也就可以从这几个方面展开,
- 事前预防 —> 提前治理保障,降低风险,通过优化、治理等手段
- 事中快速发现及恢复 —> 缩短故障恢复时长(MTTR),故障的快速发现及故障恢复
- 降低故障影响范围 —> 拆分、隔离
其中,降低故障影响范围更多也是事前预防为主,所以我们拆成事前预防
、故障发现
、故障恢复
三个部分来讲
事前预防
事前预防主要是发现风险点,并尽可能将风险降到最低。过往的实践中,我们主要会从几个角度入手,研发流程、业务及技术核心指标、专项优化。
研发流程中的保障
一般说到稳定性的时候,更多是描述线上质量,研发流程中的过程质量应该在上线前得到保障,但实际上我们会发现,很多问题都是研发流程中节点交付质量不高导致的。
而在其中最容易带来稳定性风险的节点,是技术评审及版本发布。
- 技术评审
技术评审一直是我觉得最值得投入精力的环节,评审中发现的问题往往能够规避未来很多的稳定性、扩展性和维护问题。
看两个实际案例,都是技术评审未发现问题最终带来的线上故障。
- 数据库表设计缺陷
库表设计中往往能够发现设计不合理及一些潜在的风险,原来我们就因为自增ID越界、编码不一致带来的死锁问题等引发过线上故障。
- 分片计算方案缺陷
在最初方案设计的时候,用户量还不算特别大,百万用户也只会有1000个ZK节点,可当用户量到达千万时,节点数量突增,多实例多线程并行处理时带来了ZK写流量突增,带来ZK负载。
而且当时还有一个问题是ZK节点建了没删。 实际上这个场景有很多方案可以解决,按节点Index基于用户ID来分片就不需要使用ZK节点、任务拆分投放到消息/缓存队列再异步消费,这些方法的资源消耗都会更低,且可以支持快速横向扩容。
- 版本发布
在团队早期,线上问题集中发生在发布环节,一些细枝末节的问题导致服务一上线就被线上流量打崩。
发布阶段是研发流程的最后一个阶段,流程中的问题最终都会汇总在发布时爆发,为此我们做了很多基础能力及流程规范的建设。
其实整体核心思路就是,前期评审保证方案设计正确,流程、规范、标准约束过程交付质量,发布时通过预发、灰度等策略在线上进行小规模验证。
- 研发流程控制
零散的工具能解决散点问题,但是还是依赖人的主观意识及经验来使用,而强依赖人容易带来各种失误、遗漏,因此我们也实现了一条简单的研发流水线,用于做流程中各节点的把控。
常态化治理
随着用户量、数据量及复杂度的变化,服务的状态会不断“恶化”,接口变慢、负载升高等问题逐渐显现,需要通过不断的治理来保持服务正常。
在这个过程中,最关键的往往不是如何治理优化,而是如何发现问题,这就需要比较完整的监控体系,我们放在下一个章节讲,这里先看日常治理优化。
日常中服务的治理优化,我们主要关注接口性能,接口的性能直接影响到用户体验,也是性能开始恶化时最直接的体现点。通常我们会设定接口的通用性能指标,当达到指标阈值时,就代表需要进行优化,比如接口慢于500ms、SQL耗时大于100ms等。
可以作为研发团队的线上核心指标,每月关注和回顾优化进度,常态化治理
在过往的优化中常见的问题,包括索引覆盖不全、循环调用、IO优化、锁姿势不对等等,
原来也解决过一些CAS锁带来的突发负载尖刺问题[一次Elastic APM导致的线上性能问题],其他就不展开了。这个过程基本通过持续优化解决随业务增长(伴随着用户量、数据量增长)带来的服务风险。
专项优化
除了日常治理,我们会阶段性的做一些专项优化,一般会集中在每年的旺季之前,以保障旺季的用户体验,专项优化一般会围绕系统中的关键组件、流程、存储或者架构等进行集中分析和优化。
以我们所在的行业为例,一般在3月、9月的前1~2个月集中做专项优化
- 拆分隔离
拆分隔离的好处是降低故障影响面,由于团队本身提供的是基础设施、中台服务及底层运维能力,我们更多是做业务间的隔离,简单一句话描述,就是鸡蛋不放在一个篮子
。
以实际的例子来看,
- 服务分级与集群隔离
我们团队的角色有点像是二道贩子
,从云厂商手中购买云主机,在手里维护一个主机池子,然后业务部署服务时,基于K8s调度业务Pod部署到某一台主机上,从结果上看,一台主机上可能跑了非常多不同的业务容器。
既然我们手中的是一个主机池,我们也会允许一定程度的超卖(pod request/limit)以提高资源利用率,但这也带来了一定的风险,单个业务的突发高负载可能影响同台主机上的其他业务。
因此,我们有对服务、主机池进行分级,比如A级普通的业务放在A级标签主机池;S级业务放在S级标签主机池;SS级的服务相对特殊,一般都是用户体量较大的产品,我们会单独享有独立的主机池,只有这个服务使用。
后期处于降本的考虑,也逐步在做主机池整合,但是SS级核心服务依然享有独立主机池
- 违规CDN链接处理不及时导致主域名被停止解析
故障原因非常简单,我们某个业务A有文件上传场景,有用户上传了违规文件后,导致文件下载时CDN对应的域名被封,直接停止了主域名解析。
1 |
|
由于各级DNS缓存没有完全过期,过程影响了*.mainhost.com
下大概10%~30%左右的流量,几乎所有的业务都受损。
故障的原因我们暂不深入探究,原因千千万,我们要做的是当以后相同的问题发生时,如何降低影响面。
因此我们把所有的核心业务都找了出来,为每个业务拆分了一个和主域无关,且业务独立的CDN域名。
1 |
|
这样单个业务以后由于各种原因CDN域名被封,也不会影响其他业务,更不会影响主域名。
除了这些外,中间件隔离、基础服务隔离、核心业务流程隔离等我们也有一定的实践,就不一一说明。
- 数据库
从扩容的角度来看,由于服务是无状态的,横向扩容可以快速通过增加Pod数来完成,而在数据库层面,早期一般都是单节点/主从/三元复制等部署架构,比如我们大部分业务还是用的自建MySQL实例,也能满足绝大多数的业务需求,但是随着业务增长,当活跃用户量级逐渐增长到百万时,数据库会一般都是首先出现瓶颈的地方,此时数据库扩容往往伴随业务改造、数据重平衡等问题。
我们的场景是读多写少,在数据库优化的思路上包括:
- 降低QPS,减少业务不必要的查询,或优化库表设计减少查询次数;
- 降低查询成本,主要是减少扫描行数、索引优化、优化返回数据量等;
- 从库读,增加从库数量,读流量走从库缓解主库压力,但前提业务场景可接受数据同步延迟;
- 换数据库,新生代数据库往往采用存算分离架构,天然分布式存储,具备非常好的扩展性,同时兼顾OLTP、OLAP能力;
看几个优化的案例,
- MongoDB IO优化
DBA反馈某个核心服务的MongoDB,其中核心表中数据已经达到60E,其IO在业务高峰期已经跑到90%。
而这个表的查询基本都包含了等值、范围查询和排序,之前的查询组合多,部分查询无法覆盖全部字段,且查询使用索引时也并没有遵从ESR规则,导致索引效果不好。
理想情况下,当然是所有查询都能走全部索引,但在这个量级的表中建索引成本也是非常高的,因此我们对业务接口、SQL进行分析后,只对频繁查询场景组合进行索引优化,减少扫描文档数,提高索引命中率,最终完成了IO优化。
- HTAP存储引入
在一个业务场景中,会记录某个特定的行为,数据上既要有流水数据,也需要基于时间及维度做实时聚合。之前业务方案比较复杂,通过关系型数据库记录流水,再每天定时/离线ETL等方式做聚合得到中间结果,回流到业务。
这个方案看上去好像也没有什么问题,最多就是数据T+1,但是随着用户量增加,数据量也快速膨胀,流水的数据量达到140E,且跑了5年+,按天聚合的数据量也不小,这套方案整体也比较复杂,维护、扩展成本也越来越高,数据库负载也越来越大。
中间我们用过非常多的存储,从最早的TiDB,到引入Doris来解决预聚合问题,到现在最终切到HTAP存储上,常见HTAP采用列存副本、列存引擎或物化列存索引等方式在OLTP存储上扩展OLAP能力,这个过程中我们测试了PolarDB和Oceanbase,两者均能满足需求,并最终选择了PolarDB。
在140E的数据量场景下,无论架构的复杂度,以及查询耗时对于原场景都得到了较大的优化。
- 缓存
缓存我们主要使用的Redis
,自建及云产品都有,出现故障的情况不多,更多是做治理,包括内存大小、TTL、负载优化等。在C端业务的场景下,为了给用户提供更好的体验,是会有频繁使用到缓存的,所以一旦出现问题,带来的影响也会比较大。
我们分别来看一个治理及线上故障的例子。
- 内存大小优化
早期有个业务用了云产品版Redis
64GB,在旺季前内存已经涨到了55G,眼看马上就要跑满,正常是直接升级即可,但是费用会比较高,所以优先做了一轮优化,删除了一些老旧无效且没有过期时间的key
,同时使用protobuf
代替jdk
序列化,减少序列化后对象的大小,且序列化/反序列化速度更快。
JDK序列化会带上相关类信息,而protobuf不会把schema相关信息放入序列化后的结果
优化后,Redis
的内存使用大小从55GB下降到了22GB。
- 热点key引发线上网络带宽跑满
一个业务场景有一个热点key
,由于key
和value
都比较小,日常情况下及时QPS较高,但是对负载并没有太大影响。
但是有一天,业务突然发了一个版本,研发将大量信息放入了该key
中,value
达到200KB+,而由于key
的设计问题,该key
只被分布到了集群版Redis
的某一个节点上,导致该节点网络出口流量突增,带宽跑满。
后续紧急做了版本回退,并恢复了该key
的值,才让故障恢复。长期想要避免这类问题,除了技术评审时的评估外,对于热key
的分布也需要让其尽可能分散在Redis
的不同节点上,避免流量集中带来某个节点的故障。
这个场景下是固定
key
,其实可以带上后续一些固定后缀,让其hash
到更多节点上,将压力分摊,也能一定程度缓解热点key
带来的集中负载问题。
- 日志优化
不展开说太多,但是过往日志带来的负载问题真不少,大量日志打印带来的高负载、IO跑满等等,是比较容易忽略的一个部分,我们一般也会在旺季前调整日志等级,降低可能带来的高负载风险。
上图是一个业务在高峰期时打大量日志,且日志加行号,为了计算行号带来占用了大量CPU资源。
- 压测
在每年两个旺季前,我们都会对核心业务做压测,保证旺季稳定。压测能够帮助提前识别出高负载下可能的性能问题,也能帮助做容量评估。
在我们的实践中,除了发现业务的性能问题外,压测工具上也遇到了不少问题。现在测试比较常用的是JMeter
,为了实现高流量发压的需求,需要将肉鸡组成集群共同发压,这个过程中,JMeter
发压的策略及性能(流控策略、连接复用、Dubbo插件)、压测分析(集群监控、负载判断)都可能出现问题,具体可以看我之前的文章。
【又是压测惹的祸,错误姿势引发JMeter+InfluxDB写入问题】 【又双叒是压测,来看看这次发现哪些新问题】 【一次实战压测流程及问题梳理】
(这里对于肉鸡、被压测服务/容器的负载判断是非常重要的,会直接影响压测的准确性)
为了满足高并发场景下的压测需求,我们有构建一个简单的压测平台,底层用的也是JMeter
,之后也引进了Takin
的开源版进行魔改,做线上压测。
在众多压测工具里,其实
JMeter
的性能并不好(Java写的),之前实测JMeter
、一个Go语言的压测工具、再加上徒手用Rust
写的工具,在限定相同的资源用量下,Go
语言的压测工具比JMeter
强3倍,Rust
工具比Go
工具强3倍。
- 故障演练
故障注入及演练是这几年我们才一点一点开始做的,更多是通过模拟故障发生对服务进行验证,包括:
- 故障恢复时,服务是否能自恢复
- 故障发生时,恢复手段是否生效,并在指定时间内恢复(1-5-10中的10)
在工具上,我们直接使用了开源的chaosblade
及chaosblade-box
,在业务打包镜像时,预埋chaosblade agent
启动时自动注册到box
下,再下发策略注入故障。
以我们再IoT场景下的一个故障演练为例,主要围绕业务核心服务及流程梳理故障点,并故障点进行模拟,判断服务状态。
在这个过程中,我们会验证各种故障场景下的恢复手段是否生效,且在指定时间内恢复,其中恢复手段包括扩容、中间件重建、接口/SQL级熔断等等。
故障发现
在故障发现上,主要还是依赖监控的完善,在监控体系构建上,从底层基础组件到上层业务指标、流量的监控,我们基本都实现了覆盖。
监控粒度从下往上逐渐变粗,对于底层监控,更多建设通用监控能力,满足开发、测试、运维需要;上层监控则更加业务定制化,与业务特征关联更强。
基础组件监控
更多是运维层面的监控指标,面向的用户也是运维同学,该层面的监控搭建并不复杂,业内都有对应的开源方案,通过exporter
进行指标采集,汇聚存储到prometheus
中,基于grafana
做数据展示。
同时,由于运维同学需要面对的组件类型较多,不同的组件关注指标也会有所不同。对于值班的同学来说,逐个去看指标或者分析指标告警并不容易,为了抹平不同组件的差异,让监控、告警更加好判断,我们抽象了健康度
的概念。
健康度
描述一个组件、资源的健康情况,将组件/资源的核心指标通过公示计算抽象成一个[0, 100]的分值来。
服务监控
目前我们的服务都是基于K8S
做容器化部署,所以服务监控主要围绕容器展开,包含CPU、内存使用率、网络IO等,我们的业务也都是JAVA
/Kotlin
为主,提供HTTP接口,因此也会做JVM
、接口时延的指标监控。
同时,由于大部分场景的故障最终都会导致CPU
使用率的上升,因此我们也为业务单独提供了通用的容器CPU
使用率告警。
这里也有一个特殊的背景,中间有一段时间我们在推动业务做成本控制,发现大部分业务在旺季过后并没有及时缩容,导致资源使用率较低。 在业务缩容后成本得到控制,但是也带来隐藏的风险,就是逐步业务量稳步上升后,如何发现需要扩容,资源使用率的告警就是我们应对的策略之一。
链路监控
业内全链路监控方案,最早都是基于Google Dapper
,论文中描述了构建全局唯一的链路tracdeId
,并通过透传该id
将上下游关联,得到全链路的监控信息。
在我们的全链路方案中,主要会分为四个部分需要采集链路信息,包含端侧、中间节点、服务节点及存储节点:
-
端侧链路。端侧作为链路的发起节点,需要构建全局唯一ID并向后透传,我们主要通过在
HTTP Header
预埋traceId
来实现; -
中间节点链路。这里更多是指端侧到服务节点中间的链路节点,会包含云防火墙、网关等。中间经过的云产品,我们通过对接云厂商接口获取到WAF、SLB等节点的监控信息,通过域名及URL进行链路关联;在网关上我们主要使用的是
nginx
系的网关(apisix),通过lua
插件也能实现信息采集和链路关联; -
服务节点。由于我们的业务服务都是
Java/Kotlin
为主,通过字节码注入可以比较轻松的实现traceId
透传,并通过字节码注入也能获取到部分中间件及存储的调用span
,覆盖整个链路的核心部分。 -
存储节点。通过字节码注入采集到的存储节点调用
span
,配合域名、端口也能快速存储节点关联到链路上。
通过如上链路信息的采集,我们就能知道单个链路请求的监控信息,也能聚合出某个时段的链路监控信息,实现全链路监控。
业务监控
到这一层监控,更多和业务场景的核心指标有关,我们主要提供监控能力,业务确定监控指标后通过接口上报,我们提供分析、可视化能力。同时,这里业务场景的核心指标更多是研发关心的业务指标,而非产品指标。
举个栗子,该业务是一个少儿英语学习产品。
- 概率监控
场景中,孩子完成学习任务可以概率性获得字母,集成制定字母后可以获得VIP奖励等,而在概率性获得的字母中,每个用户都会有一个字母获取概率会比较低。(类似支付宝集五福)
业务上报数据后,通过聚合得到每个时间段下发的字母数量,以及每个字母下发的概率是否符合预期。
- 胜率监控
场景中,孩子们可以相互PK答题获取积分,积分有不同的排名或者段位。同时,为了更多正向鼓励孩子学习,通过获取胜利来增加孩子的学习积极性,在PK中我们也会监控孩子的胜率,当孩子连续输了N场,或胜率低于N%时,我们会为孩子PK匹配机器人,提升孩子的获胜概率。
通过这个监控数据,我们就能知道线上PK游戏的运行情况,动态调整全局难易度、胜率等,保证在不失PK真实性的情况下,通过更多的胜利鼓励孩子持续学习。
是不是有点“XX荷官,在线发牌”后台控制胜率的感觉?(手动狗头)
流量监控
上面的四层监控,其实基本能满足大部分监控需求了。
那为什么还需要再加一层流量监控?
想象这样的场景,业务链路前段的中间件(域名解析、网关、负载均衡等等)出现问题,正常用户流量根本没有流入,这种情况下会有告警吗?
没有流量进来,也就没有异常告警、延迟告警,状态码告警也一切正常,通用的告警规则根本无法发现流量没有进来,监控看板虽然能发现,但是值班同学也并不会实时盯着监控看,那要怎么发现呢?
运维侧可能有全局流量监控,但是无法发现某个产品的流量异常,可能会被淹没在大盘流量里。
上面的故障就是在域名迁移过程中,无意中回滚了变更导致域名下的负载均衡解析异常,此时从业务监控的角度没有报任何异常,只是流量没有进来,直到用户反馈才发现了问题。
因此,我们实现了一套流量监控方案,通过流量骤升骤降、变化阈值检测及环比相似度检测来判断流量健康状态。
具体实现方案这里不细说了,感兴趣的同学可以看我之前的文章。
故障恢复
如果能第一时间发现故障,那之后就是如何快速恢复服务。
这一块我们做的并不好,过往依赖人的经验快速找到可能的问题,并尝试恢复,而恢复过程中常常会发现恢复手段无效,甚至引发更严重的结果。而在恢复过程中,容器启动、预热、流量转发、中间件重建等过程也会消耗大量时间,导致恢复时间较长。
有一年甚至因为恢复手段无效,导致故障整整持续了12小时。
为此,我们也做了一系列的尝试来保证故障场景下的恢复,核心思路就是做故障演练,通过故障演练模拟故障发生,并验证恢复手段的有效性,并保证能在期望的时间间隔内恢复。
故障注入
故障注入有非常多工具,大厂基本都有结合自身技术栈构建的故障演练平台。最早我们也考虑过基于字节码注入自研实现故障注入及控制,后来调研业内开源组件后,最终使用了阿里的chaosblade
(注入)及chaosblade-box
(控制面)来实现。
其中满足了我们大部分的故障注入场景,包含服务层面的延迟及异常、容器层面的异常等等,使用chaosblade-box
也能实现分布式场景下的多节点故障模拟。
如果只是想简单做一些基础的故障注入,使用chaosblade-agent
或者直接使用tc、stress等命令也是可以的,chaosblade的一些故障注入也是使用这些命令实现的。
开源版本的chaosblade-box应该有一些能力裁剪,比如恢复手段、监控等,这些推测和基础设施关联且没有进行抽象提供接口出来,所以控制面的功能是缺失。
恢复手段
故障恢复往往需要根据进行初步的原因分析,才能找到合适的故障恢复手段,这个过程很依赖监控的全面性。同时,在恢复手段的使用上,除了“对症下药”,还要结合业务的实际场景来进行恢复。
也简单介绍一下我们常用的基础恢复手段。
扩容
扩容主要应对的是高负载场景。说来惭愧,早期我们有30%左右的线上故障是高负载带来的。(现在想想都觉得离谱)
而高负载故障的原因可能有很多:
- 业务突发流量。常见的情形是运营活动带来的集中并发访问,比如线上/线下活动、大促、信息通知集中推送带来的并发访问等等,而这个场景下会出现问题往往是团队间的信息没有同步导致;
- 业务特性带来的高峰。这类常常是业务有周期性高峰,比如在教育场景里,上午9点/下午14点集中上课、晚上19-20点集中作业等等,随着用户量增加逐渐带来的高峰期负载非线性变化,这个场景核心原因是监控告警及响应机制的不全面,一般都可以避免;
- 其他。统归为其他吧,很多“自找苦吃”的场景到来的人为高负载,比如洗数据、改表、压测流量泄露、相关方代码问题带来的流量放大等等,只有你想不到的。
总归来看,这类高负载问题基本能通过扩容解决,常见的方式也是水平扩容和垂直扩容,水平扩容应对整体使用率水位过高的场景,垂直扩容应对业务突发尖刺负载的场景,方法比较常规不多说。
同时,这里更想聊的一个点是有效扩容,看看下面几个例子:
- 接口慢SQL带来的线上故障
故障原因很简单,某个接口访问突然变为高频,接口中存在慢SQL,在接口QPS变高的情况下大量慢查询导致MySQL负载跑满。
这种情况,用户侧请求超时,服务容器负载也会升高,如果没有下钻发现是慢查询的问题而直接水平扩容,反而会进一步加大数据库负载带来恢复难度,正确的方式应该是先熔断、限流或者走SQL rewrite。
- 故障后的大量用户重试带来二次故障
在服务故障后,用户侧会提示功能异常,如果是刚需功能场景,用户会不断的重试,此时如果仅仅只是恢复了故障服务,那么往往用户重试的翻倍流量往往会直接打垮服务,下面的图能比较直观的展示故障前后的用户访问量变化。
因此,在故障后的扩容数量已经要充足,以保证支撑服务恢复后用户的集中并发重试和访问。
同时,大量用户重试不仅会给当前产品的服务带来负载,当业务链路关联到其他产品、服务时,也需要考虑同步扩容。 (过往同样有过重试流量打垮业务链路上游其他产品服务的情况)
熔断/限流/降级
这三种恢复方式放在一起来讲,主要是本质上都是提供一定层度的有损来保证整体服务正常,在能力实现上,我们也主要基于字节码注入
来实现以上三种能力,他们的使用场景也略有不同。
- 熔断。我们更多是直接做接口级熔断,当服务存在慢接口直接拖垮服务实例时,扩容并不能解决问题,新起的服务实例也会快速被慢接口拖垮,此时我们会通过熔断接口来八正整体服务正常;
- 限流。在故障恢复的场景,限流我们用的比较少,主要还是基础设施层面不完善,无法以用户为粒度进行限流,故障时单个用户体验时好时坏,无法解决根本问题,也不利于服务快速恢复。但是我们有用在一些比较特殊的场景,比如类秒杀及降本的场景;
- 降级。使用降级恢复时,需要一些前置准备工作,预留自动降级机制、降级方案等,我们主要是对一些核心基础设施流程O
在部分故障场景下,会存在部分慢接口直接拖垮服务实例的情况,这种情况下扩容并不能解决问题,新起的服务实例也会快速被慢接口拖垮,此时就需要有更细粒度和有针对的恢复手段,我们常用的就是熔断及限流。
我们也来看几个故障场景:
- ES慢查询导致业务服务故障不可用
背景是我们有一个场景会通过ES实现资源搜索,某天业务高峰期突然收到异常告警,提示服务接口出现延迟及异常。
研发排查发现是ES高负载导致资源搜索超时,同时服务中的线程长时间阻塞也直接影响到容器健康检查,容器不断重启。
此时如果扩容并不能解决问题,新容器启动后收到依然会被延时接口阻塞,并且会进一步加重ES负载,此时最好的方式就是熔断延迟接口,通过提供有损服务来保证其他服务核心流程正常。
图中可以看到,熔断接口后,服务快速恢复正常。由于熔断的接口本身只影响查询功能,业务核心流程依旧可以正常运作。
- 服务降级保证核心链路稳定
当故障不可避免时,可以通过熔断/限流/降级来提供有损服务,不至于带来全线崩溃。
以一个用户登录流程为例,我们可以梳理出最终重要的核心P0
步骤,意味着这些步骤失败,那么主流程一定失败;其他非核心步骤在极端情况下可以熔断,不影响用户正常登录。
在整个登录流程中,只有获取用户信息
和生成登录token
是核心步骤,其他步骤在极端情况下均可熔断,例如,
- 当Redis异常时,可以直接熔断
业务注销判断
和密码错误锁定判断
,这样用户依旧可以正常登录; - 当风控系统异常,RPC调用失败时,可以直接熔断该RPC调用,此时不做风控检测,保证用户正常登录。
多云多活
多云能力的建设,我们当前还处于比较早期的阶段,目前实现了部分基础服务、简单架构业务服务的多云多活,当出现云可用区故障时,我们可以实现快速切流到不同可用区或不同的云,保证服务稳定。
不展开讲,后面单独写一篇文章讲我们的多云建设吧。
技术治理之外的稳定性
看到这里,可能你心里会有疑惑,稳定性应该是一个技术话题,为什么还需要考虑技术治理之外的角度呢?
再洗想一下,稳定性建设真的只是技术问题吗?
很长时间里,我们都认为稳定性建设是围绕平台建设展开的,提供更全的监控告警发现故障,更自动化更高效的工具快速恢复故障,但是从实际团队经历来看,平台建设只是其中一个部分,与技术治理同步进行的还有团队层面的建设。
团队层面的建设会包含两个部分,一个是能力建设,一个是氛围建设。
其中,
能力建设
主要解决能力习得与延续、方法论迭代的问题,通过带教、评审、方案模板、技术分享等方式让个人能力变为团队能力,并延续传递给新人,让整个团队都能具备稳定性实践的能力。
下图为团队的技术方案模板,及压测方法论:
流程标准及自动化
当稳定性建设到了一阶段后,我们发现技术层面的故障逐渐收敛,人为失误带来的故障依旧持续,包括高峰期运维变更、亿级表变更未审核、配置遗失等等,这些本该做对或者本不该发生的行为或事件成为故障核心原因,那就需要通过流程来进行约束。
举个例子,对于数据库变更,我们建设了标准的流水线,由技术经理、DBA审核,同时也包含一些自动检测的机制。
氛围营造
这里的方式比较泛,可以通过特殊的活动、专项来营造稳定性建设、保障的氛围。
以我们每年的旺季为例,
- 目标牵引。我们会在旺季前,从大的团队层面定下稳定性建设的目标,拆解到每个子团队和每个同学身上,让团队的同学都参与到稳定性建设的过程中;
- 集中专项。会在旺季到来的前1~2月,各个团队集中开始做优化,营造一个全员参与的氛围;
- 动员会。在旺季到来的前几周,会发起稳定性保障动员会,提前梳理要准备的关键事项,并在各个团队落地,比如监控告警、巡检的开启及验证,服务扩容,线上配置优化等等;
- 总结与复盘。旺季结束后,针对整个活动过程中出现的问题进行总结和复盘,并基于问题做下一阶段规划。
对未来的展望
记录了非常多内容,其实并不是觉得我们做的好。相反,在这个过程中能发现有非常多可以持续优化和改进的地方。
在监控告警上,方案不统一,且多层监控关联能力差,导致我们无法以更全局的视角去观察线上异常;在方案底层上,过度依赖字节码注入,无法面向未来支持更多语言的细粒度控制;在研发流程上,我们还处于很原始的状态,流程管控的缺失带来的是质量、效率低下,也影响到最终交付线上的服务稳定。
要解决这些问题,不仅是技术上的挑战,更多也是组织架构、职责分工、能力演进上的挑战。
道阻且长,行则将至。