咱們經常將緩存做爲分佈式鎖的解決方案,可是卻不能單純的判斷某個 key 是否存在 來做爲鎖的得到依據,由於不管是 exists 和 get 命名都不是線程安全的,都沒法保證只有一個線程能夠得到鎖,存在線程爭搶,可能會有多個線程同時拿到鎖的狀況(經典的 Redis 「讀後寫」的問題)。html
@Component public class LockClient { private StringRedisTemplate stringRedisTemplate; private ValueOperations<String, String> valueOperations; @Autowired public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; this.valueOperations = stringRedisTemplate.opsForValue(); } public void lockIncr() { Long lockIncr = valueOperations.increment("lockIncr", 1); // 說明拿到了鎖 if (lockIncr == 1) { // 業務操做 } } }
上面的鎖實現方式,咱們對資源作了隔離,保證只有惟一線程能夠拿到資源並執行操做。可是若是資源並非惟一線程執行的呢?存在多個線程爭搶的狀況下呢?node
public void lockSetnx() { String lock = "lockSetnx"; long millis = System.currentTimeMillis(); long timeout = millis + 3000L + 1; try { while (true) { boolean setnx = valueOperations.setIfAbsent(lock, timeout + ""); if (setnx == true) { break; } String oldTimeout = valueOperations.get(lock); // 這一步是爲了解決客戶端異常宕機,鎖沒有被正常釋放的時候。 // 當 p一、p2 同時執行到這裏,發現鎖的時間過時了。p一、p2 同時執行 getSet 命令。 // 假設 p1 先執行成功了,那麼 p1 獲得的值就是原來鎖的過時時間(能夠符合下面的判斷式),表示爭搶鎖成功。 // 假設 p2 後執行成功了,那麼 p2 獲得的值就是 p1 set 進去的值(不會符合下面的表達式),表示爭搶鎖失敗。 String oldValue = valueOperations.getAndSet(lock, timeout + ""); if (millis > Long.valueOf(oldTimeout) && millis > Long.valueOf(oldValue)) { break; } // 休眠 100 毫秒,再去爭搶鎖 Thread.sleep(100); } // 執行業務代碼 } catch (InterruptedException e) { e.printStackTrace(); } finally { if (millis < timeout) { stringRedisTemplate.delete(lock); } } }
zookeeper,天生的分佈式協調工具,生來就是爲了解決各類分佈式的難題,好比分佈式鎖、分佈式計數器、分佈式隊列等等。
zookeeper 分佈式鎖,若是本身實現的話,大抵的實現方式以下:redis
幸運的是,zookeeper recipes 客戶端爲咱們提供了多種分佈式鎖實現:緩存
zookeeper recipes 鎖的簡單使用:安全
public InterProcessMutex interProcessMutex(String lockPath) { CuratorFramework client = CuratorFrameworkFactory.newClient(zookeeper, new ExponentialBackoffRetry(1000, 3)); // 啓用命名空間,作微服務間隔離 client.usingNamespace(namespace); client.start(); return new InterProcessMutex(client, lockPath); }
public void lockUse() { InterProcessMutex interProcessMutex = interProcessMutex("/lockpath"); try { // 獲取鎖 if (interProcessMutex.acquire(100, TimeUnit.MILLISECONDS)) { // 執行業務代碼 } } catch (Exception e) { e.printStackTrace(); } finally { // 釋放鎖 try { interProcessMutex.release(); } catch (Exception e) { e.printStackTrace(); } } }