回顾系列
- 回顾 01:Coinbase re:Invent 回顾 (IND3312)
- 回顾 02:利用 AI 和 AWS 构建未来交易平台
- 回顾 03:交易创新:Jefferies 基于 Amazon Bedrock 构建的 AI 助手 (IND3315)
- 回顾 04:FSI 如何通过 Agentic AI (GBL302) 彻底改变 HFT 分析
- 回顾 05:使用 Amazon Time Sync 改进分布式系统(采用 Nasdaq)
- 回顾 06:Amazon Aurora HA 和 DR 全球弹性设计模式 (DAT442)
- 回顾 07:构建智能体式 AI:Amazon Nova Act 与 Strands Agents 实践 (DEV327)
- 回顾 08:深入探讨 Amazon Aurora 及其创新 (DAT441)
- 回顾 09:深入探讨 Amazon S3(STG407)
- 回顾 10:Nasdaq:为全球金融服务构建弹性基础设施 (HMC327)
- 回顾 11:AWS Lambda 新功能 (CNS376)
- 回顾 12:使用 Kiro 进行规范驱动开发 (DEV314)
- 回顾 13:Amazon 的 FinOps:全球电商巨头的云成本管理经验 (AMZ308)
- 回顾 14:AWS 上交易平台的 Tick-to-Trade 延迟
场次笔记
简介
- 服务器级故障处理策略
- 本系列重点介绍 S3 操作的底层细节
- 今年的重点:可用性设计方法
- 两个视角:系统级视角和服务器级视角
- 从系统级视角了解故障架构、写后读一致性和故障实现
- 术语定义
- 可用性:应对故障
- 故障:可能发生故障的组件(驱动器、服务器、机架、建筑物)
- 故障类型:永久丢失或暂时不可用(电力、网络、过载)
- 设计目标:系统围绕目标进行设计(例如 99.99% 可用性、11 个 9 的持久性、写后读一致性)
- S3 的设计目标
- 专为高可用性和持久性而设计
- 写后读一致性于 2020 年推出
- 2020 年以前,可接受的故障处理方式包括违反一致性保证
S3 在一致性功能发布之前:故障处理与索引子系统
一致性的定义
- 一致性:对某个对象执行 GET 时,结果会反映最近一次对该对象执行 PUT 后的状态
- 索引子系统
- 保存对象元数据(名称、标签、创建时间)
- 每个数据平面请求(GET、PUT、LIST、HEAD、DELETE)都会访问该系统
- 发往索引的请求比发往存储子系统的更多
- 索引系统的核心:用于持久保存索引条目的专用存储
- 基于仲裁的算法
- 使用基于仲裁的算法将数据存储在多个副本中
- 能够容忍故障
- 服务器运行在不同的可用区(AZ)中,以避免故障集中在单一故障域
- 任何单个磁盘、服务器、机架或可用区发生故障时,仅会影响一小部分数据
- 索引中的仲裁实现
- 读写操作必须访问大多数服务器
- 示例:写入值 A 在所有节点上均成功
- 写入值 B:一个节点发生故障,但 B 在大多数服务器上写入成功(不影响可用性)
- 读取方发起读取:一个服务器发生故障,但另外两个服务器返回值 B
- 通过关联的时间戳解决冲突(B 胜过 A)
- 即使服务器在请求处理中发生故障,读写操作仍能成功
- 由于存在多个节点且允许发生故障,系统具备可用性
分布式系统中基于仲裁的算法
概述
- 基于仲裁的算法要求最少数量的节点(仲裁节点数)就读取或写入数据等操作达成一致,从而确保一致性和容错能力。
- 防止部分节点发生故障时数据损坏。
关键概念
仲裁
- 定义节点总数(N)、写仲裁数(W)和读仲裁数(R)。
- 读取操作:
- 读取请求会发送到一组节点;如果收到 R 个节点的响应,操作即成功。
- 写入操作:
- 写入请求会发送到一组节点;如果 W 个节点确认,写入即提交。
- 一致性:
- 规则 R + W > N 可确保任何读取都能看到最新写入的数据,因为读取集合与写入集合必须至少有一个重叠节点。
- 容错能力:
- 如果一个节点发生故障,系统仍可形成仲裁(例如,一个 3-out-of-5 系统可以容忍 2 个节点发生故障)。
示例和应用
数据复制
- 确保集群中的所有数据副本保持一致(例如 Cassandra 等数据库)。
- 互斥(Maekawa 算法):
- 节点向其仲裁节点请求访问共享资源(临界区)的权限,以确保每次只有一个节点进入。
- 共识(Paxos):
- 分布式系统就单一值达成一致的基础协议,对于领导者选举和状态机复制至关重要。
关键参数(URW 模型)
- N:数据副本(节点)总数。
- W:写仲裁数(确认写入所需的节点数)。
- R:读仲裁数(确认读取所需的节点数)。
S3 基于仲裁的系统:确保高可用性和一致性
S3 兼容系统中的仲裁原则
- 利用仲裁原则(大多数节点达成一致)实现高可用性和一致性
- 适用于集群或边缘环境(例如 Snowball Edge 上的 Amazon S3)
S3 类系统的工作原理
分布式共识
- 监视器或管理器形成仲裁组(通常为 3 个或更多),以跟踪节点运行状况
- 将可用的存储节点告知客户端
- 写入操作:
- 只有大多数存储节点(仲裁节点)确认后,写入才会成功
- 确保整个集群的数据持久性和一致性
- 读取操作:
- 读取以达到仲裁数量的副本为目标,以保证获取最新数据
- 容错能力:
- 在总共有 2f+1 个副本时,可以容忍 f 个节点发生故障
- 只要仍能形成仲裁,就可以选出新的领导者,并继续执行操作
关键示例
Snowball Edge 上的 Amazon S3
- Snowball Edge 设备集群使用仲裁机制提供 S3 兼容存储
- 管理设备状态和数据可用性 Ceph:
- 使用监视器和 OSDs(Object Storage Daemons,对象存储守护进程)及仲裁机制。
- 作为 S3 兼容后端维护集群状态和数据可用性。
- Object Storage Daemons(OSDs,对象存储守护进程)是 Ceph 等分布式系统中负责存储数据的核心进程,作为独立存储单元管理本地磁盘(HDDs/SSDs),并处理大量非结构化数据的复制、恢复和分发,因此是构建可扩展且有韧性的对象存储的基础。
- 分布式共识库:
- GitHub 使用 S3 的条件操作(ETags)进行领导者选举和仲裁
- OSD 的关键功能:
- 数据存储:在本地文件系统(如 XFS)上存储实际的数据对象。
- 数据管理:管理整个集群中的数据复制(创建副本)和恢复(重建丢失的数据)。
- 心跳监控:检查其他 OSDs 的运行状况,并向 Ceph Monitors 报告状态。
- 网络访问:为客户端提供对所存储数据的网络访问。
核心 S3 与基于仲裁的系统
Amazon S3(核心)
- 在全球基础设施中原生提供强大的写后读一致性
- 用户无须显式管理仲裁
- S3 基于仲裁的系统(集群/边缘):
- 在用户管理的 S3 兼容设备集群中应用仲裁概念
- 确保集群内部的一致性和可用性
S3 的系统级可用性设计和缓存层
核心概念
- 提供多个可供路由的节点,并预留故障余量
- 通过配置和容量规划实现 99.99% 的可用性目标
- 2020 年以前的写后读一致性
- 由于读取和写入存在重叠(两者都必须访问大多数服务器),读取方能看到写入方写入的值
- 问题:为什么 S3 在 2020 年以前不具备写后读一致性?
- 答案:缓存层
- 缓存层
- S3 的前端会大量缓存经常访问的对象
- 不一致读取:返回值 B,而不是最后写入的值 C
- 缓存中的读取和写入并不重叠
- 缓存具备关键的可用性属性(多个主机都能接收请求,并允许发生故障)
- 缓存中的读取和写入没有重叠
- 由于 2020 年以前的设计目标不包括一致性,这是当时可接受的故障处理方式
- 缓存层示例:
- 缓存节点为空,存储中的值为 B
- 读取方通过一个缓存节点读取,返回值 B,并将其驻留在缓存中
- 对 C 的写入覆盖对象,并持久化到各副本(C C C)
- 值 B 和 C 缓存在不同的节点上(2020 年以前的设计)
- 读取被路由到先前的缓存节点,返回值 B(不一致读取)
使用缓存一致性协议解决重叠问题
最受期待的功能:一致性
- 一致性是 2020 年以前最受期待的功能
- 目标:解决缓存层中的重叠问题
- 缓存一致性协议
- 必须快速、高效且具备可用性
- 保留允许多个服务器接收请求,同时允许部分服务器发生故障的属性
- 解决方案:复制日志
- 复制日志
- 一种将节点串联起来的分布式数据结构
- 写入按顺序流经各节点
- 每个节点都将写入转发给下一个节点
- 写入通过日志后,会发送到达到仲裁数量的存储节点
- 日志会记录最近的历史,并就顺序达成一致
- 示例:A 在 B 之前
- 基于仲裁的系统无法正确推断顺序
- 日志为变更创建明确定义的顺序
- 序列号和水位线
- 为所有写入分配序列号(随时间递增)
- 示例:A(序列号 1)、B(序列号 2)、C(序列号 3)
- 存储节点在获知值的同时也会获知序列号
- 序列号与值一起存储在缓存中
- 缓存节点可以询问在给定序列号之后是否有任何写入到达
- 见证系统
- 用途:跟踪索引写入的高水位线
- 见证节点无须保存实际数据,只需保存序列号
- 见证节点可以高估最后一个序列号(告知调用方其数据已过期是安全的)
- 如果调用方被告知其数据已过期,它将从存储中读取并获得正确结果
通过动态重新配置确保故障余量和一致性
见证系统
- 位于日志旁的内存数据结构(整数数组)
- 允许缓存节点询问在缓存值之后是否有任何写入到达
- 修改后的读写算法:
- 写入会进入日志
- 读取会与见证节点通信;如果缓存有效,则从缓存返回;如果已过期,则前往存储读取
- 故障余量丧失
- 原始日志架构:如果一个节点发生故障,写入就无法继续通过系统
- 仲裁系统并发处理写入;日志则让写入按顺序通过
- 日志中的节点故障可能导致整个系统停顿
- 动态重新配置
- 引入该机制是为了解决故障余量问题
- 日志中的节点会监控彼此的可用性
- 持续相互发送 ping;消息不断通过系统
- 节点掌握邻居可用性的最新视图
- 遇到问题时,节点会请求一个基于仲裁的配置系统重新配置日志
- 节点发生故障后,会在数毫秒内完成重新配置
- 配置系统本身也基于仲裁
- 结果
- 缓存具备故障余量,且读写操作相互重叠
- S3 现在既一致又高度可用
- 通过以可用性为目标进行设计,在一致性功能发布时仍保持了高可用性
- 系统级可用性视角
- 需要有许多服务器可供选择
- 只需在其中一部分服务器上成功
- 面对故障时快速重新配置系统的能力
- 即使在日志等系统中,也始终存在基于仲裁的算法
在实现层面应对故障
理解故障
- 问题:哪些方面可能发生故障?
- 相关性故障至关重要:同时发生的故障
- 在思考可用性时,相关性故障至关重要
- 仲裁设计:一个节点发生故障没有问题,但如果所有节点都位于同一 AZ 或机架中,则无法容忍
物理故障
S3 的高层物理视图
- 多个可用区(AZ)
- 每个 AZ 都有多个服务器机架
- 每台服务器有多个硬盘
- 物理故障的类型:
- 单个组件故障:硬盘电机损坏、盘片被划伤
- 相关性故障:
- 服务器故障:所有连接的硬盘看起来都发生了故障
- 机架故障:机架中的所有驱动器都发生故障(例如交换机损坏)
- AZ 故障:整个 AZ 停机(例如断电)
- 逻辑故障
- 示例:向服务器群部署新软件
- 每次部署到少量节点,然后逐步扩大范围
- 运行新软件的一组服务器构成一个故障域
- 如果新软件中存在缺陷,所有这些服务器可能会同时发生故障
- 需要智能的部署策略来容忍潜在故障
围绕相关性故障进行设计
让工作负载跨越多个故障域
- 跨多个位置(例如 AZ)复制对象,以实现持久性和可用性
- 确保即使某个相关性故障域(例如 AZ、机架、服务器)发生故障,数据仍然可用
理解故障类型
故障即停机
- 服务器或组件停止运行(例如电源线被拔掉)
- 在容错系统中易于检测和应对
- AZ 之间交换机的故障即停机:
- 某些请求会继续成功(位于同一 AZ 内)
- 其他请求开始失败(需要经过故障交换机)
- 形成一种模糊故障模式,其中部分请求成功,部分请求失败
围绕模糊故障模式进行设计
- 冗余是关键:在 AZ 之间部署多台交换机
- 每个区域中的 3 个 AZ 以环形方式连接
- 如果一条链路发生故障(例如 AZ1 到 AZ2),则可以绕远路(AZ1 到 AZ3,再到 AZ2)
- 将可用性问题转换为延迟问题,从而保持可用性
- 强大的思维模型:将一种问题转换为另一种问题(例如将可用性问题转换为延迟问题)
有状态系统中故障即停机的挑战
有状态系统中的故障即停机
- 在 S3 等系统中难以进行推理
- 可能导致系统进入原本无法到达的状态
- 崩溃一致性
- 系统应在发生故障即停机后恢复到一致状态
- 示例:向文件写入两行文本
- 正常执行:文件包含两行内容
- 执行期间发生故障即停机:文件可能只包含第一行
- 在没有故障的情况下,系统进入了一种无法到达的状态
- 围绕无法到达的状态进行设计
- 撰写了一篇关于 Shodstor(存储节点软件)的论文来解决此问题
- 重点研究如何在存在故障和并发的情况下推理可到达的状态
处理灰色故障
灰色故障
- 比故障即停机更复杂的故障模式
- 示例:
- S3 中的一个 PUT 请求
- 请求从互联网到达一台 S3 前端服务器
- 前端服务器将数据扇出到多个存储节点
- 灰色故障的定义
- 并非完全故障(例如故障即停机),但会导致系统行为异常的故障
- 示例:前端 Web 服务器接受请求,但由于网络问题而无法访问某些下游主机
- 服务器为失败的请求返回错误,但仍继续接受并响应请求
- 使用重试处理灰色故障
- 一种强大的技术:如果请求失败,则通过不同路径重试(例如不同的前端 Web 服务器)
- AWS SDKs 具有复杂的重试策略,会有意在不同 IP 地址上重试请求
- 重试允许通过系统尝试不同的路径
- 分布式系统中重试的危险
- 如果所有服务都重试失败的请求,重试可能导致工作量大幅放大
- 示例:堆栈底部的故障可能导致重试次数达到原始请求数的 27 倍
- 过多的重试可能使系统过载
- 有意设计重试策略
- 设计重试策略时需要深思熟虑,以避免系统过载
- 示例:在堆栈中越靠下的层级采用更少重试或不重试,因为知道更高层会进行重试
- 在保护系统免受过多重试影响的同时确保可用性
- 负载引起的灰色故障
- Web 服务器因工作量过大而过载,表现为请求缓慢
- 一种强大的机制:超时(客户端让缓慢请求超时,并在其他位置重试)
- 超时的挑战
- 超时并不完美,而且很难围绕它进行设计,尤其是在结合重试时
- 示例:
- 一台请求队列已满的过载 Web 服务器
- 客户端超时并在其他位置重试
- 服务器不知道客户端已重试,因此会继续处理已超时的请求
- 导致服务器对客户端已经放弃的请求执行无用工作
- 拥塞崩溃
- 服务器只处理那些已超时并在其他位置重试的请求时所处的状态
- 连锁反应:过载服务器变得更慢,导致更多重试和队列积压
- 自我强化的循环:服务器将所有时间都用在处理因重试而产生的、已超时失败的请求上
- 解决方法:反向队列处理
- 用于处理队列已满的过载服务器的机制
- 从队列末尾开始处理(后进先出)
- 虽然不公平,但通过牺牲部分客户端的体验来让其他客户端变得非常快
自我修复系统设计
摆脱拥塞崩溃
- 反向队列处理:从队列末尾开始处理(后进先出)
- 使部分请求能够成功,从而减少缓慢且已超时请求的积压
- 退避和重试:客户端执行退避并以更慢的速度重试
- 为服务器留出时间来清理积压
- 元稳定故障模式
- 尽管原始问题已经解决,系统仍将所有时间用于处理已超时请求的状态
- 系统级故障恢复
- 目标:S3 应在无须人工干预的情况下自我修复
- 示例:Web 服务器无法成功响应请求
- 客户端在其他位置重试,但需要将该服务器移出服务
- 健康检查
- 自动修复服务器的必备工具
- 健康检查服务器向 Web 服务器发送请求以检查其功能
- 如果服务器未通过健康检查,可将其移出服务(例如从 DNS 中移除)
- 通过阻止客户端访问故障服务器来缓解故障
- S3 对 AWS 基础设施的使用
- S3 构建在 AWS 上,使用的基础设施与客户可用的基础设施相同
- Web 服务器容量:网络负载均衡器
- DNS:Route 53
- 使用 NLBs 和 Route 53 实现健康检查
确保稳健的健康检查和系统运行状况全局视图
单一健康检查的危险
- 健康检查服务器可能发生故障,从而错误地检测 Web 服务器故障
- 示例:存在网络问题的健康检查服务器将所有 Web 服务器标记为异常
- 需要同时为服务器和健康检查系统的可用性进行设计
- 健康检查的多个视角
- S3 使用来自多个来源的健康检查:
- 同一区域
- 不同区域
- 公共互联网
- 组合这些信号,形成 Web 服务器运行状况的详细视图
- 有助于诊断相关性故障(例如网络问题)
- 避免局部决策
- 一项重要原则:绝不让局部系统就服务运行状况做出局部决策
- 示例:原始健康检查根据自己看到的系统状态做出局部决策
- 分布式系统需要全局视图,以避免局部决策造成错误
- 系统运行状况全局视图
- 示例:检测并修复即将发生故障的硬盘
- 软件检测到即将发生故障的硬盘
- 触发修复措施(例如重新通电、由技术人员更换)
- 健康检查的全局速率限制器
- 健康检查服务必须先咨询全局速率限制器,然后才能更改系统
- 示例:健康检查检测到即将发生故障的硬盘,并希望将其移出服务
- 如果健康检查服务失控,速率限制器会阻止移除过多硬盘
- 可用性设计回顾
- 定义可用性和正确性目标
- 设计整体系统架构以实现目标(例如基于仲裁的架构、强一致性)
- 在实现系统各部分时理解故障模式,并在不做出局部决策的情况下进行修复
