大规模分布式系统的设计和部署实践

论文 http://mvdirona.com/jrh/talksAndPapers/JamesRH_Lisa.pdf

这篇论文主要从面向运维友好的角度,思考了大规模分布式系统的设计和部署相关的一些原则和最佳实践。

总体设计原则

We have long believed that 80% of operations issues originate in design and development, so this section on overall service design is the largest and most important.

When systems fail, there is a natural tendency to look first to operations since that is where the problem actually took place. Most operations issues, however, either have their genesis in design and development or are best solved there.

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

  1. Keep things simple and robust
  2. Design for failure

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

  1. 支持多租户,单一版本。统一运维
  2. 本地编译&构建&测试。能够在单机上做完整的测试验证。比如,我在提交代码前就可以在本地把case集跑一遍,以验证新代码是否对现有系统造成破坏
  3. 基于廉价硬件,但同时对底层组件零信任。要假设硬盘,cpu,内存,网络等等任何物理硬件,甚至是所依赖的任何底层服务都是不可靠的。Design for failure,避免单点故障,系统需要有冗余和灾备,故障恢复以及故障隔离
  4. 允许(极少情况下的)紧急人工干预,但是流程需要尽可能的自动化并且需要review
  5. 在所有层执行准入控制,支持服务降级
  6. 理解网络设计
  7. 分析吞吐率和延迟
  8. 把运维工具作为服务的一部分
  9. 理解访问模式。规划新 feature 时,一定要考虑它会给后端存储带来怎样的负载,一个最佳实践是给 SPEC (Standard Performance Evaluation Corporation,系统性能评估测试)加上一节:“这个 feature 对系统其它部分有什么影响?” ,然后在 feature 上线时验证负载的情况是否符合

自动化管理和配置

实际上我们看到,当前最手动与最自动的服务在人力开销上的差异多达两个数量级。一些面向自动化管理的最佳实践如下:

  1. Be restartable and redundant。所有操作都应是可重做的(restartable),所有持久化状态都需要进行冗余性存储
  2. 跨数据中心部署。论文中提到跨数据中心部署的主要作用是通过转移负载来减轻数据中心的压力,不过我认为这是否仅仅是一个粒度的问题?
  3. 自动配置和安装。尽量保持配置的一致性,否则会带来极大的运维负担。
  4. 代码&配置&测试 同源
    • 开发团队将配置和代码作为同一个单元进行提供
    • 对于该单元的部署测试会以与完全与运维人员线上部署方式相一致的形式进行
    • 运维人员将它们作为同一个单元进行部署
  5. 任何变更都需要有审计记录
  6. 关联故障是常见的,特别是有状态的服务
  7. 在服务级进行恢复,而不是在更低的层次做冗余
  8. 永远不要依赖本机存储不可恢复的信息。始终坚持对非临时的服务状态进行备份
  9. 保持部署过程的简
  10. Fail services regularly。停掉(take down)数据中心,关掉机架,让服务器断电。定期引入人为的故障,不断暴露出系统、网络和服务的弱点。 联想一下之前阿里光纤被挖断事件

依赖管理

大部分情况下依赖管理都不需要过分关注,但有一些特殊情况:

  1. 依赖的组件很大或者很复杂
  2. 依赖的服务非常核心,但却是单一中心化的实例

管理这种依赖,需要:

  1. Expect latency  确保所有交互都有合适的超时时间,同时提供具有幂等性的操作,允许失败重试,但重试次数要有限制。
  2. 故障隔离,如果依赖服务不可用,则尽可能的报告用户失败,fast fail
  3. 使用经验证的稳定的组件,不管是硬件还是软件
  4. 实现内部服务的监控和报警
  5. 被依赖的服务以及被依赖的组件的生产者至少需要跟依赖者达成一样的SLA
  6. 尽可能的允许服务降级而不是停止服务

发布周期和测试

规则:

  1. 生产系统要有足够的冗余,当新服务发生灾难性故障时,可以快速恢复状态
  2. 绝对不能破坏数据或状态一致性(必须先要经过严格的功能测试)
  3. 错误必须能够被检测到,同时工程团队(而不是运维)必须持续监控受测代码的系统状态
  4. 保证所有变更都能被快速回滚,同时在上线之前回滚步骤也必须经过测试

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

  1. 小流量上线,避免一刀切
  2. 快速迭代,经常发布。
  3. 利用线上数据发现问题
    • 可衡量的发布标准
    • 始终对实际数字进行收集
    • 最小化false positives(误报)
    • 分析趋势
    • 持续监控,使系统健康状态持续可见
  4. 加大工程投入。良好的工程化可以最小化运维需求,同时可以将问题消除在萌芽状态
  5. 支持版本回滚
  6. 向前兼容、向后兼容
  7. 本地编译&构建&测试。这不仅仅是开发的责任,也是测试的责任
  8. 针对负载进行压力测试
  9. 在新发布之前进行容量和性能测试
  10. 采用真实数据进行测试
  11. 运行系统级验收测试
  12. 在完整环境中进行测试开发

硬件选型和标准化

TODO

运维和容量规划

TODO

审计、监控和报警

开发过程中要付出足够的努力,来确保系统中的所有组件都可以产生相应的性能数据、健康数据以及吞吐率等数据。

在任何有配置变更发生的时候,都要在审计日志中记录下改了什么,谁改的,什么时间改的。在生产环境出现异常时,第一个要回答的问题就是最近到底进行过哪些变更。

报警是一门艺术,但如果所有的事件都报警,运维人员和开发人员就会习惯性的忽略它们,要得到正确的报警级别,有两个指标可能会有帮助:

  1. 报警和实际故障比,理想情况它的值应接近1
  2. 没有产生相应报警的系统故障数,理想情况它的值应接近0

一些监控方面的最佳实践是:

  1. 系统行为的数据是最有价值的资产。可以分析预警,不一定要等到电话响的时候才意识到故障的发生
  2. 延迟是最棘手的问题。比如像 IO 缓慢,虽未成故障但是处理变慢这样的问题。这些都很难发现,需仔细进行监测才能发现
  3. 要有足够的生产数据
    • 所用操作采用性能计数器。至少要将操作延迟及每秒的操作数记录下来。这些数据的起伏通常是一个危险信号
    • 对所有的数据操作进行审计跟踪。例如,用户经常会做哪些查询?等,对故障分析和预警有一定的帮助
    • 跟踪所有容错机制,容错机制有可能会将故障隐藏
    • 跟踪针对重要实体的所有操作
    • 保留所有的历史数据
  4. 日志可配置
  5. 保证所有被报告的错误都有与之相应的处理动作。问题会发生。事情会出错。如果代码中的一个不可恢复的错误被检测到并记入日志,或者是报告为错误,错误信息应该能揭示错误产生的可能原因及建议的处理方法。不具备可操作性的错误报告是无用的,并且时间长了它们会被忽略,而真正的故障将会被错过。
  6. 实现生产问题的快速诊断
    • 为诊断提供足够信息
    • 证据链
    • 记录所有的重要动作

优雅降级和准入控制

big red switch

以丢弃或延迟非关键负载为代价,保证关键处理过程能继续进行,这是big red switch的基本概念

准入控制

如果当前的负载系统已经无法处理了,那么再往系统中增加负载也只是会让更多的用户产生糟糕的体验

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注