并发一致性实验:幂等、缓存回填与过期锁
运行条件
这组实验配合旧文审校,帮助区分“看起来合理的流程”和“可验证的保证”。不需要 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 的前两个测试。
- 读取者取得旧数据库快照,停在屏障处。
- 写入者提交新值并删除缓存。
- 释放屏障,让读取者回填旧快照。
- 断言后续查询命中旧值。
第一项测试通过,说明缺陷被稳定重现。第二项用一个同步的版本比较与赋值步骤阻止旧值回填,后续查询得到新值,但先前在途的读仍是旧值。
这个原子步骤只存在于当前内存模型。跨数据库与 Redis 的“先比较再 set”会重新引入竞态,需要真正可执行的协调协议。不能把第二项测试当成分布式生产方案已被证明。
关联旧文:旁路缓存模式实践。
实验三:锁过期后旧持有者仍能写
文件:examples/consistency.test.js 的后两个测试。
旧持有者 A 暂停,后来的 B 获得租约并写入。A 恢复后仍能向没有防护的资源写入,覆盖 B。增加资源侧 token 校验后,资源先接受 B 的 token 2,再拒绝 A 的 token 1。
校验与写入必须在资源侧原子完成,token 必须由可靠的单调序列产生。示例用常量明确展示顺序,没有实现 Redis 租约、共识服务或故障转移。相同 token 的重复业务操作仍可能需要幂等控制。
关联旧文:分布式锁实践。
怎样把实验推进到真实系统
- 先明确要证明的性质,例如订单数、库存值、缓存陈旧时间。
- 在实际数据库与中间件版本上注入提交失败、响应丢失、租约过期和连接中断。
- 检查最终存储状态,不能只看请求返回成功。
- 将环境、命令、输入、结果和未覆盖故障记录到项目文档。