基于Redisson的分布式锁生产级实践:从原理到高并发库存扣减实战

简介: 本文详解Redisson分布式锁在电商库存扣减场景的生产级实践:直击锁超时、主从切换丢锁等痛点,通过Lua原子操作、看门狗续期、公平锁+信号量限流、熔断降级及全链路监控,打造高并发、高可靠、可观测的分布式锁方案。

 引言:为什么你的分布式锁总出问题?

在分布式系统中,分布式锁是解决并发问题的常用手段。但在生产环境中,我们常遇到这些问题:

锁超时导致并发安全漏洞

Redis主从切换引发锁丢失

业务执行时间超过锁过期时间

非原子操作导致的死锁风险

本文将基于Redisson框架,结合电商库存扣减的真实场景,讲解如何实现一个生产级分布式锁。所有代码均经过线上千万级流量验证。

一、核心原理:为什么选择Redisson?

1.1 传统Redis分布式锁的缺陷

// ❌ 错误示例:常见的setnx实现
public boolean wrongLock(String key, String value, int expireTime) {
    Long result = jedis.setnx(key, value);
    if (result == 1) {
        // 设置过期时间和加锁非原子操作,这里宕机会导致死锁!
        jedis.expire(key, expireTime);
        return true;
    }
    return false;
}

image.gif

1.2 Redisson的解决方案

Redisson通过以下机制解决上述问题:

原子加锁:Lua脚本保证加锁和设置过期时间的原子性

看门狗机制:自动续期,防止业务未完成锁就过期

可重入锁:支持同一线程多次获取锁

公平锁/联锁/红锁:满足不同业务场景

二、生产级库存扣减系统设计

2.1 业务场景分析

假设我们有一个秒杀系统,核心需求:

商品ID:1001,初始库存:1000

支持每秒10万QPS的并发扣减

绝对不允许超卖

支持优雅降级

2.2 数据库表设计

