'java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)'

Java 技術 Linux UNIX 數據庫 95後碼農 2019-07-23
"

如何用 Zookeeper 實現分佈式鎖?(附源碼)

"

如何用 Zookeeper 實現分佈式鎖?(附源碼)

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

做積極的人,而不是積極廢人!

  • 中文詳細註釋的開源項目
  • RPC 框架 Dubbo 源碼解析
  • 網絡應用框架 Netty 源碼解析
  • 消息中間件 RocketMQ 源碼解析
  • 數據庫中間件 Sharding-JDBC 和 MyCAT 源碼解析
  • 作業調度中間件 Elastic-Job 源碼解析
  • 分佈式事務中間件 TCC-Transaction 源碼解析
  • Eureka 和 Hystrix 源碼解析
  • Java 併發源碼
  • 1. 背景
  • 2. 獨佔鎖和讀寫鎖的實現
  • 2.1 獨佔鎖的實現
  • 2.2 讀寫鎖的實現
  • 3. 寫在最後
  • 參考

1. 背景

最近在學習 Zookeeper,在剛開始接觸 Zookeeper 的時候,完全不知道 Zookeeper 有什麼用。且很多資料都是將 Zookeeper 描述成一個“類 Unix/Linux 文件系統”的中間件,導致我很難將類 Unix/Linux 文件系統的 Zookeeper 和分佈式應用聯繫在一起。後來在粗讀了《ZooKeeper 分佈式過程協同技術詳解》和《從Paxos到Zookeeper 分佈式一致性原理與實踐》兩本書,並動手寫了一些 CURD demo 後,初步對 Zookeeper 有了一定的瞭解。不過比較膚淺,為了進一步加深對 Zookeeper 的認識,我利用空閒時間編寫了本篇文章對應的 demo -- 基於 Zookeeper 的分佈式鎖實現。通過編寫這個分佈式鎖 demo,使我對 Zookeeper 的 watcher 機制、Zookeeper 的用途等有了更進一步的認識。不過我所編寫的分佈式鎖還是比較簡陋的,實現的也不夠優美,僅僅是個練習,僅供參考使用。好了,題外話就說到這裡,接下來我們就來聊聊基於 Zookeeper 的分佈式鎖實現。

2. 獨佔鎖和讀寫鎖的實現

在本章,我將分別說明獨佔鎖和讀寫鎖詳細的實現過程,並配以相應的流程圖幫助大家瞭解實現的過程。這裡先說說獨佔鎖的實現。

2.1 獨佔鎖的實現

獨佔鎖又稱排它鎖,從字面意思上很容易理解他們的用途。即如果某個操作 O1 對訪問資源 R1 的過程加鎖,在操作 O1 結束對資源 R1 訪問前,其他操作不允許訪問資源 R1。以上算是對獨佔鎖的簡單定義了,那麼這段定義在 Zookeeper 的“類 Unix/Linux 文件系統”的結構中是怎樣實現的呢?在鎖答案前,我們先看張圖:

"

如何用 Zookeeper 實現分佈式鎖?(附源碼)

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

做積極的人,而不是積極廢人!

  • 中文詳細註釋的開源項目
  • RPC 框架 Dubbo 源碼解析
  • 網絡應用框架 Netty 源碼解析
  • 消息中間件 RocketMQ 源碼解析
  • 數據庫中間件 Sharding-JDBC 和 MyCAT 源碼解析
  • 作業調度中間件 Elastic-Job 源碼解析
  • 分佈式事務中間件 TCC-Transaction 源碼解析
  • Eureka 和 Hystrix 源碼解析
  • Java 併發源碼
  • 1. 背景
  • 2. 獨佔鎖和讀寫鎖的實現
  • 2.1 獨佔鎖的實現
  • 2.2 讀寫鎖的實現
  • 3. 寫在最後
  • 參考

1. 背景

最近在學習 Zookeeper,在剛開始接觸 Zookeeper 的時候,完全不知道 Zookeeper 有什麼用。且很多資料都是將 Zookeeper 描述成一個“類 Unix/Linux 文件系統”的中間件,導致我很難將類 Unix/Linux 文件系統的 Zookeeper 和分佈式應用聯繫在一起。後來在粗讀了《ZooKeeper 分佈式過程協同技術詳解》和《從Paxos到Zookeeper 分佈式一致性原理與實踐》兩本書,並動手寫了一些 CURD demo 後,初步對 Zookeeper 有了一定的瞭解。不過比較膚淺,為了進一步加深對 Zookeeper 的認識,我利用空閒時間編寫了本篇文章對應的 demo -- 基於 Zookeeper 的分佈式鎖實現。通過編寫這個分佈式鎖 demo,使我對 Zookeeper 的 watcher 機制、Zookeeper 的用途等有了更進一步的認識。不過我所編寫的分佈式鎖還是比較簡陋的,實現的也不夠優美,僅僅是個練習,僅供參考使用。好了,題外話就說到這裡,接下來我們就來聊聊基於 Zookeeper 的分佈式鎖實現。

