
1. 秒杀场景下的库存超卖问题本质当1000台iPhone15在电商平台以1元秒杀价开售时系统会在瞬间收到超过10万笔下单请求。此时如果仅靠简单的查询库存-扣减库存逻辑必然会出现第1001个用户也能成功下单的情况——这就是典型的超卖问题。超卖产生的根本原因在于并发环境下存在读-改-写竞态条件。假设当前库存为1用户A查询库存为1用户B同时查询库存也为1用户A扣减库存(1→0)并提交用户B也扣减库存(1→0)并提交 最终库存变为-1两个用户都抢到了最后一件商品在MySQL中这种现象的底层原理与事务隔离级别密切相关。即使使用REPEATABLE READ隔离级别两个事务中的SELECT看到的仍然是相同的快照数据无法感知对方的更新。而UPDATE操作会获取排他锁但为时已晚——竞态条件已经形成。关键认知超卖不是数据库的BUG而是业务逻辑没有处理好并发冲突。就像100个人同时抢会议室仅靠看白板-写名字的简单规则必然导致冲突。2. 单体架构下的库存一致性方案2.1 悲观锁方案SELECT FOR UPDATE在事务中使用SELECT stock FROM items WHERE id1 FOR UPDATE会获取行级排他锁其他事务会被阻塞直到当前事务提交。这相当于把会议室白板锁在保险箱里钥匙只有一把。实测案例-- 事务1 BEGIN; SELECT stock FROM items WHERE id1 FOR UPDATE; -- 获得锁 UPDATE items SET stockstock-1 WHERE id1; COMMIT; -- 事务2会阻塞在SELECT语句直到事务1提交性能影响在JMeter压测中该方案QPS约300平均响应时间达500ms。锁竞争导致大量事务堆积不适合真正的高并发场景。2.2 乐观锁方案版本号控制通过版本号实现CAS(Compare-And-Swap)操作UPDATE items SET stockstock-1, versionversion1 WHERE id1 AND version当前版本若更新影响行数为0说明版本已变更需要重试。这就像在白板上预留签名位置但提交时发现位置被占就重新抢位。优化技巧结合重试机制如3次自动重试版本号字段需加索引前端可配合倒计时动画降低用户焦虑压测数据显示该方案QPS可达1500但库存扣减成功率会随并发量上升而下降重试次数有限。3. 分布式环境的核心挑战与方案选型当系统采用微服务架构时库存服务可能被部署在多个Pod中传统的数据库锁机制完全失效。此时需要分布式锁来建立跨进程的互斥协议。3.1 Redis分布式锁的实现演进基础版SETNX过期时间def acquire_lock(conn, lockname, acquire_timeout10): identifier str(uuid.uuid4()) end time.time() acquire_timeout while time.time() end: if conn.setnx(lock: lockname, identifier): conn.expire(lock: lockname, 10) return identifier time.sleep(0.001) return False隐患分析SETNX和EXPIRE非原子操作可能导致死锁锁误删问题A删了B的锁锁续期问题业务未完成但锁已过期RedLock算法 采用多Redis实例部署要求超过半数节点获取成功才算真正获取锁。虽然能降低单点故障风险但实现复杂且性能较差通常需要5个实例。生产建议直接使用Redisson客户端其内置看门狗机制可自动续期并提供丰富的锁类型可重入锁、联锁等。3.2 异步扣减库存方案将库存扣减与订单创建解耦通过消息队列实现最终一致性[用户] - [秒杀服务] - [Redis预扣减] - [Kafka] - [订单服务] ↘ [返回结果]核心步骤Redis原子操作预扣减DECR stock:item1扣减成功则发MQ消息消费者异步创建订单定时任务处理未支付订单回滚库存性能数据某电商大促中该方案支撑了50万QPS的秒杀请求库存误差率0.1%。4. 混合架构下的最佳实践方案4.1 分层校验架构设计┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 流量过滤层 │───▶│ 缓存层 │───▶│ 数据库层 │ └──────────────┘ └──────────────┘ └──────────────┘ 限流/黑名单 内存标记/分布式锁 最终一致性逐层防御前端按钮置灰、验证码、频率限制网关层用户限流如令牌桶算法服务层本地缓存Redis集群数据层异步落库补偿机制4.2 库存分片技术将单个商品的1000库存拆分为10个分片stock:item1_shard0~9每个分片100库存。这样可以将并发冲突分散到不同分片// 路由算法示例 int shardKey (userId.hashCode() Integer.MAX_VALUE) % SHARD_COUNT; String redisKey stock:item1_shard shardKey; Long remain redisTemplate.opsForValue().decrement(redisKey);某手机厂商秒杀实测显示分片方案较单key方案性能提升8倍错误率下降至0.01%以下。5. 异常场景与容灾设计5.1 分布式锁失效的应对策略当Redis集群故障时可采用降级方案本地库存模式每个实例维护部分库存需提前分配预扣减异步对账先扣减后校验错误数据通过定时任务修复熔断机制库存服务不可用时快速失败5.2 数据一致性保障采用TCCTry-Confirm-Cancel模式def handle_seckill(): try: # 预留资源 reserve_result inventory_service.prepare(user_id, item_id) if not reserve_result: return 秒杀失败 # 创建订单可能失败 order_id order_service.create(...) # 确认预留 inventory_service.confirm(reserve_result.reserve_id) except Exception as e: # 取消预留 inventory_service.cancel(reserve_result.reserve_id) raise e某金融级秒杀系统采用该方案后资损率从0.5%降至0.0001%以下。在实际项目中我们曾遇到Redis网络分区导致锁失效的情况。最终通过引入ZooKeeper作为备用锁服务并设置库存操作流水表便于对账解决了该问题。这提醒我们没有银弹方案必须根据业务特点组合多种技术手段。