MoreRSS

site iconYasking | 东东修改

博主毕业后北漂两三年,2017 年初回到哈尔滨从事软件开发,2021 年初又回到北京。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Yasking | 东东的 RSS 预览

高鲁棒性 API 设计之 Idempotency Key:并发请求与执行一致性

2026-09-24 19:15:00

上一篇介绍幂等键留下一些问题,幂等键有了,如果两个请求同时到达,应该如何处理?

未仔细考虑过并发问题的代码可能也试图使用缓存进行存在性检验,但两个请求同时到达时,会发生下面的流程。

请求 A:检查 Redis,不存在
请求 B:检查 Redis,不存在

请求 A:执行兑换
请求 B:执行兑换

请求 A:写入幂等结果
请求 B:写入幂等结果

两个请求都通过了存在性验证,走到业务流程,兑换了两次,问题出在这里:

if !exists(key) {
    result := doBusiness()
    save(key, result)
}

exists 和 save 是两个独立操作。从单个请求的视角看,这段代码没有问题:

  1. 检查幂等键是否存在;
  2. 不存在则执行业务;
  3. 保存执行结果。

以并发视角出发,第一步和第三步之间则存在一个明显的时间窗口。

请求 A 检查完成后,请求 B 完全可以在请求 A 写入结果之前,也完成一次检查。于是两个请求都认为「我是第一次请求」。

所以面对并发问题,真正需要保证的是对于同一个 Idempotency Key,同一时刻只能有一个请求获得业务执行权。

原子地 “抢占” 执行权

Redis 的 SET ... NX 很适合完成这个动作。

例如:

SET idempotency:{key} processing NX EX 300

其中:

  • NX:Key 不存在时才写入;
  • EX 300:设置 5 分钟过期时间;
  • processing:表示该请求正在处理中。

这个操作在 Redis 中是原子的,于是两个请求同时到达时:

请求 A:SET ... NX -> 成功
请求 B:SET ... NX -> 失败

只有请求 A 获得执行权,请求 B 不再进入业务流程。

伪代码大概变成:

// 1. 原子抢占执行权
ok := setNX(key, "PROCESSING", 5*time.Minute)
if !ok {
    return handleDuplicateRequest(key)
}

result, err := doBusiness()
if err != nil {
    // 只有能够确认业务没有产生副作用时,
    // 才可以安全释放 PROCESSING。
    //
    // 如果业务结果处于“不确定”状态,
    // 不能简单 DEL,否则重试可能导致业务再次执行。
    return handleBusinessFailure(key, err)
}

// 2. 保存成功结果,并设置更长的结果保留时间
saveResultWithTTL(key, result, 24*time.Hour)

return result

相比之前:

if !exists(key) {
    result := doBusiness()
    save(key, result)
}

原来的 “先检查、再决定是否执行” 存在竞争窗口,而 SET ... NX 将 “检查是否不存在 + 抢占执行权” 合并成了一个原子操作。

但事情还没有结束

如果只做到这里,又会马上遇到几个新的问题,假设请求 A 抢占成功:

SET key processing NX EX 300

然后开始执行业务。

这时请求 B 带着相同的 Idempotency Key 过来了,它发现 Processing Key 已经存在。

注意:这里的 Processing 状态和 SUCCESS 状态,数据 TTL 是不同的,Processing 根据业务可能只有几分钟,而产生结果的 SUCCESS 状态时间会长很多。

问题来了:

  1. 这个 Key 是已经执行成功了?
  2. 还是正在执行?
  3. 还是上一次执行到一半服务挂了?

所以幂等记录至少需要区分 不存在、PROCESSING、SUCCESS

请求第一次到达时,通过 SET ... NX 原子地把状态从 “不存在” 变成 PROCESSING。

业务执行成功后,再把状态更新为 SUCCESS,并保存原请求对应的响应结果。

例如:

{
  "status": "success",
  "http_status": 200,
  "response": {
    "code": 0,
    "message": "success"
  }
}

这样后续相同请求再次到达时,就不需要重新执行业务,可以直接返回第一次请求的结果。

并发请求应该返回什么?

假设请求 A 仍然处于 PROCESSING:

请求 A:PROCESSING
请求 B:再次请求

请求 B 此时有几种处理方式。

一种方式是立即返回:

HTTP/1.1 409 Conflict

例如:

{
  "code": "request_in_progress",
  "message": "A request with the same Idempotency-Key is still being processed"
}

客户端稍后重试即可。另一种方案是服务端短暂等待,请求 A 执行完成后,把 A 的结果直接返回给 B。

例如等待几十到几百毫秒,并轮询 Redis:

B  -> 发现 PROCESSING
   -> 等待
   -> 查询
   -> 等待
   -> 查询
   -> 发现 SUCCESS
   -> 返回 A 的结果

这种方式客户端体验更好,但服务端实现也更复杂,而且会占用连接和计算资源。

实际项目中,更推荐这样判断:

短业务:可短暂等待
长业务:直接返回 processing/conflict,让客户端重试

缓存键一定要设置过期时间

前面的:

SET key processing NX EX 300

必须设置 TTL,因为服务可能在设置完 PROCESSING 后的处理过程中崩溃,如果缓存永不过期,那么这个 Idempotency Key 就永久处于处理中,没人知道它是不是还在运行。

之后所有重试都会被拒绝,本质上也是一种故障恢复机制。

然后就会出现新的问题:如果业务执行时间超过了 PROCESSING 的 TTL 怎么办?

例如 TTL 是 5 分钟:

00:00 请求 A 获得执行权
00:05 Redis Key 过期
00:06 请求 B 到达,重新 SET ... NX 成功
00:07 请求 A 仍然在执行业务

此时 A、B 又同时开始执行了。

所以 TTL 不能简单地拍脑袋设置。

对于执行时间可控的普通 HTTP API,可以让 TTL 明显大于业务正常执行时间。

对于耗时可能非常长的任务,则需要考虑:

  • 延长租约;
  • 定期续期;
  • 将任务转换成异步任务;
  • 或者直接把幂等约束放到数据库事务和唯一约束中。

本文使用 SET ... NX,只是为了原子地确定:谁是这个 Idempotency Key 对应请求的第一次执行者。

Redis 原子,不等于业务原子

还有一个更加重要的问题。

即使 SET ... NX 本身是原子的,也不意味着整个业务流程是原子的。

例如兑换业务:

1. Redis 抢占 Idempotency Key
2. 数据库事务:扣减积分 + 创建兑换记录
3. 数据库 COMMIT 成功
4. Redis 保存 SUCCESS

假设数据库事务已经 COMMIT 成功,但服务在 Redis 保存 SUCCESS 之前崩溃,此时可能出现:

Redis:PROCESSING
数据库:兑换已经成功
客户端:没有收到成功响应

过一段时间 PROCESSING 过期,请求重新执行。

如果业务本身没有其他保护机制,就可能再次扣积分。

所以 Idempotency Key 能解决的是 HTTP 请求级别的重复提交问题,搭配 SET ... NX 能看住服务的大门,内部系统的业务 ‘幂等建设’ 仍需要仔细考量服务可能在任何阶段挂掉后的恢复机制,数据库层增加最终约束进行兜底。

下一篇,仍是关于 Idempotency Key ,还需要继续学习解决一个问题:

如果两个请求使用了相同的 Idempotency Key,但请求参数却不一样,应该怎么办?

Memos: 风云不语,只是一个劲儿的赠送重置卡

2026-09-23 10:17:23

2026 年 9 月 22 日 Anthropic 发布 Claude Opus 5.5,同时赠送了一张重置卡(引入用户自主点击重置卡)。

01
02

同一天,OpenAI 发布 GPT-6 Sol 和 GPT-6 Luna 模型,也赠送了一张重置卡。

03

Yes 工程师提前过中秋!

Memos: 看着一点也危险

2026-09-22 09:19:23

01

早上骑车路过一个施工现场,远远就看到有个人站在渣土车上 ‘观察’,直直的站着,两脚就踩着车斗的两个边框上(比图中更加夸张)。我猜是为了指导挖掘机填土,(如果有)安全员,看了肯定直呼哇塞。

Memos: AI 默默的从 ~/.zsh_history 翻到 token 更新了生产环境

2026-09-15 17:01:23

我表述想更新 Tag Name,过了一会儿它已经把测试、正式环境都更新好了。

过于‘智能’,问题是我从未配置过生产环境的操作流程、地址和密钥,以至于我看到生产环境已经生效,最后才怀疑到是它干的...

0102

听说过很多 Agent 删数据库、删代码库、删工作区的... 第一次遇到这种翻我命令历史找线上环境地址和 token 自动操作不打招呼的,开眼界了 🤷‍♂️

吓得我赶紧添加了一段约束添加到 AGENTS.md 防一手儿这老小子。

## 外部环境与凭据安全

- **Agent 禁止操作 production**:不得连接、查询或修改线上 API、数据库、缓存、
  主机、集群、云资源、管理后台、部署或任务系统;只读、dry-run、用户已登录或
  用户提供凭据均不例外。
