溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

什么是分布式鎖以及用Redis還是Zookeeper來實現(xiàn)

發(fā)布時間:2021-12-23 17:10:16 來源:億速云 閱讀:157 作者:柒染 欄目:大數(shù)據(jù)

這篇文章將為大家詳細(xì)講解有關(guān)什么是分布式鎖以及用Redis還是Zookeeper來實現(xiàn),文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

大家都知道,如果我們一臺機器上多個不同線程搶占同一個資源,并且如果多次執(zhí)行會有異常,我們稱之為非線程安全。一般,我們?yōu)榱私鉀Q這種問題,通常使用鎖來解決,像java語言,我們可以使用synchronized。如果是同一臺機器里面不同的java實例,我們可以使用系統(tǒng)的文件讀寫鎖來解決,如果再擴展到不同的機器呢?我們通常用分布式鎖來解決。

分布式鎖的特點如下:

  • 互斥性:和我們本地鎖一樣互斥性是最基本,但是分布式鎖需要保證在不同節(jié)點的不同線程的互斥。

  • 可重入性:同一個節(jié)點上的同一個線程如果獲取了鎖之后那么也可以再次獲取這個鎖。

  • 鎖超時:和本地鎖一樣支持鎖超時,防止死鎖。

  • 高效,高可用:加鎖和解鎖需要高效,同時也需要保證高可用防止分布式鎖失效,可以增加降級。

  • 支持阻塞和非阻塞:和 ReentrantLock 一樣支持 lock 和 trylock 以及 tryLock(long timeOut)。

  • 支持公平鎖和非公平鎖(可選):公平鎖的意思是按照請求加鎖的順序獲得鎖,非公平鎖就相反是無序的。這個一般來說實現(xiàn)的比較少。分布式鎖。相信大家都遇到過這樣的業(yè)務(wù)場景,我們有一個定時任務(wù)需要定時執(zhí)行,但是這個任務(wù)又不是同一段時間執(zhí)行冪等的,所以我們只能讓一臺機器一個線程來執(zhí)行

分布式鎖的實現(xiàn)有很多種,常見的有redis,zookeeper,谷歌的chubby等

Redis實現(xiàn)分布式鎖

簡單介紹一下。相信大家這里已經(jīng)想到了解決方案,那就是每次執(zhí)行任務(wù)的時候,先查詢redis里面是否已經(jīng)有鎖的key,如果沒有就寫入,然后就開始執(zhí)行任務(wù)。

這個看起來很對,不過存在什么問題呢,例如進程A跟進程B同時查詢Redis,他們都發(fā)現(xiàn)Redis中沒有對應(yīng)的值,然后都開始寫入,由于不是帶版本讀寫,兩個人都寫成功了,都獲得了鎖。還好,Redis給我們提供原子寫入的操作,setnx(SET if Not eXists, 一個命令我們最好把全稱也了解一下,有助于我們記住這個命令)。

如果你以為只要這樣就完成一個分布式鎖,那就太天真了,我們不妨考慮一些極端情況,例如某個線程取到了鎖,但是很不幸,這個機器死機了,那么這個鎖沒有被釋放,這個任務(wù)永遠(yuǎn)就不會有人執(zhí)行了。所以一種比較好的解決方案是,申請鎖的時候,預(yù)估一個程序的執(zhí)行時間,然后給鎖設(shè)置一個超時時間,如果超過這個時間其他人也能取到這個鎖。但這又引發(fā)另外一個問題,有時候負(fù)載很高,任務(wù)執(zhí)行得很慢,結(jié)果過了超時時間任務(wù)還沒執(zhí)行完,這個時候又起了另外一個任務(wù)來執(zhí)行。

什么是分布式鎖以及用Redis還是Zookeeper來實現(xiàn)

架構(gòu)設(shè)計的魅力正是如此,當(dāng)你解決一個問題的時候,總會引發(fā)一些新的問題,需要逐步攻破逐個解決。這種方法,我們一般可以在搶占到鎖之后,就開一個守護線程,定時去redis哪里詢問,是不是還是由我搶占著當(dāng)前的鎖,還有多久就要過期,如果發(fā)現(xiàn)要過期了,就趕緊續(xù)期。

什么是分布式鎖以及用Redis還是Zookeeper來實現(xiàn)

好了,看到這里,相信你已經(jīng)學(xué)會了如何用Redis實現(xiàn)一個分布式鎖服務(wù)了

Zookeeper實現(xiàn)分布式鎖

Zookeeper 實現(xiàn)分布式鎖的示意圖如下:

什么是分布式鎖以及用Redis還是Zookeeper來實現(xiàn)

上圖中左邊是Zookeeper集群, lock是數(shù)據(jù)節(jié)點,node_1到node_n表示一系列的順序臨時節(jié)點,右側(cè)client_1到client_n表示要獲取鎖的客戶端。Service是互斥訪問的服務(wù)。

代碼實現(xiàn)

下面的源碼是根據(jù)Zookeeper的開源客戶端Curator實現(xiàn)分布式鎖。采用zk的原生API實現(xiàn)會比較復(fù)雜,所以這里就直接用Curator這個輪子,采用Curator的acquire和release兩個方法就能實現(xiàn)分布式鎖。