2. 獨佔鎖和讀寫鎖的實現

在本章,我將分別說明獨佔鎖和讀寫鎖詳細的實現過程,並配以相應的流程圖幫助大家瞭解實現的過程。這裡先說說獨佔鎖的實現。

2.1 獨佔鎖的實現

獨佔鎖又稱排它鎖,從字面意思上很容易理解他們的用途。即如果某個操作 O1 對訪問資源 R1 的過程加鎖,在操作 O1 結束對資源 R1 訪問前,其他操作不允許訪問資源 R1。以上算是對獨佔鎖的簡單定義了,那麼這段定義在 Zookeeper 的“類 Unix/Linux 文件系統”的結構中是怎樣實現的呢?在鎖答案前,我們先看張圖:

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖1 獨佔鎖的 Zookeeper 節點結構

如上圖,對於獨佔鎖,我們可以將資源 R1 看做是 lock 節點,操作 O1 訪問資源 R1 看做創建 lock 節點,釋放資源 R1 看做刪除 lock 節點。這樣我們就將獨佔鎖的定義對應於具體的 Zookeeper 節點結構,通過創建 lock 節點獲取鎖,刪除節點釋放鎖。詳細的過程如下:

  1. 多個客戶端競爭創建 lock 臨時節點
  2. 其中某個客戶端成功創建 lock 節點,其他客戶端對 lock 節點設置 watcher
  3. 持有鎖的客戶端刪除 lock 節點或該客戶端崩潰,由 Zookeeper 刪除 lock 節點
  4. 其他客戶端獲得 lock 節點被刪除的通知
  5. 重複上述4個步驟,直至無客戶端在等待獲取鎖了

上面即獨佔鎖具體的實現步驟,理解起來並不複雜,這裡不再贅述。

"

如何用 Zookeeper 實現分佈式鎖?(附源碼)

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

做積極的人,而不是積極廢人!

  • 中文詳細註釋的開源項目
  • RPC 框架 Dubbo 源碼解析
  • 網絡應用框架 Netty 源碼解析
  • 消息中間件 RocketMQ 源碼解析
  • 數據庫中間件 Sharding-JDBC 和 MyCAT 源碼解析
  • 作業調度中間件 Elastic-Job 源碼解析
  • 分佈式事務中間件 TCC-Transaction 源碼解析
  • Eureka 和 Hystrix 源碼解析
  • Java 併發源碼
  • 1. 背景
  • 2. 獨佔鎖和讀寫鎖的實現
  • 2.1 獨佔鎖的實現
  • 2.2 讀寫鎖的實現
  • 3. 寫在最後
  • 參考

1. 背景

最近在學習 Zookeeper,在剛開始接觸 Zookeeper 的時候,完全不知道 Zookeeper 有什麼用。且很多資料都是將 Zookeeper 描述成一個“類 Unix/Linux 文件系統”的中間件,導致我很難將類 Unix/Linux 文件系統的 Zookeeper 和分佈式應用聯繫在一起。後來在粗讀了《ZooKeeper 分佈式過程協同技術詳解》和《從Paxos到Zookeeper 分佈式一致性原理與實踐》兩本書,並動手寫了一些 CURD demo 後,初步對 Zookeeper 有了一定的瞭解。不過比較膚淺,為了進一步加深對 Zookeeper 的認識,我利用空閒時間編寫了本篇文章對應的 demo -- 基於 Zookeeper 的分佈式鎖實現。通過編寫這個分佈式鎖 demo,使我對 Zookeeper 的 watcher 機制、Zookeeper 的用途等有了更進一步的認識。不過我所編寫的分佈式鎖還是比較簡陋的,實現的也不夠優美,僅僅是個練習,僅供參考使用。好了,題外話就說到這裡,接下來我們就來聊聊基於 Zookeeper 的分佈式鎖實現。

2. 獨佔鎖和讀寫鎖的實現

在本章,我將分別說明獨佔鎖和讀寫鎖詳細的實現過程,並配以相應的流程圖幫助大家瞭解實現的過程。這裡先說說獨佔鎖的實現。

