导读:Uber 高吞吐量的支付系统架构之最佳实践。
Uber (优步)新近推出了一套高吞吐量的财务账簿处理系统,旨在处理其分布式会计基础设施中单个账户持续的写入争用。
该系统针对的是单个账户经历集中更新高峰的场景,这种高峰可能超出传统按请求交易处理模型的处理能力。
据Uber工程团队介绍,该设计支持每个账户每秒超过30次的更新,同时保持严格的一致性和可审计性要求。
这项功能已经集成在 Uber 的财务账簿平台中,该平台基于复式记账模型构建,用于记录系统内的所有资金流动。该模型提供了强大的正确性保证和端到端的可追溯性,但要求账户级别的更新必须严格序列化。在高度竞争情况下,例如批量调整、对账或操作更正,单个账户可能会经历持续的更新频率,从而暴露传统交易执行流程中的瓶颈。
Uber首席信息官马克·彼得斯(Mark Peters)这样说道:
对于 Uber 这样的规模而言,对亚秒级批处理和保持严格一致性的重视对于运营弹性至关重要。
Uber 团队还发表报告称,这种架构显著缩短了高容量工作负载的处理时间,提高了其市场系统中的财务对账和运营工作流程的速度。
Uber 的核心支付平台 Gulfstream 是一个基于复式记账原则构建的集成式、符合 SOX 标准的平台。它采用基于作业/交易的架构,每天处理数百万笔交易。该系统遵循资金在账户之间流动而不创建或销毁的基本会计原则,并通过用户账户变更日志 (UAC) 提供不可篡改的审计跟踪。
然而,随着平台的发展,某些用户账户经历了极端的流量高峰和数据量激增,导致快捷键出现问题,超出了标准处理流程的承受能力。到 2023 年,一些用户账户需要处理的更新操作远远超过了我们系统每秒 3-4 次的上限。
例如,大型平台运营商每天要处理针对单个用户账户的数万笔批量调整,需要 21-24 小时才能完成。车队运营商每天往往产生数十万笔交易,处理时间超过一天。车队运营商在特定时段遭遇的支付高峰会造成系统负载过高,导致处理流程不堪重负。除了车队运营商之外,某些 Uber for Business 客户还会向特定地区的乘客提供补贴。此类活动会导致每个 Uber for Business 客户每天产生数万笔交易。
传统处理方法需要对每个操作进行多次连续往返:
由于每次操作的总延迟在 130 到 370 毫秒之间,通过顺序处理实现每秒 30 次以上更新操作的目标在数学上是不可能的。
Uber最后创造了一种截然不同的方法。
人们的核心发现是,大部分处理时间都消耗在数据存储的往返通信上,而不是实际的计算上。这促使我们采用批量更新的方式,以分摊延迟成本。
Uber 团队考虑过传统的批处理方法,例如基于 Apache Kafka® 的方法,但最终放弃了这些方法,因为客户端批处理会增加延迟,消费者端 1-2 秒的延迟是正常的,而 Kafka 针对高吞吐量进行了优化,但并不针对亚秒级延迟。
为了支持信用卡冻结等实时操作,我们设置了 1 秒的软性限制。因此,我们需要一个能够以 250 毫秒为窗口批量处理操作的系统。这个时间窗口既足够短,可以满足实时操作的需求,又足够长,可以显著提高吞吐量。
Uber团队已经否决的其他架构设计方案包括:
在系统设计中,需要在每个用户账户每秒执行超过 30 次操作的情况下,保持严格的一致性保证。此外,还需要将操作批量处理在 250 毫秒的时间窗口内,以平衡延迟和吞吐量。
无论批次大小,我们都需要将数据存储交互次数降至最低,每个批次仅允许一次读取和一次写入。此外,我们还必须确保审计跟踪的不可篡改性,以符合SOX合规性要求。
最后,还需要一个能够处理数据持久性的系统,其中 Redis® 数据丢失只会影响单个操作,而不会影响整个批次。
用户账户批量处理系统采用三服务架构:批量创建服务、批量处理服务和批量后处理服务。
图 3 展示了这些服务如何协同工作。
批量创建器处理传入的操作,并将它们分组到按时间划分的批次中。它使用 Redis 进行协调,因为 Redis 具有亚毫秒级的延迟和高 RPS 能力。
批量处理服务是核心引擎,它通过一次性读取账户状态、在内存中应用所有操作并执行一次原子更新来处理批量数据。它利用跨不同区域的多个处理器来实现容错。
批量后处理服务与主路径解耦,并处理异步审计日志的生成和发布。
该系统采用 Redis 集群、用户账户存储和事务数据库。Redis 集群是批处理协调、任务队列和结果缓存的中央协调层。用户账户存储是账户余额的权威数据存储,设计为最小化交互(每个批次一次读取,一次写入)。事务数据库存储用于合规性的不可变审计跟踪(UAC)。
该架构旨在分摊数据存储往返的延迟,使其分摊到多个操作中。每个用户帐户的最大吞吐量超过每秒 30 次操作,批次内每次操作的有效延迟为 8-20 毫秒。整个批次的总耗时约为 400-650 毫秒。
用户账户批量处理系统的整体流程包括:
Uber设计的关键的架构优势和创新之处包括:
开发中的挑战
开发过程中出现了两个技术难题和解决方案:时间同步和用户帐户大小管理。
在分布式系统中,机器对时间的感知并不一致,而时间对于定义有时间限制的批次至关重要。为了解决这个问题,我们将托管每个批次的 Redis 实例指定为权威时间源。这消除了时钟同步问题,并确保了批次边界的一致性。
另一个挑战是,传统的审计日志会占用大量空间,这可能导致在高吞吐量处理期间用户帐户大小超出存储限制。为了解决这个问题,我们开发了 MicroUAC 格式,其大小小于 100 字节,远小于传统的审计日志,但同样能够保证幂等性。这一发现对于在高批处理频率下管理用户帐户大小至关重要,而这一发现源于生产测试。
Uber针对各种故障场景设计了系统。单个操作的故障会被隔离,Redis 服务中断只会导致暂时性的服务不可用,而不会影响数据完整性。为了确保审计跟踪的数据持久性,我们结合了 Redis 的速度优势和 Docstore 的持久性。多区域冗余机制也保证了即使单个区域发生故障,其他区域的执行器也能继续进行批处理。
在迁移或构建用于处理关键任务型、高吞吐量交易(例如批量订单处理或大规模财务更新)的新系统时,出错的可能性非常大。高吞吐量流程中的一个漏洞就可能导致系统性数据损坏。
为了保证完整性(所有工作均已完成)和正确性(所有数据均正确),我们采用了两种强大的技术:影子测试和压力测试。
影子测试是指将生产环境中实时流量的副本路由到新系统,而新系统的输出不会影响生产环境。其目的是将新系统的行为与可信的现有系统的行为进行比较。
验证过程分为两个关键领域:完整性验证和正确性验证。
完整性验证步骤确保新系统按预期完成所有必要的工作项。我们验证以下内容:
在正确性验证过程中,我们会确认算术运算和状态变更完全正确。一个专门的验证服务会查询两次并行运行的结果。它会将账户 A(旧系统)的最终余额或状态与账户 B(新系统)进行比较。此外,它还会将审计日志中的状态变更与原始输入进行交叉比对,以确保每笔交易都能正确更新账户状态。最终的通过标准很简单:账户 A 的最终状态及其所有相关日志必须与账户 B 完全相同。
正常负载下的正确性还不够。高吞吐量系统必须在最大压力下也能完美运行。为了确定系统极限,我们使用了一种专门的流量放大流程。我们并非简单地镜像实时流量,而是对模拟流量应用一个倍增因子(例如 10 倍、15 倍)。这使我们能够模拟远超峰值生产量的突发活动。
此压力测试有助于我们确定最大可持续吞吐量(每秒请求数)、极端负载下的系统延迟以及隐藏的瓶颈或资源争用点(例如,数据库连接池和 CPU 饱和度)。
该系统旨在处理流量激增的账户,例如大型市场运营商处理数万笔批量调整交易,或者车队运营商产生大量日常交易、支付高峰或处理奖励。
该系统现在只需几分钟即可处理以前需要 21-24 小时才能完成的金融交易。这种性能的显著提升催生了以前无法实现的全新业务功能,并使系统能够在应对极端流量高峰的同时,保持运行的稳定性。
在本篇文章中,我们详细介绍了Uber如何使用实时批处理技术,以每秒每个用户账户 30 次操作的速度处理财务更新,同时确保数据一致性。
Uber采用了三个服务架构,分别用于创建批处理、处理批处理和后处理批处理,从而确保生成审计日志。另外还讨论了如何保证容错能力并控制数据规模。这项新增功能减少了系统中的重试次数和相关噪声,显著提升了客户的最佳体验。
作者:场长
参考:
https://www.uber.com/us/en/blog/high-throughput-processing/
本篇文章为 @ 场长 创作并授权 21CTO 发布,未经许可,请勿转载。
内容授权事宜请您联系 webmaster@21cto.com或关注 21CTO 微信公众号。
该文观点仅代表作者本人,21CTO 平台仅提供信息存储空间服务。
请扫描二维码,使用微信支付哦。