CC 咖啡猫的工作空间 Coding Space

并发一致性实验:幂等、缓存回填与过期锁

运行条件

这组实验配合旧文审校,帮助区分“看起来合理的流程”和“可验证的保证”。不需要 Redis、Java 或容器。

本轮实际运行环境:Node.js 22.22.2、Python 3.12.0、SQLite 3.43.2。Python 需包含 sqlite3 模块。

在仓库根目录执行:

python3 examples/idempotency.py
node --test examples/consistency.test.js

预期分别通过 4 项 Python 测试和 4 项 Node.js 测试。数据库在临时目录创建并清理;时序实验使用显式屏障,不依赖随机 sleep。

实验一:数据库事务中的订单幂等

文件:examples/idempotency.py

订单表使用 UNIQUE(user_id, request_key) 约束,每次请求把规范化请求体作为指纹保存。BEGIN IMMEDIATE 先取得 SQLite 写事务,再检查同键记录,插入与业务结果一起提交。

输入 预期结果
16 次并发请求使用同一用户、键和请求体 相同订单 ID,数据库只有一行
首次提交后丢失响应,再建立连接重试 返回同一个已持久化 ID
同一键换一个商品 抛出指纹冲突错误
插入后、提交前抛异常 回滚后可重试
不同用户使用同一个键 可以创建各自订单

尝试改掉事务中的异常回滚,再运行测试,观察为什么“保存成功标记”和“业务完成”不能分开确认。

实验边界

此处业务效果就是新增订单记录,没有库存和外部付款。SQLite 同时只允许一个写事务,演示中的串行化不能直接等同于 MySQL 的行级并发。请求体规范化也只覆盖这个 JSON 示例,不是跨语言签名标准。生产实现仍需认证、操作类型作用域、输入校验、保留策略与故障处理。

原理来源:SQLite 事务Python sqlite3。关联旧文:接口幂等性实践

实验二:固定复现旧缓存回填

文件:examples/consistency.test.js 的前两个测试。

  1. 读取者取得旧数据库快照,停在屏障处。
  2. 写入者提交新值并删除缓存。
  3. 释放屏障,让读取者回填旧快照。
  4. 断言后续查询命中旧值。

第一项测试通过,说明缺陷被稳定重现。第二项用一个同步的版本比较与赋值步骤阻止旧值回填,后续查询得到新值,但先前在途的读仍是旧值。

这个原子步骤只存在于当前内存模型。跨数据库与 Redis 的“先比较再 set”会重新引入竞态,需要真正可执行的协调协议。不能把第二项测试当成分布式生产方案已被证明。

关联旧文:旁路缓存模式实践

实验三:锁过期后旧持有者仍能写

文件:examples/consistency.test.js 的后两个测试。

旧持有者 A 暂停,后来的 B 获得租约并写入。A 恢复后仍能向没有防护的资源写入,覆盖 B。增加资源侧 token 校验后,资源先接受 B 的 token 2,再拒绝 A 的 token 1。

校验与写入必须在资源侧原子完成,token 必须由可靠的单调序列产生。示例用常量明确展示顺序,没有实现 Redis 租约、共识服务或故障转移。相同 token 的重复业务操作仍可能需要幂等控制。

关联旧文:分布式锁实践

怎样把实验推进到真实系统

  • 先明确要证明的性质,例如订单数、库存值、缓存陈旧时间。
  • 在实际数据库与中间件版本上注入提交失败、响应丢失、租约过期和连接中断。
  • 检查最终存储状态,不能只看请求返回成功。
  • 将环境、命令、输入、结果和未覆盖故障记录到项目文档。