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