前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >译《On Designing and Deploying Internet-Scale Services》

译《On Designing and Deploying Internet-Scale Services》

作者头像
林一
发布2020-08-25 10:38:34
9060
发布2020-08-25 10:38:34
举报
文章被收录于专栏:MessageQueue

“系统-管理员”的比例通常粗略的作为了解大规模服务中管理成本的指标。在低自动化水平的服务中这个比例可能低到2:1,而在行业领先的高度自动化的服务中,这个比例可以达到2500:1。在微软的众多服务之中,Autopilot经常被认为是Windows Live Search团队成功提高“系统-管理员”比的原因。自动化管理非常重要,但更重要的还是服务本身。服务是否能高效的进行自动化?是否是运维友好的(operations-friendly)?运维友好的服务几乎不需要人工的干预,除了极个别的故障外其他情况都可以被自动的检测并恢复。本文总结MSN和Windows Live在支撑一些超大型服务过程中多年积累下来的最佳实践。

Introduction

本文总结了一系列设计和开发运维友好的服务的最佳实践。设计和部署大规模的服务是一个快速发展的领域主题,因此,任何最佳实践都会随着时间推移不断的变化和增长。我们的目标是帮助其他人:

  1. 快速提供运维友好的服务
  2. 避免非运维友好的服务带来的凌晨的电话和会议

这里的内容吸收了我们过去20年在大型数据中心软件系统和Internet规模的服务方面的经验,其中大部分来自最近领导Exchange Hosted Services团队期间。其中也包含了来自Windows Live Search,Windows Live Mail,Exchange Hosted Services,Live Communications Server,Windows Live Address Book Clearing House,MSN Spaces,Xbox Live,Rackable System Team,Messenger Operations team,Microsoft Global Foundtion Services Operations等诸多团队的经验。本文也深受Berkeley关于Recovery Oriented Computing和Stanford关于Crash-Only Software的影响。

Bill Hoffman贡献了许多本文的最佳实践,同时还有3条很值得从一开始就考虑的简单信条:

  1. Expect failures。一个组件可能在任何时候挂掉或者停止工作。依赖的组件可能在任何时候挂掉或者关闭。还有网络的故障,磁盘空间耗尽等。需要优雅的处理所有故障。
  2. Keep things simple。复杂导致问题产生。简单更容易把事情做对。避免不必要的依赖,系统的安装需要尽量的简单。单台服务器的故障不应该影响集群的其他部分。
  3. Automate everything。人会犯错,人需要休息,同时容易忘记东西。自动化的过程是可测试的,固定的,因此最终更可靠。尽可能将所有地方都自动化。

这三条信条贯穿了后面多数讨论的主线。

Recommendations

本节涵盖十小节,每小节覆盖设计和部署一个运维友好的服务的一个方面。这些小节包括整体服务设计;自动化管理和配置;依赖管理;发布周期和测试;硬件选型和标准化;运维和容量规划;审计、监控和报警;优雅降级和准入控制;客户和媒体沟通计划;客户配置于自助。

Overall Application Design(服务整体设计)

我们相信80%的运维问题都来源于设计和开发,所以本节服务的整体设计是篇幅最长,也是最重要的一节。当系统发生故障时,很自然会先去检查运维过程,因为那可能是问题发生的地方。但是大多数的运维问题,要么是设计或者开发导致,或者最好是在设计和开发阶段就解决掉。

在接下来的小节中会达成共识:严格的区分开发、测试和运维并不是最有效的工作方式。在很多的服务中我们看到低成本的管理跟开发、测试、运维团队的紧密程度密切相关。

除了本节讨论的最佳实践,下一节的“自动化管理和配置”对服务的设计也有很大的影响。高效的自动化管理和配置只有在特定的服务模型上才能实现。这是一个反复强调的主题:简单性是高效运维的关键。在硬件选型,服务设计,服务模型上的合理限制,可以大大降低管理成本,提高服务可用性。

对服务整体设计影响最大的一些运维友好的基本原则如下:

  • Design for failure。这是开发需要多组件协作的大规模服务时的一个核心理念。这些组件会发生故障,而且是频繁的发生故障。当服务规模达到10000台机器,50000块磁盘时,一天内会发生多次故障。如果硬件的故障需要人工立即介入处理,那么服务就没办法低成本和可靠的进行扩展。整个服务需要在没有人工干预的情况下在发生故障的情况依旧能够继续提供服务。故障的恢复必须是非常简单的路径,并且频繁的对该路径进行测试。Stanford的Armando Fox的观点是最好的测试失败恢复路径的方式是永远不要正确的关闭服务,而是直接让它挂掉。这听起来有点反直觉,但是如果故障恢复路径不被经常的使用,那么在真正需要时它是无法工作的。
  • Redundancy and fault recovery。大型机模型是要购买大型的、非常昂贵的服务器。大型机拥有冗余的电源、热插拔的CPU、独特的总线架构,在一个单一的、紧耦合的系统中提供了令人惊讶的IO吞吐量。这种模式明显的问题就是成本高,但即使这样,它也不是足够的可靠。为了获得五个9的可靠性,冗余是必须的。即使是四个9的可靠性要求,单系统部署也是非常困难的。这个观点已经被工业界广泛接受,但是依旧能看到部署在非常脆弱的、非冗余的数据层上的服务。

设计一个任何组件在任何时候都可以挂掉,但是仍然能满足SLA(Service Level Agreement)要求的服务需要细致的工程化。要判断是否满足这条要求,可以尝试如下方法:运维团队是否愿意且能够在任何时候,在不手动迁移工作负载的情况下,关掉任何一个服务器。如果可以,那么说明服务实现了冗余、故障检测以及自动的恢复能力。

