参考网址:
https://tech.souyunku.com6844903543590092814
https://www.jianshu.com/p/533072fa4d52
https://tech.souyunku.com/zhili/p/redisdistributelock.html
https://tech.souyunku.com6844903830442737671#heading-5
1. 分布式锁常见条件
(1)互斥性。临界区任一时刻只能被一个客户端的一个线程所执行。
(2)可重入性。获得锁的线程可以重复获得锁。
(3)获取锁和释放锁必须是相同线程。
(4)自动释放锁。获取锁线程崩溃没有主动释放锁,锁仍然可以被其它线程获取。
理解
- 条件1、2、3要求锁能够记录获取锁的机器+线程。
- 条件2要求锁需要同一个线程加锁次数进行计数
- 条件4要求锁有过期时间。
2. 基于Redis实现分布式锁
2.1 基于Redis实现分布式锁设计(未支持可重入锁)
优缺点
优点:借助redis高性能的特点,实现高性能分布式锁。
缺点:
加锁逻辑
解锁逻辑
实现设计
(1)key和value设计
key = lock.{resource};
value = {machineId}_{threadId};
(2)加锁和设置超时时间动作
需要保证set动作、expire动作原子性,主要有两种做法:
i. set时同时使用ex和nx
SET key value [EX seconds] [PX milliseconds] [NX|XX]
ii. 使用lua脚本执行set和expire
(3)释放锁操作
需要保证get动作、del动作原子性,依靠lua脚本实现原子性。
3. Redisson实现分布式锁
(1)分布式锁实现(未实现可重入锁和重试机制)
public <V> V executeReadWriteLuaScript(String luaScript, RScript.ReturnType returnType, List<Object> keys,
Object[] values) {
RScript rScript = getScript();
return rScript.eval(RScript.Mode.READ_WRITE, luaScript, returnType, keys, values);
}
public boolean lock(String resource, Object machineId, Object threadId, long expireTime) {
String key = "lock." + resource;
String value = machineId.toString() + "_" + threadId.toString();
return lock(key, value, expireTime);
}
public boolean lock(String key, String value, long expireTime) {
RBucket<String> bucket = getBucket(key);
return bucket.trySet(value, expireTime, TimeUnit.SECONDS);
}
public void unlock(String resource, Object machineId, Object threadId) {
String key = "lock." + resource;
String value = machineId.toString() + "_" + threadId.toString();
unlock(key, value);
}
public void unlock(String key, String value) {
String luaScript = "local value = redis.call('GET', KEYS[1]); " +
"if (value == ARGV[1]) then " +
" redis.call('DEL', KEYS[1]); " +
"end ";
executeReadWriteLuaScript(luaScript, RScript.ReturnType.VALUE,
Lists.newArrayList(key),
Lists.newArrayList(value).toArray());
}
(2)测试代码
@Test
public void testDistributedLock() {
Long machineId1 = 123L;
String resource = "resource";
Runnable r = () -> {
if (redisServiceWithRetry.lock(resource, machineId1, Thread.currentThread().getId(), 5)) {
try {
System.out.println(Thread.currentThread().getName() + " locked");
} catch (Exception e) {
e.printStackTrace();
} finally {
redisServiceWithRetry.unlock(resource, machineId1, Thread.currentThread().getId());
System.out.println(Thread.currentThread().getName() + " unlocked");
}
} else {
System.out.println(Thread.currentThread().getName() + " fail to lock");
}
};
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
结果: