分布式鎖(2) ----- 基於redis的分布式鎖
阿新 • • 發佈:2019-03-29
如果 lis uuid his 復制 con this 大於 gets
Redis單機版實現
set和lua實現
獲取鎖
SET resource_name my_random_value NX PX 30000
NX key不存在時才set
PX 設置過期時間
my_random_value 要保證每臺客戶端的每個鎖請求唯一,可以使用UUID+ThreadID
該命令在Redis 2.6.12才有,網上有基於setnx、epire的實現和基於setnx、get、getset的實現,這些多多少少都有點瑕疵,大概率是舊版本的redis實現,建議高版本的redis還是使用這個實現。
釋放鎖
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
通過上述lua腳本實現,先判斷鎖是否該請求擁有,防止誤解鎖。
java代碼
public boolean tryLock(long waitTime, long leaseTime) { long end = Calendar.getInstance().getTimeInMillis() + waitTime; Jedis jedis = jedisPool.getResource(); try { do { String result = jedis.set(lockName, getClientId(), "NX", "EX", leaseTime); if ("OK".equals(result)) { return true; } try { Thread.sleep(100); } catch (InterruptedException e) { return false; } } while (Calendar.getInstance().getTimeInMillis() < end); }finally { if(jedis != null) { jedis.close(); } } return false; } public boolean unlock() { String lua = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" + " return redis.call(\"del\",KEYS[1])\n" + "else\n" + " return 0\n" + "end"; Jedis jedis = jedisPool.getResource(); try { Object obj = jedis.eval(lua, Collections.singletonList(lockName), Collections.singletonList(getClientId())); if (obj.equals(1)) { return true; } }finally { if(jedis != null){ jedis.close(); } } return false; }
測試代碼
public void lockTest() { //用線程模擬進程 ExecutorService threadPool = Executors.newFixedThreadPool(20); CyclicBarrier barrier = new CyclicBarrier(20); for (int i = 0; i < 20; i++) { threadPool.execute(new RedisLockTest(barrier)); } threadPool.shutdown(); while (!threadPool.isTerminated()) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } private static class RedisLockTest implements Runnable{ private CyclicBarrier barrier; RedisLockTest(CyclicBarrier barrier){ this.barrier = barrier; } @Override public void run() { //模擬一臺客戶端一個jedisPool JedisPool jedisPool = new JedisPool("192.168.9.150", 6379); try { DistributeLock lock = new SingletonRedisLock(jedisPool, "lock"); barrier.await(); boolean flag = lock.tryLock(Integer.MAX_VALUE, 300); try { System.out.println(Thread.currentThread().getId() + "get lock:flag=" + flag); Thread.sleep(100); System.out.println(Thread.currentThread().getId() + "get unlock"); } finally { lock.unlock(); } } catch (Exception e) { e.printStackTrace(); } finally { jedisPool.close(); } } }
存在的問題
該實現簡單,但存在以下問題:
1.沒有實現可重入
2.沒有鎖續租,如果代碼在鎖的租期內沒有執行完成,那麽鎖過期會導致另一個客戶端獲取鎖
Redisson實現
private static class RedissonLockTest implements Runnable{
private CyclicBarrier barrier;
RedissonLockTest(CyclicBarrier barrier){
this.barrier = barrier;
}
@Override
public void run() {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.9.150:6379");
RedissonClient client = Redisson.create(config);
try {
RLock lock = client.getLock("lock");
barrier.await();
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "get lock");
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "get unlock");
} finally {
lock.unlock();
}
}catch (Exception e){
e.printStackTrace();
}finally {
client.shutdown();
}
}
}
Redisson的分布式鎖實現可重入,同時有LockWatchDogTimeout來實現鎖續約
github:https://github.com/redisson/redisson
Redis多實例版實現
單機實現在多實例下問題
如果Redis實現了集群,由於主從之間時通過異步復制的,假設客戶端A在主機上獲得鎖,這時在未將鎖數據復制到從機時,主機掛了,從機切換為主機,那麽從機沒有這條數據,客戶端B同樣可以獲得鎖。
RedLock算法
使用多個獨立(非集群)的實例來實現分布式鎖,由於實例獨立不需復制同步,所以沒有上述問題;而保證可用性的是靠數據冗余,將數據多存放幾份在不同的實例上。算法如下:
- 使用相同的key和value從N個實例上獲取鎖;
- 當從大於(N/2+1)個實例獲取鎖的時間<鎖的過期時間,才獲取鎖成功
- 如果獲取失敗,解鎖所有實例
Redisson實現
public void redLockTest() {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.9.150:7000");
RedissonClient client = Redisson.create(config);
Config config1 = new Config();
config1.useSingleServer().setAddress("redis://192.168.9.150:7001");
RedissonClient client1 = Redisson.create(config1);
Config config2 = new Config();
config2.useSingleServer().setAddress("redis://192.168.9.150:7002");
RedissonClient client2 = Redisson.create(config2);
try{
RLock lock = client.getLock("lock");
RLock lock1 = client1.getLock("lock");
RLock lock2 = client2.getLock("lock");
RedissonRedLock redLock = new RedissonRedLock(lock, lock1, lock2);
redLock.lock();
try{
System.out.println(Thread.currentThread().getName() + "get lock");
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "get unlock");
} finally {
redLock.unlock();
}
}catch (Exception e){
e.printStackTrace();
}finally {
client.shutdown();
client1.shutdown();
client2.shutdown();
}
}
參考資料
https://redis.io/topics/distlock
https://github.com/redisson/redisson/wiki
分布式鎖(2) ----- 基於redis的分布式鎖