作为一种设计方法,我们介绍一下常用来发现和纠正服务的潜在安全问题的安全威胁模型。在安全威胁模型中,我们考虑任何一种安全威胁,并且针对性的给出缓解方案。同样的方法可以用在容错和故障恢复的设计上。枚举所有可能出现的故障,以及可能出现的组合。对每一种故障,确保服务能够在不出现无法接受的服务质量损失的情况下继续运作,或者确定该故障的风险对服务是可以接受的。一些故障组合的处理成本是无法接受的,他们可能被认为是不会发生的。但是在做这个判断的时候一定要谨慎。我们多次发现那些认为不可能发生的故障组合最后都发生了。当规模越来越大的时候,罕见的故障会变得司空见惯。

  • Commodity hardware slice。服务的所有组件都应该使用普通的商用服务器。比如一个具有轻量级存储的服务器可能是具有双插槽,单磁盘价值1000-2500美元的2-4核系统;一个配置相似但是具有重量级存储的服务器,可能会有16-24块磁盘。关键的洞察如下:
  1. 由普通商用服务器组成的大规模集群比由少数大型服务器组成的集群更便宜
  2. 服务器性能的增长高于IO性能的增长。同样数量的磁盘,小型服务器的性能更加均衡
  3. 耗电量跟服务器数量的关系是线性关系,跟时钟频率是三次方关系,这使得高性能服务器的运营成本更高
  4. 小型服务器故障只会影响集群中工作负载的一小部分
  • Single-version software。两个因素让服务比大多数打包发行的软件开发成本更低,演进更快。在面向消费者的服务中,使用单版本软件是比较容易达成的,特别是提供免费服务的软件中。但是在面向企业的时候保持单软件版本也非常重要。企业通常会影响软件供应商,并且希望控制使用的版本。这使得运维和支持成本大大提成,因为需要支持多个版本。最经济的方式是不要让客户来控制版本,而是同时只保有一个版本。控制单一版本时需要:
  1. 发布要避免造成用户体验上的损失
  2. 对于想要控制版本升级的客户,要么自己部署一套,要么切换到愿意提供多版本支持的服务商
  • Multi-tenancy。多租户是托管所有公司和用户在一个服务中,没有物理的隔离;而单租户是将用户组隔离在一个独立的集群中。建立多租户机制的原因和单一版本观点类似,都是因为只有这样才能为建立在自动化基础上的大规模服务降低成本。

回顾一下上面提到的设计原则和考虑点:

  • Design for failure:面向故障设计系统
  • Redundancy and fault recovery:提供冗余和故障恢复
  • Commodity hardware slice:使用通用的商业服务器
  • Single-version software:使用单一版本的软件
  • Multi-tenancy:提供多租户机制

通过对服务设计和运维模式的限制,可以最大化我们构建自动化和低成本的服务的能力。在我们的这些目标和应用服务提供商或IT外包商的目标之间有一个明显的区别。那些企业通常是人力密集型的,同时更愿意运维复杂的,由客户定制的配置。

