分布式锁
分布式锁应满足
- 互斥性:同一时刻只有一个客户端能持有锁
- 可重入:同一客户端可多次获取锁
- 死锁避免:客户端崩溃后能自动释放锁
- 公平性:按请求顺序获取锁
Redis 实现
简单实现
java
public class RedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public boolean lock(String key, String value, long expireTime) {
return Boolean.TRUE.equals(
redisTemplate.opsForValue().setIfAbsent(key, value,
expireTime, TimeUnit.SECONDS));
}
public void unlock(String key, String value) {
String currentValue = (String) redisTemplate.opsForValue().get(key);
if (value.equals(currentValue)) {
redisTemplate.delete(key);
}
}
}Lua 脚本保证原子性
java
public class RedisLock {
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 void unlock(String key, String value) {
redisTemplate.execute(
new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class),
Collections.singletonList(key),
value
);
}
}完整实现
java
@Service
public class DistributedLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public String lock(String key, long expireTime, long retryTimes, long retryInterval) {
String value = UUID.randomUUID().toString();
for (int i = 0; i < retryTimes; i++) {
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(success)) {
return value;
}
try {
Thread.sleep(retryInterval);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return null;
}
public boolean unlock(String key, String value) {
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key), value
);
return result != null && result == 1;
}
}ZooKeeper 实现
java
public class ZkLock {
private static final String LOCK_PATH = "/dist-lock";
public String acquireLock(String lockName) {
String path = LOCK_PATH + "/" + lockName;
try {
String shortUUID = UUID.randomUUID().toString();
String fullPath = zkClient.createEphemeralSequential(path, shortUUID);
return fullPath;
} catch (Exception e) {
return null;
}
}
public boolean tryLock(String lockName, long timeout) {
String lockPath = acquireLock(lockName);
if (lockPath == null) return false;
// 获取所有子节点并排序
List<String> children = zkClient.getChildren(LOCK_PATH);
Collections.sort(children);
String shortPath = lockPath.substring(lockPath.lastIndexOf("/") + 1);
int index = children.indexOf(shortPath);
// 如果是最小节点,获取锁成功
if (index == 0) {
return true;
}
// 等待前一个节点删除
String waitPath = children.get(index - 1);
CountDownLatch latch = new CountDownLatch(1);
zkClient.subscribeChildChanges(LOCK_PATH + "/" + waitPath,
(parentPath, currentChilds) -> {
if (!currentChilds.contains(waitPath)) {
latch.countDown();
}
});
latch.await(timeout, TimeUnit.MILLISECONDS);
zkClient.unsubscribeChildChanges(LOCK_PATH + "/" + waitPath, null);
return true;
}
public void unlock(String lockPath) {
zkClient.delete(lockPath);
}
}Redisson 分布式锁
java
@Autowired
private RedissonClient redisson;
public void doSomething() {
RLock lock = redisson.getLock("my-lock");
try {
// 等待锁最多10秒,锁定后10分钟自动释放
if (lock.tryLock(10, 600, TimeUnit.SECONDS)) {
// 业务逻辑
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}总结
| 实现方式 | 优点 | 缺点 |
|---|---|---|
| Redis | 性能高 | 锁过期时间难确定 |
| ZooKeeper | 可靠性高 | 性能较低 |
| Redisson | 功能完善 | 依赖额外组件 |
