作为一个平台,Uber(优步)邀请用户利用它,从它身上赚钱,并因它而快乐。每天,Uber 的服务超过 1800 万次请求,使人们在谋生的同时能够自由行动、开阔思路。作为底层引擎之一,Uber Money 实现了人们参与 Uber 体验的一些最重要的方面。像这样的系统不仅应该是健壮的,而且还应该是高度可用的,对宕机采取零容忍的态度,因为我们的成功口号是“准时、准确和合规地收付款”。
Uber Money 的工程师们在拓展多个业务线,制定下一个最佳策略的同时,也在打造下一代支付平台,从而推动 Uber 的增长。在本文中,我们将介绍这个平台,并分享我们学习到的经验教训。这包括在两个异步系统之间迁移数以亿计的客户,同时保持了数据一致性,目标是对我们的用户的影响保持为零。
动机与介绍
Uber 的第五代收付平台 Gulfstream 是我们最新开发的。它是一个单一的、集成的、符合 SOX 标准的系统,建立在复式记账原则之上,并且可以自我调节。在本文中,我们讨论了旧模型中的一些不足,并在新模型中进行了修正。
遗留系统有两个内部系统。一个向乘客和 Uber Eats(优步优食)用户收取费用,另一个向餐馆和合作伙伴司机支付费用。这个遗留系统有很多缺点,例如对于端到端的资金流动就没有整体看法。它还拖慢了构建更通用功能的进程,比如 Cash Trips、Uber 需要从其他司机合作伙伴那里收取佣金等等。因此,我们希望构建一个与角色无关的系统,可以从任何用户收付资金。这样就可以让多个业务线能够更快地上网。
新系统和架构的优势
基于作业 / 订单的系统
对于运行余额和用户实体的记账来说,基于交易的系统很难扩展。跟踪和执行零和原则是很困难的。
我们的新架构现在使用基于作业 / 订单的系统。每份作业都代表着一次拼车旅行或吃饭 / 送餐。由于调整、奖励、小费等原因,同一份作业可能会有多个订单。每个订单都包含多个订单条目,每个订单条目代表着进出用户账户的金额。它们共同代表了从付款人账户到收款人账户的资金流动。所有条目的总和为零(因为系统并不能创造或销毁货币)。它从一个账户流向另一个账户。货币流动、基于订单的系统创建了类似于现实世界中的复式记账系统。
这个表格展示了一个简单的拼车旅行的示例,总票价为 20 美元,其中包括 2 美元的服务费和 18 美元的车费。所有订单条目的总和为零。
我们通过利用消息队列系统来对创建订单和处理订单进行解耦:
订单插入服务处理创建付款订单的请求。然后,它创建付款订单,将订单数据发布到消息传递主题,并将其持久化到 OrderStore。
图:用于创建订单的管道
订单处理服务使用并处理付款订单。该服务在处理初始付款订单时还会生成连续订单。它还将收款和付款请求路由到相应的支付服务提供商。最后生成收付成功或失败状态的付款结果订单。
图:用于订单处理的管道
在所有区域都具备高可用性和活跃性
服务在多个地区可用的无损消息队列系统群集上交换订单消息。我们的部署池中有备用跨区域使用者实例。如果一个区域发生故障,其他区域的服务实例仍然可以使用和处理订单消息。
我们的系统将支付账户和余额数据保存在具有多区域仲裁的存储系统中。
我们如何实现幂等性?
我们使用唯一标识符作为用户、作业和订单的标识符。并且我们会确定性地生成唯一标识符。
已处理订单的唯一标识符用于确保订单已处理一次。
资金流动是基于订单处理,该订单处理会自动更改用户的付款账户。
我们的系统保证订单是不可变的。
我们在保存订单之后才处理订单。
异步平台之间的数据一致性
由于传统支付系统的复杂性、Uber 用户群和支付数据的规模,我们花了好几年的时间才迁移到这个新的支付平台。在迁移过程中,我们需要维护两个平台以及它们之间的高度数据一致性。为了实现这一点,我们将每笔交易更改都保存在实体更改日志中,以便我们的系统通过实体更改日志的每个用户版本号对写回进行序列化。我们使用包含版本号的字段对旧系统中的每笔交易进行双重写入。这样,即使同一作业进行了多个并发调整,写回也不会出现混乱,并且最终结果始终是一致的。
图:订单处理中的实体更改日志
迁移和写回
凭借我们在 Uber 实施迁移计划的经验,我们学到了:
构建正确的仪表板来跟踪业务指标。
制定部署策略,以便我们能够快速发现并解决问题,而不会影响到很多客户。
监视系统之间的流量,以验证新系统是否如我们所期望的那样运行,并且不会对客户产生影响。
从新系统写回到遗留系统的支付数据根据每个实体更改日志版本进行序列化,以解决两个异步系统之间的竞争条件问题。
仪表板和指标
在将新系统投入生产之前,我们添加了各种不同的指标。这包括每个工作流的跟踪技术、结果、延迟和基于可观察性的指标。我们为生产流量和影子流量设置了各种警报。这有助于我们在系统上跟踪业务指标。此外,我们还设置了各种仪表板来验证我们的服务。我们还可以使用这些仪表板来了解每个活动用户执行了多少成功的业务事件,以及在不同系统之间检测到了多少异常事件。我们的值班工程师和部署主管工程师每天都会跟踪仪表板。
智能部署策略
我们将部署设计为以多步骤方式来迁移系统。我们将部署大致分为以下几个部分:
团队内服务部署以同步系统
订单数据模型有一个属性
RolloutData
,该属性在整个付款流中传递,我们使用它来决定在新的支付系统中是否有任何付款人或收款人是主要的。RolloutData
的结构如下所示:
外部部署实际上是将整个功能迁移到新系统,并弃用旧系统。
实际部署是采用多种策略的增量式缓慢部署。
我们定义了对照组和实验组,每个组中有数百名用户和合作伙伴。
我们选择了一个用户和合作伙伴数量有限的国家进行首次部署。
我们基于每个国家 / 地区的百分比进行展示,从 1% 开始逐步增加到 5%、10%、20%、50%、100%。
我们通过动态配置控制部署,这样我们就不需要在部署期间中部署代码。
顺序写回
我们在处理每个订单时都会更新付款人和收款人账户。我们的服务生成一个 EntityChangeLog 来反映账户更改的顺序。每个 ChangeLog 条目都有一个版本号,我们为每个用户递增版本号。该服务使用版本号来强制写回订单的顺序。
写回服务使用邮件队列系统中的 EntityChangeLog 事件。如果它使用的事件不是按顺序发生的,那么我们的处理逻辑将识别版本不匹配的情况,并多次重试该事件。如果仍然失败,则将协调事件发布到另一个邮件排队系统主题。使用写回服务协调的服务将接受事件。它将版本号与遗留系统中最后记录的版本号比较。我们从 OrderStore 获取所有带版本号的订单,并将它们依次写回遗留系统。
图:用于写回处理的管道
验证和重试
我们的预部署阶段在系统中设置了各种验证策略:
异步作业每 24 小时运行一次,该作业针对国家 / 地区运行,并进一步将其分类为 cityID。
在配置单元中记录的端到端调试可以按订单访问日志。
我们对系统中的订单状态执行验证,以检查我们是否端到端地处理了请求,以及是否对每个订单进行了收付款。
图:用于验证的管道
在部署阶段,我们开始扩展到越来越多的国家 / 地区,这使得所有服务的负载呈指数级增长。我们发现遗留系统和新系统之间缺少了一个重要功能。具体来说,我们必须构建一种服务来使用上面讨论过的顺序写回版本来重新调整订单顺序。除了验证之外,我们还设置了重新调整请求 API,这样我们就可以重新运行数据,以更快地收集实时分析和调试问题。由于我们将系统构建为幂等性的,这有助于我们更快地从基于服务的故障中恢复,并快速记录数据。
因为这需要手动干预,所以我们很快构建了一个用于重试队列的摄取器。这使得延迟重调机制能够在重新调整订单之前运行基本验证。这有助于防止在队列中加载错误数据。
经验教训
对于任何快速扩展并试图扩大其设计产品范围的公司来说,迁移都是一个现实。这个复杂的项目涉及多个方面,即:
构建基于订单的复式记账系统的设计选择。
两个异步系统之间的无缝迁移,具有非常高的可用性。
通过平台重新设计,对我们的客户内部和外部都没有产生任何影响。
我们相信,我们所学到的经验教训可以帮助全球工程师尝试解决如此大规模的问题。因此,我们想分享一些关键概念:
版本控制对于提高两个异步系统之间的一致性至关重要。
端到端的集成测试包括测试租期和模拟测试环境,这样我们就可以发现并修复错误。
持续验证对于迁移和部署至关重要。有了它,你就可以在部署开始后立即发现问题。
全面的监控和警报缩短了检测和缓解问题所需的时间。
高度可靠的支付系统的基础包括长时间对暂时失败的支付进行指数级的重试。
总结与未来打算
我们经历了广泛的架构设计、实现和周密思考的部署和积极监控的旅程。而且我们成功地推出了平台,在所有国家的停机时间可以忽略不计。我们无缝、高效地加入了新的业务线,即 Uber Freight(优步货运)、NEMO。
作为我们未来项目的一部分,团队已经在考虑将系统真正转换为一个平台,这将减少更新用例的工程工作量,并将我们从特定于收款人、付款人和 LOB 的模型中迁移出来,从而彻底改变 Uber 的支付方式。
我们希望这些实践和架构设计对其他大规模执行迁移的工程师和团队有所帮助。
作者介绍:
Aakriti Singla,2017 年入职 Uber,担任支付平台软件工程师,负责各种资金流动项目,以帮助 Uber 不断增长的业务需求。
Simon Wu,2015 年入职 Uber,担任软件工程师,之前曾担任支付平台部门的技术主管,目前是金融产品部门的领域主管。职责包括确保工程设计和架构质量,推动事故后审查,领导支付和金融产品领域的跨团队项目开发。
原文链接:
https://eng.uber.com/money-scale-strong-data/
评论