本文总结了过去遇到的许多次宕机事件中反复出现的问题。工程团队在处理这些事件时,某些模式(无论是作为风险还是作为资产)几乎次次都能遇到。
从这些反复出现的模式中,我们提取出了一些工程团队准备采纳的经验教训,希望你也能从中学到有用的知识并做好准备。
第 1 课:循环依赖会破坏你的运维工具
使用自己做出来的东西是一种很好的做法——毕竟,如果你都不这样做,你怎么能指望客户使用你的产品和服务呢?如果你不拿自己公司的生产力当作赌注,如何为这些产品和服务实现的流程背书呢?
但这种健康的习惯也会产生反作用,因为这种行为会造成依赖循环。所谓依赖循环是说,你依赖自己的系统……来修复你的系统。
这种依赖模式还有其他一些实例,它们都违背了一条座右铭:不要重复自己。为什么只为了监控用途就要再运行一种数据库?你的生产数据库本来运行得很好,所以把遥测数据也放在那里就行了。
这些循环也会在停机期间带来严重后果。例如,你可能需要身份验证才能访问操作系统,修复身份验证模块……或者监控本来应该正常运行的数据库以获取指标数据,找出数据库出了什么问题。总之就是这样的死循环。
甚至客户通信系统有时也会损坏,因为你用了自己的系统将系统状态传递给客户。
引文
第1集,Slack 与 TGW:Slack 无法访问控制面板来了解他们的系统出了什么问题,因为 AWS Transit Gateway 需要处于健康状态才能将 http 流量传输到控制面板上。不幸的是,这个 TGW 是不健康的。
第3集,Monzo 的 2019 年 Cassandra 宕机:Monzo 的生产数据库出现故障,要验证系统访问权限和部署代码才能解决问题,但前者必须使用这个生产数据库。
第10集,Kinesis 达到线程限制:AWS 无法更新 Kinesis 相关中断的状态页面,因为状态页面的更新依赖 Kinesis。
第11集,Salesforce 发布了有争议的事后分析:Salesforce 无法更新他们的状态页面,因为他们将其托管在基于 Heroku 的服务上,并且由于 Heroku 就是他们所有的,还集成到了他们的基础设施中,所以 Heroku 的运行状态取决于他们的系统健康状况。
第 2 课:愚蠢的自动化
大家都对现代公有云及其提供的无数 API 感到非常兴奋。弹性!编排!所有运维都可以自动化,这样人类就不会被吵醒了!
但是这种热情有时会让我们过度自动化系统,进而很难测试退化的用例。而且这些未经测试的退化用例可能会有很大的不利影响,相比之下,在健康的系统状态下,实现自动决策带来的那点效率或经济优势是不够看的。
但即使自动化确实是明智的(因为系统需要经常调整,和/或调整所涉及的经济成本较大),自动化有时也缺乏必要的“恐慌模式”,无法识别参数何时超出正常范围。在这些情况下,自动化应该停止自动化操作并通知运维人员,因为它即将开始做出一些非常不合逻辑的决定了。
引文
第1集,Slack 与 TGW:由于出现一个网络问题期间 CPU 闲置,Slack 的自动化操作丢弃了一堆他们“不需要”(旁白:可他们确实需要)的服务器,然后在流量激增时启动了过多的服务器,超出了系统上的文件描述符限制。
第6集,GitHub 的 43 秒网络分区:GitHub 的数据库自动化在一次为时 43 秒的网络分区期间,对一个主数据库进行了记录不完整的跨国提升。
第8集,Auth0 的严重拥塞的数据库:当请求因数据库瓶颈而变慢时,Auth0 启动了两倍的前端,结果带来了更大流量,让问题更严重了。
第 3 课:现在是 2021 年,数据库仍然很棘手
如果一切都是无状态的,那会怎样呢?那些讨厌的数据库总是给我们找麻烦。甚至在前端层表现出来的问题也常常是上游数据库引发的堵塞,源头可以追溯到深层服务栈的瓶颈。
这个主题的素材非常丰富,我们把它分解成三个子课程:
第 3a 课:生产数据库应该主要是点查询或严格限制的范围
生产系统喜欢平坦、均匀且差异小的负载。对数据库服务器来说,它们喜欢许多非常快速的查询,可能都是有索引支持的,这样最坏情况下成本也是可以控制的。
为确保这一点,请将你的任意批量查询放在专用的辅助服务器中,或者放在一些 OLAP 系统(如 BigQuery 或 Snowflake)中。或者转储到 CSV 和并行 grep 都可以。不管这些批量查询复杂程度如何,是不是符合你的数据集大小和流程,都请这样做。
而且,如果你对查询时间分布还不够了解,无法知道尾部是否有疯狂的表扫描,请立即添加相应的监控。
引文
第2集,Gitlab 的 2017 年 Postgres 停机:非常昂贵的、长时间运行的帐户删除操作被放在了他们的生产数据库上实时运行,导致拥塞和故障。
第5集,Auth0 悄悄丢失了一些索引:创建索引时未受监控的失败导致一些查询突然变成扫描,从而大大增加了数据库的负载并最终导致停机。
第8集,Auth0 的严重拥塞的数据库:生产系统上发生的一些特别昂贵的扫描加剧了数据库问题。
第 3b 课:避免数据库中的“中间魔法”
什么是中间魔法?我们来大致了解一下。
好选项:使用像 MySQL 这样的无聊事物并自己处理分片。这会很麻烦,因为你必须在应用层做很多额外工作,但当它崩溃时你可能会知道它是如何运作的。这在 10 年前可能是正确的想法,但现在看来也不错。
更好的选项:只需购买一个更大的服务器并使用一个未分片的 MySQL/PostgreSQL 服务器和一个或两个副本。这种办法一直都是好方案,尽可能选择它。
可能是 2021 年后最好的选项:花钱找云服务提供商为你运行数据库,包括所有备份和故障转移等业务。如果你真的喜欢,你甚至可以使用很帅气的数据库,例如 CloudSpanner、DynamoDB 之类。完全、不透明地依赖第三方在过去是不可想象的,但这可能是 2021 年最好的办法。这些大公司在这方面做得非常好,毕竟他们做得不好的话,因为你的公司就是依赖他们运营的,估计你们已经完蛋了。缺点是它会让你破费多多,因为这些服务的定价很高。
玩火选项:使用一些声称可以自动解决所有扩展和故障转移问题的东西,但你仍然需要做运维工作,而且它的生产环境历史比 MySQL 之类的东西少得多。当它出错时,很少有人知道如何操作,或充分了解其内部结构以诊断其编排流程的复杂故障模式。我们在这些停机事件中遇到的可能嫌疑人包括 MongoDB 和 Cassandra。
引文
第3集,Monzo 的 2019 年 Cassandra 停机:扩展的 Cassandra 集群有很多难以理解的配置麻烦。
第5集,Auth0 悄悄丢失了一些索引:在不降低实时流量的情况下,在 mongo 中重新同步副本是很难实现的。
第 3c 课:重点在于恢复而不是备份,还要注意它们需要多长时间
如果你不能证明你可以恢复某项内容,那么备份就没有任何意义。并且你还要恢复到正确的记录上,恢复需要的时间太久也不行。
让我们来看看有哪些情况:
备份没有运行……这怎么可能,我明明正在监控它啊!
备份在 S3 中运行并生成了一个文件。这可能要看你的备份验证到了什么地步。可能的情况是该文件为空,或者它包含的唯一有用的字符串是:
Error: permission denied on directory /data
。你的公司完蛋了。备份表面上包含大量重要数据,但在上传时已损坏。你的公司完蛋了。
备份包含有效的数据库!但是由于备份脚本中的循环错误,每个分片都是分片 0。你公司的 87.5%已经消失了。
每个备份都包含正确、有效的数据库!但是你只能通过一个 85 毫秒的链接从廉价的存储类下载它,意味着恢复需要 2 周时间。你的公司还是没了。
因此,请一定要证明你的恢复是有效的——自动化并监控这一步骤,不要只是偶尔做一次验证——并确保它们会在可接受的时间内恢复完成。4 小时的宕机会是糟糕的一天,但 4 天的宕机后,你的公司就完蛋了。确保你的公司政策可以容忍这样的恢复时间,并让你的领导签字,这样当工程团队在灾难期间需要 7 小时才能恢复数据库时,他们也不会抓狂。
引文
第2集,Gitlab 的 2017 年 Postgres 停机:备份脚本每天都在运行,将内容放到 S3 中……直到软件更新破坏了备份脚本。对应的修复还没有真正经过测试。
第6集,GitHub 的 43 秒网络分区:恢复需要很长时间(10 小时以上),尤其是在流量高峰期间,导致站点退化了很长时间。
第 4 课:分阶段慢慢部署
尽管我们尽了最大努力,错误仍然会发生。我们会引入错误、或错误配置的东西、或传播错误的防火墙规则,或其他什么事物。
但分阶段部署可以把问题锁定在确定的范围内,因此你可以在火势蔓延并烧毁整个站点之前先看到哪里在冒烟。
我们讨论过的许多团队都有一套周密的部署方法,以确保他们公司的员工是第一批尝试其服务更改的用户,然后只有一小部分客户会提前试用新的部署。
下面是一个具体的例子:
部署到你的 Dogfooding(自家用)集群——每小时或每个更改集,当前的 HEAD 版本都会部署给你的员工。这可以让你自己的团队在客户发现问题之前未雨绸缪。
金丝雀集群——按照你的发布节奏(也许每天一次?),发布候选被推送到一个小型部署,将它暴露给你的一小部分用户。一些公司会从几十个数据中心里挑一个来做金丝雀;其他公司根据他们的 user_id 或类似的东西挑出用户群的一部分来部署。发布经理可能会仔细监控金丝雀受众中这个新版本的对应指标,然后再继续……
生产。现在它开始走向更广阔的世界。根据服务的重要性和发布节奏,有时生产部署会同时进行,有时会进一步分批部署,比如一次部署一个数据中心。
对于采用这些方法的公司来说,一些小问题往往不会被大多数用户发现,因为它被自用、金丝雀或其他阶段提前捕获了。
而在公司没有使用分阶段部署的情况下,事情显然不太顺利……编写事后分析的团队往往是第一个指出分阶段部署会有多大影响的团队。
引文
第4集,一个微妙的正则表达式让 Cloudflare 瘫痪:Cloudflare 非常快速地部署了一种更昂贵的基于正则表达式的规则,结果由于 CPU 耗尽而导致整个站点瘫痪
第11集,Salesforce 发布了一个有争议的事后分析:一个 DNS 配置更改的快速部署让他们的所有名称服务器都下线了。
第 5 课:为失败做好准备,提前写好策略和计划
最后,虽然我们都愿意相信,如果测试非常彻底,并且周到地安排了所有事情,我们将不会再遇到大规模宕机事故……可我们都知道它们仍然会迟早发生。
因此,正如从许多停机事件中了解到的那样,如果在停机之前就把策略和计划内置到我们的系统和剧本中,我们就更容易从这些事件中恢复了。
策略意味着经过深思熟虑并做出决定,例如:如果整个站点因超载而停机,我们首先要减少哪些流量来恢复正常?这些流量涉及什么类型或什么类别的客户?如果这些决定是提前做出的,并由领导签字,甚至可能得到律师的验证,工程团队就更容易把压力减到阈值以下。
计划是说:我们可以设置类似“恐慌模式”之类的东西,在这种模式下编排会停止、负载均衡器变得不那么聪明,并且非必要的工作会自动暂停。我们可以有一个运行时参数,调整它可以减少一点负载,这样我们就不必关闭和打开所有东西,惊动一大堆客户了。
引文
第1集,Slack 与 TGW:Slack 使用了特使代理的恐慌模式,让负载平衡算法在过载时找到健康主机的机会大大增加。
第4集,一个微妙的正则表达式摧毁了 Cloudflare:Cloudflare 已经制定了政策和支持使用条款,允许他们在服务出现故障时关闭全球 Web 应用程序防火墙。此外,他们有一个运行时参数,允许他们无需部署代码就能立即禁用它。
第6集,GitHub 的 43 秒网络分区:GitHub 在从过载中恢复时关闭了 Webhook 调用和 GitHubPages 构建。
第9集,Coinbase 惊动了大批客户:在 Coinbase 需要配置自己的一个集群,结果在关闭/打开所有流量后惊动了大批客户,他们本应该缓慢恢复流量的。
小结
在回顾了所有这些压力巨大的宕机事件后,我们得出了一个非常令人鼓舞的结论:包括我们在上面列举的许多实践在内,一些常见实践可以预防或显著减轻各种站点停机问题带来的严重影响。
原文链接:
https://downtimeproject.com/podcast/7-lessons-from-10-outages/
评论 1 条评论