一些更具体的设计运维友好的服务的最佳实践如下:

  • Quick service health check。服务版本的构建验证测试是一个可以在开发人员的系统上运行的测试,确保服务的核心并没有被破坏。并不是所有的边界场景都会被测试,但如果这个测试通过了,代码就可以check in了。
  • Develop in the full environment。开发者需要对他们的组件进行单元测试,还要对包含他们的服务在变更后进行服务测试。要实现这一点需要支持单机部署,以及上一条的最佳实践:服务健康状态快速检查。
  • Zero trust of underlying components。假设依赖组件会挂掉,同时要确保组件能恢复并继续提供服务。恢复技术是和服务相关的,但是也有一些通用的恢复技术:
    • 以只读模式继续访问缓存数据
    • 继续对用户提供所有服务,除了那些受故障影响的用户
  • Do not build the same functionality in multiple components。要预见未来的交互方式是非常困难的,同时如果系统中存在代码冗余的话就需要在系统的多个部分中进行修复。服务总在快速的增长和变化,如果不小心,代码库就会快速的恶化。
  • One pod or cluster should not affect another pod or cluster。大部分服务是由位于多个集群或者自己群的系统共同协作组成的。他们之间要尽可能的100%独立,避免关联的故障。那些全局的服务即使具有冗余备份也是一个单点。有时候这种情况无法避免,但还是要尽可能把一个集群依赖的东西都放到集群里面。
  • Allow (rare) emergency human intervention。常见的情况是灾害后或其他紧急情况的用户数据迁移。要将系统设计的永远不需要人工干预,但是也要理解一些组合故障或者意料之外的故障发生时会需要人工干预。这些情况会发生,并且在这些情况下的错误操作通常是灾难性数据丢失的常见原因。运维工程师在凌晨两点,顶着压力工作会容易犯错误。系统设计时首先要保证绝大多数情况下不需要人工进行干预,对于需要人工干预的场景一定需要提前确定预案。预案不应该只是记录在文档中,需要执行多个步骤、容易出错的计划,需要将它们写成脚本,并且在生产环境进行验证保证它们可以正确的执行。没有在生产环境验证过的东西是无法work的。运维团队需要周期性的演练这些工具。如果演练对服务的可用性会造成很大风险,说明在设计、开发、测试方面的投入还不够。
  • Keep things simple and robust。复杂的算法和组件交互会将调试和部署变得困难。在大规模服务中,故障模式的数量在进行复杂的优化之前就已经够让人害怕了。一个总体的原则是:超过一个数量级的优化才值得去做,只有几个百分点的提升和改进是不值得去做的。
  • Enforce admission control at all levels。任何好的系统都会在入口设计准入机制。这遵循了一个长期以来被人们所接受的原则:不要让更多的作业进入负载的系统比继续接受作业导致系统震荡要好。在服务入口通常有某种形式的流控或者准入控制,但是应该在所有重要的组件的边界上都应该有准入控制——即使整体负载在可接受范围内,但是负载的变化可能会导致其中某个子组件过载。下面2.8节提到的“big red switch”就是一种在过载情况下优雅降级的方案。总体方式是在所有用户收到影响之前尝试优雅降级,而不是直接挂掉。
  • Partition the service。分区应该是细粒度且无线可调的,不绑定在任务物理世界的实体上。如果分区绑定在公司上,那么一个超大的公司可能就会超过单个分区的大小限制。我们推荐使用一个中间层查找表把细粒度实体映射到相应的数据管理系统。然后这些细粒度的分区就可以在服务器之间自由的移动。
  • Understand the network design。尽早的进行测试以便了解机架内、机架之间、数据中心之间的负载情况。应用开发同学需要了解网络设计,并且尽早和网络专家一起Review。
  • Analyze throughput and latency。为了理解其影响,需要对核心服务用户交互的吞吐、延迟进行分析。并将这件事与其他的日常数据库维护、运维配置、服务调试一样进行常规化。这将有助于发现因为某些周期性管理任务引发的问题。对于每个服务来说,所需的度量值可以与容量规划相结合,比如系统每秒的用户请求数、系统并发的用户数,或者某些可以将工作负载映射到资源需求的度量值。
  • Treat operations utilities as part of the service。由开发、测试、运维开发的运维脚本都需要在开发过程中进行代码Review,checkin到代码主干,跟代码一起已经测试和维护。通常这个工具非常重要,但是几乎没有被测试过。
  • Understand access patterns。当规划新特性时,一定要考虑它给后端存储带来的负载。通常服务模型和服务开发者所在的抽象层次太高了,以至于他们无法注意到负载给底层存储数据库带来怎样的影响。一个最佳实践是SPEC(Standard Performance Evaluation Corporation,系统性能评估测试)加上一节:“这个feature对系统其它部分有什么影响?”然后feature上线时验证负载的情况是否符合。
  • Version everything。要假设系统运行在一个多版本环境中。目标是运行单一版本,但是上线和生产测试时会有多版本共存的情况。所有组件的N和N+1版本都需要兼容。
  • Keep the unit/functional tests from the last release。这些测试是用来验证N-1版本的功能没有被破坏掉的重要手段。更进一步的,我们强烈推荐要持续在生产环境跑测试验证。
  • Aviod single points of failure。单点故障会导致故障发生时服务不可用或服务的多个部分不可用。优先采用无状态的实现。不要把请求或者用户指定给特定服务器。而是要对负载在一组服务器之间进行负载均衡。静态哈希或者其他静态的分配方式会随着时间的流逝而导致数据的倾斜。当同一组内的机器是可以相互替换的,就很容易能做到水平扩展。数据库通常都是一个单点,同时在进行互联网级别的服务设计时,如何进行数据库的扩展依然是最困难的问题。好的设计会使用细粒度分区,同时会禁止跨分区的操作使得可以高效地通过多个数据库服务器进行扩展。对所有数据库状态冗余的存储到热备的服务器上,同时要在生产环境经常进行failover测试来保证可靠性。

Automatic Management and Provisioning(自动化管理和配置)

很多服务实现需要在故障的时候向运维发报警,依赖人工干预来恢复服务。这种方式的问题首先在于人力成本高,需要7 * 24小时的运维人员成本。更重要的是当运维人员顶着巨大的压力做出决定时,有20%的概率会出现失误。这种模式成本高,又容易出错,还降低了服务的可用性。

面向自动化的设计给服务模型加了很多限制。比如,今天一些大规模的服务依赖异步数据复制的数据库系统。这样在主副本故障需要切换到从副本的时候,会因为异步复制丢失一些客户数据。但是如果不进行副本切换,会导致数据存储在故障数据库的用户服务被中断。很难将是否在这种情况下进行failover的决定进行自动化,因为它需要人为的做出判断,并且需要精确的估算出可能丢失的数据量和可能导致的服务中断时长。要实现自动化就要付出同步复制的延迟和吞吐率的代价。如果这样的话failover的切换自动化就是一个非常简单的决策:如果主挂了,就将请求路由给从。这种方式容易运维且更不容易出错。

在设计和部署之后进行服务的自动化是非常困难的。成功的自动化应当是简单和清晰的,易于做出运维判断的。这反过来又取决于服务的设计,必要的时候可以牺牲一些吞吐和延迟来达到自动化的目的。通常这里的妥协决策也是非常困难的,对于大规模的服务来说,不同的决策所带来的的运维方面的节省通常也是数量级上的差异。实际上我们看到,最手动与最自动的服务在人力的开销上差两个数量级。

面向自动化设计的最佳实践如下:

  • Be restartable and redundant。所有的操作必须可重启,并且所有持久化状态的存储都需要冗余。
  • Support geo-distribution。所有大规模的服务都应该支持跨数据中心运行。坦率的讲,我们这里提到的自动化和大多数的高效率即使是在没有地理分布的情况下也是可能的。但是跨数据中心部署的能力的缺失将会大幅推高运维成本。没有地理分布的支持,就很难通过将负载迁移到另一个数据中心来减轻当前数据中心的压力。地理分布式的缺失是一种会推高成本的运维限制。
  • Automatic provisioning and installation。配置和安装,如果是手动完成的话,成本高且易发生问题,同时细小的配置差异会慢慢散布到整个服务,使得问题越来越难以定位。
  • Configuration and code as a unit。确保:
    • 开发团队将配置和代码作为同一个单元进行提供
    • 对该单元的部署测试会以完全与运维人员线上部署方式相一致的形式进行
    • 运维人员将它们作为同一个单元进行部署

