zookeeper 實現分佈式鎖

前言:

單機應用架構中,秒殺案例使用ReentrantLcok或者synchronized來達到操做業務互斥的目的。然而在分佈式系統中,會存在多臺機器並行去實現同一個功能。也就是說,在多進程中,若是還使用以上JDK提供的進程鎖,來併發訪問數據庫資源就可能會出現數據重複、不一致的狀況。所以,須要咱們來實現本身的分佈式鎖。數據庫

實現一個分佈式鎖應該具有的特性:緩存

  • 高可用、高性能的獲取鎖與釋放鎖
  • 在分佈式系統環境下,一個方法或者變量同一時間只能被一個線程操做
  • 具有鎖失效機制,網絡中斷或宕機沒法釋放鎖時,鎖必須被刪除,防止死鎖
  • 具有阻塞鎖特性,即沒有獲取到鎖,則繼續等待獲取鎖
  • 具有非阻塞鎖特性,即沒有獲取到鎖,則直接返回獲取鎖失敗
  • 具有可重入特性,一個線程中能夠屢次獲取同一把鎖,好比一個線程在執行一個帶鎖的方法,該方法中又調用了另外一個須要相同鎖的方法,則該線程能夠直接執行調用的方法,而無需從新得到鎖

分佈式鎖幾種實現方式:bash

  • 基於數據庫實現分佈式鎖
  • 基於 Redis 實現分佈式鎖
  • 基於 Zookeeper 實現分佈式鎖

前兩種對於分佈式生產環境來講並非特別推薦,高併發下數據庫鎖性能太差,Redis在鎖時間限制和緩存一致性存在必定問題。重點實現一下 Zookeeper 如何實現分佈式鎖。網絡

實現原理

ZooKeeper是一個分佈式的,開放源碼的分佈式應用程序協調服務,它內部是一個分層的文件系統目錄樹結構,規定同一個目錄下只能存在惟一文件名。session

å¾çæºèªç½ç»

數據模型架構

  • PERSISTENT 持久化節點,節點建立後,不會由於會話失效而消失
  • EPHEMERAL 臨時節點, 客戶端session超時此類節點就會被自動刪除
  • EPHEMERAL_SEQUENTIAL 臨時自動編號節點
  • PERSISTENT_SEQUENTIAL 順序自動編號持久化節點,這種節點會根據當前已存在的節點數自動加 1

監視器(watcher)併發

當建立一個節點時,能夠註冊一個該節點的監視器,當節點狀態發生改變時,watch被觸發時,ZooKeeper將會向客戶端發送且僅發送一條通知,由於watch只能被觸發一次。分佈式

根據zookeeper的這些特性,咱們來看看如何利用這些特性來實現分佈式鎖:ide

  • 建立一個鎖目錄lock
  • 線程A獲取鎖會在lock目錄下,建立臨時順序節點
  • 獲取鎖目錄下全部的子節點,而後獲取比本身小的兄弟節點,若是不存在,則說明當前線程順序號最小,得到鎖
  • 線程B建立臨時節點並獲取全部兄弟節點,判斷本身不是最小節點,設置監聽(watcher)比本身次小的節點(只關注比本身次小的節點是爲了防止發生「羊羣效應」)
  • 線程A處理完,刪除本身的節點,線程B監聽到變動事件,判斷本身是最小的節點,得到鎖

代碼實現

儘管ZooKeeper已經封裝好複雜易出錯的關鍵服務,將簡單易用的接口和性能高效、功能穩定的系統提供給用戶。可是若是讓一個普通開發者去手擼一個分佈式鎖仍是比較困難的,在秒殺案例中咱們直接使用 Apache 開源的curator 開實現 Zookeeper 分佈式鎖。 高併發

同時參考:blog.csdn.net/u011663149/…

/**
 * 基於curator的zookeeper分佈式鎖
 */
public class CuratorUtil {
    private static String address = "192.168.1.180:2181";
    
    public static void main(String[] args) {
        //一、重試策略:初試時間爲1s 重試3次
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); 
        //二、經過工廠建立鏈接
        CuratorFramework client = CuratorFrameworkFactory.newClient(address, retryPolicy);
        //三、開啓鏈接
        client.start();
        //4 分佈式鎖
        final InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock"); 
        //讀寫鎖
        //InterProcessReadWriteLock readWriteLock = new InterProcessReadWriteLock(client, "/readwriter");
        
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        
        for (int i = 0; i < 5; i++) {
            fixedThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    boolean flag = false;
                    try {
                        //嘗試獲取鎖,最多等待5秒
                        flag = mutex.acquire(5, TimeUnit.SECONDS);
                        Thread currentThread = Thread.currentThread();
                        if(flag){
                            System.out.println("線程"+currentThread.getId()+"獲取鎖成功");
                        }else{
                            System.out.println("線程"+currentThread.getId()+"獲取鎖失敗");
                        }
                        //模擬業務邏輯,延時4秒
                        Thread.sleep(4000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally{
                        if(flag){
                            try {
                                mutex.release();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            });
        }
    }
}複製代碼

這裏咱們開啓5個線程,每一個線程獲取鎖的最大等待時間爲5秒,爲了模擬具體業務場景,方法中設置4秒等待時間。開始執行main方法,經過ZooInspector或者zk-ui 可視化監控/curator/lock下的節點以下圖:

觀察控制檯,咱們會發現只有兩個線程獲取鎖成功,另外三個線程超時獲取鎖失敗會自動刪除節點。線程執行完畢咱們刷新一下/curator/lock節點,發現剛纔建立的五個子節點已經不存在了。

相關文章
相關標籤/搜索