2.1 獨佔鎖的實現

獨佔鎖又稱排它鎖,從字面意思上很容易理解他們的用途。即如果某個操作 O1 對訪問資源 R1 的過程加鎖,在操作 O1 結束對資源 R1 訪問前,其他操作不允許訪問資源 R1。以上算是對獨佔鎖的簡單定義了,那麼這段定義在 Zookeeper 的“類 Unix/Linux 文件系統”的結構中是怎樣實現的呢?在鎖答案前,我們先看張圖:

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖1 獨佔鎖的 Zookeeper 節點結構

如上圖,對於獨佔鎖,我們可以將資源 R1 看做是 lock 節點,操作 O1 訪問資源 R1 看做創建 lock 節點,釋放資源 R1 看做刪除 lock 節點。這樣我們就將獨佔鎖的定義對應於具體的 Zookeeper 節點結構,通過創建 lock 節點獲取鎖,刪除節點釋放鎖。詳細的過程如下:

  1. 多個客戶端競爭創建 lock 臨時節點
  2. 其中某個客戶端成功創建 lock 節點,其他客戶端對 lock 節點設置 watcher
  3. 持有鎖的客戶端刪除 lock 節點或該客戶端崩潰,由 Zookeeper 刪除 lock 節點
  4. 其他客戶端獲得 lock 節點被刪除的通知
  5. 重複上述4個步驟,直至無客戶端在等待獲取鎖了

上面即獨佔鎖具體的實現步驟,理解起來並不複雜,這裡不再贅述。

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖2 獲取獨佔鎖流程圖

2.2 讀寫鎖的實現

說完獨佔鎖的實現,這節來說說讀寫鎖的實現。讀寫鎖包含一個讀鎖和寫鎖,操作 O1 對資源 R1 加讀鎖,且獲得了鎖,其他操作可同時對資源 R1 設置讀鎖,進行共享讀操作。如果操作 O1 對資源 R1 加寫鎖,且獲得了鎖,其他操作再對資源 R1 設置不同類型的鎖都會被阻塞。總結來說,讀鎖具有共享性,而寫鎖具有排他性。那麼在 Zookeeper 中,我們可以用怎樣的節點結構實現上面的操作呢?

"

如何用 Zookeeper 實現分佈式鎖?(附源碼)

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

做積極的人,而不是積極廢人!

  • 中文詳細註釋的開源項目
  • RPC 框架 Dubbo 源碼解析
  • 網絡應用框架 Netty 源碼解析
  • 消息中間件 RocketMQ 源碼解析
  • 數據庫中間件 Sharding-JDBC 和 MyCAT 源碼解析
  • 作業調度中間件 Elastic-Job 源碼解析
  • 分佈式事務中間件 TCC-Transaction 源碼解析
  • Eureka 和 Hystrix 源碼解析
  • Java 併發源碼
  • 1. 背景
  • 2. 獨佔鎖和讀寫鎖的實現
  • 2.1 獨佔鎖的實現
  • 2.2 讀寫鎖的實現
  • 3. 寫在最後
  • 參考

1. 背景

最近在學習 Zookeeper,在剛開始接觸 Zookeeper 的時候,完全不知道 Zookeeper 有什麼用。且很多資料都是將 Zookeeper 描述成一個“類 Unix/Linux 文件系統”的中間件,導致我很難將類 Unix/Linux 文件系統的 Zookeeper 和分佈式應用聯繫在一起。後來在粗讀了《ZooKeeper 分佈式過程協同技術詳解》和《從Paxos到Zookeeper 分佈式一致性原理與實踐》兩本書,並動手寫了一些 CURD demo 後,初步對 Zookeeper 有了一定的瞭解。不過比較膚淺,為了進一步加深對 Zookeeper 的認識,我利用空閒時間編寫了本篇文章對應的 demo -- 基於 Zookeeper 的分佈式鎖實現。通過編寫這個分佈式鎖 demo,使我對 Zookeeper 的 watcher 機制、Zookeeper 的用途等有了更進一步的認識。不過我所編寫的分佈式鎖還是比較簡陋的,實現的也不夠優美,僅僅是個練習,僅供參考使用。好了,題外話就說到這裡,接下來我們就來聊聊基於 Zookeeper 的分佈式鎖實現。

2. 獨佔鎖和讀寫鎖的實現

在本章,我將分別說明獨佔鎖和讀寫鎖詳細的實現過程,並配以相應的流程圖幫助大家瞭解實現的過程。這裡先說說獨佔鎖的實現。