那些将代码和配置作为一个单元,且对它们一块进行变更管理的服务会更加可靠。

  • 如果一个配置需要再生产环境变更,保证所有的变更都有审计记录:谁,何时,做了什么操作,影响那些服务器。扫描所有服务器以确保它们的状态和预期的一致。这有助于尽早发现服务器配置错误及发现未审计的服务器配置变化。
  • Manage server roles or personalities rather than servers。每个系统角色或者personality需要支持按需增减服务器。
  • Multi-system failures are common。要意识到某些故障可能会影响很多主机。不幸的是,具有状态的服务,也不可避免的是对拓扑敏感的。关联故障仍会是现实存在的。
  • Recover at the service level。在服务级别上进行故障处理和错误纠正,保证完整的上下文执行环境可用,而不是在更低的软件层上进行。比如,将冗余构建到服务中,而不是依赖于在更低层的软件上进行恢复。
  • Never rely on local storage for non-recoverable information。始终坚持对非临时的服务状态进行备份。
  • Keep deploy simple。文件拷贝是理想的方案,因为它提供了部署的灵活性。最小化外部依赖。避免使用复杂的部署脚本。应当避免在同一个服务器上运行不同组件或者相同组件的不同版本。
  • Fail services regularly。停掉数据中心,关闭机架,让服务器掉电。定期引入人为故障,不断暴露系统、网络、服务中的弱点。不想在生产环境进行测试的服务,也是没有信心保证服务可以经历故障的考验的。同时如果没有生产环境的测试演练,故障恢复手段就没有办法在需要的时候工作。

Dependency Management(依赖管理)

大规模服务中的依赖管理通常得不到应有的关注。一般来说,小的组件或者服务的依赖又不足以证明显示依赖的管理所带来的复杂性。只有在如下情况,依赖管理才会显得有意义:

  1. 依赖的组件很大或者很复杂
  2. 依赖的服务的价值在于它是单一中心实例的

第一种情况的实例是存储和一致性算法的实现。第二种情况的实例是身份管理系统。这些系统的价值在于,他们是单一的共享实例,无法采用多实例避免这种依赖。

假设满足以上条件,管理他们的最佳实践如下:

  • Expect latency。调用外部组件可能会需要很长时间完成。不要让一个组件或者服务的延迟导致其他不相关的地方的延迟。确保所有的操作都有超时,避免某项资源被长期占有。操作幂等性允许操作超时之后重启请求,及时请求已经完全成功或者部分成功。确保所有重启都会被报告,同时限制重启次数避免反复失败的请求消耗过多的系统资源。
  • Isolate failures。架构必须能够防止级联故障,总是“fail fast”的。依赖的服务发生了故障,就将他们标记为不可用并不再使用他们,避免线程一直等待失败的组件。
  • Use shipping and proven components。使用经过验证的组件总是好过那些使用起来像在刀口舔血的新技术。稳定的软件版本总是比尝鲜的版本更好,无论新的feature多么的诱人。改规则也同样适用于硬件。
  • Implement inter-service monitoring and alerting。如果服务正在使得它所依赖的组件过载,被依赖的组件需要能够知道这种情况。并且如果它不能自动对过载的情况进行处理,那么需要发送出告警。如果运维人员也无法快速解决问题,还需要能快速联系到两个团队的工程师。相关联的团队需要保证有工程师可以随时被联系到。
  • Dependent services require the same design point。被依赖的组件和服务只要需要和依赖者保持相同的SLA。
  • Decouple components。在可能的情况下,确保组件在其他组件发生故障是可以继续工作,可能是以降级的模式。例如,不是对每个连接都重新做认证,而是维护一个会话秘钥,每几个小时更新一下连接状态。这样认证服务器的负载会更轻一下,同时也保证了短暂的网络故障不会导致故障恢复后的登录风暴。

Release Cycle and Testing(发布周期和测试)

在生产环境下的测试是必须的,所有大规模的服务都应该把它们作为QA方案的一部分。对于大多数服务来说,都会有一个尽可能接近生产环境的测试环境。同时,所有优秀的团队都会用实际生产环境的负载来驱动测试环境。但是,无论多么好的测试环境,都会和生产环境存在差异,因为随着测试环境和生产环境的接近,成本也会逐步和生产环境接近。

我们推荐在新版本的服务经过标准的单元测试、功能测试和类生产环境的测试后,就进入到一个受限制的生产环境进行最后的测试。我们不想进入生产环境的软件无法工作,或者给数据一致性带来一些风险,因此一切要务必小心。严格遵循以下规则:

  1. 生产环境要保证冗余,当新服务发生故障时能快速的恢复状态
  2. 绝对不能破坏数据或状态的一致性
  3. 错误必须能被检测到,同时工程师团队必须持续监控受测试代码系统状态
  4. 保证所有变更能被回滚,且回滚操作是经过验证的

这看上去非常危险,但是我们发现通过这种技术确实提升了新版本发布时的用户体验。部署时并不求快,而是将一个系统先部署到一个数据中心运行一段时间,然后再部署到其他的数据中心。然后再把某个数据中心所有系统升级,最后如果质量符合要求再将所有数据中心的系统升级。这样可以在服务处于危险之前发现问题,同时在升级版本时切实的提供更好的用户体验。一刀切的部署方式是非常危险的。

我们青睐的另一种可能违反直觉的方式是,在每天中午而不是晚上部署。在晚上部署,出错的风险更高,而且在晚上部署如果出错的话能处理这些问题的工程师肯定更少。

