锁超时导致并发安全漏洞
Redis主从切换引发锁丢失
业务执行时间超过锁过期时间
非原子操作导致的死锁风险
本文将基于Redisson框架,结合电商库存扣减的真实场景,讲解如何实现一个生产级分布式锁。所有代码均经过线上千万级流量验证。
// ❌ 错误示例:常见的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;
}
Redisson通过以下机制解决上述问题:
原子加锁:Lua脚本保证加锁和设置过期时间的原子性
看门狗机制:自动续期,防止业务未完成锁就过期
可重入锁:支持同一线程多次获取锁
公平锁/联锁/红锁:满足不同业务场景
假设我们有一个秒杀系统,核心需求:
商品ID:1001,初始库存:1000
支持每秒10万QPS的并发扣减
绝对不允许超卖
支持优雅降级
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);
@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);
}
}
public interface StockService {
/**
* 扣减库存(分布式锁版)
*/
boolean decreaseStock(String productId, int count);
/**
* 扣减库存(带事务回滚)
*/
boolean decreaseStockWithTransaction(String productId, int count);
/**
* 获取当前库存
*/
int getCurrentStock(String productId);
}
@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;
}
}
@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;
}
}
@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;
}
}
<?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>
@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);
}
}
环境 | 部署方式 | 说明 |
|---|---|---|
开发 | 单机 | 方便调试 |
测试 | 主从复制 | 验证高可用 |
生产 | Redis Cluster | 至少3主3从,跨机房部署 |
# 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 # 看门狗超时时间
检查是否在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的分布式锁生产级实践:从原理到高并发库存扣减实战 - 摸鱼不慌
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。