一次电商秒杀系统架构评审:从本地锁到分布式锁的演进与取舍

2026年4月5日,某电商平台在备战618大促前夕,技术团队召开了一场关于秒杀系统架构升级的评审会。当前系统在高并发场景下频繁出现超卖问题,QPS峰值突破8000时,库存扣减错误率高达3.7%。业务方明确要求:在30天内完成架构改造,保证库存强一致性,同时将系统吞吐量提升至15000 QPS以上,且不允许引入新的中间件依赖(如ZooKeeper)。

团队最初提出两套方案:方案A采用本地锁 + 数据库乐观锁,方案B采用Redis分布式锁 + Lua脚本原子扣减。评审会上,双方围绕性能、一致性、运维成本和故障恢复能力展开激烈讨论。最终,团队在充分评估后选择了一条折中路径:基于Redisson实现分布式锁,结合本地缓存预热与异步日志补偿机制,构建最终一致性的高可用秒杀架构。

问题背景:超卖频发,系统濒临崩溃

当前秒杀系统采用Spring Boot + MySQL架构,核心扣减逻辑如下:

java 复制代码
@Transactional
public boolean deductStock(Long itemId, int quantity) {
    Product product = productMapper.selectById(itemId);
    if (product.getStock() < quantity) {
        return false;
    }
    product.setStock(product.getStock() - quantity);
    return productMapper.updateById(product) > 0;
}

在高并发场景下,多个线程同时读取到相同库存值,导致超卖。尽管已添加synchronized关键字,但由于服务部署在4台机器上,本地锁无法跨JVM生效。团队尝试引入数据库悲观锁(SELECT FOR UPDATE),但压测显示TPS骤降至1200,无法满足业务需求。

错误直觉:本地锁 + 乐观锁就能解决问题?

方案A主张:"既然分布式锁复杂,不如回归本地锁 + 数据库乐观锁"。其核心逻辑是:

  1. 使用synchronized保证单节点内线程安全;
  2. 在更新时增加版本号校验(UPDATE product SET stock = stock - ?, version = version + 1 WHERE id = ? AND version = ?);
  3. 若更新失败,重试3次。

表面看似乎合理:乐观锁避免了行锁竞争,重试机制可应对冲突。但评审会上,资深架构师指出三大致命缺陷:

  • 跨节点失效:4台机器各自持锁,无法阻止并发写入;
  • 重试风暴:高并发下大量请求重试,数据库连接池被打满;
  • 版本号竞争:即使库存充足,因版本号冲突导致大量请求失败,用户体验差。

压测结果验证了担忧:在5000并发下,成功扣减率仅68%,平均响应时间飙升至1.2秒,MySQL CPU使用率持续超过90%。

正确方案:Redisson分布式锁 + 异步补偿机制

方案B提出使用Redis分布式锁,但直接使用SETNX存在锁过期、误删等问题。团队最终选择Redisson框架,其内置看门狗机制可自动续期,避免业务未执行完锁已释放。

核心实现如下:

