Airbnb 通过重新设计支付数据读取流程来优化客户端集成,并实现高达 150 倍的性能提升。
介绍
近年来,Airbnb 将大部分后端服务从单体应用重构成面向服务的架构(SOA)。这种行业标准架构为 Airbnb 这样规模的公司带来了无数好处,但也不是没有遇到挑战。由于数据分散在众多的服务中,很难以一种简单而有效的方式为客户提供他们所需的信息,特别是对于支付等复杂领域来说。随着 Airbnb 的发展,这个问题开始出现在许多新场景中,比如房东收入、生成税单和支付通知,所有这些都需要从支付系统读取数据。
在这篇博文中,我们将介绍 Airbnb 的统一支付数据读取层。这个自定义的读取层用于减少客户端集成的难度和复杂性,同时大幅提升了查询性能和可靠性。通过这种重构,我们能够为我们的房东和客户以及负责信任度、合规性和客户支持的内部团队提供极大优化的体验。
Airbnb 支付平台的演变
支付是 Airbnb App 最早推出的一项功能。自联合创始人 Nate 第一次提交代码以来,支付平台经历了快递的成长和演变,并且随着 Airbnb 不断扩大全球影响力,继续以更快的速度发展。
与其他公司类似,Airbnb 也是从单体应用程序架构开始的。由于最初的功能很有限,支付数据的读写流程都“相对”简单。
简化的 Airbnb 单体架构图。支付模式不是很复杂,功能也很有限
可以预见的是,随着 Airbnb 的快速增长和扩张,这种架构无法很好地扩展。支付和技术栈的其他部分开始迁移到 SOA 架构,对现有的架构进行了重大改革,并带来了许多优势,包括:
不同的服务之间有了清晰的边界,为更好的领域所有权和更快的迭代带来了可能性。
数据被规范地划分为多个领域,从而获得了更好的正确性和一致性。
要了解更多信息,请阅读我们关于支付 SOA 迁移的博文。
在迁移到 SOA 架构之后,每个支付子领域都有自己的服务和数据表,但更多的特性意味着更复杂和规范化的数据。
新架构带来新挑战
SOA 架构为我们带来了一个更有弹性、可伸缩和可维护的支付系统。在这个漫长而复杂的迁移过程中,保证系统的正确性是我们的首要任务。数据被标准化,并分散在许多支付领域中,由不同的团队负责。这种分工存在一种副作用:为了获取所有所需的数据,呈现层需要与多个支付服务集成。
迁移到 SOA 架构后支付数据的读取流程。呈现服务调用一个或多个支付服务,并在应用层聚合数据
我们相信,Airbnb 对房东和客人社区来说是透明的。与支付和收益相关的表面呈现了很多细节,包括费用、交易日期、货币、金额和总收益。在迁移到 SOA 架构之后,我们需要与多个服务打交道,并从更多的表中读取数据,以获得所需的信息。自然,当我们想要添加带有支付数据的新表面,或者当我们想要扩展现有的表面以提供额外的细节时,这个架构就给我们带来了挑战。我们需要解决三个主要的问题。
首先,客户端需要充分了解支付领域才能调用正确的服务和 API。对于其他团队的客户端工程师来说,这需要投入大量的时间,并拖慢了上线的整体速度。在支付服务端,工程师需要提供持续的指导,这也占据了他们工作的很大一部分时间。
其次,在很多情况下,为了满足客户端的需求,我们必须同时修改多个支付 API。当出现了太多的接触点,就很难对请求进行优先排序,因为必须有多个团队参与其中。这个问题也对上线时间造成了显著的负面影响。如果协调进行得不顺利,就不得不放慢或推迟上线。类似地,当支付团队更新 API 时,他们必须确保所有的呈现服务都应用了这些变更,这将减缓支付系统的开发进度。
第三,复杂的读取流程在技术质量方面并没有达到我们想要的水平。应用程序级别的聚合可以应对一般的应用场景,但是对于大房东来说,他们可能在我们的平台上每年有数千个预订,所以我们还有改进的空间。为了获得长期的系统可靠性,我们需要找到一个能够提供更好的性能、可靠性和可伸缩性的解决方案。
支付统一数据读取层
为了实现目标,我们需要重新思考客户端如何与支付平台集成。
统一的入口点
我们的第一个任务是统一支付数据读取入口点。为此,我们利用了 Airbnb 的面向数据服务网格Viaduct,客户端直接查询“实体”,而不需要调用几十个服务和 API。在这种新的架构中,客户端只需要处理必需的数据实体,而不必与个体支付服务通信。
呈现服务只与读取层打交道,不与个体支付服务通信
在这些入口点中,我们提供了尽可能多的过滤选项,让每个 API 都能够过滤和聚合客户端的复杂性。这也大大减少了需要公开的 API 的数量。
统一的高级数据实体
统一的入口点是一个很好的开始,但它并不能降低所有的复杂性。在支付领域,我们有 100 多个数据模型,要理清楚它们的职责,需要大量的领域知识。如果我们只是在统一入口点上公开这些模型,对于客户端工程师来说仍然需要掌握很多的上下文信息。
我们没有让客户端处理这种复杂性,而是选择通过更高级别的领域实体尽可能隐藏支付的内部细节。我们因此将核心支付数据减少到不到十个高级别实体,大大减少了公开的支付内部细节的数量。有了这些新实体,客户端就不受支付平台变化的影响。当内部的业务逻辑发生变化时,我们会保持实体 Schema 不变,客户端不需要进行任何迁移。新架构的原则如下:
简单:为非支付团队的工程师而设计,并使用了常见的术语。
可扩展:保持与存储 Schema 松散耦合,并对概念进行封装,以防支付服务内部发生变化,同时支持快速迭代。
丰富:隐藏复杂性而不是数据。如果客户端需要获取数据,应该可以在实体中找到它们。
公开更清晰的高级领域实体,隐藏支付内部细节,同时保护客户端免受频繁的 API 变更的影响
物化非规范化的数据
统一的入口点和实体大大降低了客户端接入的复杂性。但“如何”获取数据和昂贵的应用层聚合仍然是一个巨大的挑战。客户端能够顺利地与支付系统集成固然重要,但我们也应该让社区能够享受我们平台提供的良好体验。
我们发现的核心问题是客户端在查询时依赖了很多表和服务。一个可行的解决方案是去规范化——本质上就是将这些昂贵的操作从查询时转移到摄取时。我们调研了不同的方式来预先对支付数据进行去规范化,并将复制延迟控制在 10 秒以内。幸运的是,家园基金会团队的朋友正在尝试一个读优化的存储框架,它采用事件驱动的 Lambda 来实现二级索引。有了这个框架,团队可以通过数据变更捕获机制获得近实时的数据,也可以利用存储在 Hive 中的每日数据库转储来获得历史数据。此外,与其他现有的内部解决方案相比,这个框架的维护需求(例如在线和离线摄入数据使用的是同一套用 Java 编写的代码)要少得多。
支付服务如何使用读优化的存储框架。它为离线和近实时的数据提供摄取流程,并在二者之间共享业务逻辑
在结合上述的所有改进后,新的支付读取流程如下所示:
最终的支付数据读取架构。客户端不需要知道任何与支付服务或其内部相关的信息
我们通过非规范化的读优化存储索引来提供数据,具备很高的可靠性和性能。
迁移和提升:交易历史
针对新的统一数据读取架构的第一个测试场景是交易历史。房东通过交易历史页面来查看他们过去和未来的支付记录和顶级盈利指标(例如支付总额)。
从技术方面来看,这是我们最为复杂的支付流程之一。其中涉及了许多不同的细节,数据来自 10 多张支付表。这在过去已经导致了一些问题,包括超时、缓慢的加载时间、由于硬依赖导致的宕机,以及由于复杂的实现导致的迭代速度缓慢。在从单体迁移到 SOA 架构时,我们就决定对交易历史进行深度重构,而不是给它贴一副创可贴。这有助于确保长期的成功,并为我们的房东提供最好的用户经验。
交易历史页面和简化的高级架构视图。Airbnb 的单体 App 就像是一个呈现服务,从多个支付服务和遗留数据库获取数据
我们的统一读取层非常适用于这个场景。我们以交易历史的数据为起点,推出新的 API 和高级实体,为类似领域的所有数据读取用例提供服务。
在确定了实体及其 Schema 之后,我们开始对数据进行去规范化。我们借助读优化存储框架将 10 多张表的数据反规范化成几个 Elasticsearch 索引。我们不仅大大减少了查询的接触点,而且利用存储层进行更高效的分页和聚合操作。经过近两年的努力,我们迁移了 100%的流量,并实现了高达 150 倍的延迟改进,同时将可靠性从 96%提高到 99.9%以上。
在重构之后,交易历史所需的支付数据由支付服务的读优化存储提供,客户端在统一数据读取层上使用定义良好且可扩展的 Schema 来访问数据
解锁新体验:客户支付历史
我们的下一个应用场景是“客户支付历史”,源自 Airbnb 公司年度黑客马拉松。这个黑客马拉松项目旨在为我们的客户提供一个详细而简单的方式来跟踪他们的付款和退款。与交易历史类似,这个场景也需要来自多个支付服务和数据库(包括许多遗留数据库)的信息。
客户支付历史也从统一读取层获得许多好处:一个新的统一实体和未来类似的应用场景,以及一个支持多种不同过滤器的可扩展 API。我们借助读优化存储框架将遗留和 SOA 支付表中的数据非规范化并存储到 Elasticsearch 索引中,这大大降低了查询的复杂性和成本。
我们在 2021 年冬季版本上线时发布了这个新页面,大幅减少了与客户付款相关问题的客服工单,这为 2021 年节省了近 150 万美元的成本。这也说明了我们正在向一个更强大的具有高可靠性和低延迟的技术基础迈进。
客户可以通过客户付款记录来跟踪他们的付款和退款情况
这个架构与交易历史非常相似,数据通过统一的 API 和 Schema 提供给客户端,并由二级存储提供支持。
在通过交易历史和客户支付历史公开这些新实体后,我们开始在许多其他关键的场景中使用相同的数据流,提供高效支付数据服务。
总结
微服务和 SOA 架构帮助后端开发团队独立扩展和开发各个领域服务,将彼此之间的影响降到最小。同样重要的是,我们要确保这些服务的客户端及其数据在新的行业标准架构下不会受到额外的挑战。
在这篇文章中,我们介绍了一些潜在的解决方案,通过提供统一 API 和高级实体对调用者隐藏内部服务和架构的复杂性。我们还建议在摄入数据时利用非规范化二级数据存储来执行昂贵的连接和转换操作,确保客户端查询能够保持简单和高性能。正如我们所演示的那样,支付等复杂领域可以从这些方法中获得显著的好处。
原文链接:https://medium.com/airbnb-engineering/unified-payments-data-read-at-airbnb-e613e7af1a39
评论