2.1 獨佔鎖的實現

獨佔鎖又稱排它鎖,從字面意思上很容易理解他們的用途。即如果某個操作 O1 對訪問資源 R1 的過程加鎖,在操作 O1 結束對資源 R1 訪問前,其他操作不允許訪問資源 R1。以上算是對獨佔鎖的簡單定義了,那麼這段定義在 Zookeeper 的“類 Unix/Linux 文件系統”的結構中是怎樣實現的呢?在鎖答案前,我們先看張圖:

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖1 獨佔鎖的 Zookeeper 節點結構

如上圖,對於獨佔鎖,我們可以將資源 R1 看做是 lock 節點,操作 O1 訪問資源 R1 看做創建 lock 節點,釋放資源 R1 看做刪除 lock 節點。這樣我們就將獨佔鎖的定義對應於具體的 Zookeeper 節點結構,通過創建 lock 節點獲取鎖,刪除節點釋放鎖。詳細的過程如下:

  1. 多個客戶端競爭創建 lock 臨時節點
  2. 其中某個客戶端成功創建 lock 節點,其他客戶端對 lock 節點設置 watcher
  3. 持有鎖的客戶端刪除 lock 節點或該客戶端崩潰,由 Zookeeper 刪除 lock 節點
  4. 其他客戶端獲得 lock 節點被刪除的通知
  5. 重複上述4個步驟,直至無客戶端在等待獲取鎖了

上面即獨佔鎖具體的實現步驟,理解起來並不複雜,這裡不再贅述。

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖2 獲取獨佔鎖流程圖

2.2 讀寫鎖的實現

說完獨佔鎖的實現,這節來說說讀寫鎖的實現。讀寫鎖包含一個讀鎖和寫鎖,操作 O1 對資源 R1 加讀鎖,且獲得了鎖,其他操作可同時對資源 R1 設置讀鎖,進行共享讀操作。如果操作 O1 對資源 R1 加寫鎖,且獲得了鎖,其他操作再對資源 R1 設置不同類型的鎖都會被阻塞。總結來說,讀鎖具有共享性,而寫鎖具有排他性。那麼在 Zookeeper 中,我們可以用怎樣的節點結構實現上面的操作呢?

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖3 讀寫鎖的 Zookeeper 節點結構

在 Zookeeper 中,由於讀寫鎖和獨佔鎖的節點結構不同,讀寫鎖的客戶端不用再去競爭創建 lock 節點。所以在一開始,所有的客戶端都會創建自己的鎖節點。如果不出意外,所有的鎖節點都能被創建成功,此時鎖節點結構如圖3所示。之後,客戶端從 Zookeeper 端獲取 /share_lock 下所有的子節點,並判斷自己能否獲取鎖。如果客戶端創建的是讀鎖節點,獲取鎖的條件(滿足其中一個即可)如下:

  1. 自己創建的節點序號排在所有其他子節點前面
  2. 自己創建的節點前面無寫鎖節點

如果客戶端創建的是寫鎖節點,由於寫鎖具有排他性。所以獲取鎖的條件要簡單一些,只需確定自己創建的鎖節點是否排在其他子節點前面即可。

不同於獨佔鎖,讀寫鎖的實現稍微複雜一下。讀寫鎖有兩種實現方式,各有異同,接下來就來說說這兩種實現方式。

讀寫鎖的第一種實現

第一種實現是對 /share_lock 節點設置 watcher,當 /share_lock 下的子節點被刪除時,未獲取鎖的客戶端收到 /share_lock 子節點變動的通知。在收到通知後,客戶端重新判斷自己創建的子節點是否可以獲取鎖,如果失敗,再次等待通知。詳細流程如下:

  1. 所有客戶端創建自己的鎖節點
  2. 從 Zookeeper 端獲取 /share_lock 下所有的子節點,並對 /share_lock 節點設置 watcher
  3. 判斷自己創建的鎖節點是否可以獲取鎖,如果可以,持有鎖。否則繼續等待
  4. 持有鎖的客戶端刪除自己的鎖節點,其他客戶端收到 /share_lock 子節點變動的通知
  5. 重複步驟2、3、4,直至無客戶端在等待獲取鎖了

上述步驟對於的流程圖如下:

"

如何用 Zookeeper 實現分佈式鎖?(附源碼)

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

