Redis:探索将 Redis 作为无服务器数据库来解决 API 中的幂等性问题 🛵 📬 Middy 的幂等中间件

2025-06-08

Redis:探索将 Redis 作为无服务器数据库来解决 API 中的幂等性

🛵 📬 Middy 的幂等中间件

Redis 意味着快速。这一直是我的印象。但与此同时,对我来说,Redis“仅仅”(好像这还不够)是一个缓存存储。令我惊讶的是,Redis 的功能远不止于此。

这将是探索AWS 无服务器数据库现状系列文章的第一篇,我选择了 Redis,只是想尝试一些不同于常见选择(DynamoDB)的东西(同时也想尝试一下第三方产品)。在接下来的几周里,我计划探索 AWS 上一些与无服务器架构配合使用的数据库产品,并探讨它们各自的优缺点。

无服务器 Redis

Redis 的定义如下

Redis 是一个开源(BSD 许可)的内存数据结构存储,可用作数据库、缓存和消息代理。Redis 提供多种数据结构,例如字符串、哈希、列表、集合、支持范围查询的有序集合、位图、超日志、地理空间索引和流。

你可以查阅几篇深入描述每种数据结构的论文,以便做出更明智的决定。由于它是开源的,理论上我们可以在服务器中实现它,但如果继续走无服务器路线,那将是一个奇怪的,甚至是错误的选择。这里介绍的是Uptash,一个基于Redis 的无服务器数据库产品。在“使用无服务器 Redis 进行微博”中,你可以了解 CRUD 的工作原理;如果更注重框架,你可以观看Lee Robinson 的精彩视频,了解如何在 Next.js 中使用它:

foo正如我在开篇文章中承诺的那样,我希望进行深入的探索,而不仅仅是我们那些平庸的bar例子(顺便说一句,Upstash 的官方文档在这方面也存在问题)。对于 Redis,我几乎只停留在键/值存储类型,但这个解决方案可以解锁许多用例,因为它允许你访问 GraphQL API,并且你可以拥有一个文档齐全的资源管理器。

Upstash 产品

在开始之前:对我来说,设置价格上限和账单无意外始终是一个优势,在这种情况下,您甚至无需提供信用卡信息即可开始使用。但是,当您超过免费套餐时,您需要输入信用卡信息,这与价格上限功能无关——这完全合理。我之前在《致 AWS 的公开信:请给我们一个价格上限》一文中讨论过这个问题,至少对我来说,作为一名居住在巴西的开发人员,AWS 极其忽视了这一点。

我的目标是尝试一些有价值的用例,同时深入探索数据处理,并希望解决一个非常重要的问题,至少对我来说是这样:幂等性。所以没有什么比key/ valuestore更花哨的了,但我认为探索一个对于任何生产级函数来说都很重要的特性。另外,请注意,虽然这是Powertools库中的一个特性NodeJS,但我遵循的是官方Powertools库的步骤Python。我希望即使你不是这个Node生态系统的成员,也能从中获得一些价值。

幂等性:自己做

幂等运算可以重复任意次数,结果始终相同。在算术中,将零加到一个数字上就是幂等的。某些类型的 AWS Lambda 请求应该运行多次,当你的函数不是幂等的时,就会产生不一致的问题。AWS 有一个页面介绍了如何实现这一急需的功能。是的,这很经典,让你的 API 幂等确实有一个非常重要的原因

为什么幂等性很重要:

  • 你有一个 Lambda 函数,它通过 API 进行支付并保存到某个数据库中。如果其中一个成功,另一个失败,你的 Lambda 函数会再次运行,最终你可能会遇到两个收银机,甚至更糟……你的客户会被重复收费。
  • 您不想或不需要重新处理某些可能耗费大量时间或资源的请求。也许用户正在一次又一次地尝试执行某个操作。

API 具备幂等性并不像很多人想象的那么简单。你可以先看看AWS 的Lambda Powertools for Python,它出色地解释了这个用例并给出了具体的实现方法。Malcolm Featonby也写了一篇很棒的文章, 《使用幂等 API 实现安全的重试》 ,它在 Amazon Builder's Library 中被列为300 级架构,属于高级分类。

让我们从Lambda Powertools 的范围开始:

幂等性键是整个事件或事件的特定配置子集的哈希表示,调用结果以 JSON 序列化形式存储在持久存储层中。

首先,我创建一个新的数据库。我使用的是 WordPress 著名的“5 分钟安装”功能。Upstash 声称只需 30 秒即可安装完成,这可不是开玩笑。

创建数据库

在这里您可以看到我选择启用强一致性模式强一致性。强一致性提供最新数据,但代价是高延迟。最终一致性提供低延迟,但可能会使用过时的数据回复读取请求,因为数据库的所有节点可能都没有更新的数据。因为这是一个非常复杂的主题,而这个简短的解释只是非常简短地开始触及它的表面,我建议您阅读Martin Kleppmann的书《设计数据密集型应用程序可靠、可扩展和可维护系统背后的大创意》 。我通常大部分时间都使用最终一致性。我在这里选择强一致性,因为我想尽最大努力做到尽可能的幂等,否则我们可能会得到假阴性。

每个 Lambda 都会收到一个event作为其请求的一部分。

因此,我们在 lambda 函数中要做的第一件事就是创建整个对象的哈希表示event。我将使用 Node 原生crypto库。

const hash = crypto
               .createHash(sha256)
               .update(event)
               .digest(base64);
Enter fullscreen mode Exit fullscreen mode

这将为给定事件生成一个唯一标识符,并将其用作 Redis 数据库中的唯一键。