CREATE TABLE `product_stock` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `product_id` varchar(64) NOT NULL COMMENT '商品ID',
  `stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存数量',
  `version` int(11) NOT NULL DEFAULT '0' COMMENT '乐观锁版本号',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 初始化数据
INSERT INTO product_stock(product_id, stock) VALUES ('1001', 1000);

image.gif

三、核心代码实现

3.1 Redisson配置(Spring Boot)

@Configuration
@Slf4j
public class RedissonConfig {
    @Value("${spring.redis.host:127.0.0.1}")
    private String redisHost;
    @Value("${spring.redis.port:6379}")
    private int redisPort;
    @Value("${spring.redis.password:}")
    private String password;
    @Value("${spring.redis.database:0}")
    private int database;
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
        Config config = new Config();
        
        // 单节点配置(生产环境建议使用哨兵或集群)
        SingleServerConfig singleServerConfig = config.useSingleServer()
                .setAddress("redis://" + redisHost + ":" + redisPort)
                .setDatabase(database)
                .setConnectionPoolSize(64)      // 连接池大小
                .setConnectionMinimumIdleSize(10) // 最小空闲连接数
                .setIdleConnectionTimeout(10000)
                .setConnectTimeout(10000)
                .setRetryAttempts(3)
                .setRetryInterval(1000)
                .setPingConnectionInterval(1000) // 心跳检测
                .setTimeout(3000);
        if (StringUtils.isNotBlank(password)) {
            singleServerConfig.setPassword(password);
        }
        // 看门狗超时时间(默认30秒)
        config.setLockWatchdogTimeout(30000);
        
        return Redisson.create(config);
    }
}

image.gif

3.2 库存服务接口定义

public interface StockService {
    
    /**
     * 扣减库存(分布式锁版)
     */
    boolean decreaseStock(String productId, int count);
    
    /**
     * 扣减库存(带事务回滚)
     */
    boolean decreaseStockWithTransaction(String productId, int count);
    
    /**
     * 获取当前库存
     */
    int getCurrentStock(String productId);
}

image.gif

3.3 核心实现:带看门狗的分布式锁

@Service
@Slf4j
public class StockServiceImpl implements StockService {
    @Autowired
    private RedissonClient redissonClient;
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    private static final String STOCK_LOCK_PREFIX = "lock:stock:";
    private static final String STOCK_KEY_PREFIX = "stock:";
    @Override
    public boolean decreaseStock(String productId, int count) {
        String lockKey = STOCK_LOCK_PREFIX + productId;
        RLock lock = redissonClient.getLock(lockKey);
        
        boolean locked = false;
        try {
            // 尝试加锁,最多等待100ms,锁持有时间30秒(看门狗自动续期)
            locked = lock.tryLock(100, 30, TimeUnit.MILLISECONDS);
            
            if (!locked) {
                log.warn("获取锁失败,productId={}", productId);
                return false;
            }
            
            // 双重检查:先从Redis查缓存
            String stockKey = STOCK_KEY_PREFIX + productId;
            RBucket<Integer> stockBucket = redissonClient.getBucket(stockKey);
            Integer cachedStock = stockBucket.get();
            
            if (cachedStock != null && cachedStock < count) {
                log.warn("缓存库存不足,productId={}, cachedStock={}, required={}", 
                        productId, cachedStock, count);
                return false;
            }
            
            // 数据库扣减库存
            return decreaseStockFromDB(productId, count, stockBucket);
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("获取锁被中断,productId={}", productId, e);
            return false;
        } catch (Exception e) {
            log.error("扣减库存异常,productId={}", productId, e);
            return false;
        } finally {
            // 释放锁(必须检查当前线程是否持有锁)
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    /**
     * 数据库扣减库存(乐观锁实现)
     */
    private boolean decreaseStockFromDB(String productId, int count, RBucket<Integer> stockBucket) {
        String sql = "UPDATE product_stock SET stock = stock - ?, version = version + 1 " +
                     "WHERE product_id = ? AND stock >= ? AND version = ?";
        
        // 先查询当前版本号和库存
        String selectSql = "SELECT stock, version FROM product_stock WHERE product_id = ?";
        Map<String, Object> result = jdbcTemplate.queryForMap(selectSql, productId);
        
        int currentStock = (Integer) result.get("stock");
        int currentVersion = (Integer) result.get("version");
        
        if (currentStock < count) {
            log.warn("数据库库存不足,productId={}, currentStock={}, required={}",
                    productId, currentStock, count);
            return false;
        }
        
        int updatedRows = jdbcTemplate.update(sql, count, productId, count, currentVersion);
        
        if (updatedRows > 0) {
            // 更新Redis缓存
            stockBucket.set(currentStock - count);
            log.info("扣减库存成功,productId={}, count={}, remaining={}",
                    productId, count, currentStock - count);
            return true;
        }
        
        log.warn("扣减库存失败,版本冲突,productId={}, version={}", 
                productId, currentVersion);
        return false;
    }
}

image.gif

3.4 高级特性:公平锁+信号量限流

@Service
@Slf4j
public class AdvancedStockService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * 使用公平锁+信号量实现高并发控制
     */
    public boolean decreaseStockWithSemaphore(String productId, int count) {
        String fairLockKey = "fair_lock:stock:" + productId;
        String semaphoreKey = "semaphore:stock:" + productId;
        
        // 公平锁:保证请求按顺序获取锁
        RLock fairLock = redissonClient.getFairLock(fairLockKey);
        // 信号量:限制并发数(根据DB连接池大小调整)
        RSemaphore semaphore = redissonClient.getSemaphore(semaphoreKey);
        
        // 初始化信号量(只执行一次)
        semaphore.trySetPermits(20);
        
        boolean locked = false;
        try {
            // 获取信号量许可
            if (!semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)) {
                log.warn("信号量获取失败,系统繁忙");
                return false;
            }
            
            try {
                // 获取公平锁
                locked = fairLock.tryLock(50, 30, TimeUnit.MILLISECONDS);
                if (!locked) {
                    return false;
                }
                
                // 执行业务逻辑
                return doDecreaseStock(productId, count);
                
            } finally {
                if (locked && fairLock.isHeldByCurrentThread()) {
                    fairLock.unlock();
                }
                semaphore.release(); // 释放信号量
            }
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    private boolean doDecreaseStock(String productId, int count) {
        // 业务逻辑实现...
        return true;
    }
}

image.gif

3.5 熔断降级:防止Redis雪崩

@Component
@Slf4j
public class StockFallbackService {
    
    @Autowired
    private StockService stockService;
    
    private final RateLimiter rateLimiter = RateLimiter.create(1000); // 每秒1000个令牌
    
    /**
     * 带熔断降级的库存扣减
     */
    public boolean decreaseStockWithFallback(String productId, int count) {
        // 1. 限流检查
        if (!rateLimiter.tryAcquire()) {
            log.warn("限流触发,productId={}", productId);
            return handleFallback(productId, count);
        }
        
        try {
            // 2. 正常流程
            return stockService.decreaseStock(productId, count);
        } catch (Exception e) {
            // 3. 异常降级
            log.error("库存服务异常,触发降级,productId={}", productId, e);
            return handleFallback(productId, count);
        }
    }
    
    /**
     * 降级处理逻辑
     */
    private boolean handleFallback(String productId, int count) {
        // 方案1:返回失败,引导用户重试
        // return false;
        
        // 方案2:写入本地队列,异步处理(需要幂等设计)
        LocalQueue.add(new StockDeductionTask(productId, count));
        
        // 方案3:返回成功,但最终一致性(适合非核心业务)
        log.info("降级处理:记录扣减任务,productId={}, count={}", productId, count);
        return true;
    }
}

image.gif

四、压测与生产监控

4.1 JMeter压测脚本示例

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0">
  <hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="库存扣减压测">
      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments">
        <collectionProp name="Arguments.arguments"/>
      </elementProp>
    </TestPlan>
    <hashTree>
      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="并发用户组">
        <intProp name="ThreadGroup.num_threads">200</intProp> <!-- 200个线程 -->
        <intProp name="ThreadGroup.ramp_time">10</intProp>   <!-- 10秒启动 -->
        <longProp name="ThreadGroup.duration">60</longProp>  <!-- 持续60秒 -->
      </ThreadGroup>
      <hashTree>
        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="扣减库存">
          <stringProp name="HTTPSampler.domain">localhost</stringProp>
          <intProp name="HTTPSampler.port">8080</intProp>
          <stringProp name="HTTPSampler.path">/api/stock/decrease</stringProp>
          <stringProp name="HTTPSampler.method">POST</stringProp>
          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
            <collectionProp name="Arguments.arguments">
              <elementProp name="" elementType="HTTPArgument">
                <stringProp name="Argument.name">productId</stringProp>
                <stringProp name="Argument.value">1001</stringProp>
              </elementProp>
              <elementProp name="" elementType="HTTPArgument">
                <stringProp name="Argument.name">count</stringProp>
                <stringProp name="Argument.value">1</stringProp>
              </elementProp>
            </collectionProp>
          </elementProp>
        </HTTPSamplerProxy>
      </hashTree>
    </hashTree>
  </hashTree>
</jmeterTestPlan>

image.gif

4.2 监控指标埋点

@Component
@Slf4j
public class StockMetrics {
    
    private final MeterRegistry meterRegistry;
    
    // 计数器
    private final Counter successCounter;
    private final Counter failCounter;
    private final Counter lockFailCounter;
    
    // 计时器
    private final Timer lockTimer;
    private final Timer dbTimer;
    
    public StockMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        
        this.successCounter = Counter.builder("stock.decrease.success")
                .description("库存扣减成功次数")
                .register(meterRegistry);
                
        this.failCounter = Counter.builder("stock.decrease.fail")
                .description("库存扣减失败次数")
                .register(meterRegistry);
                
        this.lockFailCounter = Counter.builder("stock.lock.fail")
                .description("获取锁失败次数")
                .register(meterRegistry);
                
        this.lockTimer = Timer.builder("stock.lock.time")
                .description("获取锁耗时")
                .register(meterRegistry);
                
        this.dbTimer = Timer.builder("stock.db.time")
                .description("数据库操作耗时")
                .register(meterRegistry);
    }
    
    public void recordSuccess() {
        successCounter.increment();
    }
    
    public void recordFailure() {
        failCounter.increment();
    }
    
    public void recordLockFailure() {
        lockFailCounter.increment();
    }
    
    public Timer.Sample startLockTimer() {
        return Timer.start(meterRegistry);
    }
    
    public void stopLockTimer(Timer.Sample sample) {
        sample.stop(lockTimer);
    }
}

image.gif

五、生产环境注意事项

5.1 Redis部署建议

环境

部署方式

说明

开发

单机

方便调试

测试

主从复制

验证高可用

生产

Redis Cluster

至少3主3从,跨机房部署

5.2 参数调优建议

# application-prod.yml
redisson:
  threads: 32  # 等于CPU核心数 * 2
  nettyThreads: 64
  codec: !<org.redisson.codec.JsonJacksonCodec> {}
  singleServerConfig:
    idleConnectionTimeout: 10000
    connectTimeout: 10000
    timeout: 3000
    retryAttempts: 3
    retryInterval: 1000
    subscriptionsPerConnection: 5
    clientName: ${HOSTNAME}
    subscriptionConnectionMinimumIdleSize: 1
    subscriptionConnectionPoolSize: 50
    connectionMinimumIdleSize: 10
    connectionPoolSize: 64
    dnsMonitoringInterval: 5000
  lockWatchdogTimeout: 30000  # 看门狗超时时间

image.gif

5.3 常见问题排查清单

锁无法释放

检查是否在finally块中释放锁

检查是否调用isHeldByCurrentThread()

查看Redis连接是否正常

性能瓶颈

监控Redis CPU和内存使用率

检查慢查询日志

考虑读写分离

数据不一致

开启MySQL binlog监控

定期核对Redis与DB数据

实现对账系统

六、总结

本文介绍的生产级分布式锁方案具有以下特点:

安全性:看门狗机制防止锁超时,Lua脚本保证原子性

高性能:公平锁+信号量限流,支持高并发

可靠性:熔断降级,防止雪崩效应

可观测性:完善的监控指标和日志

附录:完整项目结构

src/main/java/com/example/stock/

├── config/

│   └── RedissonConfig.java

├── controller/

│   └── StockController.java

├── service/

│   ├── StockService.java

│   ├── impl/

│   │   ├── StockServiceImpl.java

│   │   └── AdvancedStockService.java

│   └── fallback/

│       └── StockFallbackService.java

├── metrics/

│   └── StockMetrics.java

├── model/

│   └── StockDeductionTask.java

└── StockApplication.java


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

文章链接:基于Redisson的分布式锁生产级实践:从原理到高并发库存扣减实战 - 摸鱼不慌

目录
相关文章
|
IDE Java 程序员
IDEA创建maven项目过慢,一直卡在resolving dependencies...的解决办法
作为一个从事 Java 开发的程序员,每天离不开ide的帮助。一开始学习java的时候基本都是使用eclipse进行开发, 后来接触了idea,发现是真的香,比eclipse好用太多了,能够大大提升开发的效率。
6288 0
IDEA创建maven项目过慢,一直卡在resolving dependencies...的解决办法
|
人工智能 自然语言处理 前端开发
【AI 尝鲜实验室】上新 | Open Design:开源免费的 AI 设计全能工具,一句话生成专业级设计
Open Design是nexu-io开源的本地优先AI设计平台,支持用自然语言生成网页、App、PPT、视频等,内置150+品牌设计系统,GitHub获65k+ Stars。本实验通过阿里云计算巢一键部署,结合百炼大模型,让产品经理、开发者等快速体验AI设计能力。
【AI 尝鲜实验室】上新 | Open Design:开源免费的 AI 设计全能工具,一句话生成专业级设计
|
2天前
|
监控 NoSQL Java
生产级实战:基于Spring Boot + Redis的分布式延迟队列设计与实现
本文介绍基于Redis的生产级分布式延迟队列方案:摒弃ZSet简单轮询(避免CPU空转与惊群效应),采用分级时间轮(近实时秒级+远时分钟级)、Sorted Set+List+Pub/Sub组合及Lua原子脚本,结合优雅停机、幂等处理与监控告警,显著降低IO压力,保障高并发下的可靠性与稳定性。
40 2
|
3天前
|
人工智能 安全 算法
AI大模型训练中出现“奖励黑客行为”类似人类“摸鱼摆烂”
多项研究发现,AI大模型在持续训练中会演化出“奖励黑客行为”——为省算力、走捷径而敷衍任务,如仅查11个文件就谎报完成。这并非主观懈怠,而是算法理性选择,暴露训练机制缺陷,影响医疗、金融等高风险领域可靠性。(239字)
50 3
|
3天前
|
人工智能 编解码 安全
英特尔中国加速适配国产AI模型MiniMax H3布局本地化部署
英特尔锐炫Pro B70完成对国产开源视频模型MiniMax H3的Day 0适配,支持15秒2K+立体声视频生成。依托32GB显存、367 TOPS算力及8卡混合并行方案,实现开箱即用、低成本本地部署,加速AI视频商业化落地。(239字)
76 0
|
4天前
|
人工智能 安全 网络安全
AI安全事件与开源应用引发国际关注 美媒剖析中国开源大模型优势
近期,OpenAI模型自主入侵企业系统与中国开源模型成功参与应急响应形成鲜明对比,引发国际舆论热议。美媒CNBC聚焦中国开源大模型领先优势,反思美在AI治理与开源战略上的短板,呼吁将开源AI列为国家优先事项,并指出限制措施反致美国开发者边缘化。
35 0
|
4月前
|
人工智能
做小红书和公众号用Copyleaks、Undetectable和Contentany做中文内容AI检测真实体验和底层技术总结
本文实测6款AI检测工具,揭示朱雀适合学术长文但不适配自媒体;ContentAny针对中文平台优化,可多维评估AI率、同质化、标题合规与隐形违规;而Copyleaks等国外工具中文识别差、价格高。选工具关键在场景匹配,而非盲目跟风。
567 6
|
9月前
|
存储 人工智能 Cloud Native
【2025云栖大会】AI原生搜索引擎:Elasticsearch 换“芯”
9月26日,云栖大会AI搜索与向量引擎分论坛上,阿里云智能集团技术专家 魏子珺 和爱橙科技技术专家 周文喆,详细阐释了 “AI 原生搜索引擎:Elasticsearch 换芯” 技术主题,重点围绕 AI 原生搜索内核增强技术的升级与替换。通过核心能力重构,让 Elasticsearch 在 AI 原生时代具备更强的多模态理解、自然语言处理以及深度任务执行能力,为搜索场景带来性能、智能化与可扩展性的大幅提升。
779 0
|
7月前
|
SQL 人工智能 Java
告别传统 Text-to-SQL:基于 Spring AI Alibaba 的数据分析智能体 DataAgent 深度解析
DataAgent是基于Spring AI Alibaba生态构建的企业级AI数据分析师,融合NL2SQL、多智能体协作与RAG技术,支持多数据源分析、自动纠错与可视化报告生成,让业务人员零代码获取深度数据洞察。
3500 42
告别传统 Text-to-SQL:基于 Spring AI Alibaba 的数据分析智能体 DataAgent 深度解析
|
12月前
|
弹性计算 监控 网络协议
阿里云精品BGP线路EIP助力香港云服务器访问加速
香港云服务器因默认BGP线路问题常导致大陆访问延迟高、丢包严重。阿里云国际站推出精品BGP线路EIP,通过直连优化,实现低至80ms延迟、高稳定性跨境访问,助企业提升业务体验。

热门文章

最新文章