认真写文章,用心做分享。公众号:Java耕耘者 整理的资料也会放在里面。
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 executeReadWriteLuaScript(String luaScript, RScript.ReturnType returnType, List 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 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)测试代码
@Testpublic 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(); }}
结果: