生产级实战:基于Redisson的分布式锁高可用方案与源码级剖析

简介: 本文详解电商秒杀场景下Redisson分布式锁的生产级实践:直击超卖、死锁、误删等5大痛点,对比Jedis阐明Redisson优势;涵盖配置优化、看门狗机制源码剖析、熔断降级、公平锁/读写锁应用,并提供避坑指南与完整代码示例。(239字)

 一、背景与痛点

在电商秒杀场景中,我们面临以下挑战:

  1. 超卖问题:多个实例同时扣减库存。
  2. 死锁问题:服务宕机导致锁未释放。
  3. 锁误删:A线程删掉了B线程的锁。
  4. 业务超时:业务执行时间超过锁过期时间。
  5. Redis抖动:Redis超时导致大面积锁失效。

二、为什么选择 Redisson 而非原生 Jedis?

原生的 SET key random_value NX PX 30000 虽然能实现互斥,但我们需要自己处理:

  • 锁续期(Watchdog):业务没执行完,锁过期了怎么办?
  • 可重入性:同一个线程内递归调用怎么办?
  • 集群容错:Redis Master宕机,锁信息未同步到Slave怎么办?(虽然Redisson也无法完全解决Redlock争议,但在CAP中做了很好的折中)。

Redisson 底层封装了 Netty,实现了 Lua脚本原子操作看门狗机制,是Java分布式锁的事实标准。

三、生产级代码实战

1. 依赖配置 (POM)

<dependencies>
    <!-- Redisson Starter -->
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.23.4</version>
    </dependency>
    <!-- 连接池 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency></dependencies>

image.gif

2. Redisson 生产级配置 (YAML)

生产环境务必配置 连接池超时时间重试机制

spring:
  redis:
    host: ${REDIS_HOST:127.0.0.1}
    port: 6379
    password: ${REDIS_PASSWORD}
    database: 0
    timeout: 3000ms # 命令超时
    lettuce:
      pool:
        max-active: 32 # 连接池最大连接数
        max-idle: 16
        min-idle: 4
        max-wait: 1000ms # 连接池获取连接最大等待时间

image.gif

3. Redisson Config Bean (Java Config)

@Configurationpublic class RedissonConfig {    @Value("${spring.redis.host}")
    private String host;    @Value("${spring.redis.port}")
    private String port;    @Value("${spring.redis.password}")
    private String password;    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {        Config config = new Config();        // 单节点模式(生产环境建议哨兵或集群模式)
        SingleServerConfig singleServerConfig = config.useSingleServer()
                .setAddress("redis://" + host + ":" + port)
                .setPassword(password)
                .setDatabase(0);        // 生产级关键参数
        singleServerConfig.setConnectionPoolSize(64);      // 连接池大小
        singleServerConfig.setConnectionMinimumIdleSize(10); // 最小空闲连接
        singleServerConfig.setIdleConnectionTimeout(10000); // 空闲连接超时
        singleServerConfig.setConnectTimeout(3000);        // 连接超时
        singleServerConfig.setTimeout(3000);               // 命令等待超时
        singleServerConfig.setRetryAttempts(3);            // 命令重试次数
        singleServerConfig.setRetryInterval(1500);          // 重试间隔
        // 看门狗超时时间(默认30秒,如果未自定义,锁过期时间会以此为准)
        config.setLockWatchdogTimeout(30 * 1000);        return Redisson.create(config);
    }
}

image.gif

4. 核心业务:库存扣减 Service (含熔断与降级)

这是本文的重点。我们不仅要加锁,还要处理 业务异常Redis不可用 的情况。

