面向 django 的底层教程:高并发下的“幂等 + 锁 + 约束”三件套
面向 django 的底层教程:高并发下的“幂等 + 锁 + 约束”三件套
做接口抗并发,先从“写路径”入手:让同一业务请求只生效一次,且代价最小。下面给出从数据库到应用层的闭环方案。
- 数据库唯一约束(最后兜底)
为业务幂等键建唯一索引,如
order_no或(user_id, biz_key)。
CREATE UNIQUE INDEX uniq_pay ON pay(order_no);
PostgreSQL 可用 upsert:
INSERT INTO pay(order_no, amount, status)
VALUES (%s,%s,'CREATED')
ON CONFLICT (order_no) DO NOTHING;
返回受影响行数即可判断是否首次写入。
- 乐观锁(行版本保护)
为行加
version字段:
rows = Pay.objects.filter(id=pid, version=v).update(status='PAID', version=v+1)
if rows == 0: raise ConcurrencyError
避免长事务导致的表级锁竞争。
- 分布式锁(短临界区) 对跨表聚合、库存扣减,用 Redis 短锁:
with redis.lock(f"lock:inv:{sku}", timeout=3, blocking_timeout=1):
deduct_inventory(sku, qty)
锁只包裹最小临界区,防止放大延迟。
-
幂等键传递与过期 把
Idempotency-Key作为请求头下发,服务端以key -> result写入 Redis(TTL=30min),重复请求直接回放结果,既省数据库,也保证一致响应。 -
任务重试与去重 Celery 任务签名加去重键:
sig = task.signature(args, kwargs, immutable=True)
if not redis.setnx(f"dedup:{sig.id}", 1): return
redis.expire(f"dedup:{sig.id}", 1800)
sig.apply_async(retry=True, max_retries=5)
- 观测与回放 记录“第一次命中/重复回放/冲突异常”的计数与耗时,埋点到 Prometheus;当冲突率上升,优先检查唯一索引与锁粒度。
三件套组合顺序是:唯一约束兜底 → 乐观锁保护更新 → 分布式锁限临界区 → 幂等回放降噪。做到这点,接口就能在抖动与重试里保持“只成功一次”。
评论 0