一些发布周期和测试方面的最佳实践:

  • Ship often:直觉上看,人们可能会认为发布越频繁会越困难,同时也越容易出错。但是,我们却发现发布越频繁大爆炸式的变更会越少。这样发布的质量倾向于越高,客户的体验也会更好。一次良好的发布应该是用户体验可能发生变化,但是围绕着可用性和延迟的运维性问题数在整个发布周期内应该保持不变。我们比较喜欢以三个月为一个发布周期,但是也可以视具体情况作出调整。我们感觉,正常来说应该都不多于三个月,而且有些服务已经是以周围单位进行发布了。如果周期超过三个月,通常都会很危险。
  • Use production data to find problems。大规模系统中的质量保证,是个数据挖掘和可视化问题,而不是测试问题。每个人都应该专注于从生产环境中的大量数据中获取到最优价值的信息。可以采取如下的一些策略:
    • Measureable release criteria。定义出关于所期望的用户体验的具体标准,并只需检测它。如果可用性的目标设置为99%,那么就对可用性进行测量看它是否达标。如果无法达标,就需要进行报警和诊断。
    • Tune goals in real time。不必为确定目标是99%还是99.9%或其他目标而烦恼,而是要设定一个可接受的目标,然后在系统建立生产稳定性时逐步提高它。
    • Always collect the actual numbers。收集实际指标,而不是收集红色和绿色或其他摘要报告。摘要报告和图形很有用,但需要原始数据进行诊断。
    • Minimize false positives。当数据不正确时,人们会很快停止关注。重要的是不要过度告警,否则运维人员会学会忽略它们。这是非常重要的,因为过度告警导致被忽略后可能会引起真正的告警被忽略。
    • Analyze trends。这可用于预测问题。例如,当系统中的数据移动与通常的速率不同时,通常会预示更大的问题。仔细挖掘现有可用数据。
    • Make the system health highly visible。需要整个组织的全球可用的,实时的服务运行状况显示。拥有内部网站的人们可以随时访问以了解服务的当前状态。
    • Monitor continuously。需要指出的是,人们每天都必须查看所有数据。每个人都应该这样做,但要使其明确成为团队子集的工作。
  • Invest in engineering。良好的工程设计可以最大程度地减少运维需求,并在问题真正成为运维问题之前解决它们。组织常常会扩大运维团队来解决规模问题,却从未花时间设计可扩展的,可靠的体系结构。那些起初没考虑过的运维问题的服务,往往后面都会变的手忙脚乱。
  • Support version roll-back。版本回滚支持应该是强制性的,同时在上线之前需要经过测试和证明。如果没有回滚支持,任何形式的生产级别测试都将是危险的。回滚到前一版本就像是降落伞上的“开伞索”,应该保持它在任意一次部署中都是可用的。
  • Maintain forward and backward compatibility。这个关键点与上一个紧密相关。更改文件格式,界面,日志记录,调试,仪表,监视和组件之间的联系点都是潜在的风险。不要剥夺对旧文件格式的支持,直到将来没有机会回滚到旧文件格式。
  • Single-server deployment。这既是测试要求,又是开发要求。整个服务必须易于托管在单个系统上。如果某些组件无法进行单服务器部署(例如,对外部非单框可部署服务的依赖),请编写模拟器以允许单服务器测试。没有这个,单元测试将很难并且不会完全发生。而且,如果很难运行整个系统,则开发人员将倾向于采用组件视图而不是系统视图。
  • Stress test for load。以两倍(或更多)的负载运行生产系统的一些微小子集,以确保了解高于预期负载的系统行为,并且系统不会随着负载的增加而崩溃。
  • Perform capacity and performance testing prior to new releases。由于工作负载特性会随时间变化,因此在服务级别以及针对每个组件都必须执行此操作。需要尽早发现系统内部的问题和性能下降。
  • Build and deploy shallowly and iteratively。在开发早期先把整个骨架搭好,这个完整的骨架可能做不了什么,但是它可以让测试和开发人员更具有产品思维,可以一开始就站在用户的角度思考问题。在构建软件系统时这是一个非常好的实践,对于构建服务来说也尤为重要。
  • Test with real data。将生产中的用户请求和工作负载搬到测试环境中。挑选一些生产数据,并将他们放到测试环境中。多样化的真实用户群体在BUG的发现上总是更具有创造力。显然,必须要进行隐私承诺,非常关键的是永远不会泄露会生产系统。
  • Run system-level acceptance tests。在本地运行一些可以加速迭代开发过程的完整性检查。为避免产生繁重的维护成本,它们应当都是系统级的。
  • Test and develop in full environments。留出一定的硬件,以可以进行特定的规模的测试。最重要的是,在这些环境中采用和生产环境相同的数据集合和挖掘技术,以最大化资源投入的价值。

Hardware Selection and Standardization(硬件选型和标准化)

采用SKU(Stock Keeping Unit,库存单元)标准化的常见理由是批量采购可以节省大批资金。毋庸置疑这是正确的,但是硬件标准化更大的需求是他可以支持更快的服务器部署和规模增长。如果每个服务器都是自己采购的基础设施,那么它们需要:

  1. 确定哪些硬件当前具有最好的性价比
  2. 订购这些硬件
  3. 硬件被安装到数据中心,进行硬件认证,软件部署

这些工作通常需要一个月,且很容易因为种种问题而投入更多的时间。

更好的方式是采用包含了硬件的SKU、自动化管理和配置设施的“service fabric”,让所有服务运行在它之上。如果一个测试集群需要添加更多的机器,可以通过网页服务来提交请求,之后很快就可以直接使用。如果一个小服务越来越成功,也可以从现有的资源池中不断的添加机器。这种方法保证了如下两个关键原则:1. 所有服务,哪怕是小服务,都在使用自动化管理管理和配置设施;2. 新服务可以快速的进行部署和测试。