做積極的人,而不是積極廢人!

  • 中文詳細註釋的開源項目
  • RPC 框架 Dubbo 源碼解析
  • 網絡應用框架 Netty 源碼解析
  • 消息中間件 RocketMQ 源碼解析
  • 數據庫中間件 Sharding-JDBC 和 MyCAT 源碼解析
  • 作業調度中間件 Elastic-Job 源碼解析
  • 分佈式事務中間件 TCC-Transaction 源碼解析
  • Eureka 和 Hystrix 源碼解析
  • Java 併發源碼
  • 1. 背景
  • 2. 獨佔鎖和讀寫鎖的實現
  • 2.1 獨佔鎖的實現
  • 2.2 讀寫鎖的實現
  • 3. 寫在最後
  • 參考

1. 背景

最近在學習 Zookeeper,在剛開始接觸 Zookeeper 的時候,完全不知道 Zookeeper 有什麼用。且很多資料都是將 Zookeeper 描述成一個“類 Unix/Linux 文件系統”的中間件,導致我很難將類 Unix/Linux 文件系統的 Zookeeper 和分佈式應用聯繫在一起。後來在粗讀了《ZooKeeper 分佈式過程協同技術詳解》和《從Paxos到Zookeeper 分佈式一致性原理與實踐》兩本書,並動手寫了一些 CURD demo 後,初步對 Zookeeper 有了一定的瞭解。不過比較膚淺,為了進一步加深對 Zookeeper 的認識,我利用空閒時間編寫了本篇文章對應的 demo -- 基於 Zookeeper 的分佈式鎖實現。通過編寫這個分佈式鎖 demo,使我對 Zookeeper 的 watcher 機制、Zookeeper 的用途等有了更進一步的認識。不過我所編寫的分佈式鎖還是比較簡陋的,實現的也不夠優美,僅僅是個練習,僅供參考使用。好了,題外話就說到這裡,接下來我們就來聊聊基於 Zookeeper 的分佈式鎖實現。

2. 獨佔鎖和讀寫鎖的實現

在本章,我將分別說明獨佔鎖和讀寫鎖詳細的實現過程,並配以相應的流程圖幫助大家瞭解實現的過程。這裡先說說獨佔鎖的實現。

2.1 獨佔鎖的實現

獨佔鎖又稱排它鎖,從字面意思上很容易理解他們的用途。即如果某個操作 O1 對訪問資源 R1 的過程加鎖,在操作 O1 結束對資源 R1 訪問前,其他操作不允許訪問資源 R1。以上算是對獨佔鎖的簡單定義了,那麼這段定義在 Zookeeper 的“類 Unix/Linux 文件系統”的結構中是怎樣實現的呢?在鎖答案前,我們先看張圖:

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖1 獨佔鎖的 Zookeeper 節點結構

如上圖,對於獨佔鎖,我們可以將資源 R1 看做是 lock 節點,操作 O1 訪問資源 R1 看做創建 lock 節點,釋放資源 R1 看做刪除 lock 節點。這樣我們就將獨佔鎖的定義對應於具體的 Zookeeper 節點結構,通過創建 lock 節點獲取鎖,刪除節點釋放鎖。詳細的過程如下:

  1. 多個客戶端競爭創建 lock 臨時節點
  2. 其中某個客戶端成功創建 lock 節點,其他客戶端對 lock 節點設置 watcher
  3. 持有鎖的客戶端刪除 lock 節點或該客戶端崩潰,由 Zookeeper 刪除 lock 節點
  4. 其他客戶端獲得 lock 節點被刪除的通知
  5. 重複上述4個步驟,直至無客戶端在等待獲取鎖了

上面即獨佔鎖具體的實現步驟,理解起來並不複雜,這裡不再贅述。

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖2 獲取獨佔鎖流程圖

2.2 讀寫鎖的實現

說完獨佔鎖的實現,這節來說說讀寫鎖的實現。讀寫鎖包含一個讀鎖和寫鎖,操作 O1 對資源 R1 加讀鎖,且獲得了鎖,其他操作可同時對資源 R1 設置讀鎖,進行共享讀操作。如果操作 O1 對資源 R1 加寫鎖,且獲得了鎖,其他操作再對資源 R1 設置不同類型的鎖都會被阻塞。總結來說,讀鎖具有共享性,而寫鎖具有排他性。那麼在 Zookeeper 中,我們可以用怎樣的節點結構實現上面的操作呢?

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖3 讀寫鎖的 Zookeeper 節點結構

在 Zookeeper 中,由於讀寫鎖和獨佔鎖的節點結構不同,讀寫鎖的客戶端不用再去競爭創建 lock 節點。所以在一開始,所有的客戶端都會創建自己的鎖節點。如果不出意外,所有的鎖節點都能被創建成功,此時鎖節點結構如圖3所示。之後,客戶端從 Zookeeper 端獲取 /share_lock 下所有的子節點,並判斷自己能否獲取鎖。如果客戶端創建的是讀鎖節點,獲取鎖的條件(滿足其中一個即可)如下:

  1. 自己創建的節點序號排在所有其他子節點前面
  2. 自己創建的節點前面無寫鎖節點

如果客戶端創建的是寫鎖節點,由於寫鎖具有排他性。所以獲取鎖的條件要簡單一些,只需確定自己創建的鎖節點是否排在其他子節點前面即可。

不同於獨佔鎖,讀寫鎖的實現稍微複雜一下。讀寫鎖有兩種實現方式,各有異同,接下來就來說說這兩種實現方式。

讀寫鎖的第一種實現

第一種實現是對 /share_lock 節點設置 watcher,當 /share_lock 下的子節點被刪除時,未獲取鎖的客戶端收到 /share_lock 子節點變動的通知。在收到通知後,客戶端重新判斷自己創建的子節點是否可以獲取鎖,如果失敗,再次等待通知。詳細流程如下:

  1. 所有客戶端創建自己的鎖節點
  2. 從 Zookeeper 端獲取 /share_lock 下所有的子節點,並對 /share_lock 節點設置 watcher
  3. 判斷自己創建的鎖節點是否可以獲取鎖,如果可以,持有鎖。否則繼續等待
  4. 持有鎖的客戶端刪除自己的鎖節點,其他客戶端收到 /share_lock 子節點變動的通知
  5. 重複步驟2、3、4,直至無客戶端在等待獲取鎖了

上述步驟對於的流程圖如下:

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖4 獲取讀寫鎖實現1流程圖

上面獲取讀寫鎖流程並不複雜,但卻存在性能問題。以圖3所示鎖節點結構為例,第一個鎖節點 host1-W-0000000001 被移除後,Zookeeper 會將 /share_lock 子節點變動的通知分發給所有的客戶端。但實際上,該子節點變動通知除了能影響 host2-R-0000000002 節點對應的客戶端外,分發給其他客戶端則是在做無用功,因為其他客戶端即使獲取了通知也無法獲取鎖。所以這裡需要做一些優化,優化措施是讓客戶端只在自己關心的節點被刪除時,再去獲取鎖。

讀寫鎖的第二種實現

在瞭解讀寫鎖第一種實現的弊端後,我們針對這一實現進行優化。這裡客戶端不再對 /share_lock 節點進行監視,而只對自己關心的節點進行監視。還是以圖3的鎖節點結構進行舉例說明,host2-R-0000000002 對應的客戶端 C2 只需監視 host1-W-0000000001 節點是否被刪除即可。而 host3-W-0000000003 對應的客戶端 C3 只需監視 host2-R-0000000002 節點是否被刪除即可,只有 host2-R-0000000002 節點被刪除,客戶端 C3 才能獲取鎖。而 host1-W-0000000001 節點被刪除時,產生的通知對於客戶端 C3 來說是無用的,即使客戶端 C3 響應了通知也沒法獲取鎖。這裡總結一下,不同客戶端關心的鎖節點是不同的。如果客戶端創建的是讀鎖節點,那麼客戶端只需找出比讀鎖節點序號小的最後一個的寫鎖節點,並設置 watcher 即可。而如果是寫鎖節點,則更簡單,客戶端僅需對該節點的上一個節點設置 watcher 即可。詳細的流程如下:

  1. 所有客戶端創建自己的鎖節點
  2. 從 Zookeeper 端獲取 /share_lock 下所有的子節點
  3. 判斷自己創建的鎖節點是否可以獲取鎖,如果可以,持有鎖。否則對自己關心的鎖節點設置 watcher
  4. 持有鎖的客戶端刪除自己的鎖節點,某個客戶端收到該節點被刪除的通知,並獲取鎖
  5. 重複步驟4,直至無客戶端在等待獲取鎖了

上述步驟對於的流程圖如下:

"

如何用 Zookeeper 實現分佈式鎖?(附源碼)

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

做積極的人,而不是積極廢人!

  • 中文詳細註釋的開源項目
  • RPC 框架 Dubbo 源碼解析
  • 網絡應用框架 Netty 源碼解析
  • 消息中間件 RocketMQ 源碼解析
  • 數據庫中間件 Sharding-JDBC 和 MyCAT 源碼解析
  • 作業調度中間件 Elastic-Job 源碼解析
  • 分佈式事務中間件 TCC-Transaction 源碼解析
  • Eureka 和 Hystrix 源碼解析
  • Java 併發源碼
  • 1. 背景
  • 2. 獨佔鎖和讀寫鎖的實現
  • 2.1 獨佔鎖的實現
  • 2.2 讀寫鎖的實現
  • 3. 寫在最後
  • 參考

1. 背景

最近在學習 Zookeeper,在剛開始接觸 Zookeeper 的時候,完全不知道 Zookeeper 有什麼用。且很多資料都是將 Zookeeper 描述成一個“類 Unix/Linux 文件系統”的中間件,導致我很難將類 Unix/Linux 文件系統的 Zookeeper 和分佈式應用聯繫在一起。後來在粗讀了《ZooKeeper 分佈式過程協同技術詳解》和《從Paxos到Zookeeper 分佈式一致性原理與實踐》兩本書,並動手寫了一些 CURD demo 後,初步對 Zookeeper 有了一定的瞭解。不過比較膚淺,為了進一步加深對 Zookeeper 的認識,我利用空閒時間編寫了本篇文章對應的 demo -- 基於 Zookeeper 的分佈式鎖實現。通過編寫這個分佈式鎖 demo,使我對 Zookeeper 的 watcher 機制、Zookeeper 的用途等有了更進一步的認識。不過我所編寫的分佈式鎖還是比較簡陋的,實現的也不夠優美,僅僅是個練習,僅供參考使用。好了,題外話就說到這裡,接下來我們就來聊聊基於 Zookeeper 的分佈式鎖實現。

2. 獨佔鎖和讀寫鎖的實現

在本章,我將分別說明獨佔鎖和讀寫鎖詳細的實現過程,並配以相應的流程圖幫助大家瞭解實現的過程。這裡先說說獨佔鎖的實現。

2.1 獨佔鎖的實現

獨佔鎖又稱排它鎖,從字面意思上很容易理解他們的用途。即如果某個操作 O1 對訪問資源 R1 的過程加鎖,在操作 O1 結束對資源 R1 訪問前,其他操作不允許訪問資源 R1。以上算是對獨佔鎖的簡單定義了,那麼這段定義在 Zookeeper 的“類 Unix/Linux 文件系統”的結構中是怎樣實現的呢?在鎖答案前,我們先看張圖:

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖1 獨佔鎖的 Zookeeper 節點結構

如上圖,對於獨佔鎖,我們可以將資源 R1 看做是 lock 節點,操作 O1 訪問資源 R1 看做創建 lock 節點,釋放資源 R1 看做刪除 lock 節點。這樣我們就將獨佔鎖的定義對應於具體的 Zookeeper 節點結構,通過創建 lock 節點獲取鎖,刪除節點釋放鎖。詳細的過程如下:

  1. 多個客戶端競爭創建 lock 臨時節點
  2. 其中某個客戶端成功創建 lock 節點,其他客戶端對 lock 節點設置 watcher
  3. 持有鎖的客戶端刪除 lock 節點或該客戶端崩潰,由 Zookeeper 刪除 lock 節點
  4. 其他客戶端獲得 lock 節點被刪除的通知
  5. 重複上述4個步驟,直至無客戶端在等待獲取鎖了

上面即獨佔鎖具體的實現步驟,理解起來並不複雜,這裡不再贅述。

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖2 獲取獨佔鎖流程圖

2.2 讀寫鎖的實現

說完獨佔鎖的實現,這節來說說讀寫鎖的實現。讀寫鎖包含一個讀鎖和寫鎖,操作 O1 對資源 R1 加讀鎖,且獲得了鎖,其他操作可同時對資源 R1 設置讀鎖,進行共享讀操作。如果操作 O1 對資源 R1 加寫鎖,且獲得了鎖,其他操作再對資源 R1 設置不同類型的鎖都會被阻塞。總結來說,讀鎖具有共享性,而寫鎖具有排他性。那麼在 Zookeeper 中,我們可以用怎樣的節點結構實現上面的操作呢?

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖3 讀寫鎖的 Zookeeper 節點結構

在 Zookeeper 中,由於讀寫鎖和獨佔鎖的節點結構不同,讀寫鎖的客戶端不用再去競爭創建 lock 節點。所以在一開始,所有的客戶端都會創建自己的鎖節點。如果不出意外,所有的鎖節點都能被創建成功,此時鎖節點結構如圖3所示。之後,客戶端從 Zookeeper 端獲取 /share_lock 下所有的子節點,並判斷自己能否獲取鎖。如果客戶端創建的是讀鎖節點,獲取鎖的條件(滿足其中一個即可)如下:

  1. 自己創建的節點序號排在所有其他子節點前面
  2. 自己創建的節點前面無寫鎖節點

