生产级实战:基于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的分布式锁高可用方案与源码级剖析 - 摸鱼不慌

目录
相关文章
|
1月前
|
消息中间件 设计模式 算法
责任链模式是什么?适用场景 + 实战示例,告别 if-else 泥潭
责任链模式是什么?本文用请假审批的 Java 实战讲透责任链模式,手把手把 if-else 重构出责任链,并说清它的适用场景与坑。
91 1
责任链模式是什么?适用场景 + 实战示例,告别 if-else 泥潭
|
1月前
|
机器学习/深度学习 人工智能 JSON
随机森林结合大模型处理医疗表格数据,实现高精度建模搭配大模型医学文本归因19.5
本文提出“随机森林+大模型”融合方案,破解医疗表格数据分析两大痛点:树模型精度高但不可解释,大模型可生成报告却数值建模弱。前者负责精准分类与量化特征权重,后者基于医学指南将权重转化为通俗归因报告,兼顾精度、可解释性与落地成本,专为结构化检验数据设计。
|
1月前
|
人工智能 运维 自然语言处理
大型企业怎么做数据治理?有哪些好用的数据治理工具?
本文剖析大型企业数据治理四大核心挑战,提出“OneData+治理开发一体化+AI赋能+资产服务化”破局方法论,并重点介绍阿里云瓴羊Dataphin——一款深度融合阿里巴巴实战经验的一站式智能数据治理平台,助力企业构建高质量、可信赖、可运营的数据资产体系。(239字)
|
1月前
|
SQL 监控 关系型数据库
从库延迟排查实战:主从同步慢了,业务方比你先知道
主从延迟是DBA最头疼的问题之一,因为业务方永远比你先知道——刚下单的订单在查询页消失了、刚提交的表单在报表里找不到。但当你打开监控,Seconds_Behind_Master可能还是0。本文从主从延迟的三种本质成因出发,拆解大事务阻塞、并行复制瓶颈、从库负载干扰三大核心场景,提供一套从现象到根因的完整排查路径,帮助读者在业务方投诉之前就把问题摁住。
|
1月前
|
缓存 运维 架构师
基于 RAG + LangChain + FastAPI 搭建生产级私有知识库问答系统(完整可运行)
本文以十年架构师视角,详解如何用RAG解决大模型落地三大痛点:知识滞后、私有文档不可读、幻觉。提供一套生产级、可运行、可扩展的FastAPI+LangChain+Chroma后端方案,含文档解析、智能分块、混合检索、重排、缓存与评估,代码经实测,开箱即用。(239字)
250 4
|
29天前
|
存储 监控 API
基于 RAG + LangChain 搭建企业级私有知识库问答系统(2026 实战版)
本文是作者基于多个企业RAG知识库落地经验的实战总结,提供完整可运行代码与十年避坑指南。涵盖文档解析、混合检索、向量存储、DeepSeek接入、结果重排、拒答机制及效果评估,助你构建本地可运行、生产可扩展的企业级私有知识库系统。(239字)
382 1
|
30天前
|
人工智能 JSON NoSQL
从零构建 AI Agent:基于 LangGraph 的多工具智能体实战(含完整代码)
本文详解如何用LangGraph从零构建生产级AI Agent:支持自主规划、多工具调用(天气/搜索/计算/笔记)、失败重试与Redis会话记忆。代码开箱即用,涵盖架构设计、状态图实现及流式输出等核心能力,助企业突破RAG局限,落地真实业务场景。
254 0
|
28天前
|
人工智能 开发框架 Java
如何入门学习 Agent 开发?
本文分享Agent开发实战经验:强调甄别一手资讯、聚焦Context本质而非框架、坚持实操落地、重视效果评测与自我迭代,助新手避开玄学误区,从真实场景出发高效入门。(238字)
89 5
|
28天前
|
人工智能 算法 API
【第二部分:大模型应用开发基础】9. RAG 是什么,它与 Agent 有什么关系?——从知识库问答到 Agentic RAG
RAG 通过文档解析、切分、Embedding、混合检索、Rerank 与引用机制,让大模型在回答问题时能够按需获取企业知识,而不是依赖训练数据“记住一切”。文章进一步介绍 RAG 如何从固定的检索增强生成流程演进到 Agentic RAG:由 Agent 判断是否需要检索、如何规划 Query、证据是否充分,并在必要时继续改写和多轮检索。同时梳理 RAG、Memory、Tool 与 Agent 的边界,强调知识库问答系统并不等同于 Agent,RAG 只是 Agent 获取外部知识的一种能力。
224 2
|
1月前
|
人工智能 编解码 安全
英特尔中国加速适配国产AI模型MiniMax H3布局本地化部署
英特尔锐炫Pro B70完成对国产开源视频模型MiniMax H3的Day 0适配,支持15秒2K+立体声视频生成。依托32GB显存、367 TOPS算力及8卡混合并行方案,实现开箱即用、低成本本地部署,加速AI视频商业化落地。(239字)
231 1

热门文章

最新文章