针对硬件选型的最佳实践如下:

  • Use only standard SKUs。如果生产环境中只有单个或少量SKU,就可以实现资源在服务间的按需流转。成本效益最好的一种模式是开发一种包含了自动化管理和配置、硬件以及一组标准共享服务集合的标准服务托管框架。标准化SKU是实现该目标的核心要求。
  • Purchase full racks。采购硬件以机柜或者多个机柜为单位进行测试和验证。在大多数数据中心,机器组柜和堆垛成本是非常高的。
  • Write to a hardware abstraction。编写服务只依赖于硬件的抽象,不要暴露硬件SKU,服务既不暴露SKU也不应该依赖于它内部的细节。这样才可以在具有更高性价比的系统可用时将现有设备替换掉。SKU应当只是一个包含了CPU和磁盘数据,及最低内存要求的抽象描述。更细粒度上的SKU信息不应该被利用。
  • Abstract the network and naming。使用DNS和CNAMEs尽可能的将网络和命名进行抽象。始终坚持使用CNAME。硬件可能损坏、到期或者挪作它用。在代码的任何地方不要依赖于机器的名称。代表DNS中的CNAME与需要改变配置文件甚至是代码相比,要容易的多。如果需要避免更新DNS缓存,需要记得将TTL设置的足够低以保证变更尽可能快的被Push。

Operations and Capacity Planning

要高效运维服务,关键在于构建系统时消除各种运维交互过程。目标在于让一个高可靠的、7*24小时可用的服务只需要5*8小时工作的运维团队维护即可。

但是总会发生不寻常的故障,而且系统或多个系统可能会无法恢复正常运行。要理解这种可能性的发生并将受损系统下线过程自动化。依赖于运维人员手动更新SQL或者是使用特别的技术移动数据,都可能会招致灾难。处理故障的过程中也容易产生失误。运维团队需要预料遇到各种故障时的正确应对手段,并将这些过程提前进行编写和测试。一般来说,开发团队要将紧急恢复操作自动化,并要对它们进行测试。很明显无法穷举所有的故障,但是通常通过一小组恢复动作的集合就可以从类型广泛的故障中恢复过来。本质上来说,是需要构建和测试可以根据故障范围和危害以不同方式使用及组合的“恢复内核”。

恢复脚本需要在生产环境进行测试。没有经过频繁测试的工具是无法工作的,因此不要去实现团队没有勇气去使用的任何东西。如果在生产环境测试的风险太高,那么说明脚本要么没有准备好,要么是在紧急情况下使用时也不安全。这里的关键点在于故障总是会发生,那些恢复步骤没有按照预期执行而导致小故障变成大故障的情况也是屡见不鲜。要预料到这些情况,并且要能在不造成更多的数据丢失和不可用时间的前提下,将服务恢复过程自动化。

  • Make the development team responsible。在这方面,Amazon应该算是最激进的了,他们的格言是“you built it, you manage it”。他们的主张比我们现在的方式要强一些,但是无疑这是一个正确的方向。如果开发总是被在午夜吵醒,那么自动化会是必然的结果。但是如果是运维团队经常被吵醒,通常的反应是增加运维团队的人手。
  • Soft delete only。绝不要删除任何东西,只是将它们标记为删除。当有新数据进来时,将请求记录下来。保存一个以两周为单位的滚动变更历史,以便从错误中恢复。
  • Track resource allocation。理解容量规划中的额外负载带来的成本开销。每个服务都应该开发一些像并发在线用户、每秒请求数或者其他的一些相关指标。无论是何种指标,都需要在负载的此种测量方法和所需的资源之间有一个直接的已知关系。所估计的负载数应通过销售和市场团队的反馈得到,同时将会被运维团队用来进行容量规划。不同的服务具有不同的变化速度,同时有不同的订购周期要求。对我们的服务来说,每90天会更新一下市场预测,每30天更新一次容量规划和订购需求。
  • Make one change at a time。碰到问题时应该一次只对环境做一个变更。这看起来显而易见,但是我们也多次看到过因为多个变更导致的因果关系无法确定的情况的发生。
  • Make Everything configurable。任何可能在系统中发生变更的东西都应该是在生产环境下可配置和调整的,而不需要改变代码。即使某个值看起来没有很好的在生产环境中发生变更的理由,如果很容易的话,也应该将它们做成可配置的。但是不要在生产环境中随意的改变它们,同时应该对系统连同哪些将在生产中使用的配置一块进行严格的测试。但是当生产环境出现问题时,与编码、编译、测试、部署相比,简单地修改配置总是要更简单、安全、快速。

Auditing,Monitoring and Alerting

运维人员无法在部署中再对服务进行调整。在开发过程中要付出足够努力,来确保系统中所有的组件都可以产生相应的性能数据、健康数据以及吞吐率等。

有任何配置发生变更的时候,都要在审计日志中记录下改了什么,谁改的,什么时候改的。在生产环境出现异常时,第一个要回答的问题就是最近到底进行了哪些变更。如果没有审计跟踪,那么答案通常是“没有改变过什么”,而通常最后可能的情况就是因为某个变更导致了问题。

