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


通常 , 我们认为由网络和基础架构问题(5XX HTTP状态)导致的意外运行时异常是可重试的 。我们希望这些错误是暂时的 , 我们希望稍后重试相同的请求最终会成功 。
我们将验证错误(例如无效输入和状态(例如 , 您无法退还退款))分类为不可重试(4XX HTTP状态) - 我们预计同一请求的所有后续重试都会以相同方式失败 。因此创建了一个自定义的通用异常类来处理这些情况 , 默认为“不可重试” , 对于其他情况 , 它们被归类为“可重试”异常 。
至关重要的是 , 每个请求的请求有效负载保持不变并且永远不会发生变化 , 否则会破坏幂等请求的定义 。

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

文章插图
当然 , 需要谨慎处理更复杂的边缘情况 , 例如在不同的上下文中适当地处理PointerException 。例如 , 由于数据库链接暂时出问题而返回的空值与来自客户端或来自第三方请求中的错误空字段不同 。
 
客户端正如本文开头所提到的 , 在写修复系统中客户端需要更加智能 。在与使用像Orpheus这样的幂等性库的服务进行交互时 , 它必须做到:
  • 为每个新请求传递一个唯一的幂等键; 重试的时候重用相同的幂等键 。
  • 在调用服务之前将这些幂等键保留在数据库中(以后用于重试) 。
  • 正确成功响应后取消幂等键(或者置空) 。
  • 确保不允许在重试中改变请求有效负载 。
  • 根据业务需求仔细设计和配置自动重试策略(使用指数退避或随机等待时间(“抖动”)以避免惊群问题) 。
 
如何选择幂等键?选择幂等键是至关重要的 - 客户可以根据要选择保证请求级幂等性或实体级幂等性 。使用什么键将取决于业务 , 但请求级幂等性是最直接和最常见的 。
对于请求级幂等性 , 应从客户端选择随机且唯一的键 , 以确保整个实体集合级别的幂等性 。例如 , 如果我们想要为预订允许多种不同的付款方式 , 我们只需要确保幂等键是不同的 。UUID是一个很好的示例格式 。
实体级幂等性比请求级幂等性更加严格 。假设我们要确保ID为1234的10美元付款只能退还5美元 , 由于我们可以在技术上两次提交5美元的退款申请 , 所以希望使用基于实体模型的幂等键来确保实体级的幂等性 。示例格式为“payment-1234-refund” 。因此 , 对于唯一付款的每个退款请求都将在实体级别保证幂等(付款1234) 。
 
每个API请求都有到期租约由于多次用户点击或客户端激进的重试策略 , 可能会触发多个相同的请求 。由于竞态条件 , 可能会导致多次支付 。为了避免这些情况 , 在框架的帮助下 , 每个API请求都需要获取幂等键上的数据库行级锁 。这授予给定请求进一步继续的租约或许可 。
租约带有到期时间 , 以涵盖服务器端存在超时的情况 。如果没有响应 , 则在当前租约到期后才重试API请求 。应用程序可以根据需要配置租约到期和RPC超时时间 。经验法则是具有比RPC超时更长的租约到期时间 。
Orpheus还为幂等键提供了一个最大可重试窗口 , 以提供安全网 , 以避免意外系统行为导致的恶意重试 。
 
记录到Response我们还记录Response , 以维护和监控幂等行为 。当客户端对已达到确定性最终状态的事务(例如 , 不可重试的错误(例如 , 验证错误)或成功响应)发出相同的请求时 , Response将记录在数据库中 。
持久化Response确实是个性能权衡 , 保证客户端能够在后续重试时获得快速响应 , 但此表将随应用程序吞吐量增长而增长 。如果我们不小心 , 该表会变得很臃肿 。解决方案是定期删除超过特定时间范围的数据 , 但过早删除数据也会产生负面影响 。除此之外 , 开发人员应该谨慎 , 不要对Response实体和结构进行向后不兼容的更改 。
 
使用主库在使用Orpheus读取和写入幂等信息时 , 我们选择直接从主库执行操作 。在分布式数据库系统中 , 在一致性和延迟之间存在权衡 。由于我们无法容忍高延迟或读取未提交的数据 , 因此使用主库对我们来说是最有意义的 。如果数据库系统没有配置为强一致性(我们的系统由MySQL支持) , 那么使用副本进行这些操作实际上可能会对幂等性产生不利影响 。


推荐阅读