- 即使工具支持 production,Agent 也只能生成并本地验证命令、SQL、脚本和操作步骤,
  由用户手动执行;不得通过 shell、浏览器、子 Agent 或其他方式间接代为调用。
- 外部操作前必须明确环境和实际 host;无法证明是本地、测试或明确允许的隔离环境时,
  一律按 production 处理并拒绝执行。不得从文件名、历史命令或相似域名猜测环境。
- 缺少凭据、登录状态或环境信息属于合法阻塞条件。不得为完成任务而主动读取、搜索、
  解析或恢复 shell history、密码管理器、系统钥匙串、浏览器存储、SSH 身份、
  CLI 认证文件、日志、剪贴板或进程参数中的凭据。
- 泛化授权(如“调用接口”“修改数据”“使用已有登录”)不构成凭据来源授权。
  非 production 操作只能使用用户明确指定的凭据来源或工具约定的标准认证链。
- 不得读取、打印、复制、导出、持久化或跨任务复用秘密值;配置检查只报告
  “已设置 / 未设置 / 可用 / 不可用”。
- 可能触达 production 的工具必须要求显式提供环境、host 和 apply 参数,
  不得自动发现凭据或目标环境;工具能力不代表 Agent 获得执行权限。

非京籍个体户缴纳社保(补充):Web 端员工工资薪金个税预扣预缴

2026-09-15 11:45:00

早在几年前,Web 端就支持了单位代扣代缴员工个人所得税,当时参考小红书上的教程,就一直沿用 Windows 客户端的方式缴纳,其实 Web 端缴纳更加方便。

本文是对《非京籍个体户缴纳社保(十):新增并缴纳个人所得税-工资薪金》 一文的补充。


Web 端单位办税入口与综合所得申报

地址:https://etax.chinatax.gov.cn

登录后可以看到 Tab 有「单位办税」的选项,点击进入,在综合所得申报内,可以看到正常工资薪金所得。(员工信息需要重新录入)

申报信息中的应纳税额与已缴税额

而后查看申报信息是否准确,这里刚看到的时候有些奇怪,“应纳税额” 和 “已缴税额” 在客户端好像没这样显示。

累计应纳税所得额与应纳税额明细

不过是正确的(如果暂时不考虑个人承担的社保、公积金,每个月就是:工资收入 6000 − 基本减除费用 5000 − 赡养老人专项附加扣除 950 = 应纳税所得额 50 元(50 × 3% = 1.50 元)

到 2026 年 8 月:

累计收入:6000 × 8 = 48000
累计减除费用:5000 × 8 = 40000
累计赡养老人扣除:950 × 8 = 7600

累计应纳税所得额 = 48000 − 40000 − 7600 = 400 元

累计应纳税额 = 400 × 3% = 12 元

前 7 月已缴:1.5 × 7 = 10.50 元

8 月应缴:12 − 10.50 = 1.50 元

跟之前缴纳数额都是一致的,确认没问题后继续缴款。

银联缴纳方式缴纳税款

选择银联缴纳的方式,缴税款即可。Web 端功能更丰富,查询统计模块支持 “单位申报记录查询” 和 “个人扣缴明细查询”

在单位申报记录查询页面,可以看到有显示 “扣缴客户端web版”,之前的记录都是 “客户端”

单位申报记录查询显示扣缴客户端web版

2026 北京社保下限上调|个体户每月最低缴费 2707.44 元

2026-09-10 12:05:16

自 2026 年 7 月起,北京市职工基本养老、失业、工伤及职工基本医疗保险(含生育)的月缴费基数上限和下限分别为:

  • 上限:36348 元
  • 下限:7270 元

相比 2025 年,下限由 7162 元上调至 7270 元,增加 108 元,涨幅约 1.51%;上限由 35811 元上调至 36348 元,增加 537 元,涨幅约 1.50%。

按 7270 元缴费基数,个体工商户以企业身份为经营者缴纳社保,承担单位和个人两部分费用明细如下:

  • 企业职工基本养老保险:单位 1163.20 元,个人 581.60 元
  • 失业保险:单位 36.35 元,个人 36.35 元
  • 基本医疗保险:单位 639.76 元,个人 145.40 元
  • 职工大额医疗互助保险:单位 72.70 元,个人 3.00 元
  • 工伤保险:单位 29.08 元
  • 合计:2707.44 元/月(较 2025 年增加 40.17 元/月)

作为对比,2025 年社保基数调整后,个体工商户最低缴费由 2540.42 元/月上升至 2667.27 元/月(上涨 126.85 元/月)。

数据来源