报警是一门艺术。有一种倾向是对所有的事件进行报警,开发者期望可以从中发现有趣的事情,这就导致很多服务的第一版通常都会产生大量根本没有人会看的无用报警信息。要提高效率,每个报警都应该对应一个问题。否则,运维团队将会习惯于忽视它们。我们不知道除了交互式的对报警进行调整外,还有何灵丹妙药使每个报警都正确,以保证所有关键事件都会被报警,在不需要采取任何措施的情况下就不报警。要得到正确报警的级别,有两个指标可能会有帮助:1. 报警和实际故障比(理想情况应该接近于1);2.没有产生相应报警的系统故障数(理想情况应该是0)。

  • Instrument everything。对通过系统的每个客户交互和事件进行检测,并报告异常。这是一个“runners”可以施展的地方,但是仅有他们还不够。如果只使用runners,我们曾看到一个严重的问题甚至需要几天时间才能被发现,因为标准工作负载一直被处理的很好,然后还要再花几天时间才能查到原因。
  • Data is most valuable asset。如果对系统正常行为没有很好的理解,那么在它发生异常时也无法很好的进行处理。需要对关于系统行为的大量数据进行收集以确定系统状态是否正常。很多服务都经历过灾难性的故障,但是只有在电话铃响起时人们才意识到故障的发生。
  • Have a customer view of service。执行End-to-End测试。仅有Runners虽然不足,但是他们能用来确保服务在完整的运行。确保复杂和重要的路径能被Runners测试到。避免误报,如果Runners的失败可以被忽略,那么就修改测试使他们不会误报。再次强调,如果人们习惯了忽略数据,真实发生时也会被忽略。
  • Instrumentation required for production testing。对生产环境测试进行监测。要想安全的在生产环境进行测试,需要进行完整的测试和监控。如果组件发生了故障,需要能快速的监控发现。
  • Latencies are the toughest problem。比如像IO缓慢,虽然未造成故障但是会导致处理变慢之类的问题。这些问题很难发现,需要仔细的进行监控才能发现。
  • Have sufficient production data。为了发现问题,数据必须是有效的。及早的构建细粒度的监控,否则后面的改造成本会很高。我们所依赖的重要数据包括:
    • Use performance counters for all operations。至少将操作延迟,以及每秒的请求数记录下来。这些数据的起伏通常是一个危险的信号。
    • Audit all operations。当某人做了某事,尤其是重要之事,都要进行记录。这样做主要有两个目的:首先,日志可以用来进行挖掘以找出用户经常进行哪些操作;其次可以用它来在发现问题时帮助进行debug。
    • Track all fault tolerance mechanisms。容错机制会将故障隐藏掉。每当重试发生,或将数据从一个地方拷贝到另一个地方,或机器重启以及服务重启时,都进行跟踪。在容错机制隐藏了小故障时,要能了解到并对它进行跟踪,防止小故障演变成大故障。我们曾有一个2000台机器组成的服务在几天内慢慢地下降到只有400台可用,但是一开始却没人注意到。
    • Track operations against important entities。为发生在每个重要实体上的事件记录一个审计日志,无论实体是一个文档还是多个文档组成的集合。当执行数据分析时,通常能够通过数据发现异常。要了解数据的来源及其所经过的处理过程。到了项目后期再添加这些功能会变得困难。
    • Asserts。自由的在整个产品中使用断言。收集产生的日志以及程序奔溃后的core文件,并对他们进行研究。对于那些在同一个集成边界内运行了多个服务,以及无法使用断言的地方,需要追踪日志。
    • Keep historical data。历史上的性能和日志数据对于趋势分析和问题诊断来说都是非常必要的。
  • Configurable logging。支持可配置的日志,日志可以根据需求选择性的进行打开、关闭。
  • Expose health information for monitoring。考虑多种从外部对系统健康状况进行监控的方法,并使之易于在生产环境下进行监控。
  • Make all reported errors actionable。问题总会发生,事情总会出错。如果代码中一个不可恢复的错误被检测并记录到了日志,或者报告为错误,错误信息应该能揭示错误产生的可能原因及建议的处理方法。不具备可操作性的错误报告是无用的,并且时间长了它们会被忽略掉,而真正的故障会被错过。
  • Enable quick diagnosis of production problem
    • Give enough information to diagnose。当问题被标记出来,提供足以让人进行诊断的信息。否则,进入门槛将会更高,同时标记也会被忽略。比如不要仅仅说是“有10个查询没有返回结果”,还需要再加上“下面是它们的列表及问题发生的次数”——重要的是要给出失败的上下文信息便于定位问题。
    • Chain of evidence。确保有一个可以用于开发人员诊断问题的从头到尾的完整路径。这通常是通过日志实现的。
    • Debugging in production。我们喜欢这种模式,在这种模式中,系统几乎不会被包括运维在内的任何人接触,而debug是通过让系统产生快照,将内存内容dump出来,然后转移出生产环境再进行的。当在生产环境中进行Debug是唯一选择时,开发者是不二人选。确保他们对于可以在生产环境可以进行哪些操作是要经过严格的培训的。我们的经验表明,系统在生产环境中被触碰地越少客户满意度越高。因此我们建议一定要竭尽全力避免对生产环境中的系统进行改动。
    • Record all significant actions。每当系统执行重要动作时,尤其是收到网络的请求或者修改数据时,要记录下发生的事情。包括用户何时发送了命令以及系统内部做了什么。有了这些记录,对问题的调查大有裨益。更重要的是,还可以开发出挖掘工具来找到有用的聚合结果,比如当前用户正在执行哪些种类的查询。

Graceful Degradation and Admission Control

有些时候比如收到DOS攻击或者模式发生某些改变时,会导致负载突然爆发。服务需要能够进行优雅的降级及准入控制。如果在9.11期间,大多数新闻服务都崩溃了,无法为用户提供任何服务。与此相比,就算是仅能够提供所有文章的一部分自己也是一个更好的选择。两个最佳实践:“Big Red Switch”和准入控制,需要针对每个服务进行量身定制。但是这两个都是非常强大和必要的。

  • Support “Big Red Switch”。支持应急开关。大体来说“Big Red Switch”是一种当服务不再满足SLA或者迫在眉睫时,可以采取的经过设计和测试的动作。将优雅的降级比喻为“Big Red Switch”,稍微有些不太恰当,但核心的意思是指那种可以在紧急情况下摒弃那些非关键负载的能力。