我将利用Middy,这是 Node 中 Lambda 的轻量级中间件,它具有一个特别酷的功能(对于当前任务非常有用),即类似洋葱的中间件模式实现,以及创建可以在处理程序之后之前读取函数的中间件的能力,这对于幂等 API 至关重要。

Middy 中间件

之前我们必须将其event变成哈希;

const createHash = (event: any): string => {
  return crypto
           .createHash(sha256)
           .update(JSON.stringify(event))
           .digest(base64);
};
Enter fullscreen mode Exit fullscreen mode

请注意,那里有一个明确的 any — 神话中的、在生产中不存在的any

然后,检查 Redis 数据库中该哈希值是否已存在于表中。我们将使用 lib 来完成此操作ioredis。我们将向正在创建的中间件传递一个选项。

//in the handler
import Redis from ioredis;

// handler code
handler.use(jsonBodyParser()).use(
 idempotent({
   client: new Redis(process.env.UPSTASH_REDISS)
 })
);
Enter fullscreen mode Exit fullscreen mode

Redis 实例正在rediss://通过环境接收包含您的用户名和密码凭证的字符串。这不是最安全的方法,您可以将此 URL 存储在AWS Systems Manager中,然后以安全的方式导入,这样甚至可以根据需要轮换凭证。我在这里为了测试而使用了这种快捷方式,但我肯定您永远不会在生产环境中这样做,对吧?

眨眼

无论如何,我们需要解析结果,因为我们将在下一个执行阶段将它们保存为字符串。

// in the middleware
const hash = createHash(request.event);

const getByHash = await options.client.get(hash);

if (getByHash) {
  return JSON.parse(getByHash);
}
Enter fullscreen mode Exit fullscreen mode

如果发生错误,我们会收到一个null响应,这对于一个非常简单的检查来说非常棒。如果收到null,我们不需要做任何事情,函数将继续执行其他中间件、处理程序,然后执行后续的执行顺序。如果这个 get 不为null ,我们必须返回after函数存储的响应

{
  statusCode: 200,
  body: {\n  “data”: {\n    “message”: “Hello from the other Side!”\n  }\n}
}
Enter fullscreen mode Exit fullscreen mode

然后我们将调用return response您的中间件。这将提前停止执行,并且不会传递给 lambda 的任何其他部分,因此该中间件需要是第一个(如果不是第一个可用的话)的中间件之一。

之后我们必须保存该事件哈希的响应

const hash = createHash(request.event);
const responseStr = JSON.stringify(request.response);
await options.client.set(hash, responseStr);
Enter fullscreen mode Exit fullscreen mode

仅此而已。请注意,此阶段和执行发生在处理程序通过处理程序发送响应之后。

就是这样。请注意,此阶段和执行发生在处理程序通过处理程序发送响应之后。

是的,它确实很快。Upstash 自己的博客上发表了一篇名为《无服务器数据库延迟比较》的文章,之后Hacker News 上也对此进行了讨论。我没有做过基准测试,甚至没有计划这么做,但作为一名用户,我感觉它已经达到了极致的即时性。而这个功能无疑符合我的预期。

好了,各位,“就这样吧”。

开玩笑的,当然不是。但这只是一个开始。由于碰撞的可能性几乎为零(即两个哈希值相等),只有完全相同的请求才会得到相同的响应。但如果我们想检查某些键,例如x-idempotence请求头中的键,甚至是请求正文中的字段,我们也可以将其作为目标。

如果你想了解这个实现并寻求帮助,我把这段代码打包成 NodeJS Lambdas 代码,作为Middy的中间件提供给你使用。这个库接受一些选项,用于设置 headers、body 中的键和路径:

GitHub 徽标 ibrahimcesar / middy幂等

🛵 📬 适用于您的 AWS Lambdas 的 Idempotence Middy 中间件

🛵 📬 Middy 的幂等中间件

适用于 AWS Lambdas 的幂等 Middy 中间件

TypeScript

版本   执照   GitHub 按标签发布问题

在🇧🇷巴西开发

🛵 它做什么

Middy是一个非常简单的中间件引擎,它允许您在使用 Node.js 时简化 AWS Lambda 代码。该中间件旨在简化幂等 API 的实现。

让 API 具备幂等性并不像很多人想象的那么简单,你可以看看AWS 的Lambda Powertools for Python,它很好地解释了这个用例以及具体的实现方法。Malcolm Featonby也写了一篇很棒的文章,使用幂等 API 实现安全的重试,它在 Amazon Builder's Library 中被列为300 级架构,属于高级分类。

🚀 安装

使用您最喜欢的包管理器:

yarn add middy-idempotent
Enter fullscreen mode Exit fullscreen mode
npm install middy-idempotent -S
Enter fullscreen mode Exit fullscreen mode

用法

此外@middy/core,您还必须使用@middy/http-json-body-parser这个中间件……





至于上面的库,您可以实现 SSM,这样它就不会将您的秘密字符串放置在您的基础设施环境中,但我计划至少在接下来的几天内添加另一个存储提供商 DynamoDB。

接下来,我将测试另一个无服务器数据库产品,虽然不是那种“Hello World”式的示例,但会用更实用、更有价值的用例——至少,这是我的希望!也请在评论区留下你的想法、见解和见解!

继续阅读:https://dev.to/aws-builders/redis-exploring-redis-as-serverless-database-to-solve-idempotence-in-apis-2gma
PREV
📂 仓库管理:组织和优化你的项目🗂️
NEXT
新功能:无服务器框架中的 DynamoDB 流过滤