@Service@Slf4jpublic class InventoryServiceImpl implements InventoryService {    @Autowired
    private RedissonClient redissonClient;    @Autowired
    private StringRedisTemplate redisTemplate;    private static final String STOCK_KEY = "seckill:stock:%s";    private static final String LOCK_KEY = "seckill:lock:%s";    /**
     * 扣减库存(生产级实现)
     * @param productId 商品ID
     * @param quantity 数量
     * @return 是否成功
     */
    @Override
    public boolean decreaseStock(Long productId, Integer quantity) {        String lockKey = String.format(LOCK_KEY, productId);        RLock lock = redissonClient.getLock(lockKey);        boolean locked = false;        try {            // 1. 尝试加锁,最多等待100ms,锁自动释放时间30s(看门狗会自动续期,除非服务宕机)
            // 参数说明:waitTime, leaseTime, unit
            // 如果leaseTime不设置(-1),则启用看门狗机制
            locked = lock.tryLock(100, -1, TimeUnit.MILLISECONDS);            if (!locked) {                // 2. 获取锁失败,快速失败(防止线程堆积)
                log.warn("Product {} lock acquisition failed, system busy.", productId);                return false;
            }            // 3. 双重检查,防止Redis锁失效后的极端情况(虽然Redisson已处理,但作为防御性编程)
            if (!lock.isHeldByCurrentThread()) {
                log.error("Lock is not held by current thread! Thread: {}", Thread.currentThread().getName());                return false;
            }            // 4. 执行核心业务逻辑
            String stockKey = String.format(STOCK_KEY, productId);            Long remainStock = redisTemplate.opsForValue().decrement(stockKey, quantity);            if (remainStock == null) {                // Redis中无此Key,初始化库存(通常从DB加载,此处简化)
                // 生产环境此处应有缓存预热逻辑
                initStock(productId);
                remainStock = redisTemplate.opsForValue().decrement(stockKey, quantity);
            }            if (remainStock < 0) {                // 5. 库存不足,回滚(Lua脚本保证原子性)
                log.warn("Product {} stock not enough, remain: {}", productId, remainStock);
                redisTemplate.opsForValue().increment(stockKey, quantity);                return false;
            }            // 6. 扣减成功,发送MQ异步写数据库(最终一致性)
            sendDeductionMessage(productId, quantity);
            log.info("Product {} stock decreased successfully. Remain: {}", productId, remainStock);            return true;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("Thread interrupted while acquiring lock for product {}", productId, e);            return false;
        } catch (RedisTimeoutException | RedisCommandExecutionException e) {            // 7. 熔断降级:Redis超时或执行异常
            // 生产环境此处应接入 Hystrix 或 Sentinel
            log.error("Redis error occurred while processing product {}. Entering degrade mode.", productId, e);            // 降级策略:记录日志,返回失败,或者根据业务允许读本地缓存(视业务而定)
            return handleFallback(productId, quantity);
        } catch (Exception e) {
            log.error("Unexpected error while decreasing stock for product {}", productId, e);            return false;
        } finally {            // 8. 释放锁(必须确保是当前线程持有的锁)
            if (locked && lock.isHeldByCurrentThread()) {                try {
                    lock.unlock();
                } catch (IllegalMonitorStateException e) {                    // 防止锁已过期自动释放导致的误删异常
                    log.warn("Attempted to unlock a lock that was not held by current thread or already expired.");
                }
            }
        }
    }    /**
     * 降级处理逻辑
     */
    private boolean handleFallback(Long productId, Integer quantity) {        // 方案A:返回失败,让用户重试
        // 方案B:将请求放入本地队列,待Redis恢复后执行(复杂度高)
        // 方案C:限流,直接返回“系统繁忙”
        log.info("Executing fallback logic for product {}", productId);        return false;
    }    private void initStock(Long productId) {        // 从数据库加载库存
        int dbStock = 1000; // 模拟DB查询
        String stockKey = String.format(STOCK_KEY, productId);
        redisTemplate.opsForValue().setIfAbsent(stockKey, String.valueOf(dbStock));
    }    private void sendDeductionMessage(Long productId, Integer quantity) {        // 模拟发送MQ
        log.info("Sending MQ message: productId={}, quantity={}", productId, quantity);
    }
}

image.gif

5. 高级特性:公平锁与读写锁

在某些场景下,我们需要更精细的控制。

5.1 公平锁 (Fair Lock)

