您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(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等
簡單介紹一下。相信大家這里已經(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í)行。
架構(gòu)設(shè)計的魅力正是如此,當(dāng)你解決一個問題的時候,總會引發(fā)一些新的問題,需要逐步攻破逐個解決。這種方法,我們一般可以在搶占到鎖之后,就開一個守護線程,定時去redis哪里詢問,是不是還是由我搶占著當(dāng)前的鎖,還有多久就要過期,如果發(fā)現(xiàn)要過期了,就趕緊續(xù)期。
好了,看到這里,相信你已經(jīng)學(xué)會了如何用Redis實現(xiàn)一個分布式鎖服務(wù)了
Zookeeper 實現(xiàn)分布式鎖的示意圖如下:
上圖中左邊是Zookeeper集群, lock是數(shù)據(jù)節(jié)點,node_1到node_n表示一系列的順序臨時節(jié)點,右側(cè)client_1到client_n表示要獲取鎖的客戶端。Service是互斥訪問的服務(wù)。
下面的源碼是根據(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é)果如下:
可以看到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é)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責(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)容。