高可用架构设计——故障响应
一、微服务时代的运维
1.以应用为中心
- 微服务架构模式下,必须换一个思路来重新定义和思考运维,运维一定要与微服务架构本身紧密结合起来。
- 合理的组织架构是保障技术架构落地的必要条件,用技术手段来解决运维过程中遇到的效率和稳定问题才是根本解决方案
- You Build It,You Run It。工程师可以随时向生产环境提交代码或者发布新的服务,但是同时你作为 Owner,要对你发布的代码和线上服务的稳定运行负责。Owner 意识很重要,正确的做事方式需要引导,这就是优秀和极致的距离
2.从生命周期的视角看待应用运维体系建设
从“应用生命周期管理”的角度分阶段去梳理。对于一个对象,既然有生命周期,就会有不同的生命周期阶段,那这个对象在不同的阶段,可能就会具备不同的属性、关系和场景。
(1)应用的生命周期
- 应用创建
-
- 基础信息: 应用名,责任人,gitlab, 代码类型,是否核心应用
- 基础服务(mysql,cache,vip, dns, topic)关系
- 研发阶段
-
- 持续集成
- 工具链支持
- 上线阶段
-
- 上线,灰度,测试
- 运行阶段
-
- 运维角度:
-
-
- 运行状态相关的属性:进程,端口,目录,日志
- 相关联的基础服务的各项运行指标
-
-
- 业务角度:
-
-
- 持续集成过程
- 依赖管理
- 链路跟踪的场景
-
-
- 异常变化
-
-
- 基础设施异常
- 基础服务异常
- 应用服务异常
- 活动大促
- 第三方依赖故障: IDC,
-
- 销毁阶段
-
- 关联资源清理
(2)总结
- 运维架构的切入点:从生命周期入手,划分阶段,提炼属性,理清关系,固化基础信息(CMDB),实现运维场景
- 在思考问题和设计解决方案的时候,一定要从实际出发、从问题出发、从基础出发,理清自己的需求和痛点,然后再去寻求解决方案。
- 日常接触到的各种技术解决方案,都是在解决应用生命周期不同阶段中应用自身或者应用与周边关系的问题,或者是所面对的场景问题
二、故障应急响应
故障应急响应是维持系统高可用的最后一个机会,这个环节的不专业表现,对于稳定来说是最后彻底的失守
1.解决流程
生产环境永远不允许调试问题,出现问题立刻回退,查问题要去测试环境。
(1)快速止血
问题排查的第一步,一定是先把血止住,及时止损。
- 发布期间开始报错,且发布前一切正常(或者发布了不长时间后出错)?先回滚,恢复正常后再慢慢排查。
-
- 回滚内容需要包含上一个版本的所有修改,不管该修改是否可能导致问题的产生。
- 运维不应以开发意见为主导,应按照手册走规范的完整的回滚流程。
- 应用已经稳定运行很长一段时间,突然开始出现进程退出现象?很可能是内存泄漏,试试重启。
- 只有少数固定机器报错?试试隔离这部分机器(关闭流量入口)。
- 单用户流量突增导致服务不稳定?勇敢推送限流规则。
- 下游依赖挂了导致服务雪崩?执行降级预案。
(2)保留现场
- 隔离一两台机器:将这部分机器入口流量关闭,让它们静静等待你的检阅。
- Dump 应用快照:常用的快照类型一般就是线程堆栈和堆内存映射。
- 所有机器都回滚了,咋办?如果你的应用监控运维体系足够健全,那么你还有多维度的历史数据可以回溯:应用日志、中间件日志、GC 日志、内核日志、Metrics 指标等。
(3)定位原因
- 关联近期变更:90% 以上的线上问题都是由变更引发。
- 全链路追踪分析:不要只盯着自己的应用不放,你需要把排查 scope 放大到全链路。
- 还原事件时间线:你需要做的是把不同时间点的所有事件线索都串起来,重建和还原整个案发过程。
- 找到 Root Cause:排查问题多了你会发现,很多疑似原因往往只是另一个更深层次原因的表象结果之一。
- 尝试复现问题:如果可以,最好能把问题稳定复现出来,这样才更有说服力。
(4)解决问题
最后,问题根因已经找到,如何完美解决收尾?几个基本原则:
- 修复也是一种变更,需要经过完整的回归测试、灰度发布;切忌火急火燎上线了 bugfix,结果引发更多的 bugs to fix。
- 修复发布后,一定要做线上验证,并且保持观察一段时间,确保是真的真的修复了。
- 最后,如果问题已经上升到了故障这个程度,那就好好做个故障复盘吧。整个处理过程一定还有提升空间,你的经验教训对其他同学来说也是一次很好的输入和自查机会是。
2.结构化问题解决
- 问题定义:清晰的描述问题现象、影响,其中影响要尽量量化。例如xx时xx分开始,xx服务异常,成功率从99%下跌到90%。
- 临时解决:基于预案的临时解决方案和实施结果,包括符合条件的预案执行,或者应用发布过程中出现的异常后立即回滚。
- 分析问题原因:结合已知因素,找到问题的根本原因。
- 制定解决方案。
- 实施解决方案。
- 标准化解决方案:将解决方案标准化,举一反三,避免同类问题继续发生。
3.故障排查流程
从理论上讲,将故障排查过程定义为反复采用假设 - 验证排除手段的过程:针对某系统的一些观察结果和对该系统运行机制的理论认知,不断提出一个造成系统问题的假设,进而针对这些假设进行测试和排除。
从收到系统问题报告开始处理问题。通过观察监控系统的监测指标和日志信息了解系统 目前的状态。再结合对系统构建原理、运行机制,以及失败模型的了解,提出一些可能 的失败原因。 接下来,可以用以下两种方式测试假设是否成立。
- 第一种方式,可以将我们的假设与观 察到的系统状态进行对比,从中找 出支持假设或者不支持假设的证据。
- 另一种方式是, 可以主动尝试 “治疗” 该系统,也就是对系统进行可控的调整,然后再观察操作的结果。 这种方式务必要谨慎,以避免带来更大的故障。但它的确可以让我们更好地理解系统目前 的状态,排查造成系统问题的可能原因。
真正意义上的 “线上调试” 是很少发生的,毕竟我们遇到故障的时候,首先不是排查故障 而是去恢复它,这有可能会破坏掉部分的现场。所以,服务端软件的 “线上调试” 往往在 事后发生,我们主要依赖的就是日志。这里的日志是广义的,它包括监控系统背后的各类观 测指标的时序数据,以及应用程序的程序日志。
为了排查故障,平常就需要准备。如果缺乏足够的日志信息,我们很有可能就无法定位 到问题的原因。 首先,必须能够检查系统中每个组件的工作状态,以便了解整个系统是不是在正常工 作。 在理想情况下,监控系统完整记录了整个系统的监控指标。这些监控指标是我们找到问题所 在的开始。查看基于时间序列的监控项的报表,是理解某个系统组件工作情况的好办法,可 以通过几个图表的相关性来初步进行问题根源的判定。
但是要记住,相关性(Correlation)不等于因果关系(Causation)。一些同时出现的现 象,例如集群中的网络丢包现象和硬盘不可访问的现象可能是由同一个原因造成的,比如说 断电。但是网络丢包现象并不是造成硬盘损坏现象的原因,反之亦然。况且,随着系统部署 规模的不断增加,复杂性也在不断增加,监控指标越来越多。不可避免的,一些现象会恰巧
和另外一些现象几乎同时发生。所以相关性(Correlation)只能找到问题的怀疑对象,但 是,它是否是问题根源需要进一步的分析。 问题分解(Divide & Conquer)也是一个非常有用的通用解决方案。在一个多层系统中, 整套系统需要多层组件共同协作完成。最好的办法通常是从系统的一端开始,逐个检查每一 个组件,直到系统最底层。
第三种重要的排查问题手段,是提供服务状态查询 API 和监控页面来暴露当前业务系统的 状态。通过监控页面可以显示最近的 RPC 请求采样信息。这样我们可以直接了解该软件服 务器正在运行的状态,包括与哪些机器在通信等等,而不必去查阅具体的架构文档。另外, 这些监控页面可能同时也显示了每种类型的 RPC 错误率和延迟的直方图,这样可以快速查 看哪些 RPC 存在问题。 这种方法很像 X-RequestTrace 机制,它非常轻便。如果故障的现场还在,或者故障过去 的时间还不长,那么它将非常有助于问题的排查。但是如果问题的现场已经被破坏,那我们就只能另寻他途。
4.关键角色
突发异常的情况都各有不同,很难有一个完全统一而且颗粒度很细的标准流程,但是可以提前约定好几个关键角色,定义角色的作用和关键动作,来提升协作效率。主要包括这些角色:
- 指挥员:负责组织和协调故障快速恢复、故障群里通报相关进展。
- 通讯员:负责收集、记录关键信息,并在故障群等渠道跟相关团队沟通。
- 快恢负责人:根据故障现象、监控大盘,决策并执行预案。
- 问题诊断负责人:定位故障根本原因,当快恢不起作用的话,该角色至关重要。
(1)指挥员
①指挥员的选择
- 第一接警人:默认第一个收到告警、投诉反馈的技术人员作为指挥员。第一接警人判断是否能够指挥,或者是否有自己熟悉且充分演练的预案可用,如果可以则立即恢复服务,否则联系专职指挥员接手。在专职指挥员接手之前,第一接警人就是默认的指挥员。
- 专职指挥员:团队 Leader 和稳定性负责人是大多数风险的最佳指挥员,当应急团队建立联系后,指挥员可以交由 TL 或团队内的稳定性负责人。
- 各级TL:当故障时长和等级持续上升后,根据实际情况会上升,由更高层级 TL 接掌指挥员角色,以协调更多资源加入。
②指挥员关键动作
- 确认问题:确定该次突发事件的现象、影响。
- 确定角色:确定参与该次事件处理的关键角色,包括通讯员、快恢负责人、问题诊断负责人。
- 向上沟通:让组织中关键角色知晓该问题,这样在需要时候,可以更快地调动更多人员和资源参与进来。
- 协调:协助快恢负责人和问题诊断负责人解决问题,在信息、领域专家等资源上给予支援。
③对指挥员的要求
- 启动:确定人员,并通过视频会议、故障群等方式建立起应急小组。
- 前期:紧盯快恢负责人进展,优先落地快恢,而不是分析根本原因。当快恢不生效后,也要继续探索可能的快恢手段,例如回滚近期的变更等操作。过往的故障时长没有满足1-5-10的案例中,大多数情况下都是指挥员在分析问题根本原因,错失了快恢的最佳时机。
- 中期:尝试大量手段都无法恢复服务的话,重心逐渐转移到问题诊断负责人这里,找到根本原因。通常进入到这个阶段故障还没恢复的话,就是大故障了,1-5-10基本上是无法达标的。
- 后期:组织团队继续观察,确认不会问题再复现。组织善后和复盘等工作。
(2)通讯员
如果故障不能在第一时间通过预案恢复的话,通讯员将会是仅次于指挥员的角色。高效组织信息收集、整理,会让整个应急小组更快速度找到解方案。
①通讯员选择
- 专职通讯员:在团队内有一定稳定性认知,然后通常又不是快恢负责人和问题诊断负责人第一人选的那个同学。
- 其他不参与问题诊断和快恢的团队成员。
②通讯员关键动作
- 持续确认问题和通报:随着时间推移,问题的现象、影响面也在动态变化,需要定期通报(故障群、电话会议等渠道),前期要做到5分钟换一次通报,随着时间推移,后期可以改成15分钟、30分钟等间隔。
- 信息收集:按照标准模版,为该问题建立一个统一的文档,把文档链接放到群公告、故障群中。并持续将收集的关键信息更新进去。方便后续加入到应急小组的同学快速了解上下文。
- 收集舆情:这一点跟信息收集有重叠,之所以特别强调出来,是因为该环节通常容易被忽略,技术同学容易陷入在技术指标中,对于舆情缺乏关注。
- 对外发声:联系客服负责人,与客服团队合作,安抚客户。
③对通讯员关键要求
- 前期要快:快速收集关键信息,黄金10分钟内要做到每分钟有信息更新,并持续通报。
- 通报及时:好的信息通报是告知下次通报时间,例如xx问题yy正在处理中,目前情况是zzz,xx分钟后将进行下一次通报。如果有可靠和及时的通报,关注该问题的人只需持续留意信息通报即可,避免非专业的插手影响应急小组快速反应。
- 联系外部支援:涉及到外部依赖方的时候,例如OSS、MySQL等,通过指挥员、应用Owner等渠道知晓外部接口人的时候,及时组织外部接口人加入到应急小组中来,并向对方通报问题上下文。
(3)快恢负责人
期望是所有的风险都能够通过快恢来解决,如果不能的话,也是第一时间探讨其他可行的快恢方案(比如回滚等操作)。
①快恢负责人选择
- 应用Owner/核心骨干。
- 执行过该应用预案的团队成员:我们鼓励团队之间交叉执行预案,当应用Owner联系不上的时候,其他同学也可以通过预案来协助问题恢复。
②快恢负责人关键动作
- 执行快恢预案:根据问题现象,找到预案大盘,根据大盘上监控指标指引去执行相应的预案。
- 制定其他候选恢复方案:当已知快恢预案不生效时候,分析可能的变更等因素,通过回滚等方法尝试恢复。必要时候,让指挥员协调更多人进来支持。
③快恢负责人关键要求
- 以恢复服务为第一优先级,问题根因分析请交给问题诊断负责人。
- 既定预案不能快恢,也要继续探索其他可能的恢复手段。
(4)问题诊断负责人
通常不希望这个人出现在故障1-5-10的恢复环节,但是当快恢失效并且短时间内缺乏有效手段恢复服务的话,最后只能靠问题诊断负责人来找到根本原因,并制定解决方案。
①问题诊断负责人选择
- 应用Owner/骨干:了解相关代码的人最适合去做问题诊断。
- 领域专家:比如网络问题,可以从集团找到该领域专家协助参与进来。
②问题诊断人关键要求
- 根据收集的信息,找到问题根本原因。
- 向指挥员、通讯员提出要求,把外部支援邀请加入到应急小组中。
5.预演
故障应急也需要重点锻炼。一些可以锻炼的机会包括:
- 真实的故障场景。
- 红蓝对抗演习:与SRE联动,通过突袭方式,模拟一次故障。
- 常规报警升级:TL或者稳定性负责人随机抽取一个短信告警,人为将其升级为故障,进入故障应急响应流程。
三、预案
1.六大预案
(1)切流
切流是一种常见的技术手段,用于在发生单机房故障时保持服务的连续性和可用性。由于流量切换是一种无损操作,它成为处理此类问题的首要选择。在服务部署时,应实施多机房冗余部署策略,至少确保两个机房的冗余,以便在一个机房发生故障时,能够迅速将流量切换到其他正常运行的机房。这里我来介绍几种常见的切流场景。
- 客户端自动故障转移:一种常见的做法是让服务端配置多个域名,端上自动探测这些域名的健康状况。当某个域名的失败率异常升高时,客户端可以自动执行故障转移。这种方法的优势在于能够在故障发生时自动切换服务,无需人工干预,有效应对机房故障和域名封禁等问题。
- DNS 层面的流量切换:面对网络故障,如特定地区的网络问题,可以通过修改 DNS 记录来实现流量的切换。这种方法的优点在于操作简单,只需更改 DNS 设置即可。然而,DNS 更新通常需要至少 5 分钟才能生效,且完全收敛可能需要更长的时间,这是它主要的局限性。
- 4 层(LVS)流量切换:在正常情况下,4 层负载均衡设备具备自动故障转移功能,能够在下游服务出现故障时自动切换流量。这种切换的生效时间通常在秒级,响应速度较快。缺点是一旦 LVS 本身也受到了影响就只能通过客户端或者 DNS 层面进行切换。
- 7 层流量切换(如 Nginx):面对内部机房问题,可以在 Nginx 层面统一切换流量,这种方法的优点是切换迅速,但通常需要人工干预。人工操作缺点就是操作时间会相对比较长。
- 服务层的切换,比如利用服务网格技术进行服务调用之间的跨机房切换,这种方式的优缺点跟 7 层切换类似,都需要人工切换。
- 数据库层面的流量切换:如果数据库所在的机房发生故障,我们可以通过数据库层面来控制流量切换,也可以在业务的服务层进行配置,统一执行。推荐的做法是由数据库层面进行流量切换,因为这种方式更灵活、更快,同时呢,也能降低业务操作带来的风险。
注意,虽然流量切换是一种有效的手段,但在执行前必须进行充分的容量评估和准备。在高峰期进行流量切换时,需要确保目标机房有足够的容量来承载转移过来的流量。
(2)重启
重启服务是一种常见的预案手段,但它也具有潜在的风险。因此在执行重启操作时,必须谨慎地分级进行,在紧急情况下也要考虑操作的影响范围。在每次重启后仔细观察,确保服务运行正常。只有在确认无误后,才能继续下一步的重启操作。通过这种方式,我们可以最大限度地减少因重启导致的服务中断风险。
(3)降级
降级旨在确保服务在面临资源限制或故障时仍能保持关键功能的运行。所以降级的主要目的其实有两个:资源重新分配和故障屏蔽隔离。
- 资源重新分配:当系统容量不足时,通过降级非核心服务来释放资源,优先保障核心功能或服务的运行。
- 故障屏蔽隔离:在服务出现问题难以迅速定位或恢复的时候,通过逐步降级非核心服务,来达到快速恢复的目的。例如,在一个大规模故障中,如果不确定是哪个接口导致的问题,可以通过逐步降级各个接口来定位问题源头。逐一移除二级接口,如果问题依旧,继续简化至一级服务,并逐一检查一级服务的每个接口,直至找到故障原因并恢复服务。
下面我们来看下实施降级的具体策略。
- 梳理和分级:首先,我们需要梳理服务和接口,把它们分成三个等级。第一级是最关键的服务和接口;第二级是比较重要的服务和接口;第三级就是那些非核心的可以增加用户体验的接口。
- 设置明确的开关:确保有明确的降级开关,并保持上下游服务的连续性。避免出现下游服务已降级,而上游服务仍在不断重试的情况,这可能导致更严重的后果。必须确保上下游服务同步降级,并能识别降级状态。
- 演练:由于降级是一个有损的操作,必须通过演练来验证预案的可靠性。演练可以从测试环境开始,逐步过渡到线上灰度环境(针对特定用户群体),再到线上部分环境(按比例进行)。在演练过程中,要注意控制影响范围,确保不会对生产环境造成不必要的损害。
(4)限流
限流是确保系统稳定性和性能的关键策略,主要通过控制并发数和请求量来实现。并发数是指系统能同时处理的任务数量。请求量是指单位时间内系统接收的请求总数。限流一定要多层进行,7 层限流、waf 限流、服务层限流、DB 限流。同时限流一定要依次往下减少,因为上游是对下游的保护。
(5)回滚
通常分为分批和蓝绿两种策略,它们各有优势和局限性。
- 分批回滚是一种逐步替换旧实例(老版本)的部署方法,通过逐个替换为新实例(新版本),实现服务的平滑过渡。在这个过程中,新旧实例会短暂共存。这种方法的优势在于操作简单,资源消耗相对较少。然而,它的主要缺点是在更新过程中,新旧实例可能因为同时存在,增加了系统不稳定的风险。
- 蓝绿回滚涉及同时运行两个完全独立的环境——蓝色环境(旧版本)和绿色环境(新版本)。当新版本准备就绪后,流量会从蓝色环境切换到绿色环境。如果新版本出现问题,可以迅速将流量切回蓝色环境,实现快速回滚。这种方式的优点是能够快速响应问题并保持服务的稳定性,同时新旧版本之间相互隔离,减少了相互影响的风险。不过,蓝绿部署的缺点在于资源消耗较大,因为它需要同时维护两套独立、完整的环境。
2.如何保障预案有效执行?
通过对关键服务进行定期的故障演练,我们可以在真正的业务故障发生之前识别潜在的弱点和风险点,并制定相应的应对措施。这种做法不仅能增强团队对服务的信心,还能提高处理突发事件的能力。演练的频率最好是每月一次,如果时间间隔太久的话,演练可能就会失去作用。
- 定义清晰的触发条件:明确在何种场景和标准下可以启动预案。例如,如果直播服务无法建立新的长连接,且单列请求量下降超过 90%。
- 量化的决策指标:设定可量化的指标来指导决策。例如,如果下游风控服务不可用导致 API 整体延迟超过预定阈值,并且故障持续时间超过 10 分钟。
- 可观测性:确保故障发生和预案执行后,系统是可观测的。这意味着需要有适当的监控和日志记录系统来追踪系统状态。
- 简单明了的执行步骤:预案的执行步骤应该清晰、简洁,并经过实践验证。这有助于在紧急情况下快速且无误地执行预案。
- 可回滚性:在故障得到解决后,应能够对预案执行的结果进行回滚。