分布式锁详解
概述
分布式锁是分布式系统中用于控制多个进程或线程对共享资源访问的重要机制。在微服务架构中,当多个服务实例需要访问同一资源时,分布式锁能够确保数据的一致性和操作的原子性。本文将从基础实现到高级特性,层层深入讲解Redis分布式锁的实现原理和最佳实践。
1. 简单Redis分布式锁实现
1.1 基本原理
最简单的分布式锁实现是利用Redis的SETNX
命令(SET if Not eXists),当key不存在时设置成功返回1,存在时设置失败返回0。
1.2 简单实现源码
@Component
public class SimpleRedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String LOCK_PREFIX = "distributed_lock:";
private static final int RETRY_INTERVAL = 100; // 毫秒
private static final int MAX_RETRY_TIMES = 100;
/**
* 尝试获取锁(阻塞等待版本)
* @param lockKey 锁的key
* @param value 锁的值
* @param timeoutMs 超时时间(毫秒)
* @return 是否获取成功
*/
public boolean lock(String lockKey, String value, long timeoutMs) {
String key = LOCK_PREFIX + lockKey;
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < timeoutMs) {
// 尝试获取锁
Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value);
if (Boolean.TRUE.equals(success)) {
return true;
}
// 获取失败,等待后重试
try {
Thread.sleep(RETRY_INTERVAL);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
/**
* 释放锁
* @param lockKey 锁的key
* @return 是否释放成功
*/
public boolean unlock(String lockKey) {
String key = LOCK_PREFIX + lockKey;
Boolean deleted = redisTemplate.delete(key);
return Boolean.TRUE.equals(deleted);
}
/**
* 使用示例
*/
public void businessMethod() {
String lockKey = "order_process";
String lockValue = UUID.randomUUID().toString();
if (lock(lockKey, lockValue, 5000)) { // 5秒超时
try {
// 执行业务逻辑
processOrder();
} finally {
// 释放锁
unlock(lockKey);
}
} else {
throw new RuntimeException("获取锁超时");
}
}
private void processOrder() {
// 模拟业务处理
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
1.3 缺陷分析
graph LR
A[简单分布式锁] --> B[缺陷1: 无过期时间]
A --> C[缺陷2: 无法处理宕机]
A --> D[缺陷3: 可能误删锁]
A --> E[缺陷4: 性能问题]
B --> B1[进程宕机导致死锁]
B --> B2[锁永远无法释放]
C --> C1[客户端宕机锁无法释放]
C --> C2[依赖手动删除]
D --> D1[任何客户端都能删除锁]
D --> D2[无法验证锁的所有权]
E --> E1[频繁轮询消耗CPU]
E --> E2[网络开销大]
style B fill:#ffcdd2
style C fill:#ffcdd2
style D fill:#ffcdd2
style E fill:#fff3e0
主要问题:
- 死锁风险:如果获取锁的进程宕机,锁永远无法释放
- 误删问题:任何客户端都可以删除锁,无法验证所有权
- 性能问题:频繁轮询检查锁状态,消耗CPU和网络资源
- 无超时机制:锁没有自动过期时间
2. 宕机与过期处理 + 防止死锁
2.1 问题分析
为了解决死锁问题,我们需要为锁设置过期时间。但这又引入了新的问题:如何确保锁的获取和过期时间设置是原子操作?
sequenceDiagram
participant Client as 客户端
participant Redis as Redis服务器
participant System as 系统
Client->>Redis: SETNX lock_key value
Redis-->>Client: 1 (成功)
Note over System: 客户端在设置过期时间前宕机
Client-xSystem: 进程终止
Note over Redis: 锁永远不会过期,造成死锁
rect rgb(255, 200, 200)
Note over Redis: 死锁状态
end
2.2 改进实现:原子性设置过期时间
@Component
public class ImprovedRedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String LOCK_PREFIX = "distributed_lock:";
private static final int DEFAULT_EXPIRE_TIME = 30; // 30秒
private static final int RETRY_INTERVAL = 100;
/**
* 获取锁(带过期时间,原子操作)
* @param lockKey 锁的key
* @param value 锁的值
* @param expireSeconds 过期时间(秒)
* @param timeoutMs 获取锁的超时时间(毫秒)
* @return 是否获取成功
*/
public boolean lock(String lockKey, String value, int expireSeconds, long timeoutMs) {
String key = LOCK_PREFIX + lockKey;
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < timeoutMs) {
// 使用SET命令的NX和EX参数,原子性设置值和过期时间
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, value, Duration.ofSeconds(expireSeconds));
if (Boolean.TRUE.equals(success)) {
return true;
}
// 获取失败,等待后重试
try {
Thread.sleep(RETRY_INTERVAL);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
/**
* 释放锁
* @param lockKey 锁的key
* @return 是否释放成功
*/
public boolean unlock(String lockKey) {
String key = LOCK_PREFIX + lockKey;
Boolean deleted = redisTemplate.delete(key);
return Boolean.TRUE.equals(deleted);
}
/**
* 检查锁是否存在
* @param lockKey 锁的key
* @return 锁是否存在
*/
public boolean isLocked(String lockKey) {
String key = LOCK_PREFIX + lockKey;
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
/**
* 获取锁的剩余时间
* @param lockKey 锁的key
* @return 剩余时间(秒),-1表示永不过期,-2表示key不存在
*/
public long getLockTTL(String lockKey) {
String key = LOCK_PREFIX + lockKey;
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 安全的业务执行方法
*/
public <T> T executeWithLock(String lockKey, int expireSeconds,
long timeoutMs, Supplier<T> business) {
String lockValue = generateLockValue();
if (lock(lockKey, lockValue, expireSeconds, timeoutMs)) {
try {
return business.get();
} finally {
unlock(lockKey);
}
} else {
throw new RuntimeException("获取锁超时: " + lockKey);
}
}
private String generateLockValue() {
return Thread.currentThread().getId() + ":" + UUID.randomUUID().toString();
}
}
2.3 改进后的优势
flowchart LR
A[改进版分布式锁] --> B[原子性操作]
A --> C[自动过期]
A --> E[状态监控]
B --> B1[SET NX EX 一条命令]
B --> B2[避免竞态条件]
C --> C1[防止死锁]
C --> C2[自动释放资源]
E --> E1[TTL监控]
E --> E2[锁状态查询]
style B fill:#e8f5e8
style C fill:#e8f5e8
style E fill:#fff3e0
2.4 仍存在的问题
尽管解决了死锁问题,但仍然存在:
- 误删问题:任何客户端都可以删除锁
- 锁过期问题:业务执行时间超过锁过期时间
- 非原子释放:检查和删除不是原子操作
3. 防止误删Key的问题
3.1 问题场景分析
sequenceDiagram
participant A as 线程A
participant B as 线程B
participant Redis as Redis
A->>Redis: SET lock_key valueA NX EX 10
Redis-->>A: OK
Note over A: 执行业务逻辑(耗时15秒)
Note over Redis: 10秒后锁自动过期
Redis->>Redis: DEL lock_key (自动过期)
B->>Redis: SET lock_key valueB NX EX 10
Redis-->>B: OK
Note over A: 业务逻辑执行完毕
A->>Redis: DEL lock_key
Redis-->>A: 1
Note over B: 线程B的锁被误删!
rect rgb(255, 200, 200)
Note over Redis: 数据不一致风险
end
3.2 解决方案:值校验
@Component
public class SafeRedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String LOCK_PREFIX = "distributed_lock:";
/**
* 生成唯一的锁值
* 包含:进程ID + 线程ID + 时间戳 + 随机UUID
*/
private String generateLockValue() {
return ManagementFactory.getRuntimeMXBean().getName() + ":" +
Thread.currentThread().getId() + ":" +
System.currentTimeMillis() + ":" +
UUID.randomUUID().toString();
}
/**
* 获取锁
* @param lockKey 锁的key
* @param expireSeconds 过期时间(秒)
* @param timeoutMs 获取锁超时时间(毫秒)
* @return 锁的值,null表示获取失败
*/
public String lock(String lockKey, int expireSeconds, long timeoutMs) {
String key = LOCK_PREFIX + lockKey;
String value = generateLockValue();
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < timeoutMs) {
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, value, Duration.ofSeconds(expireSeconds));
if (Boolean.TRUE.equals(success)) {
return value; // 返回锁的值
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
return null;
}
/**
* 安全释放锁(仍存在原子性问题)
* @param lockKey 锁的key
* @param expectedValue 期望的锁值
* @return 是否释放成功
*/
public boolean unlockUnsafe(String lockKey, String expectedValue) {
String key = LOCK_PREFIX + lockKey;
// 获取当前值
String currentValue = redisTemplate.opsForValue().get(key);
// 比较值是否匹配
if (expectedValue.equals(currentValue)) {
// 删除锁
Boolean deleted = redisTemplate.delete(key);
return Boolean.TRUE.equals(deleted);
}
return false;
}
/**
* 使用示例
*/
public void safeBusinessMethod() {
String lockKey = "safe_order_process";
String lockValue = lock(lockKey, 30, 5000);
if (lockValue != null) {
try {
// 执行业务逻辑
processOrder();
} finally {
// 安全释放锁
boolean released = unlockUnsafe(lockKey, lockValue);
if (!released) {
log.warn("锁释放失败,可能已被其他线程释放: {}", lockKey);
}
}
} else {
throw new RuntimeException("获取锁失败: " + lockKey);
}
}
private void processOrder() {
// 模拟业务处理
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
3.3 原子性问题分析
即使加了值校验,上述实现仍然存在原子性问题:
sequenceDiagram
participant A as 线程A
participant Redis as Redis
Note over A: 准备释放锁
A->>Redis: GET lock_key
Redis-->>A: valueA
Note over A: 值匹配,准备删除
Note over Redis: 此时锁可能已过期并被其他线程获取
A->>Redis: DEL lock_key
Redis-->>A: 1
Note over A: 仍然可能误删其他线程的锁
rect rgb(255, 200, 200)
Note over Redis: 竞态条件仍然存在
end
问题根源:GET和DEL不是原子操作,在两个操作之间可能发生:
- 锁过期
- 其他线程获取到锁
- 当前线程误删了其他线程的锁
4. Lua脚本保证原子性
4.1 Lua脚本原理
Redis的Lua脚本具有原子性,整个脚本会作为一个原子操作执行,不会被其他命令打断。
flowchart LR
subgraph "传统方式(非原子)"
A1[GET lock_key] --> A2[比较值] --> A3[DEL lock_key]
A4[其他操作可能插入]
A1 -.-> A4
A2 -.-> A4
end
subgraph "Lua脚本(原子)"
B1[Lua脚本开始] --> B2[GET + 比较 + DEL] --> B3[脚本结束]
B4[其他操作等待]
B1 --> B4
B3 --> B4
end
style B2 fill:#e8f5e8
style A4 fill:#ffcdd2
4.2 Lua脚本实现
@Component
public class LuaRedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String LOCK_PREFIX = "distributed_lock:";
// 释放锁的Lua脚本
private static final String RELEASE_LOCK_SCRIPT =
"if redis.call('GET', KEYS[1]) == ARGV[1] then " +
" return redis.call('DEL', KEYS[1]) " +
"else " +
" return 0 " +
"end";
// 续期锁的Lua脚本
private static final String RENEW_LOCK_SCRIPT =
"if redis.call('GET', KEYS[1]) == ARGV[1] then " +
" return redis.call('EXPIRE', KEYS[1], ARGV[2]) " +
"else " +
" return 0 " +
"end";
// 获取锁的Lua脚本(带重试逻辑)
private static final String ACQUIRE_LOCK_SCRIPT =
"local key = KEYS[1] " +
"local value = ARGV[1] " +
"local expire = ARGV[2] " +
"local result = redis.call('SET', key, value, 'NX', 'EX', expire) " +
"if result then " +
" return 1 " +
"else " +
" return 0 " +
"end";
/**
* 获取锁
* @param lockKey 锁的key
* @param expireSeconds 过期时间(秒)
* @param timeoutMs 获取锁超时时间(毫秒)
* @return 锁的值,null表示获取失败
*/
public String lock(String lockKey, int expireSeconds, long timeoutMs) {
String key = LOCK_PREFIX + lockKey;
String value = generateLockValue();
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < timeoutMs) {
// 执行Lua脚本获取锁
Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
return (Long) connection.eval(
ACQUIRE_LOCK_SCRIPT.getBytes(),
ReturnType.INTEGER,
1,
key.getBytes(),
value.getBytes(),
String.valueOf(expireSeconds).getBytes()
);
});
if (result != null && result == 1) {
return value;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
return null;
}
/**
* 原子性释放锁
* @param lockKey 锁的key
* @param expectedValue 期望的锁值
* @return 是否释放成功
*/
public boolean unlock(String lockKey, String expectedValue) {
String key = LOCK_PREFIX + lockKey;
// 执行Lua脚本释放锁
Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
return (Long) connection.eval(
RELEASE_LOCK_SCRIPT.getBytes(),
ReturnType.INTEGER,
1,
key.getBytes(),
expectedValue.getBytes()
);
});
return result != null && result == 1;
}
/**
* 续期锁
* @param lockKey 锁的key
* @param expectedValue 期望的锁值
* @param expireSeconds 新的过期时间(秒)
* @return 是否续期成功
*/
public boolean renewLock(String lockKey, String expectedValue, int expireSeconds) {
String key = LOCK_PREFIX + lockKey;
Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
return (Long) connection.eval(
RENEW_LOCK_SCRIPT.getBytes(),
ReturnType.INTEGER,
1,
key.getBytes(),
expectedValue.getBytes(),
String.valueOf(expireSeconds).getBytes()
);
});
return result != null && result == 1;
}
private String generateLockValue() {
return ManagementFactory.getRuntimeMXBean().getName() + ":" +
Thread.currentThread().getId() + ":" +
System.nanoTime() + ":" +
UUID.randomUUID().toString();
}
/**
* 使用示例
*/
public void atomicBusinessMethod() {
String lockKey = "atomic_order_process";
String lockValue = lock(lockKey, 30, 5000);
if (lockValue != null) {
try {
// 执行业务逻辑
processOrder();
// 如果需要,可以续期锁
if (needMoreTime()) {
renewLock(lockKey, lockValue, 30);
continueProcessing();
}
} finally {
// 原子性释放锁
boolean released = unlock(lockKey, lockValue);
if (!released) {
log.warn("锁释放失败,可能已过期: {}", lockKey);
}
}
} else {
throw new RuntimeException("获取锁失败: " + lockKey);
}
}
private void processOrder() {
// 业务逻辑
}
private boolean needMoreTime() {
// 判断是否需要更多时间
return false;
}
private void continueProcessing() {
// 继续处理
}
}
4.3 Lua脚本优势
graph LR
A[Lua脚本分布式锁] --> B[原子性保证]
A --> C[性能优化]
A --> D[功能扩展]
A --> E[安全性提升]
B --> B1[GET + 比较 + DEL 原子执行]
B --> B2[避免竞态条件]
C --> C1[减少网络往返]
C --> C2[服务器端执行]
D --> D1[支持续期操作]
D --> D2[复杂逻辑封装]
E --> E1[防止误删]
E --> E2[值校验保护]
style B fill:#e8f5e8
style C fill:#e1f5fe
style D fill:#fff3e0
style E fill:#f3e5f5
5. 可重入锁 + 设计模式
5.1 可重入锁原理
可重入锁允许同一个线程多次获取同一把锁,这在递归调用或嵌套方法中非常有用。
sequenceDiagram
participant Thread as 线程A
participant Redis as Redis
Thread->>Redis: 第一次获取锁
Redis-->>Thread: 成功,计数=1
Thread->>Redis: 第二次获取锁(同一线程)
Redis-->>Thread: 成功,计数=2
Thread->>Redis: 第三次获取锁(同一线程)
Redis-->>Thread: 成功,计数=3
Thread->>Redis: 第一次释放锁
Redis-->>Thread: 成功,计数=2
Thread->>Redis: 第二次释放锁
Redis-->>Thread: 成功,计数=1
Thread->>Redis: 第三次释放锁
Redis-->>Thread: 成功,计数=0,锁被删除
5.2 可重入锁实现
@Component
public class ReentrantRedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String LOCK_PREFIX = "reentrant_lock:";
// 可重入锁获取脚本
private static final String REENTRANT_LOCK_SCRIPT =
"local key = KEYS[1] " +
"local field = ARGV[1] " +
"local expire = ARGV[2] " +
"local current = redis.call('HGET', key, field) " +
"if current == false then " +
" redis.call('HSET', key, field, 1) " +
" redis.call('EXPIRE', key, expire) " +
" return 1 " +
"else " +
" local count = tonumber(current) + 1 " +
" redis.call('HSET', key, field, count) " +
" redis.call('EXPIRE', key, expire) " +
" return count " +
"end";
// 可重入锁释放脚本
private static final String REENTRANT_UNLOCK_SCRIPT =
"local key = KEYS[1] " +
"local field = ARGV[1] " +
"local current = redis.call('HGET', key, field) " +
"if current == false then " +
" return 0 " +
"else " +
" local count = tonumber(current) - 1 " +
" if count > 0 then " +
" redis.call('HSET', key, field, count) " +
" return count " +
" else " +
" redis.call('HDEL', key, field) " +
" if redis.call('HLEN', key) == 0 then " +
" redis.call('DEL', key) " +
" end " +
" return 0 " +
" end " +
"end";
// 线程本地存储,记录当前线程持有的锁
private final ThreadLocal<Map<String, LockInfo>> threadLocks =
ThreadLocal.withInitial(HashMap::new);
/**
* 锁信息
*/
private static class LockInfo {
private String value;
private int count;
public LockInfo(String value, int count) {
this.value = value;
this.count = count;
}
// getters and setters
public String getValue() { return value; }
public int getCount() { return count; }
public void setCount(int count) { this.count = count; }
}
/**
* 获取可重入锁
* @param lockKey 锁的key
* @param expireSeconds 过期时间(秒)
* @param timeoutMs 获取锁超时时间(毫秒)
* @return 是否获取成功
*/
public boolean lock(String lockKey, int expireSeconds, long timeoutMs) {
String key = LOCK_PREFIX + lockKey;
String threadId = getThreadId();
// 检查当前线程是否已经持有锁
Map<String, LockInfo> locks = threadLocks.get();
LockInfo lockInfo = locks.get(lockKey);
if (lockInfo != null) {
// 当前线程已持有锁,直接重入
Long count = redisTemplate.execute((RedisCallback<Long>) connection -> {
return (Long) connection.eval(
REENTRANT_LOCK_SCRIPT.getBytes(),
ReturnType.INTEGER,
1,
key.getBytes(),
threadId.getBytes(),
String.valueOf(expireSeconds).getBytes()
);
});
if (count != null && count > 0) {
lockInfo.setCount(count.intValue());
return true;
}
}
// 尝试获取新锁
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < timeoutMs) {
Long count = redisTemplate.execute((RedisCallback<Long>) connection -> {
return (Long) connection.eval(
REENTRANT_LOCK_SCRIPT.getBytes(),
ReturnType.INTEGER,
1,
key.getBytes(),
threadId.getBytes(),
String.valueOf(expireSeconds).getBytes()
);
});
if (count != null && count > 0) {
// 获取成功,记录锁信息
locks.put(lockKey, new LockInfo(threadId, count.intValue()));
return true;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
/**
* 释放可重入锁
* @param lockKey 锁的key
* @return 是否释放成功
*/
public boolean unlock(String lockKey) {
String key = LOCK_PREFIX + lockKey;
String threadId = getThreadId();
Map<String, LockInfo> locks = threadLocks.get();
LockInfo lockInfo = locks.get(lockKey);
if (lockInfo == null) {
// 当前线程未持有锁
return false;
}
Long count = redisTemplate.execute((RedisCallback<Long>) connection -> {
return (Long) connection.eval(
REENTRANT_UNLOCK_SCRIPT.getBytes(),
ReturnType.INTEGER,
1,
key.getBytes(),
threadId.getBytes()
);
});
if (count != null) {
if (count == 0) {
// 锁完全释放
locks.remove(lockKey);
} else {
// 更新重入计数
lockInfo.setCount(count.intValue());
}
return true;
}
return false;
}
private String getThreadId() {
return Thread.currentThread().getId() + ":" +
ManagementFactory.getRuntimeMXBean().getName();
}
/**
* 清理线程本地存储
*/
public void cleanup() {
threadLocks.remove();
}
}
5.3 可重入锁优势
graph LR
A[可重入锁] --> B[避免死锁]
A --> C[支持递归]
A --> D[简化编程]
A --> E[性能优化]
B --> B1[同一线程多次获取]
B --> B2[计数管理]
C --> C1[递归方法调用]
C --> C2[嵌套锁场景]
D --> D1[无需手动管理]
D --> D2[自动计数]
E --> E1[减少锁竞争]
E --> E2[本地缓存优化]
style A fill:#ffeb3b
style B fill:#e8f5e8
style C fill:#e1f5fe
style D fill:#fff3e0
style E fill:#f3e5f5
6. 自动续期机制
6.1 问题背景
在实际业务中,很难准确预估业务执行时间。如果设置的锁过期时间太短,可能导致业务还未执行完锁就过期了;如果设置太长,又会影响系统的响应性。自动续期机制可以解决这个问题。
sequenceDiagram
participant Business as 业务线程
participant Watchdog as 看门狗线程
participant Redis as Redis
Business->>Redis: 获取锁(30秒过期)
Redis-->>Business: 成功
Business->>Watchdog: 启动看门狗
loop 每10秒检查一次
Watchdog->>Redis: 检查锁是否存在
Redis-->>Watchdog: 存在
Watchdog->>Redis: 续期30秒
Redis-->>Watchdog: 成功
end
Business->>Business: 业务执行完成
Business->>Watchdog: 停止看门狗
Business->>Redis: 释放锁
Redis-->>Business: 成功
6.2 看门狗机制实现
@Component
public class WatchdogRedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private final ScheduledExecutorService watchdogExecutor =
Executors.newScheduledThreadPool(10, r -> {
Thread t = new Thread(r, "redis-lock-watchdog");
t.setDaemon(true);
return t;
});
// 存储看门狗任务
private final ConcurrentHashMap<String, WatchdogTask> watchdogTasks =
new ConcurrentHashMap<>();
private static final String LOCK_PREFIX = "watchdog_lock:";
private static final int DEFAULT_EXPIRE_TIME = 30; // 30秒
private static final int WATCHDOG_INTERVAL = 10; // 10秒续期一次
// 续期脚本
private static final String RENEW_SCRIPT =
"if redis.call('GET', KEYS[1]) == ARGV[1] then " +
" return redis.call('EXPIRE', KEYS[1], ARGV[2]) " +
"else " +
" return 0 " +
"end";
/**
* 看门狗任务
*/
private class WatchdogTask {
private final String lockKey;
private final String lockValue;
private final int expireSeconds;
private volatile boolean running = true;
private ScheduledFuture<?> future;
public WatchdogTask(String lockKey, String lockValue, int expireSeconds) {
this.lockKey = lockKey;
this.lockValue = lockValue;
this.expireSeconds = expireSeconds;
}
public void start() {
this.future = watchdogExecutor.scheduleAtFixedRate(
this::renewLock,
WATCHDOG_INTERVAL,
WATCHDOG_INTERVAL,
TimeUnit.SECONDS
);
}
private void renewLock() {
if (!running) {
return;
}
try {
String key = LOCK_PREFIX + lockKey;
Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
return (Long) connection.eval(
RENEW_SCRIPT.getBytes(),
ReturnType.INTEGER,
1,
key.getBytes(),
lockValue.getBytes(),
String.valueOf(expireSeconds).getBytes()
);
});
if (result == null || result == 0) {
// 续期失败,锁可能已被其他线程获取或已过期
log.warn("锁续期失败,停止看门狗: {}", lockKey);
stop();
} else {
log.debug("锁续期成功: {}", lockKey);
}
} catch (Exception e) {
log.error("锁续期异常: {}", lockKey, e);
}
}
public void stop() {
running = false;
if (future != null) {
future.cancel(false);
}
watchdogTasks.remove(lockKey);
}
}
/**
* 获取锁并启动看门狗
* @param lockKey 锁的key
* @param expireSeconds 过期时间(秒)
* @param timeoutMs 获取锁超时时间(毫秒)
* @return 锁的值,null表示获取失败
*/
public String lockWithWatchdog(String lockKey, int expireSeconds, long timeoutMs) {
String key = LOCK_PREFIX + lockKey;
String value = generateLockValue();
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < timeoutMs) {
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, value, Duration.ofSeconds(expireSeconds));
if (Boolean.TRUE.equals(success)) {
// 获取锁成功,启动看门狗
WatchdogTask watchdog = new WatchdogTask(lockKey, value, expireSeconds);
watchdogTasks.put(lockKey, watchdog);
watchdog.start();
log.info("获取锁成功并启动看门狗: {}", lockKey);
return value;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
return null;
}
/**
* 释放锁并停止看门狗
* @param lockKey 锁的key
* @param expectedValue 期望的锁值
* @return 是否释放成功
*/
public boolean unlockWithWatchdog(String lockKey, String expectedValue) {
// 先停止看门狗
WatchdogTask watchdog = watchdogTasks.get(lockKey);
if (watchdog != null) {
watchdog.stop();
}
// 释放锁
String key = LOCK_PREFIX + lockKey;
String releaseScript =
"if redis.call('GET', KEYS[1]) == ARGV[1] then " +
" return redis.call('DEL', KEYS[1]) " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
return (Long) connection.eval(
releaseScript.getBytes(),
ReturnType.INTEGER,
1,
key.getBytes(),
expectedValue.getBytes()
);
});
boolean success = result != null && result == 1;
if (success) {
log.info("释放锁成功: {}", lockKey);
} else {
log.warn("释放锁失败: {}", lockKey);
}
return success;
}
/**
* 手动续期
* @param lockKey 锁的key
* @param expectedValue 期望的锁值
* @param expireSeconds 新的过期时间(秒)
* @return 是否续期成功
*/
public boolean renewLock(String lockKey, String expectedValue, int expireSeconds) {
String key = LOCK_PREFIX + lockKey;
Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
return (Long) connection.eval(
RENEW_SCRIPT.getBytes(),
ReturnType.INTEGER,
1,
key.getBytes(),
expectedValue.getBytes(),
String.valueOf(expireSeconds).getBytes()
);
});
return result != null && result == 1;
}
private String generateLockValue() {
return ManagementFactory.getRuntimeMXBean().getName() + ":" +
Thread.currentThread().getId() + ":" +
System.nanoTime() + ":" +
UUID.randomUUID().toString();
}
/**
* 使用示例
*/
public void businessMethodWithWatchdog() {
String lockKey = "long_running_task";
String lockValue = lockWithWatchdog(lockKey, DEFAULT_EXPIRE_TIME, 5000);
if (lockValue != null) {
try {
// 执行可能耗时很长的业务逻辑
longRunningTask();
} finally {
// 释放锁并停止看门狗
unlockWithWatchdog(lockKey, lockValue);
}
} else {
throw new RuntimeException("获取锁失败: " + lockKey);
}
}
private void longRunningTask() {
// 模拟长时间运行的任务
try {
Thread.sleep(60000); // 1分钟
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@PreDestroy
public void shutdown() {
// 关闭看门狗线程池
watchdogExecutor.shutdown();
try {
if (!watchdogExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
watchdogExecutor.shutdownNow();
}
} catch (InterruptedException e) {
watchdogExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
6.3 续期策略对比
graph LR
A[续期策略] --> B[固定间隔续期]
A --> C[动态间隔续期]
A --> D[看门狗机制]
B --> B1[简单实现]
B --> B2[固定10秒续期]
B --> B3[可能过度续期]
C --> C1[根据业务进度调整]
C --> C2[智能续期间隔]
C --> C3[复杂度较高]
D --> D1[自动检测锁状态]
D --> D2[失败自动停止]
D --> D3[最佳实践]
style D fill:#e8f5e8
style B fill:#fff3e0
style C fill:#e1f5fe
6.4 看门狗机制优势
- 自动化管理:无需手动计算业务执行时间
- 故障恢复:续期失败时自动停止,避免资源浪费
- 性能优化:合理的续期间隔,平衡性能和安全性
- 线程安全:使用线程安全的数据结构管理看门狗任务
7. Redisson分布式锁实现
7.1 Redisson概述
Redisson是一个在Redis基础上实现的Java驻内存数据网格,提供了丰富的分布式对象和服务,其中包括功能强大的分布式锁实现。
flowchart TB
A[Redisson分布式锁] --> B[可重入锁]
A --> C[公平锁]
A --> D[读写锁]
A --> E[信号量]
A --> F[多锁]
B --> B1[RLock]
B --> B2[自动续期]
B --> B3[Lua脚本实现]
C --> C1[RFairLock]
C --> C2[FIFO队列]
C --> C3[防止饥饿]
D --> D1[RReadWriteLock]
D --> D2[读锁共享]
D --> D3[写锁独占]
E --> E1[RSemaphore]
E --> E2[限制并发数]
F --> F1[RMultiLock]
F --> F2[RedLock算法]
style A fill:#ffeb3b
style B fill:#e8f5e8
style C fill:#e1f5fe
style D fill:#fff3e0
style E fill:#f3e5f5
style F fill:#ffcdd2
7.2 Redisson核心Lua脚本
-- Redisson可重入锁获取脚本(简化版)
local key = KEYS[1]
local threadId = ARGV[1]
local ttl = tonumber(ARGV[2])
-- 检查锁是否存在
if redis.call('exists', key) == 0 then
-- 锁不存在,直接获取
redis.call('hset', key, threadId, 1)
redis.call('pexpire', key, ttl)
return nil
end
-- 检查是否是当前线程持有的锁
if redis.call('hexists', key, threadId) == 1 then
-- 可重入,增加计数
redis.call('hincrby', key, threadId, 1)
redis.call('pexpire', key, ttl)
return nil
end
-- 返回锁的剩余时间
return redis.call('pttl', key)
7.3 Redisson使用示例
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://localhost:6379")
.setPassword("your-password")
.setDatabase(0)
.setConnectionMinimumIdleSize(10)
.setConnectionPoolSize(20)
.setIdleConnectionTimeout(30000)
.setConnectTimeout(5000)
.setTimeout(3000)
.setRetryAttempts(3)
.setRetryInterval(1500);
return Redisson.create(config);
}
}
@Service
public class RedissonLockService {
@Autowired
private RedissonClient redissonClient;
/**
* 基本可重入锁使用
*/
public void basicLockExample() {
RLock lock = redissonClient.getLock("order_lock");
try {
// 尝试获取锁,最多等待10秒,锁定后30秒自动释放
boolean acquired = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (acquired) {
try {
// 执行业务逻辑
processOrder();
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
} else {
throw new RuntimeException("获取锁超时");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁被中断", e);
}
}
/**
* 可重入锁演示
*/
public void reentrantLockExample() {
RLock lock = redissonClient.getLock("reentrant_lock");
try {
lock.lock(30, TimeUnit.SECONDS);
// 第一层业务逻辑
processLevel1();
// 嵌套调用,再次获取同一把锁
nestedMethod();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private void nestedMethod() {
RLock lock = redissonClient.getLock("reentrant_lock");
try {
lock.lock(30, TimeUnit.SECONDS);
// 第二层业务逻辑
processLevel2();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 公平锁使用
*/
public void fairLockExample() {
RFairLock fairLock = redissonClient.getFairLock("fair_lock");
try {
// 公平锁按照请求顺序获取锁
boolean acquired = fairLock.tryLock(10, 30, TimeUnit.SECONDS);
if (acquired) {
try {
// 执行需要公平性的业务逻辑
processFairTask();
} finally {
if (fairLock.isHeldByCurrentThread()) {
fairLock.unlock();
}
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* 读写锁使用
*/
public void readWriteLockExample() {
RReadWriteLock rwLock = redissonClient.getReadWriteLock("rw_lock");
// 读操作
RLock readLock = rwLock.readLock();
try {
readLock.lock(30, TimeUnit.SECONDS);
// 执行读操作
String data = readData();
} finally {
if (readLock.isHeldByCurrentThread()) {
readLock.unlock();
}
}
// 写操作
RLock writeLock = rwLock.writeLock();
try {
writeLock.lock(30, TimeUnit.SECONDS);
// 执行写操作
writeData("new data");
} finally {
if (writeLock.isHeldByCurrentThread()) {
writeLock.unlock();
}
}
}
/**
* 多锁使用(RedLock算法)
*/
public void multiLockExample() {
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
RMultiLock multiLock = new RMultiLock(lock1, lock2, lock3);
try {
// 同时获取多个锁
boolean acquired = multiLock.tryLock(10, 30, TimeUnit.SECONDS);
if (acquired) {
try {
// 执行需要多个资源的业务逻辑
processMultiResource();
} finally {
multiLock.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void processOrder() {
// 业务逻辑
}
private void processLevel1() {
// 第一层业务逻辑
}
private void processLevel2() {
// 第二层业务逻辑
}
private void processFairTask() {
// 公平任务处理
}
private String readData() {
// 读取数据
return "data";
}
private void writeData(String data) {
// 写入数据
}
private void processMultiResource() {
// 多资源处理
}
}
8. Spring Integration分布式锁实现
8.1 Spring Integration概述
Spring Integration提供了RedisLockRegistry
来实现分布式锁,它是Spring生态系统的一部分,与Spring框架集成度很高。
flowchart LR
A[Spring Integration锁] --> B[RedisLockRegistry]
A --> C[LockRegistry接口]
A --> D[与Spring集成]
B --> B1[基于Redis实现]
B --> B2[简单易用]
B --> B3[功能相对基础]
C --> C1[统一锁接口]
C --> C2[可扩展性]
D --> D1[Spring Boot自动配置]
D --> D2[与事务集成]
D --> D3[AOP支持]
style A fill:#4caf50
style B fill:#e8f5e8
style C fill:#e1f5fe
style D fill:#fff3e0
8.2 Spring Integration配置
@Configuration
@EnableIntegration
public class SpringIntegrationLockConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
LettuceConnectionFactory factory = new LettuceConnectionFactory(
new RedisStandaloneConfiguration("localhost", 6379)
);
factory.setDatabase(0);
return factory;
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
return new RedisLockRegistry(redisConnectionFactory, "spring-integration-lock", 60000);
}
}
@Service
public class SpringIntegrationLockService {
@Autowired
private RedisLockRegistry lockRegistry;
/**
* 基本锁使用
*/
public void basicLockUsage() {
Lock lock = lockRegistry.obtain("order_process");
try {
// 尝试获取锁,最多等待5秒
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// 执行业务逻辑
processOrder();
} finally {
lock.unlock();
}
} else {
throw new RuntimeException("获取锁超时");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁被中断", e);
}
}
/**
* 模板方法使用
*/
public <T> T executeWithLock(String lockKey, long timeout, TimeUnit unit, Supplier<T> task) {
Lock lock = lockRegistry.obtain(lockKey);
try {
if (lock.tryLock(timeout, unit)) {
try {
return task.get();
} finally {
lock.unlock();
}
} else {
throw new RuntimeException("获取锁超时: " + lockKey);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁被中断: " + lockKey, e);
}
}
/**
* 批量处理示例
*/
public void batchProcessWithLock() {
List<String> orderIds = Arrays.asList("order1", "order2", "order3");
orderIds.parallelStream().forEach(orderId -> {
String lockKey = "order_lock_" + orderId;
executeWithLock(lockKey, 10, TimeUnit.SECONDS, () -> {
processOrderById(orderId);
return null;
});
});
}
private void processOrder() {
// 业务逻辑
}
private void processOrderById(String orderId) {
// 根据订单ID处理业务
}
}
8.3 自定义注解实现
/**
* 分布式锁注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {
/**
* 锁的key,支持SpEL表达式
*/
String key();
/**
* 锁等待时间(秒)
*/
long waitTime() default 10;
/**
* 锁过期时间(秒)
*/
long leaseTime() default 60;
/**
* 获取锁失败时的处理策略
*/
LockFailStrategy failStrategy() default LockFailStrategy.EXCEPTION;
enum LockFailStrategy {
EXCEPTION, // 抛出异常
RETURN_NULL, // 返回null
SKIP // 跳过执行
}
}
/**
* 分布式锁AOP切面
*/
@Aspect
@Component
@Slf4j
public class DistributedLockAspect {
@Autowired
private RedisLockRegistry lockRegistry;
private final SpelExpressionParser parser = new SpelExpressionParser();
@Around("@annotation(distributedLock)")
public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
String lockKey = parseLockKey(distributedLock.key(), joinPoint);
Lock lock = lockRegistry.obtain(lockKey);
try {
boolean acquired = lock.tryLock(distributedLock.waitTime(), TimeUnit.SECONDS);
if (acquired) {
try {
log.debug("成功获取分布式锁: {}", lockKey);
return joinPoint.proceed();
} finally {
lock.unlock();
log.debug("释放分布式锁: {}", lockKey);
}
} else {
return handleLockFailure(distributedLock.failStrategy(), lockKey);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁被中断: " + lockKey, e);
}
}
private String parseLockKey(String keyExpression, ProceedingJoinPoint joinPoint) {
if (!keyExpression.contains("#")) {
return keyExpression;
}
// 创建SpEL上下文
StandardEvaluationContext context = new StandardEvaluationContext();
// 获取方法参数
Object[] args = joinPoint.getArgs();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] paramNames = signature.getParameterNames();
// 将参数添加到SpEL上下文
for (int i = 0; i < paramNames.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
// 解析SpEL表达式
Expression expression = parser.parseExpression(keyExpression);
return expression.getValue(context, String.class);
}
private Object handleLockFailure(DistributedLock.LockFailStrategy strategy, String lockKey) {
switch (strategy) {
case EXCEPTION:
throw new RuntimeException("获取分布式锁失败: " + lockKey);
case RETURN_NULL:
log.warn("获取分布式锁失败,返回null: {}", lockKey);
return null;
case SKIP:
log.warn("获取分布式锁失败,跳过执行: {}", lockKey);
return null;
default:
throw new RuntimeException("未知的锁失败策略: " + strategy);
}
}
}
/**
* 使用示例
*/
@Service
public class OrderService {
@DistributedLock(key = "order_lock_#{#orderId}", waitTime = 10, leaseTime = 30)
public void processOrder(String orderId) {
// 处理订单逻辑
log.info("处理订单: {}", orderId);
}
@DistributedLock(
key = "user_operation_#{#userId}_#{#operation}",
waitTime = 5,
leaseTime = 60,
failStrategy = DistributedLock.LockFailStrategy.RETURN_NULL
)
public String userOperation(Long userId, String operation) {
// 用户操作逻辑
return "操作成功";
}
}
9. Redisson vs Spring Integration 对比分析
9.1 功能特性对比
特性 | Redisson | Spring Integration |
---|---|---|
基础锁 | ✅ RLock | ✅ RedisLockRegistry |
可重入锁 | ✅ 原生支持 | ❌ 不支持 |
公平锁 | ✅ RFairLock | ❌ 不支持 |
读写锁 | ✅ RReadWriteLock | ❌ 不支持 |
信号量 | ✅ RSemaphore | ❌ 不支持 |
多锁 | ✅ RMultiLock | ❌ 不支持 |
自动续期 | ✅ 看门狗机制 | ❌ 固定过期时间 |
Lua脚本 | ✅ 内置优化 | ❌ 基础实现 |
集群支持 | ✅ 完整支持 | ✅ 基础支持 |
Spring集成 | ✅ 良好 | ✅ 原生集成 |
9.2 性能对比
graph TD
A[性能对比] --> B[Redisson]
A --> C[Spring Integration]
B --> B1[Lua脚本优化]
B --> B2[连接池管理]
B --> B3[看门狗机制]
B --> B4[批量操作支持]
C --> C1[简单实现]
C --> C2[基础连接管理]
C --> C3[固定过期时间]
C --> C4[单一操作]
B1 --> B1_1[减少网络往返]
B2 --> B2_1[高效连接复用]
B3 --> B3_1[智能续期]
B4 --> B4_1[批量锁操作]
C1 --> C1_1[实现简洁]
C2 --> C2_1[Spring管理]
C3 --> C3_1[可能过早释放]
C4 --> C4_1[网络开销较大]
style B fill:#e8f5e8
style C fill:#fff3e0
9.3 使用场景对比
Spring Integration适用场景:
- 项目复杂度较低,只需要基础的互斥锁功能
- 已经使用Spring生态,希望保持技术栈一致性
- 快速开发,优先考虑开发效率
- 团队对分布式锁需求相对简单
Redisson适用场景:
- 需要高级锁特性(可重入锁、公平锁、读写锁等)
- 高性能要求,需要处理高并发场景
- 复杂业务场景,需要多种同步工具
- 对锁的可靠性和功能完整性要求较高
总结
本文从基础的Redis分布式锁实现开始,逐步深入讲解了分布式锁的各种问题和解决方案:
- 简单实现:使用SETNX命令实现基础锁,但存在死锁风险
- 过期处理:通过SET NX EX原子操作解决死锁问题
- 防误删:使用唯一值校验防止误删其他线程的锁
- 原子性:通过Lua脚本保证操作的原子性
- 可重入性:实现可重入锁支持嵌套调用
- 自动续期:看门狗机制解决锁过期问题
- 框架对比:详细对比Redisson和Spring Integration的优缺点
在实际项目中,建议根据具体需求选择合适的方案:
- 简单场景:使用Spring Integration的RedisLockRegistry
- 复杂场景:使用Redisson获得更丰富的功能和更好的性能
- 特殊需求:基于本文的实现原理自定义分布式锁
无论选择哪种方案,都要注意监控、测试和故障处理,确保分布式锁在生产环境中的稳定性和可靠性。