防止饥饿线程,先到先得。

public boolean processFairly(Long orderId) {    RLock fairLock = redissonClient.getFairLock("ORDER_FAIR_LOCK:" + orderId);    try {
        fairLock.lock();        // 业务逻辑
        return true;
    } finally {
        fairLock.unlock();
    }
}

image.gif

5.2 读写锁 (ReadWrite Lock)

适用于读多写少的场景(如商品详情页)。

public ProductInfo getProductInfo(Long productId) {    RReadWriteLock rwLock = redissonClient.getReadWriteLock("PRODUCT_RW_LOCK:" + productId);    RLock readLock = rwLock.readLock();    try {
        readLock.lock();        // 读缓存或DB
        return fetchFromCacheOrDb(productId);
    } finally {
        readLock.unlock();
    }
}public void updateProductInfo(Long productId, ProductInfo info) {    RReadWriteLock rwLock = redissonClient.getReadWriteLock("PRODUCT_RW_LOCK:" + productId);    RLock writeLock = rwLock.writeLock();    try {
        writeLock.lock();        // 更新DB和缓存
        updateDbAndCache(productId, info);
    } finally {
        writeLock.unlock();
    }
}

image.gif

四、源码级深度剖析:看门狗机制 (Watchdog)

很多同学只知道看门狗会自动续期,但不知道原理。我们来看 RedissonLock 的核心源码:

// RedissonLock.javaprivate void scheduleExpirationRenewal(long threadId) {    ExpirationEntry entry = new ExpirationEntry();    ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);    if (oldEntry != null) {
        oldEntry.addThreadId(threadId);
    } else {
        entry.addThreadId(threadId);        // 核心:开启定时任务
        renewExpiration();
    }
}private void renewExpiration() {    Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {        @Override
        public void run(Timeout timeout) throws Exception {            // 执行Lua脚本,刷新过期时间
            if (renewInternal()) {                // 递归调用,直到锁释放或线程中断
                renewExpiration();
            }
        }
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); // 默认10秒续期一次}

image.gif

原理总结

  1. 如果我们在加锁时未指定 leaseTime,Redisson会启用看门狗。
  2. 看门狗会在锁过期前 1/3 的时间(默认10秒)执行一次 renew 操作,重置过期时间。
  3. 如果服务宕机,Netty的 TimerTask 停止,锁会在30秒后自动释放,避免死锁。

五、生产环境避坑指南

  1. 时钟同步:确保集群内所有服务器NTP时间同步,虽然Redisson主要依赖Redis时间,但业务逻辑可能依赖本地时间。
  2. Key设计:锁的Key必须唯一且具有业务含义(如 order_id, product_id),避免锁范围过大(全局锁)或过小(无效锁)。
  3. 避免热Key:如果某个商品是超级热点(如iPhone首发),单个Redis Key可能会成为瓶颈。解决方案:Key分片(将库存拆分成10份,Key为 stock:product_id:01stock:product_id:10)。
  4. 监控告警:监控Redis的 blocked_clientsconnected_clients 以及 lock wait time。一旦锁等待时间过长,立即告警。
  5. Lua脚本原子性:Redisson的所有锁操作都是Lua脚本,保证了 SET + EXPIRE 的原子性,这是生产级应用的基石。

六、总结

生产级的分布式锁不仅仅是 SETNX。本文通过 Redisson 实现了具备 自动续期可重入防误删 以及 熔断降级 能力的库存扣减方案。

本文由 摸鱼不慌 发布,转载请注明出处。

文章链接:生产级实战:基于Redisson的分布式锁高可用方案与源码级剖析 - 摸鱼不慌

目录
相关文章
|
6天前
|
存储 弹性计算 缓存
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
本文更新了2026年阿里云全系列云服务器租赁活动报价,所有特惠资源均可前往阿里云活动中心选购,整体覆盖从个人入门到企业级高性能场景的全梯度需求。其中轻量应用服务器主打极致性价比,2核2G峰值200M带宽配置每日10点、15点限时抢购价仅38元/年,2核4G配置379元/年起;高性价比的经济型e实例、通用算力型u2i实例覆盖2核4G至4核32G全档位,适配开发测试与中小型企业业务;搭载英特尔至强6处理器的第九代c9i企业级实例算力较上代提升20%,支撑高并发生产环境,不同实例规格价差清晰,用户可根据自身业务负载与预算灵活选型。
1597 116
|
7天前
|
人工智能 程序员 API
Codex 接入 DeepSeek-V4-Flash:还能补上识图,提供两套方案
Codex 接入 DeepSeek-V4-Flash 怎么配?本文覆盖 CLI 与桌面端,再用 qwen3-vl-flash 补识图,两套方案可直接照做
1083 5
|
13天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1954 9
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
7天前
|
编解码 人工智能 安全
2核4G/4核8G/8核16G阿里云服务器如何选择实例?经济型e、通用算力型u2i与计算型c9i选哪个?
本文介绍了阿里云2核4G、4核8G、8核16G三档主流配置下经济型e、通用算力型u2i和计算型c9i三种实例的最新活动价格与适用场景。同配置下三者价差显著,以2核4G为例,经济型e低至599.93元/年,计算型c9i则高达1742.08元/年。文章详细解析了各实例的性能定位:经济型e适合轻负载入门场景,u2i兼顾稳定算力与性价比,c9i凭借第9代至强处理器与芯片级安全能力支撑高性能业务。同时提示用户可叠加满减优惠券享受折上折,建议根据业务负载与预算综合决策。
535 112
|
19天前
|
人工智能 前端开发 Linux
Codex 桌面版安装 + CC Switch 接入第三方 API 完整教程(2026 最新)
2026最新教程:手把手教你安装Codex桌面版,通过CC Switch v3.17.0一键接入Fenno等国产API(兼容OpenAI Responses格式),跳过账号登录,完整启用代码审查、多步任务与上下文感知功能。零基础友好,全程图文实操。(239字)
2723 4
|
11天前
|
存储 人工智能 关系型数据库
阿里云AI产品与云产品最新组合套餐:Token Plan、AI coding及云服务器和建站等组合优惠价
阿里云推出全新“算力+模型+应用”一站式云与AI组合套餐活动,覆盖从个人开发者到中大型企业的全场景需求。核心亮点为分三档定价的Token Plan订阅服务,支持Qwen3.8-Max-Preview大模型调用,错峰时段最低可享0.2折优惠。活动同步推出AI Coding、智能体部署、云电脑托管、0代码建站等十余类场景化组合,搭配99元/年的普惠云服务器、88元/年的入门数据库等经典特惠产品,还为企业提供1V1定制化AI转型方案,大幅降低了不同用户群体拥抱AI的技术门槛与采购成本。
729 111
|
21天前
|
人工智能 JSON 安全
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
阿里云AI安全产品联动防御Fastjson攻击
2651 13
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
|
7天前
|
人工智能 JSON Shell
2026AI漫剧本地全开源方案(附各个软件模型链接),8G显卡也能流畅运行
这是一套完全本地化部署的AI漫剧生成技术链路:涵盖LLM剧本分镜生成、FLUX文生图(IP-Adapter人脸锁定)、StoryDiffusion时序连贯控制、LTX-2.3唇形同步视频生成,及ComfyUI全流程调度。零云端费用,仅耗硬件算力,单集2–4小时可产出竖屏短视频,适配抖音/B站分发。
|
5天前
|
人工智能 API 开发工具
2026 零基础本地 AI 漫剧完整实操教程(8G 笔记本显卡可用|附可直接复制命令与代码)
本方案提供完全离线、本地运行的漫剧全自动制作流程:RTX3060/4050 8G显卡即可驱动,涵盖Qwen写分镜→ComfyUI统一角色绘图→LTX2.3图生微动画→Qwen3-TTS本地配音→FFmpeg自动合成,全程无水印、免API、不限次。专为低显存优化,解决变脸、闪烁、爆内存三大痛点。(239字)