以下文章来源于架构师社区,作者丁浪
作者:丁浪,目前在创业公司担任高级技术架构师。曾就职于阿里巴巴大文娱和蚂蚁金服。具有丰富的稳定性保障,全链路性能优化的经验。架构师社区特邀嘉宾!
阅读本文,你将会收获:
高并发、大流量场景的常见问题和应对手段知名互联网公司的高可用架构和稳定性保障体系前言
我从业之初便开始扮演“救火队员”角色,经常去线上执行“救火”、止损、攻关等应急工作,再通过分析、推理、验证…“抽丝剥茧”的找出背后的根本原因,仿佛自己是个“经验丰富、从容冷静、思维缜密”的侦探。
以前我一直认为线上问题定位、分析处理能力是架构师的“看家功底”并常引以为傲。
但最近这两年我开始思考,其实“防火”比“救火”更重要,正如一句古话“上医治未病,中医治欲病,下医治已病”。下面我将为大家分享稳定性治理经验和阿里的稳定性保障体系。
稳定性治理的常见场景
突发大流量
相信大家对上图并不陌生,尤其在刚刚过去的双11、双12中。这是电商大促场景中执行了最常用的自动预案-“限流保护”,并非很多朋友说的“宕机”、“崩溃”。
“限流”是应对高并发或者突发大流量场景下的“三板斧”之一,不管是在电商大促、社交媒体突发热点事件(例如:遇到“知名女星出轨”),还是在常态下都是非常有必要的保护手段。本质上就是检查到当前请求量即将超出自身处理能力时,自动执行拒绝(或者执行“请求排队”),从而防止系统被彻底压垮。
不稳定服务
讲到“限流”,那就不得不提另外一板斧“降级”。除了我们之前所提到的“开关降级”(关闭次要功能或服务)、兜底、降低一致性等之外,在技术层面最常用就是“自动熔断降级”。“限流”是为了防止大流量压垮系统,而“熔断”是为了防止不稳定的服务引发超时或等待,从而级联传递并最终导致整个系统雪崩。
如图所示,假设服务D此时发生了故障或者FullGC等,则会导致上游的服务G、F中产生大量等待和异常,并级联传递给最上游的服务A、B。即便在服务G、F中设置了“超时”(如果没有设置“超时”那情况就更糟糕了),那么也会导致线程池中的大量线程资源被占用。如果服务H、I和服务G、F在同一个应用中且默认共用同一个线程池,那么也会因为资源耗尽变得不可用,并最终导致最上游的服务A和服务B整体不可用,全部链路都将异常,线上核心系统发生这种事故那就是灾难。
假如我们在检查到服务G和服务F中RT明显变长或者异常比例增加时,能够让其自动关闭并快速失败,这样H和I将不会受影响,最上游的服务A和服务B还能保证“部分可用”。
举个现实生活中更通俗的例子,当你们家的电器发生短路时空气开关会自动跳闸(保险丝会自动“熔断”),这就是通过牺牲你们家的用电而换回小区的正常供电,否则整个线路都会烧毁,后果会不堪设想。
所以,你得结合实际业务场景先找出哪些接口、服务是可以被“降级”的。
架构单点
这个事件大概发生在年,被载入了支付宝的“史册”,也推动了蚂蚁金服整体LDC架构(三地五中心的异地多活架构)的演进。
异地多活架构
突破单机房容量限制防机房单点,高可用内建质量
根据以往的经验,60%以上的故障都是由变更引起的,请牢记变更“三板斧”:
1.可回滚/可应急2.可灰度3.可监控预防质量事故的常用手段:
做好分析、设计、评审,容量评估,规避风险制定规范,控制流程,加入代码扫描和检查等阉割做好CodeReview测试用例覆盖(通过率、行覆盖率、分支覆盖率),变更全量回归尽可能的自动化,避免人肉(易出错),关键时刻执行doublecheck感兴趣的读者可以参考我之前发布在“架构师社区”的文章《关于故障和稳定性的一点思考》,里面有一些实践经验和思考,这里不再仔细展开。
高可用架构的基石--稳定性保障体系
从故障视角来看稳定性保障
稳定性保障的核心目标
尽早的预防故障,降低故障发生几率。及时预知故障,发现定义故障。故障将要发生时可以快速应急。故障发生后能快速定位,及时止损,快速恢复。故障后能够从中吸取教训,避免重复犯错。仔细思考一下,所有的稳定性保障手段都是围绕这些目标展开的。
稳定性保障体系
上图涵盖了稳定性体系的各个方面,下面我来一一讲解。
应用架构稳定性
应用架构稳定性相对是比较广的话题,按我的理解主要包括很多设计原则和手段:
架构设计简单化。系统架构简单清晰,易于理解,同时也需要考虑到一定的扩展性,符合软件设计中KISS原则。现实中存在太多的“过度设计”和“为了技术而技术”,这些都是反例,架构师需懂得自己权衡;拆分。拆分是为了降低系统的复杂度,模块或服务“自治”,符合软件设计中“单一职责”原则。拆分的太粗或者太细都会有问题,这里没有什么标准答案。应该按照领域拆分(感兴趣同学可以学习下DDD中的限界上下文),结合业务复杂程度、团队规模(康威定律)等实际情况来判断。可以想象5个人的小团队去维护超过30多个系统,那一定是很痛苦的;隔离。拆分本质上也是一种系统级、数据库级的隔离。此外,在应用内部也可以使用线程池隔离等。分清“主、次”,找出“高风险”的并做好隔离,可以降低发生的几率;冗余。避免单点,容量冗余。机房是否单点,硬件是否单点,应用部署是否单点,数据库部署是否单点,链路是否单点…硬件和软件都是不可靠的,冗余(“备胎”)是高可用保障的常规手段;无状态、一致性、并发控制、可靠性、幂等性、可恢复性…等。比如:投递了一个消息,如何保障消费端一定能够收到?上游重试调用了你的接口,保证数据不会重复?Redis节点挂了分布式锁失效了怎么办?…这些都是在架构设计和功能设计中必须考虑的;尽可能的异步化,尽可能的降低依赖。异步化某种程度可以提升性能,降低RT,还能减少直接依赖,是常用的手段;容错模式(上篇文章中有介绍)我在团队中经常强调学会“面向失败和故障的设计”,尽可能做一个“悲观主义者”,或许有些同学会不屑的认为我是“杞人忧天”,但事实证明是非常有效的。
从业以来我有幸曾在一些高手身边学习,分享受益颇多的两句话:
出来混,迟早要还的;
不要心存侥幸,你担心的事情迟早要发生的;
上图是比较典型的互联网分布式服务化架构,如果其中任意红色的节点出现任何问题,确定都不会影响你们系统正常运行吗?
限流降级
前面介绍过了限流和降级的一些场景,这里简单总结下实际使用中的一些关键点。
以限流为例,你需要先问自己并思考一些问题:
你限流的实际目的是什么?仅仅只做过载保护?需要什么限流策略,是“单机限流”还是“集群限流”?需要限流保护的资源有哪些?网关?应用?水位线在哪里?限流阈值配多少?…
同理,降级你也需要考虑:
系统、接口依赖关系哪些服务、功能可以降级掉是使用手工降级(在动态配置中心里面加开关)还是自动熔断降级?熔断的依据是什么?哪些服务可以执行兜底降级的?怎么去兜底(例如:挂了的时候走缓存或返回默认值)?…这里我先卖下关子,篇幅关系,下篇文章中我会专门讲解。
监控预警
上图是我个人理解的一家成熟的互联网公司应该具备的监控体系。
前面我提到过云原生时代“可观察性”,也就是监控的三大基石。
这里再简单补充一下“健康检查”,形成经典的“监控四部曲”:
关于健康检查,主要就分为“服务端轮询”和“客户端主动上报”2种模式,总的来讲各有优缺点,对于类似MySql这类服务是无法主动上报的(除非借助第三方代理)。需要注意的是传统基于端口的健康检查很难识别“进程僵死”的问题。
提到监控那就不得不提GoogleSRE团队提出的监控四项黄金指标:
延迟:响应时间
流量:请求量、QPS
错误:错误数、错误率
饱和度:资源利用率
类似的还有USE方法,RED方法,感兴趣的读者可以自行查阅相关资料。
云原生时代通常是应用/节点暴露端点,监控服务端采用pull的方式采集指标,当前比较典型的就是Prometheus监控系统,感兴趣的可以详细了解。
当然,以前也有些监控是通过agent采集指标数据然后上报到服务端的。
除了监控自身,告警能力也是非常重要的。告警的阈值配多少也需要技巧,配太高了不灵敏可能会错过故障,配太低了容易“监控告警疲劳”。
强弱依赖治理
这是网上找的一张“系统依赖拓扑图”,可见面对这种复杂性,无论是通过个人经验,还是借助“链路跟踪工具”都很难快速理清的。
何为强弱依赖
A系统需要借助B系统的服务完成业务逻辑,当B挂掉的时候会影响A系统中主业务流程的推进,这就是“强依赖”。举个例子:下单服务依赖“生成订单号”,这就是“强依赖”,“扣库存”这也是“强依赖”;
同理,当A依赖的B系统挂掉的时候,不会影响主流程推进,那么就是“弱依赖”。例如:购物车页面中显示的库存数是非必须的;
如何梳理出这种强弱依赖,这个在阿里内部是有专门的中间件可以做的,目前开源社区没有替代品,可能就要结合链路跟踪+个人的经验,针对系统级,接口级去做链路梳理。我们重点