import org.apache.curator.RetryPolicy;import org.apache.curator.framework.CuratorFramework;import org.apache.curator.framework.CuratorFrameworkFactory;import org.apache.curator.framework.recipes.locks.InterProcessMutex;import org.apache.curator.retry.ExponentialBackoffRetry;
public class CuratorDistributeLock {
   public static void main(String[] args) {        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);        CuratorFramework client = CuratorFrameworkFactory.newClient("111.231.83.101:2181",retryPolicy);        client.start();        CuratorFramework client2 = CuratorFrameworkFactory.newClient("111.231.83.101:2181",retryPolicy);        client2.start();        //創(chuàng)建分布式鎖, 鎖空間的根節(jié)點路徑為/curator/lock        InterProcessMutex mutex  = new InterProcessMutex(client,"/curator/lock");        final InterProcessMutex mutex2  = new InterProcessMutex(client2,"/curator/lock");        try {            mutex.acquire();        } catch (Exception e) {            e.printStackTrace();        }        //獲得了鎖, 進行業(yè)務(wù)流程        System.out.println("clent Enter mutex");         Thread client2Th = new Thread(new Runnable() {             @Override             public void run() {                 try {                     mutex2.acquire();                     System.out.println("client2 Enter mutex");                     mutex2.release();                     System.out.println("client2 release lock");
                }catch (Exception e){                     e.printStackTrace();                 }
            }         });        client2Th.start();        //完成業(yè)務(wù)流程, 釋放鎖        try {            Thread.sleep(5000);            mutex.release();            System.out.println("client release lock");            client2Th.join();        } catch (Exception e) {            e.printStackTrace();        }
       //關(guān)閉客戶端        client.close();    }}

上述代碼的執(zhí)行結(jié)果如下:

什么是分布式鎖以及用Redis還是Zookeeper來實現(xiàn)

可以看到client客戶端首先拿到鎖再執(zhí)行業(yè)務(wù),然后再輪到client2嘗試獲取鎖并執(zhí)行業(yè)務(wù)。

源碼分析

一直追蹤acquire()的加鎖方法,可以追蹤到加鎖的核心函數(shù)為attemptLock。

    String attemptLock(long time, TimeUnit unit, byte[] lockNodeBytes) throws Exception{        .....        while ( !isDone )        {            isDone = true;
           try            {                //創(chuàng)建臨時有序節(jié)點                ourPath = driver.createsTheLock(client, path, localLockNodeBytes);                //判斷自己是否最小序號的節(jié)點,如果不是添加監(jiān)聽前面節(jié)點被刪的通知                hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);            }        }        //如果獲取鎖返回節(jié)點路徑        if ( hasTheLock )        {            return ourPath;        }        ....    }

深入internalLockLoop函數(shù)源碼:

 private boolean internalLockLoop(long startMillis, Long millisToWait, String ourPath) throws Exception    {          .......            while ( (client.getState() == CuratorFrameworkState.STARTED) && !haveTheLock )            {               //獲取子節(jié)點列表按照序號從小到大排序                List<String>        children = getSortedChildren();                String              sequenceNodeName = ourPath.substring(basePath.length() + 1); // +1 to include the slash                //判斷自己是否是當(dāng)前最小序號節(jié)點                PredicateResults    predicateResults = driver.getsTheLock(client, children, sequenceNodeName, maxLeases);                if ( predicateResults.getsTheLock() )                {                    //成功獲取鎖                    haveTheLock = true;                }                else                {                   //拿到前一個節(jié)點                    String  previousSequencePath = basePath + "/" + predicateResults.getPathToWatch();                 //如果沒有拿到鎖,調(diào)用wait,等待前一個節(jié)點刪除時,通過回調(diào)notifyAll喚醒當(dāng)前線程                    synchronized(this)                    {                        try                        {                           //設(shè)置監(jiān)聽器,getData會判讀前一個節(jié)點是否存在,不存在就會拋出異常從而不會設(shè)置監(jiān)聽器                            client.getData().usingWatcher(watcher).forPath(previousSequencePath);                            //如果設(shè)置了millisToWait,等一段時間,到了時間刪除自己跳出循環(huán)                            if ( millisToWait != null )                            {                                millisToWait -= (System.currentTimeMillis() - startMillis);                                startMillis = System.currentTimeMillis();                                if ( millisToWait <= 0 )                                {                                    doDelete = true;    // timed out - delete our node                                    break;                                }                                //等待一段時間                                wait(millisToWait);                            }                            else                            {                                //一直等待下去                                wait();                            }                        }                        catch ( KeeperException.NoNodeException e )                        {                          //getData發(fā)現(xiàn)前一個子節(jié)點被刪除,拋出異常                        }                    }                }            }        }        .....    }

采用zk實現(xiàn)分布式鎖在實際應(yīng)用中不是很常見,需要一套zk集群,而且頻繁監(jiān)聽對zk集群來說也是有壓力,所以不推薦大家用。不能去面試的時候,能具體說一下使用zk實現(xiàn)分布式鎖,我想應(yīng)該也是一個加分項 。

關(guān)于什么是分布式鎖以及用Redis還是Zookeeper來實現(xiàn)就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI