夜深人靜了,咱們來學學分佈式鎖

記錄一下今天的文章開始寫的時間00:53,夜深人靜了,咱們來學一下分佈式鎖,咱們要悄悄地學習,而後經驗全部人。redis

什麼是分佈式鎖?分佈式鎖又能夠解決哪些問題呢?

在咱們的系統尚未使用分佈式架構的時候,咱們能夠用同步鎖或者Lock鎖,來保證多線程併發的時候,同一時間只有一個線程修改共享變量或者執行代碼塊,可是當咱們如今大部分系統都是分佈式集羣部署的,單純的同步鎖和Lock鎖只能保證單個實例上的數據一致性,多實例就失去了做用。算法

這個時候就須要使用分佈式鎖來保證共享資源的原子性,好比咱們電商系統裏面的扣減庫存,當單量小的時候問題不大,若是單量很大,同一時間多個實例都在併發處理扣減庫存的業務的時候,就可能存在超賣的問題。數據庫

分佈式鎖的實現?

常見的分佈式鎖有數據庫實現分佈式鎖、Zookeeper實現分佈式鎖、Redis實現分佈式鎖、Redisson實現。其中數據庫實現分佈式鎖比較簡單,也很容易理解,直接基於數據庫實現就能夠了,在一些分佈式的業務中也常用,可是這種方式也是效率最低的,通常是不使用的,咱們就着重介紹一下其餘三種方式的實現。多線程

Zookeeper實現分佈式鎖

使用Zookeeper來實現分佈式鎖就比較常見,好比不少項目就使用Zookeeper做爲分佈式註冊中心,就喜歡用Zookeeper來實現分佈式鎖,這主要是藉助於Zookeeper的兩大特性:順序臨時節點、Watch機制。架構

順序臨時節點:熟悉Zookeeper的同窗都知道,Zookeeper提供了多層級的節點命名空間,每一個節點都是用斜槓分隔的路徑來表示,相似於咱們的文件夾。節點又分爲持久節點和臨時節點,節點還能夠標記爲有序,當節點被標記爲有序性,這個節點就具備順序自增的特色,咱們就能夠藉助這個特色來建立咱們所需的節點。併發

Watch機制:Watch機制是Zookeeper另外一個重要的特性,咱們能夠在指定節點上註冊一些Watcher,在一些特定的事情觸發的時候,通知用戶這個事件。框架

Zookeeper實現分佈式鎖的過程

咱們先建立一個持久節點做爲父節點,每當須要訪問建立分佈式鎖的時候,就在這個父節點下建立相應的臨時的順序子節點,以臨時節點名稱、父節點名稱和順序號組成特色的名稱。在創建子節點後,對父節點下以這個這個子節點名稱開頭的子節點進行排序,判斷剛創建的節點順序號是否是最小的,若是是最小的則獲取鎖,若是不是最小節點,則阻塞等待鎖,而且在獲取該節點的上一順序節點註冊Watcher,等待節點對應的操做得到鎖。dom

當業務處理完以後,刪除該節點,關閉zk,進而觸發Watcher,釋放該鎖。分佈式

上圖就是就是嚴格按照順序訪問的分佈式鎖實現,更多的時候咱們引入一些框架來幫助咱們實現,好比最經常使用的Curator框架,代碼以下:ide

InterProcessMutex lock = new InterProcessMutex(client, lockPath);
if ( lock.acquire(maxWait, waitUnit) ) {
    try {
        // 業務處理
    }
    finally{
        lock.release();
    }
}

Zookeeper來實現分佈式鎖自然的優點就是,Zookeeper是集羣實現的,咱們生產環境通常也是集羣部署的,能夠避免單點問題,穩定性較好,能保證每次操做均可以釋放鎖。

缺點就是,頻繁的建立刪除節點,加上註冊watch事件,對於zookeeper集羣的壓力比較大,性能這一塊也比不上Redis實現的分佈式鎖。

Redis實現分佈式鎖

Redis實現的分佈式鎖,最爲複雜,可是性能確是最佳的,因此在對性能要求更高的系統裏,咱們都選擇使用Redis來實現分佈式鎖。利用Redis實現分佈式鎖,通常都是使用SETNX實現,舉個簡單的例子:

public static boolean getDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

    String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

    if ("OK".equals(result)) {
         return true;
    }
    return false;
}

SETNX方法保證設置鎖和鎖過時時間的原子性,可是對於鎖的過時時間設置咱們要注意,若是執行業務

時間比較長,咱們設置的過時時間又比較短的狀況下就會形成,業務還沒執行完,鎖已釋放的問題。因此咱們須要根據實際業務處理來評估設置鎖的過時時間,來保證業務能夠正常的處理完。

Redisson實現分佈式鎖

Redisson是架設在Redis基礎上的一個Java駐內存數據網格。Redisson在基於NIO的Netty框架上,充分的利用了Redis鍵值數據庫提供的一系列優點,在Java實用工具包中經常使用接口的基礎上,爲使用者提供了一系列具備分佈式特性的經常使用工具類。性能也比咱們經常使用的jedis好一些。

Redisson不論是單節點模式仍是集羣模式,都很好的實現了分佈式鎖,通常用的多的都是集羣模式,在集羣模式下,Redisson使用RedLock算法,很好的處理了Master節點宕機時切換到另一個Master節點過程當中多個應用得到鎖。

Redisson集羣模式獲取鎖的實現就是,在不一樣節點上獲取鎖,每一個節點上獲取鎖都有超時時間,若是獲取鎖超時就認爲這個節點不可用,當成功獲取鎖的個數超過Redis節點的半數,且獲取鎖消耗的時間還沒超過鎖過時時間,則認爲獲取鎖成功。獲取鎖成功後從新計算鎖釋放時間,由原來的鎖釋放時間減去獲取鎖消耗的時間,若是最終獲取鎖失敗,已經獲取鎖成功的節點也會釋放鎖。

圖片

具體的代碼實現:

引入依賴

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.1</version>
</dependency>

Redisson配置文件:

@Bean
public RedissonClient redissonClient() {
    Config config = new Config();
    config.useClusterServers()
            .setScanInterval(3000// 集羣狀態掃描間隔時間,單位是毫秒
            .addNodeAddress("redis://192.168.0.1:6379).setPassword("666")
            .addNodeAddress("
redis://192.168.0.2:6379").setPassword("666")
            .addNodeAddress("redis://192.168.0.3:6379")
            .setPassword("666");
    return Redisson.create(config);
}

獲取鎖操做:

long waitTimeout = 10;
long leaseTime = 1;
RLock lock1 = redissonClient1.getLock("lock1");
RLock lock2 = redissonClient2.getLock("lock2");
RLock lock3 = redissonClient3.getLock("lock3");

RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);

redLock.trylock(waitTimeout,leaseTime,TimeUnit.SECONDS);
try{
    //...
}finally{
    redLock.unlock();
}

總結

實現分佈式鎖的方式不止這三種,最簡單的就是數據庫實現,Zookeeper實現也相對比較簡單,可是性能最好的仍是Redis實現,可是可靠性方面,Zookeeper基於分佈式集羣,具備自然的優點,可靠性相對更高。若是業務場景對性能要求不是很高的時候,優先使用Zookeeper實現分佈式鎖。

相關文章
相關標籤/搜索