Redis+Token方案在分布式下易因网络超时、主从复制延迟、实例切换导致失效,仅适用于日志上报等弱一致性场景;强一致性操作如支付、订单必须用数据库唯一联合索引兜底;两者可组合使用,Redis前置拦截、DB最终校验,但需对齐生命周期与错误处理逻辑。
API 接口的防重操作(即幂等性)不能只靠前端拦截或按钮置灰,后端必须做校验;Redis 防重 Token 和数据库唯一联合索引是两种主流方案,但适用场景、一致性保障和失败路径完全不同,选错会导致重复扣款、订单重复创建等严重问题。
为什么 Redis + Token 方案容易在分布式下失效
典型做法是:接口首次调用生成随机 token 存入 Redis(带过期时间),客户端后续请求携带该 token,服务端先 GETDEL 校验是否存在。看似原子,但实际有三个隐性断裂点:
- 客户端生成
token后未成功提交(网络超时),重试时用了新token,旧token仍滞留在 Redis 中——导致合法重试被拒 - Redis 主从异步复制,
GETDEL在从节点执行可能读到过期但未同步删除的token,出现“已使用却校验通过” - 服务集群中某实例处理成功但响应丢失,客户端重试发往另一实例,此时 Redis 中已无该
token,重复操作被放行
所以 Redis + Token 只适合对一致性要求不高的场景(如日志上报、非资金类状态更新),且必须配合客户端幂等重试策略(如指数退避 + 唯一请求 ID 上报)。
什么时候该用数据库 唯一联合索引 而不是 Redis
涉及资金、库存、订单创建等强一致性操作,必须依赖数据库最终一致性兜底。核心思路是:把业务天然具备的“唯一性维度”作为索引字段,让数据库拒绝重复插入。
- 支付回调:用
out_trade_no+pay_channel建联合唯一索引,重复回调直接触发IntegrityError - 用户注册:用
phone+tenant_id(多租户场景)索引,避免同一租户下手机号重复注册 - 优惠券领取:用
user_id+coupon_template_id索引,防止同一用户重复领同一张券
注意:索引字段必须覆盖所有判重维度,且不能含可空字段(NULL 在唯一索引中不参与比较);异常捕获要精确到 psycopg2.errors.UniqueViolation 或 django.db.IntegrityError,不能笼统 except Exception。
Python 3.14.3
微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。
Redis Token 和 DB 唯一索引 能否组合使用
可以,但必须分清职责:Redis 做“快速前置拦截”,DB 做“最终一致性兜底”。关键在于两者的生命周期和失败处理逻辑要对齐。
- Redis 中
token过期时间应略长于 DB 操作耗时上限(例如 DB 写入平均 200ms,设为 2s),避免 Redis 已删但 DB 还没写完 - DB 插入失败时,如果错误是
UniqueViolation,说明是真实重复请求,直接返回成功(因业务已存在);如果是其他错误(如连接超时),需记录告警并人工核查,不能自动重试 - 不要在 Redis 校验通过后、DB 写入前做任何耗时操作(如远程调用、复杂计算),否则窗口期会放大重复风险
这种组合在高并发抢购类场景较常见,但增加了一层协调成本,中小项目建议优先用纯 DB 唯一索引,更可控。
真正难的不是实现,而是识别哪些操作必须幂等、哪些字段组合才构成业务意义上的“唯一”。比如“修改用户昵称”通常不需要幂等,但“扣除账户余额”只要参数相同就必须保证结果一致——这个判断得从领域模型里抠,而不是堆技术方案。