Spring Boot 3.x分布式锁的两种工程实现
微服务架构下,多个服务实例并发访问共享资源是常见场景。库存扣减、定时任务去重、订单防重复提交等业务都需要分布式锁。Spring Boot 3.x项目中,基于Redis实现分布式锁有两条技术路线:原生Redis SET命令加锁和Redisson框架。本文从可靠性、性能、运维复杂度三个维度做实测对比,给出生产选型建议。
方案一:基于Redis SET命令的原生实现
核心逻辑是用SET key value NX EX ttl原子命令获取锁,用Lua脚本保证解锁的原子性:
@Component
public class RedisDistributedLock {
private final StringRedisTemplate redisTemplate;
private static final String UNLOCK_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
public boolean tryLock(String key, String value, long expireSeconds) {
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, value, Duration.ofSeconds(expireSeconds));
return Boolean.TRUE.equals(result);
}
public boolean unlock(String key, String value) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);
Long result = redisTemplate.execute(script,
Collections.singletonList(key), value);
return Long.valueOf(1L).equals(result);
}
}
加锁时value使用UUID,解锁时验证value一致性,防止误解锁其他线程持有的锁。
可重入锁的改造
原生方案不支持可重入。要实现可重入,需用Hash结构记录持有者ID和重入次数:
-- 加锁Lua脚本
local lockKey = KEYS[1]
local lockValue = ARGV[1]
local expireTime = tonumber(ARGV[2])
if redis.call('exists', lockKey) == 0 then
redis.call('hset', lockKey, 'id', lockValue, 'count', 1)
redis.call('expire', lockKey, expireTime)
return 1
end
if redis.call('hget', lockKey, 'id') == lockValue then
redis.call('hincrby', lockKey, 'count', 1)
redis.call('expire', lockKey, expireTime)
return 1
end
return 0
方案二:基于Redisson框架的实现
Redisson内置了完整的分布式锁实现,包含可重入、看门狗续期、联锁(MultiLock)、读写锁等高级特性。
Maven依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.27.0</version>
</dependency>
配置:
spring:
redis:
host: 127.0.0.1
port: 6379
password: yourpassword
redisson:
file: classpath:redisson.yaml
使用方式:
@Service
public class OrderService {
private final RedissonClient redissonClient;
public String createOrder(String productId) {
RLock lock = redissonClient.getLock("order:lock:" + productId);
try {
if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
// 执行业务逻辑
return doCreateOrder(productId);
}
throw new RuntimeException("获取锁失败");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("锁等待中断", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
两种方案的实测对比
测试环境:4节点Spring Boot服务 + 3节点Redis Sentinel,并发100线程抢同一把锁。
指标对比:
| 指标 | 原生Redis方案 | Redisson方案 |
| 单次加锁延迟 | 1.2ms | 2.8ms |
| 可重入支持 | 需自行实现 | 原生支持 |
| 看门狗续期 | 不支持 | 默认30s续期 |
| 主从切换锁丢失 | 有风险 | 支持RedLock |
| 代码复杂度 | 高(自行维护Lua脚本) | 低(声明式调用) |
| 依赖体积 | 无额外依赖 | 3.2MB |
| 10万次加锁吞吐 | 82,000 ops | 35,000 ops |
生产选型建议
选择原生Redis方案的场景:
– 锁使用场景简单(不可重入即可满足),且对性能极度敏感
– 依赖管控严格,不允许引入Redisson等重型框架
– 已有完善的Redis运维体系,自行维护Lua脚本成本可控
选择Redisson方案的场景:
– 需要可重入锁、读写锁、联锁等高级特性
– 业务持锁时间不确定,需要看门狗自动续期防止锁过期
– 主从架构下对锁安全性要求高,需RedLock防主从切换丢锁
– 团队Redis运维经验有限,希望用成熟框架减少自研风险
在大多数Spring Boot微服务项目中,Redisson方案的综合收益远高于原生方案。性能差距在常规QPS下可忽略不计——2.8ms的加锁延迟对99%的业务场景无感知影响。仅在单QPS超过5万且逻辑极简的场景下,原生方案的性能优势才有实际意义。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot3x-zheng-he-redis-shi-xian-fen-bu-shi-suo/