手记

如何设计一个高效的幂等API接口

幂等性API表示,无论该API使用相同的请求体/参数被成功调用多少次,数据/系统状态都将保持相同。

对于一个遵循良好原则的RESTful API来说,除了POST和PATCH之外,其他所有方法都是幂等的。但在某些特定情况下(例如,创建支付、上传文件、微服务通信……),如果我们希望让API更健壮,实现幂等性至关重要。

在这篇文章中,我们将提出一种不仅关注API性能,还具有幂等性的机制。我们将讨论以下几点:—— 为什么我们需要API具有幂等性—— 如何提高API性能—— 提出一种高效且实用的幂等性机制。

(如果您想了解如何在Java Spring Boot应用中实现这个机制,请参阅我的另一篇文章:https://medium.com/gitconnected/spring-boot-3-build-the-efficient-idempotent-api-by-redis-8d8ef70d2574

为什么需要幂等的API?

在软件工程界,我们被告知网络并不总是那么可靠。一个简单的做法就是在请求失败后重试。然而随之而来的问题是:不仅发送请求也不那么可靠,接收响应同样不稳定

如上图所示,有两种不同的情况会导致服务器1无法收到响应。然而,对于服务器2中的数据状态而言,情况则完全不同。当请求未能发送时,服务器2不会接收到任何数据,此时资源尚未创建。在这种情况下,可以安全地重试。相反,如果问题是响应未正确返回到服务器1,服务器2已经生成了资源。在这种情况下,如果没有适当的保护措施,直接重试是不合适的。

没有适当的保护措施就进行重试可能导致严重问题,比如向客户重复收费、在服务器上生成重复的文件或导致数据不一致等问题。

一种建立安全的重试机制的解决方案,实现幂等的 API,确保提供一个健壮的 API 和一个具有弹性的系统。

如果你觉得这篇文章有价值,请考虑通过请买我一杯啤酒 🍺来支持我的工作。别忘了分享和点赞这篇文章,也可以在Medium上关注我,以获取更多未来的技术故事。感谢您的支持,祝您读得开心!

您的慷慨支持让我更有动力创作更多系统设计方面的内容。

如何提升性能

一个具有幂等性的 API 完全依靠数据库(DB)来检查请求是否已被处理过,这会增加数据库的负担,从而可能影响我们 API 的性能。

考虑常见的一些重试场景,主要包括请求超时后的重试和由异步消息消费者发起的重试等。大多数重试一般会在短时间内发生

因此,我们建议添加一个额外的内存缓存服务器(比如 Redis),通过缓存这些数据,从而优化该机制的性能,从而提升其效率。

高效的幂等(执行一次或多次效果相同的操作)机制

我提出的机制流程如下。

在这个机制中,我们有 8个进程和5个决策点。请注意,整个 流程必须放在auth-filter之后,以防止未经授权的请求访问缓存数据。现在,我们来逐一看看(P代表流程;D代表决策)。

主要流程
  • P1: 带有**rid**的请求
    在调用幂等性API时,需要提供一个唯一的rid,通常由时间戳、用户ID和一个随机值组合而成。
  • D1: 检查缓存中是否已有**rid**
    服务器的首个操作是检查缓存中是否已有rid,根据是否有rid来决定后续步骤。
流程1:缓存未找到时
  • P4: 使用**rid**初始化缓存并设置is-donefalse
    该操作应在同一个事务内执行,以避免发生竞态条件,可能使用 Java Redis 客户端中的 setIfAbsent 方法或 Redis 中的 setnx 方法。
  • D3: 检查数据库中是否存在**rid******
    在真正处理请求之前,仍需检查请求是否已被处理过,以防止重复处理。
  • P5: 如果没有缓存或数据库中的记录,执行原始 API 过程
    应执行原始过程以处理请求。
  • D4: 根据处理结果采取不同的行动
    在处理请求后,成功执行将导致缓存更新并响应调用方。否则,需要进行详细的错误检查以决定下一步该做什么。
  • D5: 适当的异常处理对错误分类至关重要
    可重试错误包括对第三方 API 或其他数据源的网络错误,文件系统等。不可重试错误,如验证错误、内部服务器错误等,需要修改代码或调整设置才能成功重试。
  • P6: 当得到明确的结果(成功或不可重试的错误)时,更新缓存
    将实际响应更新到缓存中,并将 is-done 设置为 true
  • P7: 对于可重试的错误,应该删除缓存
    以准备处理重试请求。
  • P8: 不论处理结果怎样,都需要向调用方返回响应
当存在缓存时
  • D2: 已完成状态
    获取缓存中的 is-done 属性值,并检查该属性,它是在子流1中被设置或更新的。
  • P2: 返回缓存响应
    如果缓存中的 is-done 属性为 true,表示这是一个之前完成的请求,应立即返回该缓存响应。
  • P3: 返回正在处理中的错误
    如果缓存中的 is-done 属性为 false,表示请求正在被处理,为防止竞态条件或其他多线程问题的发生,应返回一个错误代码,告知调用者请求正在处理中,并建议稍后再试。
简单总结一下

这篇文章介绍了一种与缓存服务器协作的幂等机制,以增强性能为目的。在实现过程中需要注意一些关键点。

  1. 唯一请求标识符(rid
    客户端必须在其请求中包含一个 rid,以确保每个请求都具有唯一标识符。
  2. 置于认证过滤器之后
    为了安全考虑,幂等性处理流程应置于认证过滤器之后。
  3. 原子缓存操作
    检查缓存是否存在以及创建缓存条目应原子性地进行,以确保一致性保持。
  4. 不存在缓存时的数据库集成
    尽管使用了缓存机制,当缓存不存在时,需要数据库来检查其存在性。
  5. 利用额外属性
    在缓存条目中添加类似is-done这样的额外属性,以表示请求的处理状态。
  6. 异常处理的重试性定义
    定义在请求处理过程中遇到的每种异常的重试性,以帮助决定是否进行缓存更新或删除。

然后我打算试试在我的event-sourcing poc项目(事件源POC项目)实现这个机制,下次聊。

参考
  • 这里有一些关于幂等 REST API 的链接:了解更多信息,以及一篇关于如何使用幂等 API 构建弹性系统的文章:阅读更多
0人推荐
随时随地看视频
慕课网APP