本文介绍了 Uber 的全局操作协调平台 The Accounter,讨论了如何协调日益增长的平台规模和团队运维需求。原文:The Accounter: Scaling Operational Throughput on Uber’s Stateful Platform
简介
上一篇文章介绍了 Uber 的有状态平台 Odin,讨论了如何协调日益增长的平台规模和团队运维需求。多个冲突的操作可能会在没有集中协调的情况下破坏存储集群,导致可用性或持久性问题。如图1所示,当不协调的变更操作基于仲裁的存储集群时,会导致问题。这篇文章探讨了如何通过引入操作的全局协调来克服这个问题并扩展 Odin 的吞吐量。
Odin 操作基于 Cadence 工作流实现,当参与者(无论是人工的还是自动化的)想要操作一个托管的存储集群时,会通过工作流来操作。工作流由操作(如系统状态的更改)和等待周期(如等待系统收敛)组成,它们共同协调系统从一种状态到另一种状态的转换。工作流的执行时间从几秒钟(如升级容器映像)到几小时(如在主机之间迁移工作负载)不等(Uber 集群使用本地挂载磁盘)。我们将这些工作流称为运维操作。
我们需要某种机制来限制新操作的启动,或者换句话说,需要回答以下问题:给定当前情况,在此集群上继续执行此操作是否安全?
我们的设计要求如下:
- 独立的变更回路:这些回路应该彼此独立,这对于扩展开发高级功能至关重要。换句话说,变更回路不应该硬编码规则来确定何时在集群上执行操作是安全的。
- 特定技术栈策略:Odin 管理 Uber 的所有有状态技术栈,每种技术栈对集群操作都有独特的安全容忍度。因此,不同技术栈可能需要不同的策略。
- 平台范围限制:系统应该支持在平台上的所有技术栈/操作中强制执行全局限制。
我们选择的解决方案是一个名为 The Accounter 的全局软件组件,可以提供运维操作协调服务。它的名字反映了核心目的:作为跟踪所有正在进行的操作的中心化注册表,理解操作之间的关系,并充当发起新操作的看门人。一个好的思维模式是将 The Accounter 视为高级中断预算(disruption budget)或模糊信号量。
当启动某个操作时,必须首先向 The Accounter 请求对目标存储集群进行操作的权限,我们将此过程称为获取授权(claim)。授权涵盖整个操作,其中可能涉及对系统状态的多次更改。
The Accounter 使用特定于技术栈的策略来确定是否可以授权。该策略接受两个输入:集群运行状态(存储在 Grail 中的当前运行状态的集合)和当前正在进行的操作(跟踪并存储在 etcd 中)。
集群健康
Odin 存储集群由一个或多个工作负载组成,如 Raft、Apache Cassandra 或类似的数据库集群节点。每个工作负载都是容器的集合:一个工作容器、一个主数据库容器,可能还有几个边车容器。worker 负责管理数据库和边车容器的主机级生命周期,持续监控工作负载状态,并与控制平面通信。最近的工作负载状态存储在 Grail 中。
在确定集群运行状态时,除了在单个工作负载中可以观察到的信号外,还必须考虑其他几个运行状态信号。例如,集群是否存在未备份数据?集群是否因过度的数据变换或增加的客户端负载而承受压力?存储团队通常会像这样管理/收集集群级别的健康信息,Odin 提供了将集群状态提取到 Grail 中的方法。
对不健康工作负载的容忍度因具体技术栈而异,这在特定于技术栈的健康策略中得到体现,稍后将详细介绍。
进行中的操作
系统使用两个关键概念为正在进行的操作建模:操作(operations) 和 组(groups)。
每个操作都由一个操作对象表示,该对象包含关键细节,例如目标存储工作负载、操作类型(如清除或停机时间)及其对存储集群的潜在中断。每个操作都与一个或多个组相关联。
组跟踪链接到它的操作的数量,并存储超出操作计数的额外元数据。例如,记录最近开始和完成的操作。该数据允许对每个组内允许的操作实施基于时间的流控。
尽管有许多组,但大致可以分为两种类型:平台范围的组(例如,像地区、域和机架这样的故障域)和特定于技术栈的组(例如,单个存储集群和工作负载)。一个全局组跟踪所有正在进行的操作。
平台范围的组强制执行全局并发限制,有助于防止 Odin 和底层基础设施过载。同时,特定于技术栈的策略利用集群和工作负载组来保护集群可用性和持久性。
集群上允许的操作数量因技术栈而异。有些技术栈限制一次只能操作一个工作负载,而另一些则允许对一定比例的集群进行并发操作。可以动态创建特定的组,以跟踪集群中特定工作负载子集(如角色)上的操作。这种灵活性支持针对不同存储技术栈的需求定制更细微的安全策略。
下图说明了如何对操作进行建模,操作对象链接到几个组对象。当操作完成或失败时,这些显式关联有助于执行清理过程。
安全策略
既然可以有效测量集群运行状态并获得所有正在进行的操作的概述,就可以引入安全策略了。安全策略是可编码中断预算,允许表达特定于技术栈的策略,以便决定处理存储集群中冲突的操作。
安全策略包括两部分:健康策略(health polic) 和 限制策略(limit policy)。
健康策略基于最新收集的集群运行状态信息来确定是否可以执行请求的操作。例如,技术团队可能希望防止在客户机负载增加或工作负载不健康的集群上执行操作。
另一方面,限制策略可以限制影响组的并发操作的数量,在执行顺序操作的过程中实现宽限期,或者提供独占性操作,在某个组正在操作的时候拒绝其他请求。在我们希望操作单个机架时,这尤其有用。
The Accounter 为实现策略提供了一组功能,例如:
- 测量工作负载和群集运行状况的方法
-
CheckMaxOperations(group, max)
:检查指定组的最大操作数 -
CheckElapsedFromLastClaim(group, duration)
:检查自从上次将操作与指定组相关联的声明开始,是否已经过去了给定的时间 -
CheckElapsedFromLastUnclaim(group, duration)
:检查自从上次从指定组释放与操作相关的声明以来,是否已经过去了给定的时间
健康评估是一种即时检查,不能提供关于操作安全性的硬保证。运行状态数据收集涉及分布式系统中的延迟,这意味着可能会根据过时的集群运行状态视图批准两个同时进行的中断操作请求。限制策略执行的检查能够确实提供硬保证,因为响应操作是通过 etcd 事务提交的。我们来探讨一下它是如何工作的。
架构
为了持久化操作和组,我们使用 etcd 作为键值存储。当一个工作流想要对某个存储集群进行变更时,将经历以下过程:
- 想要进行授权的工作流调用 The Accounter,携带包含有关目标存储工作负载和操作目的的信息。
- The Accounter 从 Grail 检索当前群集运行状态,从 etcd 检索当前操作状态。
- 在评估目标工作负载之前,The Accounter 会评估整个平台的并发性和速率限制。
- 接下来,针对 etcd 中的状态评估特定于技术栈的健康和限制策略。如果任何一个策略失败,授权将立即被拒绝,而不会进行操作。
- 如果满足所有标准,则构建所需变更的单个事务并提交给 etcd。
- 授权被接受或拒绝取决于 etcd 事务是否成功。
- 继续执行操作。
- 操作完成后,工作流负责通过 The Accounter 释放授权。
etcd 中的变更以事务方式执行,以确保对正在进行的操作的一致视图。具体来说,在提交变更之前,使用乐观锁来验证关于组内操作数量的假设。事务构建器为安全策略开发人员抽象了复杂性,给他们一种直接在内存中工作的印象。这种方法类似于 etcd 的 STM(Software Transactional Memory,软件事务性内存)库,但进行了优化以提高吞吐量。
如果由于乐观并发冲突而拒绝事务,则会在内部重试几次。如果请求被拒绝,只要该操作仍然相关,就依赖该操作进行重试。如果拒绝是由于违反了组的速率限制,则 The Accounter 会提供一个有意义的回避时间,操作可以基于该时间来决定如何继续进行。
为了避免所有授权都必须从 etcd 中获取状态,首先根据不断更新的数据内存快照进行评估。如果授权违反了基于缓存状态的任何一个策略,将立即被拒绝,而不会尝试将事务提交给 etcd。这很有必要,因为系统必须扩展到每秒 3,000-4,000 次授权尝试,其中大部分流量来自平台对工作负载的审核以及尝试授权。
etcd 事务最终接受授权并以事务方式检查操作限制,并在事务提交时进行授权。
授权生命周期
操作通常是分层的,因此我们设计了 The Accounter 来支持从父操作到子操作的授权传递。传递的授权是可重入的,意味着当子操作试图获取授权时,将变成空操作。这种设计允许更复杂的操作,同时保持操作逻辑的简单性。开发人员不需要了解整个操作结构来确定是否已经获得授权,而只需根据需要使用授权,系统知道如何进行正确处理。
操作负责在完成后释放授权。但在某些情况下,操作可能会被终止、意外失败或报错。虽然这种情况很少见,但在操作规模比较大的情况下确实会发生。系统必须确保授权最终被释放,因为过期的授权可能会阻塞操作吞吐量。The Accounter 总是可以追溯操作,因为操作在数据模型中链接到它们的授权,从而有助于识别不活跃的操作并安全的释放过期的授权。
审计
权力越大,责任越大。将策略开发委托给技术团队有一定风险,过于保守的策略可能会阻碍平台执行全团队操作的能力。Uber 将不同技术栈的工作负载放在同一台主机上,导致同一台主机上有数百个工作负载。当平台必须释放一台主机时,必须首先释放所有工作负载(即转移到其他主机)。限制性安全策略增加了只能部分释放主机的风险。
为了解决这个问题,我们实施了广泛的审计系统。该系统持续评估工作负载的可授权性,提供跨平台操作的准确快照。此信息发布到 Grail,并由变更回路作为预过滤器使用,以确定可行的操作。
此外,Odin 团队利用这些数据来深入了解那些操作被长时间阻塞的工作负载,从而使 Odin 团队能够提醒负责受影响存储技术栈的团队。
替代性方案
我们至少尝试过另外两种协调操作的常见方法:分布式锁管理器和Kubernetes 中断预算。在这里,我们将解释它们与 The Accounter 有何不同。
分布式锁管理器通常涉及获取集群上的锁,确保一次只能执行一个操作。但是,考虑到在 odin 中操作单个工作负载所需时间较长(主要是由于本地附加磁盘),为单个工作负载操作锁定整个集群是低效且不切实际的。
对于锁,一个更灵活的替代方案是将锁扩展到信号量,允许同时授予预定义数量的令牌。这类似于 Kubernetes 所采取的方法,其中中断预算设置了固定数量的前期操作。但是,The Accounter 只关注计数操作,将执行限制的责任留给单独的策略,从而与这些方法有所不同,并在策略设计方面提供了更大的灵活性。例如,它可以指定只允许一定数量的优化性调度。不过,如果主机出现故障,请求紧急转移,总会被允许。将这些紧急情况保留在模型中是一个优势,因为该策略可以声明,从那时起,在主机故障完全修复之前,不允许进行任何优化性调整。这种灵活性在保持操作效率的同时适应实时条件方面至关重要。
规模化
The Accounter 现在已经完全集成到所有操作中,平台完成的操作数量很大。我们来看看目前的数据:
流量
每小时 30 万授权评估
每小时 700 万次授权评估预演
活跃操作和组
2000 个活跃操作
70 万个不同的组
结果
多年来,The Accounter 显著提高了 Odin 的运行效率,使小团队能够安全管理数千个集群,促进了集中式的效率项目,并赋予了领导层权力,使其将 Uber 的物理基础设施视为灵活和临时的。此外,The Accounter 通过明确的关注点分离保持了团队独立性,变更回路所有者只关注确定哪些操作是必要的,而无需担心安全考虑。
一个值得注意的例子是我们采用静态加密的努力。我们已经能够完全中心化驱动这个过程,所要做的就是让自动化系统将工作负载通过静态加密转移到主机上,而 The Accounter 会确保这一切的安全。这个过程涉及迁移 210 万个 cpu 和 1.6 EiB 本地挂载磁盘。在过去,这样的运维需要广泛的计划和执行,涉及所有技术栈的利益相关者,耗费数年的工程时间。现在,他们不需要做任何事情。
未来的工作
The Accounter 范式正在积极发展,我们正在努力解决目前的一些局限性。需要改进的一个重要领域是对操作优先级的支持。目前,操作依赖于持续轮询来获取授权,这会产生不必要的流量,并且不允许不同操作类型的优先级。当低优先级的优化性操作阻塞了高优先级的人工操作时,就变得特别成问题。另一个有趣的领域是直接在 The Accounter 中定义断路器的能力。目前,Odin 中的每个循环都实现了这个功能,以防止问题导致的不当行为。我们的目标是将其提供作为 The Accounter 的内置功能,简化过程并提高整体系统的弹性。
总结
在这篇文章中,我们介绍了 The Accounter,一个全局协调系统,旨在提高 Uber 有状态平台 Odin 的吞吐量和操作安全性。通过提供操作协调即服务(operation coordination as-a-service)机制,可以在保证集群安全、避免操作冲突的同时,高效执行大规模操作。它跟踪正在进行的操作,执行特定于技术栈的策略,并确保只有在安全的情况下才启动新操作。The Accounter 显著提高了 Uber 的运维效率,使小团队能够安全管理数千个集群,并驱动静态加密迁移等中心化流程。
你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!
本文由mdnice多平台发布