基于Zookeeper的分布式共享鎖

基于Zookeeper的分布式共享鎖

實(shí)現(xiàn)原理

  1. 基于Zookeeper椅野、Lock實(shí)現(xiàn)的分布共享式鎖
  2. 構(gòu)造初始化Zookeeper連接
  3. 在lock中嘗試獲取鎖(tryLock)
    1. 首先創(chuàng)建當(dāng)前連接的節(jié)點(diǎn)
    2. 獲取所有相關(guān)節(jié)點(diǎn),并排序
    3. 若當(dāng)前為只有一個(gè)節(jié)點(diǎn)或?yàn)樽钚≈蛋兀苯臃祷孬@取鎖成功
    4. 否則獲取前一個(gè)節(jié)點(diǎn),監(jiān)聽事件猖任,讓當(dāng)前節(jié)點(diǎn)進(jìn)入等待狀態(tài)
  4. 如果監(jiān)聽到事件為上刪除事件忿偷,釋放鎖
  5. 刪除節(jié)點(diǎn),釋放資源

代碼實(shí)現(xiàn)

  @Data
  @Slf4j
  public class MyZkDistributedLock implements Lock, Watcher {
  
      // 超時(shí)時(shí)間
      private static final int SESSION_TIMEOUT = 5000;
      // zookeeper server列表
      private String hosts;
      private String groupNode = "locks";
      private String subNode = "sub";
  
      private String lockName;
      private ZooKeeper zk;
      // 當(dāng)前client創(chuàng)建的子節(jié)點(diǎn)
      private String thisPath;
      // 當(dāng)前client等待的子節(jié)點(diǎn)
      private String waitPath;
      private List<Exception> exceptionList = new ArrayList<>();
      private CountDownLatch latch = new CountDownLatch(1);
  
      public MyZkDistributedLock(String hosts, String lockName) {
          this.hosts = hosts;
          this.lockName = lockName;
          try {
              // 連接zookeeper
              zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);
              Stat stat = zk.exists(groupNode, false);
              if (stat == null) {
                  // 如果根節(jié)點(diǎn)不存在日丹,則創(chuàng)建根節(jié)點(diǎn)
                  zk.create(groupNode, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,
                          CreateMode.PERSISTENT);
              }
          } catch (IOException e) {
              log.error("Zk連接異常", e);
              exceptionList.add(e);
          } catch (InterruptedException e) {
              log.error("Zk連接異常", e);
              exceptionList.add(e);
          } catch (KeeperException e) {
              log.error("Zk連接異常", e);
              exceptionList.add(e);
          }
      }
  
      @Override
      public void lock() {
          if (exceptionList.size() > 0) {
              throw new LockException(exceptionList.get(0));
          }
          try {
              if (this.tryLock()) {
                  log.info("------------>線程:{},鎖:{},獲得", Thread.currentThread().getName(), lockName);
                  return;
              } else {
                  // 等待鎖
                  this.latch.await();
              }
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
  
      @Override
      public void lockInterruptibly() throws InterruptedException {
  
      }
  
      @Override
      public boolean tryLock() {
          try {
              String splitStr = "_lock_";
              if (lockName.contains(splitStr)) {
                  throw new MyZkDistributedLock.LockException("鎖名有誤");
              }
              // 創(chuàng)建子節(jié)點(diǎn)
              thisPath = zk
                      .create("/" + groupNode + "/" + lockName + subNode + splitStr, null,
                              Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
  
              // 注意, 沒有必要監(jiān)聽"/locks"的子節(jié)點(diǎn)的變化情況
              List<String> childrenNodes = zk.getChildren("/" + groupNode, false);
  
              // 取出所有l(wèi)ockName的鎖
              List<String> lockObjects = new ArrayList<String>();
              for (String node : childrenNodes) {
                  String _node = node.split(splitStr)[0];
                  if (_node.equals(lockName)) {
                      lockObjects.add(node);
                  }
              }
  
              // 列表中只有一個(gè)子節(jié)點(diǎn), 那肯定就是thisPath, 說明client獲得鎖
              if (lockObjects.size() == 1) {
                  return true;
              } else {
                  String thisNode = thisPath.substring(("/" + groupNode + "/").length());
                  // 排序
                  Collections.sort(lockObjects);
                  int index = lockObjects.indexOf(thisNode);
                  if (index == -1) {
                      // never happened
                  } else if (index == 0) {
                      // inddx == 0, 說明thisNode在列表中最小, 獲得鎖
                      return true;
                  } else {
                      // 獲得排名比thisPath前1位的節(jié)點(diǎn)
                      this.waitPath = "/" + groupNode + "/" + lockObjects.get(index - 1);
                      // 在waitPath上注冊監(jiān)聽器, 當(dāng)waitPath被刪除時(shí), zookeeper會回調(diào)監(jiān)聽器的process方法
                      zk.getData(waitPath, true, new Stat());
                  }
              }
          } catch (Exception e) {
  
          }
          return false;
  
      }
  
      @Override
      public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
          return tryLock();
      }
  
      @Override
      public void unlock() {
          try {
              log.info("釋放鎖 {}", thisPath);
              zk.delete(thisPath, -1);
              thisPath = null;
              zk.close();
          } catch (InterruptedException e) {
              e.printStackTrace();
          } catch (KeeperException e) {
              e.printStackTrace();
          }
      }
  
      @Override
      public Condition newCondition() {
          return null;
      }
  
      @Override
      public void process(WatchedEvent event) {
          try {
              // 發(fā)生了waitPath的刪除事件
              if (event.getType() == EventType.NodeDeleted && event.getPath().equals(waitPath)) {
                  this.latch.countDown();
              }
          } catch (Exception e) {
              e.printStackTrace();
          }
  
      }
  
      public class LockException extends RuntimeException {
  
          private static final long serialVersionUID = 1L;
  
          public LockException(String e) {
              super(e);
          }
  
          public LockException(Exception e) {
              super(e);
          }
      }
  }

總結(jié)

總體來說瓷翻,實(shí)現(xiàn)并不難聚凹,我認(rèn)為主要就是排序號監(jiān)聽上一個(gè)節(jié)點(diǎn)的刪除事件割坠,依此類推齐帚,最后實(shí)現(xiàn)所有節(jié)點(diǎn)的監(jiān)聽

代碼地址

源碼GitHub地址

參考

分布式鎖與實(shí)現(xiàn)(二)——基于ZooKeeper實(shí)現(xiàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市彼哼,隨后出現(xiàn)的幾起案子对妄,更是在濱河造成了極大的恐慌,老刑警劉巖敢朱,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剪菱,死亡現(xiàn)場離奇詭異摩瞎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)孝常,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門旗们,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人构灸,你說我怎么就攤上這事上渴。” “怎么了喜颁?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵稠氮,是天一觀的道長。 經(jīng)常有香客問我半开,道長隔披,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任寂拆,我火速辦了婚禮奢米,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纠永。我一直安慰自己恃慧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布渺蒿。 她就那樣靜靜地躺著痢士,像睡著了一般。 火紅的嫁衣襯著肌膚如雪茂装。 梳的紋絲不亂的頭發(fā)上怠蹂,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機(jī)與錄音少态,去河邊找鬼城侧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛彼妻,可吹牛的內(nèi)容都是我干的嫌佑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼侨歉,長吁一口氣:“原來是場噩夢啊……” “哼屋摇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起幽邓,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤炮温,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后牵舵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柒啤,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡倦挂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了担巩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片方援。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖涛癌,靈堂內(nèi)的尸體忽然破棺而出肯骇,到底是詐尸還是另有隱情,我是刑警寧澤祖很,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布笛丙,位于F島的核電站,受9級特大地震影響假颇,放射性物質(zhì)發(fā)生泄漏胚鸯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一笨鸡、第九天 我趴在偏房一處隱蔽的房頂上張望姜钳。 院中可真熱鬧,春花似錦形耗、人聲如沸哥桥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拟糕。三九已至,卻和暖如春倦踢,著一層夾襖步出監(jiān)牢的瞬間送滞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工辱挥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留犁嗅,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓晤碘,卻偏偏與公主長得像褂微,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子园爷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容