java 复制代码
public boolean deductStockWithLock(Long itemId, int quantity) {
    RLock lock = redissonClient.getLock("stock:lock:" + itemId);
    try {
        boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
        if (!locked) {
            log.warn("获取锁失败,itemId={}", itemId);
            return false;
        }
        // 再次查询库存,防止锁等待期间库存变化
        Product product = productMapper.selectById(itemId);
        if (product.getStock() < quantity) {
            return false;
        }
        product.setStock(product.getStock() - quantity);
        int updated = productMapper.updateById(product);
        if (updated > 0) {
            // 异步记录日志,用于后续对账
            stockLogService.asyncLog(itemId, quantity, "DEDUCT");
        }
        return updated > 0;
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

为进一步提升性能,团队引入以下优化:

  • 本地缓存预热:启动时加载热门商品库存至Caffeine缓存,减少数据库查询;
  • 锁粒度细化 :按商品ID分段加锁(如stock:lock:1001),避免全局锁竞争;
  • 异步日志补偿:扣减成功后异步写入日志表,定时任务对账修复异常数据;
  • 熔断降级:当Redis不可用时,自动降级为本地锁 + 限流,保障系统可用性。

压测结果显示:在15000 QPS下,成功扣减率达99.98%,平均响应时间稳定在80ms以内,Redis CPU使用率控制在40%以下。

技术取舍与风险边界

尽管方案B表现优异,但团队仍明确其边界条件:

  • 不适用于超高频扣减:若单商品QPS超过5万,建议引入分桶扣减或预扣库存机制;
  • 依赖Redis稳定性:需部署Redis Cluster,配置持久化与哨兵机制;
  • 最终一致性容忍:异步日志补偿存在毫秒级延迟,需业务接受短暂不一致;
  • 锁续期开销:看门狗机制会定期续约,增加网络开销,需合理设置超时时间。

最终,团队决定分阶段上线:先灰度10%流量验证稳定性,再逐步全量。同时建立监控大盘,实时跟踪锁等待时间、扣减成功率、Redis延迟等关键指标。

技术补丁包

  1. Redisson分布式锁实现原理 原理:基于Redis的SET resource_name unique_value NX PX timeout命令实现互斥锁,通过看门狗线程自动续期(默认30秒,每10秒续一次)。 设计动机:解决原生Redis锁在业务执行时间长于锁超时时间时的误释放问题。 边界条件:必须确保unique_value唯一(通常用UUID),避免误删其他线程的锁;业务逻辑需在finally块中释放锁。 落地建议:使用tryLock(long waitTime, long leaseTime, TimeUnit unit)方法,明确指定等待时间和持有时间,避免无限阻塞。

  2. 本地锁在分布式环境中的局限性 原理:synchronizedReentrantLock仅作用于单个JVM,无法跨进程同步。 设计动机:简化单机并发控制,性能极高(纳秒级)。 边界条件:仅适用于单实例部署或无需跨节点协调的场景。 落地建议:在分布式系统中,本地锁仅可用于保护非关键路径的本地状态,如线程池配置、本地缓存更新等。

  3. 数据库乐观锁与悲观锁的适用场景 原理:乐观锁通过版本号或CAS机制实现无锁更新;悲观锁通过SELECT FOR UPDATE提前加锁。 设计动机:乐观锁适用于读多写少、冲突概率低的场景;悲观锁适用于写密集、强一致性要求的场景。 边界条件:乐观锁在高并发下重试成本高;悲观锁易造成死锁和性能瓶颈。 落地建议:秒杀场景优先选择分布式锁+数据库校验,而非纯乐观/悲观锁。

  4. 异步日志补偿机制设计要点 原理:核心操作成功后,异步记录操作日志,后台任务定期扫描异常状态进行修复。 设计动机:解耦核心流程与对账逻辑,提升系统吞吐量。 边界条件:需保证日志写入的可靠性(如写入本地文件+MQ双写);补偿任务需具备幂等性。 落地建议:日志表设计包含操作类型、商品ID、数量、时间戳、状态等字段,便于追踪与修复。

  5. 锁粒度细化的最佳实践 原理:将全局锁拆分为多个细粒度锁(如按商品ID、用户ID、订单类型等维度)。 设计动机:减少锁竞争,提升并发能力。 边界条件:避免过度细化导致锁数量爆炸(如百万级商品ID);需防止死锁(按固定顺序加锁)。 落地建议:使用ConcurrentHashMap缓存锁对象,避免频繁创建;设置最大锁数量限制,防止内存溢出。

相关推荐
要开心吖ZSH17 小时前
Java AI Agent 开发中的 RAG 实现方案及小白入门指南
java·ai·agent·rag
掉鱼的猫17 小时前
Java 流程编排新范式 Solon Flow:一个引擎,七种节点,覆盖规则/任务/工作流/AI 编排全场景
java·workflow
郭龙_Jack17 小时前
架构设计与技术选型
架构设计·技术选型
Aaa1111144317 小时前
四类地址 逻辑地址 线性地址 虚拟地址 物理地址
java
小则又沐风a17 小时前
深入了解进程概念 第二章
java·linux·服务器·前端
程序猿进阶17 小时前
OpenClaw Mac 安装教程
java·macos·ai·架构·agent·openclaw
凯瑟琳.奥古斯特18 小时前
信号分类与特性解析
java·开发语言·职场和发展
Mahir0818 小时前
Redis 分布式锁与 Redisson 深度解析:从原生实现到工业级解决方案
数据库·redis·分布式·缓存·面试
JAVA面经实录91718 小时前
JVM 性能监控 + 全链路分析实战 + 性能优化(完整版)
java·jvm
号码认证服务18 小时前
小米、OPPO、VIVO手机支持号码认证显示公司名吗?
java·服务器·网络·经验分享·智能手机·云计算·php