基于Redisson的分布式锁原理深度解析与优化实践
分布式环境下,锁的实现至关重要。本文将从技术背景与应用场景出发,结合核心原理、关键源码、实际示例,深入剖析Redisson分布式锁的实现机制,并给出性能优化建议,帮助后端开发者在高并发场景下稳健落地分布式锁。
一、技术背景与应用场景
随着微服务、云原生架构的普及,多个服务实例常常并发访问同一共享资源,例如:
- 订单重复提交防重:避免高并发下生成重复订单。
- 库存并发扣减:保证库存不出现超卖。
- 分布式定时任务:集群环境中同节点只执行一次任务。
传统的JVM层面synchronized
或ReentrantLock
无法跨进程、跨机器使用,需要依赖外部组件。基于Redis的分布式锁具备高性能、部署简单、可扩展等优势,是业界主流选择之一。Redisson作为一款功能丰富、社区活跃的Redis客户端,为分布式锁提供了完整实现。
二、核心原理深入分析
Redisson的分布式锁主要有以下几种实现:
RLock
(可重入锁)RSemaphore
(信号量)RReadWriteLock
(读写锁)
本文重点关注RLock
的实现原理,核心流程如下:
- 客户端调用
lock.lock()
时,向Redis发送Lua脚本,该脚本会:- 先检查当前客户端持有锁的重入计数,若已持有则直接++并续期。
- 若无持有,则尝试设置key(
SET NX PX
),成功即获锁,设置内置看门狗续期机制。
- 看门狗机制:Redisson启动了一个内部定时任务,每隔
lockWatchdogTimeout/3
毫秒,续期锁的TTL,以保证长时间业务执行不超时。 - 解锁时,客户端执行解锁Lua脚本:
- 判断当前clientId是否与锁中存储一致,若一致则--重入计数,若计数为0则删除锁并取消续期任务。
2.1 Lua脚本核心代码
-- lock.lua
local key = KEYS[1]
local clientId = ARGV[1]
local ttl = tonumber(ARGV[2])
-- 重入
if (redis.call('HEXISTS', key, clientId) == 1) then
redis.call('HINCRBY', key, clientId, 1)
redis.call('PEXPIRE', key, ttl)
return nil
end
-- 初次获取
if (redis.call('EXISTS', key) == 0) then
redis.call('HSET', key, clientId, 1)
redis.call('PEXPIRE', key, ttl)
return nil
end
-- 其他客户端已占用,返回剩余TTL
return redis.call('PTTL', key)
2.2 看门狗(LockWatchdog)实现
Redisson在org.redisson.lock
包下实现了看门狗续期任务:
public class LockWatchdog extends ScheduledService {
private final String lockName;
public LockWatchdog(...){ }
@Override
public void run() {
try {
// 发送续期命令,延长TTL
commandExecutor.evalWriteAsync(..., RScript.Mode.READ_WRITE, unlockScript, RScript.ReturnType.STATUS, Arrays.asList(lockName), clientId, lockWatchdogTimeout);
} catch (Exception e) {
log.error("Lock watchdog renew error", e);
}
}
}
默认lockWatchdogTimeout
为30秒,业务执行时间小于该值时可不设置自定义TTL。
三、关键源码解读
3.1 锁实例生成
RLock lock = redisson.getLock("order:lock");
public class RedissonLock implements RLock {
private final CommandAsyncExecutor commandExecutor;
private final String name;
private final long lockWatchdogTimeout;
public void lock() {
lock(DEFAULT_ACQUIRY_RETRY_MILLIS, DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
}
public void lock(long leaseTime, TimeUnit unit) {
// leaseTime为-1时,启用看门狗续期
internalLock(leaseTime, unit);
}
}
3.2 加锁的内部逻辑
private void internalLock(long leaseTime, TimeUnit unit) {
long threadId = Thread.currentThread().getId();
String clientId = getClientId(threadId);
long ttl = unit.toMillis(leaseTime) > 0 ? unit.toMillis(leaseTime) : lockWatchdogTimeout;
while (true) {
Long result = tryAcquireAsync(leaseTime, unit).get();
if (result == null) {
// 获得锁,启动Watchdog续期
scheduleWatchdog(clientId);
return;
}
Thread.sleep(DEFAULT_ACQUIRY_RETRY_MILLIS);
}
}
四、实际应用示例
以下示例展示如何在Spring Boot项目中引入Redisson分布式锁:
- 引入依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.6</version>
</dependency>
- 配置文件(
application.yml
):
redisson:
address: "redis://127.0.0.1:6379"
lockWatchdogTimeout: 30000
- 业务代码:
@Service
public class OrderService {
private final RLock orderLock;
private final RedissonClient redissonClient;
@Autowired
public OrderService(RedissonClient client) {
this.redissonClient = client;
this.orderLock = redissonClient.getLock("order:lock");
}
public void createOrder(String userId) {
orderLock.lock();
try {
// 核心业务:检查库存、写入订单表
processOrder(userId);
} finally {
orderLock.unlock();
}
}
}
五、性能特点与优化建议
- watchDog续期带来额外心跳开销,可根据业务情况调小
lockWatchdogTimeout
或显式指定leaseTime
。 - 高并发场景下热点锁可能成为瓶颈,可结合Redisson的
RPermitExpirableSemaphore
分布式信号量进行限流降级。 - 对比Zookeeper实现的分布式锁,Redisson更轻量,适合高TPS场景,但Redis单点故障需配合哨兵/集群部署。
- 对锁竞争激烈的场景,可采用业务层面分段锁(Hash槽分段)或增强键前缀随机化,降低热点。
- 监控锁的使用情况:结合Redisson API获取当前线程持有信息,并结合Prometheus采集告警。
总结:本文从原理到实践,全面解析了基于Redisson的分布式锁机制并提供优化建议,旨在帮助开发者在高并发生产环境中稳健落地。