分佈式鎖(1) ----- 介紹和基於數據庫的分佈式鎖 分佈式鎖(2) ----- 基於redis的分佈式鎖 分佈式鎖(3) ----- 基於zookeeper的分佈式鎖 代碼:https://github.com/shuo123/distributeLockhtml
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仍是使用這個實現。java
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
經過上述lua腳本實現,先判斷鎖是否該請求擁有,防止誤解鎖。git
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.沒有鎖續租,若是代碼在鎖的租期內沒有執行完成,那麼鎖過時會致使另外一個客戶端獲取鎖github
Redisson是redis推薦的分佈式鎖實現開源項目。Redisson的分佈式鎖實現可重入,同時有LockWatchDogTimeout來實現鎖續約 github:https://github.com/redisson/redissonredis
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(); } } }
若是Redis實現了集羣,因爲主從之間時經過異步複製的,假設客戶端A在主機上得到鎖,這時在未將鎖數據複製到從機時,主機掛了,從機切換爲主機,那麼從機沒有這條數據,客戶端B一樣能夠得到鎖。算法
使用多個獨立(非集羣)的實例來實現分佈式鎖,因爲實例獨立不需複製同步,因此沒有上述問題;而保證可用性的是靠數據冗餘,將數據多存放幾份在不一樣的實例上。算法以下:數據庫
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/wikidom