如果客戶端創建的是寫鎖節點,由於寫鎖具有排他性。所以獲取鎖的條件要簡單一些,只需確定自己創建的鎖節點是否排在其他子節點前面即可。

不同於獨佔鎖,讀寫鎖的實現稍微複雜一下。讀寫鎖有兩種實現方式,各有異同,接下來就來說說這兩種實現方式。

讀寫鎖的第一種實現

第一種實現是對 /share_lock 節點設置 watcher,當 /share_lock 下的子節點被刪除時,未獲取鎖的客戶端收到 /share_lock 子節點變動的通知。在收到通知後,客戶端重新判斷自己創建的子節點是否可以獲取鎖,如果失敗,再次等待通知。詳細流程如下:

  1. 所有客戶端創建自己的鎖節點
  2. 從 Zookeeper 端獲取 /share_lock 下所有的子節點,並對 /share_lock 節點設置 watcher
  3. 判斷自己創建的鎖節點是否可以獲取鎖,如果可以,持有鎖。否則繼續等待
  4. 持有鎖的客戶端刪除自己的鎖節點,其他客戶端收到 /share_lock 子節點變動的通知
  5. 重複步驟2、3、4,直至無客戶端在等待獲取鎖了

上述步驟對於的流程圖如下:

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖4 獲取讀寫鎖實現1流程圖

上面獲取讀寫鎖流程並不複雜,但卻存在性能問題。以圖3所示鎖節點結構為例,第一個鎖節點 host1-W-0000000001 被移除後,Zookeeper 會將 /share_lock 子節點變動的通知分發給所有的客戶端。但實際上,該子節點變動通知除了能影響 host2-R-0000000002 節點對應的客戶端外,分發給其他客戶端則是在做無用功,因為其他客戶端即使獲取了通知也無法獲取鎖。所以這裡需要做一些優化,優化措施是讓客戶端只在自己關心的節點被刪除時,再去獲取鎖。

讀寫鎖的第二種實現

在瞭解讀寫鎖第一種實現的弊端後,我們針對這一實現進行優化。這裡客戶端不再對 /share_lock 節點進行監視,而只對自己關心的節點進行監視。還是以圖3的鎖節點結構進行舉例說明,host2-R-0000000002 對應的客戶端 C2 只需監視 host1-W-0000000001 節點是否被刪除即可。而 host3-W-0000000003 對應的客戶端 C3 只需監視 host2-R-0000000002 節點是否被刪除即可,只有 host2-R-0000000002 節點被刪除,客戶端 C3 才能獲取鎖。而 host1-W-0000000001 節點被刪除時,產生的通知對於客戶端 C3 來說是無用的,即使客戶端 C3 響應了通知也沒法獲取鎖。這裡總結一下,不同客戶端關心的鎖節點是不同的。如果客戶端創建的是讀鎖節點,那麼客戶端只需找出比讀鎖節點序號小的最後一個的寫鎖節點,並設置 watcher 即可。而如果是寫鎖節點,則更簡單,客戶端僅需對該節點的上一個節點設置 watcher 即可。詳細的流程如下:

  1. 所有客戶端創建自己的鎖節點
  2. 從 Zookeeper 端獲取 /share_lock 下所有的子節點
  3. 判斷自己創建的鎖節點是否可以獲取鎖,如果可以,持有鎖。否則對自己關心的鎖節點設置 watcher
  4. 持有鎖的客戶端刪除自己的鎖節點,某個客戶端收到該節點被刪除的通知,並獲取鎖
  5. 重複步驟4,直至無客戶端在等待獲取鎖了

上述步驟對於的流程圖如下:

java晉級大牛篇:如何用 Zookeeper 實現分佈式鎖?(源碼資料)

圖5 獲取讀寫鎖實現2流程圖

3. 寫在最後

本文較為詳細的描述了基於 Zookeeper 分佈式鎖的實現過程,並根據上面描述的兩種鎖原理實現了較為簡單的分佈式鎖 demo,代碼放在了 github 上,需要的朋友自取。因為這只是一個簡單的 demo,代碼實現的並不優美,僅供參考。最後,如果你覺得文章還不錯的話,歡迎點贊。如果有不妥的地方,也請提出來,我會虛心改之。好了,最後祝大家生活愉快,再見。

參考

"

相關推薦

推薦中...