支付核心系统设计:Airbnb的分布式事务方案简介( 二 )


 
解决方案我们希望能够唯一地识别每个请求 。此外 , 我们需要准确跟踪和管理特定请求在其生命周期中的位置 。
我们在多种支付服务中实施并使用了“Orpheus” , 这是一种通用的幂等库 。Orpheus是传说中的希腊神话英雄 。
我们选择了实现幂等库作为解决方案 , 因为它提供低延迟 , 同时仍然提供高速变更的产品代码和低速变更的系统管理代码之间的隔离 。在高层次上 , 它包含以下:

  • 幂等key被传递到框架中 , 表示单个幂等请求
  • 始终从主数据库读取和写入(为了一致性)幂等信息表
  • 通过使用JAVA lambda组合数据库事务 , 确保原子性
  • 错误被分类为“可重试”或“不可重试”
接下来我们将详细说明具有幂等性保证的复杂分布式系统如何能够自我修复并达到最终一致 。我们还将介绍一些该方案应该注意的设计权衡和带来的额外的复杂性 。
 
最小化数据库提交幂等系统的关键要求之一是只产生两个结果 , 即成功或失败 , 具有一致性 。否则 , 数据有偏差可能导致数小时排查错误时间和付款出问题 。由于数据库提供ACID属性 , 因此数据库事务可以有效地用于原子写入 , 确保一致性 。一次数据库提交可以保证其作为一个单元的一致性 。
Orpheus假设每个标准API请求都分为三个不同的阶段:Pre-RPC , RPC和Post-RPC 。
“RPC”是指客户端向远程服务器发出请求并等待该服务器响应的过程 。在支付API的上下文中 , 我们将RPC称为对下游服务的请求 , 其可以包括外部支付服务和收单银行等 。简而言之 , 如下是每个阶段发生的事情:
  • Pre-RPC:付款请求的详细信息记录在数据库中 。
  • RPC:请求通过网络对外部服务进行实时处理 , 并收到响应 。这是一个执行幂等计算或RPC的过程(例如 , 如果是重试尝试 , 则首先查询事务状态) 。
  • Post-RPC:来自外部服务的响应的详细信息记录在数据库中 , 包括其是否成功以及错误请求是否可重试 。
为了保持数据完整性 , 我们遵循两个基本规则:
  • 在Pre-RPC和Post-RPC阶段 , 没有远程服务交互
  • RPC阶段中没有数据库交互
我们希望避免将网络调用与数据库操作混在一起 。在pre-RPC和post-RPC阶段网络调用(RPC)易受攻击的 , 并可能导致连接池快速耗尽和性能下降之类的不良后果 。简而言之 , 网络调用本质上是不可靠的 。因此 , 我们将Pre和Post-RPC阶段处理数据库事务 。
我们还想要说明单个API请求可能包含多个RPC 。Orpheus支持多RPC请求 , 但在这篇文章中 , 我们只想用简单的单RPC案例来说明我们的思考过程 。
如下面的示例图所示 , 所有Pre-RPC和Post-RPC阶段中的数据库提交都合并为一个数据库事务,这确保了原子性。动机是系统应该以可恢复的方式出现故障 。例如 , 如果在多次数据库提交过程中有几次失败 , 那么系统地跟踪每个失败发生的位置将非常困难 。请注意 , 所有网络通信(RPC)都与数据库事务明确分开 。
支付核心系统设计:Airbnb的分布式事务方案简介

文章插图
这里的数据库提交包括幂等库的数据库提交和应用程序数据库提交 , 所有这些提交都组合在同一个代码块中 。如果不小心组织 , 这里的代码就会非常混乱 。我们认为产品开发人员不应该负责保证幂等库的操作 。
 
Java Lambdas 组合事务值得庆幸的是 , Java lambda表达式可以将多个提交无缝地组合成单个数据库事务 , 也不会影响可测试性和代码可读性 。
下面是一个示例 , 简化了Orpheus的使用 , 其中Java lambdas如下:
public Response processPayment(InitiatePaymentRequest request, UriInfo uriInfo) throws YourCustomException { return orpheusManager.process( request.getIdempotencyKey, uriInfo, // 1. Pre-RPC-> { // Record payment request information from the request object PaymentRequestResource paymentRequestResource = recordPaymentRequest(request); return Optional.of(paymentRequestResource);


推荐阅读