“Big Red Switch”概念是以丢弃或者延迟非关键负载为代价,保证关键处理过程能继续进行的关键。根据设计,这种情况不应当发生,但是真的发生时不失为一个好办法。等待服务已经处于危险之下时在考虑这些就太迟了。如果有些负载可以被暂时放入队列后面再处理,那这也可以作为“Big Red Switch”的候选。另外如果在关闭高级查询的情况下,事务可以继续执行,那么这也可以作为一个候选。关键在于确定碰到问题时系统所需的最小集合,然后实现关闭那些不必要的服务的开关,并进行测试。需要注意的是,正确的“Big Red Switch”应该是可逆的。同时应对开关的重置进行测试,确保所有服务可以回到完整服务的状态,包括所有的批处理任务及先前被暂停的非关键工作。

  • Control Admission。第二重要概念是准入控制。如果当前的负载系统已经无法处理了,那么再往系统中增加负载也只是会让更多的用户产生糟糕的体验。如何实现这一点与系统本身有关,某些系统比其他系统更容易完成。以电子邮件处理服务为例,如果系统已经超负荷,并且开始排队,此时最好不要再接收邮件而是让它们在来源队列中等候。这样可以产生效果并能实际减少总体服务延迟的关键原因是,如果队列形成处理时间会变得更慢。如果我们不允许队列的建立,那么吞吐量会更高一些。另一种技术是:提供服务时高端用户优先级高于普通用户,注册用户高于访客用户,访客高于普通用户,如果以“使用和购买”模式访问的。
  • Meter Admission。另一个非常重要的概念是上面所确定的准入控制点的修改。如果系统发生故障并宕机,为确保一切正常恢复时必须能够缓缓进行。比如先让一个用户进入,然后每10秒允许10个用户进入,如此慢慢加大。对于每个服务来说,拥有一个细粒度的旋钮可以在重新上线或者从灾难性失败中恢复时缓慢增加使用量是至关重要的。在任何服务的第一个版本中很少会包含这种能力。对于一个有客户的服务来说,一定有方法来通知它的客户端暂时无法提供服务及何时恢复服务。这就允许客户端在可以接受的情况下继续修改本地数据,同时也可以让客户端暂时退下避免干扰服务,让服务可以尽快恢复上线。这样也同时为服务owner提供了一个直接与用户沟通的机会,并可借此调整他们的期望。另外的一个可以避免所有客户端同时涌向服务端的技巧是:故意制造抖动和对每个实体进行自动备份。

Customer and Press Communication Plan

系统发生故障时,需要就由此引发的延迟和其他问题与客户沟通。沟通应以可选的方式通过多种媒介进行:RSS、网站、即时消息、电子邮件等。对于那些具有客户端的服务来说,让服务具备通过客户端直接与用户联系的能力是非常有用的。可以在特定的时间,或者时间段,要求客户端下线。如果支持的话也可以让客户端运行在断连或者缓存的模式下,可以通过客户端将系统状态告知用户,并说明预计会在何时恢复。

即使没有客户端,用户也可以通过其他方式与系统交互,比如通过网页将系统状态公布给用户。如果用户能够了解什么正在发生,并且对服务何时恢复有一个合理的预期,就会大大提高他们的满意度。对于服务提供者来说,有一个隐瞒系统问题的自然倾向,但是随着时间的推移,我们越来越确信,将服务的可用性信息提供给客户几乎总是可以提高他们的满意度。即使对于免费的系统来说,如果人们可以知道系统正在发生什么,并且什么时候能够恢复,他们对服务的信心就不会轻易的放弃使用。

某些特定的事件会引发媒体的报道,如果事先考虑到了这些场景的应对,那么服务将会给人们带来更好的印象。像大量数据丢失或者损坏,安全入侵,隐私侵犯以及长时间的服务停机等问题可能引起媒体的关注。事先准备好沟通计划。知道需要与谁、何时、怎么通电话。沟通方案的框架应该提前准备好草案。针对每种类型的灾难,都应该提前准备好沟通计划,包括与谁、何时、如何沟通。

Customer Self-Provisioning and Self-Help

客户自配置可以大大降低成本,同时提升客户满意度。如果客户可以简单地通过打开网页,输入所需数据,然后就可以开始使用服务,这会比他需要打电话然后在呼叫处理队列里浪费大量时间,满意度要高很多。我们一直觉得主要的移动运营商因为一直未为那些不愿意致电的客户服务的人们提供自助服务,错过了一个既节省成本又可以提高用户满意度的机会。

Conclusion

对于大规模互联网服务来说,降低运维成本和提高服务可靠性始于编写运维友好的服务。在本文中,我们定义了什么是运维友好,并总结了来自于从事大规模服务的工程师在服务设计、部署、开发和运维方面的最佳实践。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-08-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 MessageQueue 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Introduction
  • Recommendations
    • Overall Application Design(服务整体设计)
      • Automatic Management and Provisioning(自动化管理和配置)
        • Dependency Management(依赖管理)
          • Release Cycle and Testing(发布周期和测试)
            • Hardware Selection and Standardization(硬件选型和标准化)
              • Operations and Capacity Planning
                • Auditing,Monitoring and Alerting
                  • Graceful Degradation and Admission Control
                    • Customer and Press Communication Plan
                      • Customer Self-Provisioning and Self-Help
                      • Conclusion
                      相关产品与服务
                      负载均衡
                      负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档