LabHub
学习 学习路径 课程

系统间对接 (EAI)

用幂等键防住重复接收

在 LabHub 中继续学习

目标

设计幂等键,使用数据库约束阻止重复,在并发执行时也保持安全,并实现先保存后响应,从而构建可安全接收重发请求的接收端。

为什么重要

在存在异步集成和重试的环境中,重复并非例外,而是默认情况。然而,如果在应用程序中采用조회 후 없으면 삽입的方式防止重复,**两个请求同时到达的瞬间就会失守。**这是因为查询与插入之间存在空隙。数据库约束可以消除这一空隙,即使应用程序有缺陷也不会被绕过。再进一步,把首次响应保存下来,并在收到重复请求时原样返回,发送方就可以在超时后放心重发。这正是支付 API 使用 Idempotency-Key 的原因。

步骤

  1. /root/i/idem.db(sqlite)中创建 inbox_log 表。列为 msg_id, biz_key, status, response, created_at,且 msg_id 必须具有PRIMARY KEY 或 UNIQUE 约束
  2. 编写 /root/i/key.md。正文必须包含以下三项。
    • 该接口采用什么作为幂等键,以及原因
    • 为什么不能只使用 order_no
    • 幂等历史的保留期限及其依据 必须同时出现 멱등키, 보관, 수정 三个词。
  3. 创建 /root/i/apply.sh。接收两个参数(DB파일 JSON파일):
    • 如果是新消息,则加载并在第一行输出 applied,退出码为 0
    • 如果已经存在,则不执行任何操作,在第一行输出 duplicate,退出码为 0 必须采用利用约束的方式,而不是查询后再插入。
  4. 加载 /opt/lab/fixtures/eai/idem/messages/ 中的所有 JSON,加载时使用 apply.sh
    • inbox_log 的行数必须等于不同 msg_id 的数量
    • 将因重复而忽略的 msg_id 按升序保存到 /root/i/dup.txt
  5. 创建 /root/i/race.sh。接收两个参数(DB파일 JSON파일),对同一条消息同时尝试加载 10 次,并在最后一行输出 rows=<해당 msg_id 의 행 수>。该值必须为 1。将运行结果保存到 /root/i/race.txt。(为应对 sqlite 锁竞争,请设置 PRAGMA busy_timeout。)
  6. 创建 /root/i/purge.sh。接收两个参数(DB파일 보관일수),只删除 created_at 早于保留天数的行,并在最后一行输出 deleted=<건수> remain=<건수>
  7. 扩展 apply.sh,实现先保存后响应
    • 处理新消息时,把响应 JSON 保存到 response 列,并在 applied 后一行(第二行)输出该响应
    • 遇到重复消息时,在 duplicate 后一行(第二行)原样输出已保存的首次响应 对同一消息应用两次时,第二次输出的第 2 行必须与第一次响应相同。将确认结果保存到 /root/i/replay.txt
  8. 编写 /root/i/report.md。把发生重复的四种路径分别写成一项,并为每条路径同时写明防御位置。必须同时出现 재시도, , 수동, 배치 四个词。

参考

幂等历史表

/root/i/idem.db(sqlite)中创建 inbox_log 表。列为 msg_id, biz_key, status, response, created_at,且 msg_id 必须具有PRIMARY KEY 或 UNIQUE 约束

防止重复的最后一道防线不是应用程序,而是数据库约束。请先确定应在哪个列上设置约束。

幂等键设计文档

编写 /root/i/key.md。正文必须包含以下三项。

仅使用业务键往往不够。同一订单可能存在修改报文,报文编号也可能按天循环。

加载脚本

创建 /root/i/apply.sh。接收两个参数(DB파일 JSON파일):

键已处理时,应当不执行任何操作并明确告知。查询后插入会在并发执行时失守,因此请使用约束。

批量加载与重复统计

加载 /opt/lab/fixtures/eai/idem/messages/ 中的所有 JSON,加载时使用 apply.sh

必须从加载结果中分别统计新消息和重复消息。保留重复键列表,可用于日后的原因分析。

防御并发执行

创建 /root/i/race.sh。接收两个参数(DB파일 JSON파일),对同一条消息同时尝试加载 10 次,并在最后一行输出 rows=<해당 msg_id 의 행 수>。该值必须为 1。将运行结果保存到 /root/i/race.txt。(为应对 sqlite 锁竞争,请设置 PRAGMA busy_timeout。)

即使并行多次插入同一条消息,也必须只保留一条。sqlite 经常发生锁竞争,因此需要设置等待时间。

按保留期限清理

创建 /root/i/purge.sh。接收两个参数(DB파일 보관일수),只删除 created_at 早于保留天数的行,并在最后一行输出 deleted=<건수> remain=<건수>

清理的瞬间,相应区间的重复防御也会消失。必须只按期限条件删除;按数量删除可能会删掉最近的数据。

先保存后响应

扩展 apply.sh,实现先保存后响应

向重复请求原样返回首次响应后,从发送方角度看,重发就会完全安全。这是支付 API 采用的方式。

整理重复发生路径

编写 /root/i/report.md。把发生重复的四种路径分别写成一项,并为每条路径同时写明防御位置。必须同时出现 재시도, , 수동, 배치 四个词。

重复会通过四种路径到来。请同时写明每条路径中哪一个位置是防线。