国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|數(shù)據(jù)庫技術(shù)|

服務(wù)器之家 - 數(shù)據(jù)庫 - Redis - 淺談分布式鎖的幾種使用方式(redis、zookeeper、數(shù)據(jù)庫)

淺談分布式鎖的幾種使用方式(redis、zookeeper、數(shù)據(jù)庫)

2019-11-15 16:26南北雪樹 Redis

這篇文章主要介紹了淺談分布式鎖的幾種使用方式(redis、zookeeper、數(shù)據(jù)庫),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

Q:一個業(yè)務(wù)服務(wù)器,一個數(shù)據(jù)庫,操作:查詢用戶當前余額,扣除當前余額的3%作為手續(xù)費

  • synchronized
  • lock
  • dblock

Q:兩個業(yè)務(wù)服務(wù)器,一個數(shù)據(jù)庫,操作:查詢用戶當前余額,扣除當前余額的3%作為手續(xù)費

  • 分布式鎖

我們需要怎么樣的分布式鎖?

  • 可以保證在分布式部署的應(yīng)用集群中,同一個方法在同一時間只能被一臺機器上的一個線程執(zhí)行。
  • 這把鎖要是一把可重入鎖(避免死鎖)
  • 這把鎖最好是一把阻塞鎖(根據(jù)業(yè)務(wù)需求考慮要不要這條)
  • 這把鎖最好是一把公平鎖(根據(jù)業(yè)務(wù)需求考慮要不要這條)
  • 有高可用的獲取鎖和釋放鎖功能
  • 獲取鎖和釋放鎖的性能要好

一、基于數(shù)據(jù)庫實現(xiàn)的分布式鎖

基于表實現(xiàn)的分布式鎖

?
1
2
3
4
5
6
7
CREATE TABLE `methodLock` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '鎖定的方法名',
`desc` varchar(1024) NOT NULL DEFAULT '備注信息',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存數(shù)據(jù)時間,自動生成',
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='鎖定中的方法';

當我們想要鎖住某個方法時,執(zhí)行以下SQL:
insert into methodLock(method_name,desc) values (‘method_name',‘desc')
因為我們對method_name做了唯一性約束,這里如果有多個請求同時提交到數(shù)據(jù)庫的話,數(shù)據(jù)庫會保證只有一個操作可以成功,那么我們就可以認為操作成功的那個線程獲得了該方法的鎖,可以執(zhí)行方法體內(nèi)容。

當方法執(zhí)行完畢之后,想要釋放鎖的話,需要執(zhí)行以下Sql:
delete from methodLock where method_name ='method_name'

上面這種簡單的實現(xiàn)有以下幾個問題:

  • 這把鎖強依賴數(shù)據(jù)庫的可用性,數(shù)據(jù)庫是一個單點,一旦數(shù)據(jù)庫掛掉,會導(dǎo)致業(yè)務(wù)系統(tǒng)不可用。
  • 這把鎖沒有失效時間,一旦解鎖操作失敗,就會導(dǎo)致鎖記錄一直在數(shù)據(jù)庫中,其他線程無法再獲得到鎖。
  • 這把鎖只能是非阻塞的,因為數(shù)據(jù)的insert操作,一旦插入失敗就會直接報錯。沒有獲得鎖的線程并不會進入排隊隊列,要想再次獲得鎖就要再次觸發(fā)獲得鎖操作。
  • 這把鎖是非重入的,同一個線程在沒有釋放鎖之前無法再次獲得該鎖。因為數(shù)據(jù)中數(shù)據(jù)已經(jīng)存在了。
  • 這把鎖是非公平鎖,所有等待鎖的線程憑運氣去爭奪鎖。

當然,我們也可以有其他方式解決上面的問題。

  • 數(shù)據(jù)庫是單點?搞兩個數(shù)據(jù)庫,數(shù)據(jù)之前雙向同步。一旦掛掉快速切換到備庫上。
  • 沒有失效時間?只要做一個定時任務(wù),每隔一定時間把數(shù)據(jù)庫中的超時數(shù)據(jù)清理一遍。
  • 非阻塞的?搞一個while循環(huán),直到insert成功再返回成功。
  • 非重入的?在數(shù)據(jù)庫表中加個字段,記錄當前獲得鎖的機器的主機信息和線程信息,那么下次再獲取鎖的時候先查詢數(shù)據(jù)庫,如果當前機器的主機信息和線程信息在數(shù)據(jù)庫可以查到的話,直接把鎖分配給他就可以了。
  • 非公平的?再建一張中間表,將等待鎖的線程全記錄下來,并根據(jù)創(chuàng)建時間排序,只有最先創(chuàng)建的允許獲取鎖

基于排他鎖實現(xiàn)的分布式鎖

除了可以通過增刪操作數(shù)據(jù)表中的記錄以外,其實還可以借助數(shù)據(jù)中自帶的鎖來實現(xiàn)分布式的鎖。

我們還用剛剛創(chuàng)建的那張數(shù)據(jù)庫表。可以通過數(shù)據(jù)庫的排他鎖來實現(xiàn)分布式鎖。 基于MySql的InnoDB引擎,可以使用以下方法來實現(xiàn)加鎖操作:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean lock(){ 
  connection.setAutoCommit(false);
  while(true){   
    try{     
      result = select * from methodLock where method_name=xxx for update;     
      if(result==null){       
        return true;     
      }   
    }catch(Exception e){
 
    }
    sleep(1000);
  }
  return false;
}

在查詢語句后面增加for update,數(shù)據(jù)庫會在查詢過程中給數(shù)據(jù)庫表增加排他鎖。當某條記錄被加上排他鎖之后,其他線程無法再在該行記錄上增加排他鎖。

我們可以認為獲得排它鎖的線程即可獲得分布式鎖,當獲取到鎖之后,可以執(zhí)行方法的業(yè)務(wù)邏輯,執(zhí)行完方法之后,再通過以下方法解鎖:

public void unlock(){ connection.commit(); }

通過connection.commit();操作來釋放鎖。

這種方法可以有效的解決上面提到的無法釋放鎖和阻塞鎖的問題。

阻塞鎖? for update語句會在執(zhí)行成功后立即返回,在執(zhí)行失敗時一直處于阻塞狀態(tài),直到成功。

鎖定之后服務(wù)宕機,無法釋放?使用這種方式,服務(wù)宕機之后數(shù)據(jù)庫會自己把鎖釋放掉。

但是還是無法直接解決數(shù)據(jù)庫單點、可重入和公平鎖的問題。

總結(jié)一下使用數(shù)據(jù)庫來實現(xiàn)分布式鎖的方式,這兩種方式都是依賴數(shù)據(jù)庫的一張表,一種是通過表中的記錄的存在情況確定當前是否有鎖存在,另外一種是通過數(shù)據(jù)庫的排他鎖來實現(xiàn)分布式鎖。

數(shù)據(jù)庫實現(xiàn)分布式鎖的優(yōu)點

直接借助數(shù)據(jù)庫,容易理解。

數(shù)據(jù)庫實現(xiàn)分布式鎖的缺點

會有各種各樣的問題,在解決問題的過程中會使整個方案變得越來越復(fù)雜。

操作數(shù)據(jù)庫需要一定的開銷,性能問題需要考慮。

二、基于緩存的分布式鎖

相比較于基于數(shù)據(jù)庫實現(xiàn)分布式鎖的方案來說,基于緩存來實現(xiàn)在性能方面會表現(xiàn)的更好一點。

目前有很多成熟的緩存產(chǎn)品,包括Redis,memcached等。這里以Redis為例來分析下使用緩存實現(xiàn)分布式鎖的方案。

基于Redis實現(xiàn)分布式鎖在網(wǎng)上有很多相關(guān)文章,其中主要的實現(xiàn)方式是使用Jedis.setNX方法來實現(xiàn)。

?
1
2
3
4
5
6
7
8
9
10
public boolean trylock(String key) { 
  ResultCode code = jedis.setNX(key, "This is a Lock."); 
  if (ResultCode.SUCCESS.equals(code))   
    return true
  else   
    return false;
}
public boolean unlock(String key){
  ldbTairManager.invalid(NAMESPACE, key);
}

以上實現(xiàn)方式同樣存在幾個問題:

1、單點問題。

2、這把鎖沒有失效時間,一旦解鎖操作失敗,就會導(dǎo)致鎖記錄一直在redis中,其他線程無法再獲得到鎖。

3、這把鎖只能是非阻塞的,無論成功還是失敗都直接返回。

4、這把鎖是非重入的,一個線程獲得鎖之后,在釋放鎖之前,無法再次獲得該鎖,因為使用到的key在redis中已經(jīng)存在。無法再執(zhí)行setNX操作。

5、這把鎖是非公平的,所有等待的線程同時去發(fā)起setNX操作,運氣好的線程能獲取鎖。

當然,同樣有方式可以解決。

  • 現(xiàn)在主流的緩存服務(wù)都支持集群部署,通過集群來解決單點問題。
  • 沒有失效時間?redis的setExpire方法支持傳入失效時間,到達時間之后數(shù)據(jù)會自動刪除。
  • 非阻塞?while重復(fù)執(zhí)行。
  • 非可重入?在一個線程獲取到鎖之后,把當前主機信息和線程信息保存起來,下次再獲取之前先檢查自己是不是當前鎖的擁有者。
  • 非公平?在線程獲取鎖之前先把所有等待的線程放入一個隊列中,然后按先進先出原則獲取鎖。

redis集群的同步策略是需要時間的,有可能A線程setNX成功后拿到鎖,但是這個值還沒有更新到B線程執(zhí)行setNX的這臺服務(wù)器,那就會產(chǎn)生并發(fā)問題。

redis的作者Salvatore Sanfilippo,提出了Redlock算法,該算法實現(xiàn)了比單一節(jié)點更安全、可靠的分布式鎖管理(DLM)。

Redlock算法假設(shè)有N個redis節(jié)點,這些節(jié)點互相獨立,一般設(shè)置為N=5,這N個節(jié)點運行在不同的機器上以保持物理層面的獨立。

算法的步驟如下:

1、客戶端獲取當前時間,以毫秒為單位。

2、客戶端嘗試獲取N個節(jié)點的鎖,(每個節(jié)點獲取鎖的方式和前面說的緩存鎖一樣),N個節(jié)點以相同的key和value獲取鎖。客戶端需要設(shè)置接口訪問超時,接口超時時間需要遠遠小于鎖超時時間,比如鎖自動釋放的時間是10s,那么接口超時大概設(shè)置5-50ms。這樣可以在有redis節(jié)點宕機后,訪問該節(jié)點時能盡快超時,而減小鎖的正常使用。

3、客戶端計算在獲得鎖的時候花費了多少時間,方法是用當前時間減去在步驟一獲取的時間,只有客戶端獲得了超過3個節(jié)點的鎖,而且獲取鎖的時間小于鎖的超時時間,客戶端才獲得了分布式鎖。

4、客戶端獲取的鎖的時間為設(shè)置的鎖超時時間減去步驟三計算出的獲取鎖花費時間。

5、如果客戶端獲取鎖失敗了,客戶端會依次刪除所有的鎖。

使用Redlock算法,可以保證在掛掉最多2個節(jié)點的時候,分布式鎖服務(wù)仍然能工作,這相比之前的數(shù)據(jù)庫鎖和緩存鎖大大提高了可用性,由于redis的高效性能,分布式緩存鎖性能并不比數(shù)據(jù)庫鎖差。但是,有一位分布式的專家寫了一篇文章《How to do distributed locking》,質(zhì)疑Redlock的正確性。

該專家提到,考慮分布式鎖的時候需要考慮兩個方面:性能和正確性。

如果使用高性能的分布式鎖,對正確性要求不高的場景下,那么使用緩存鎖就足夠了。

如果使用可靠性高的分布式鎖,那么就需要考慮嚴格的可靠性問題。而Redlock則不符合正確性。為什么不符合呢?專家列舉了幾個方面。

現(xiàn)在很多編程語言使用的虛擬機都有GC功能,在Full GC的時候,程序會停下來處理GC,有些時候Full GC耗時很長,甚至程序有幾分鐘的卡頓,文章列舉了HBase的例子,HBase有時候GC幾分鐘,會導(dǎo)致租約超時。而且Full GC什么時候到來,程序無法掌控,程序的任何時候都可能停下來處理GC,比如下圖,客戶端1獲得了鎖,正準備處理共享資源的時候,發(fā)生了Full GC直到鎖過期。這樣,客戶端2又獲得了鎖,開始處理共享資源。在客戶端2處理的時候,客戶端1 Full GC完成,也開始處理共享資源,這樣就出現(xiàn)了2個客戶端都在處理共享資源的情況。

淺談分布式鎖的幾種使用方式(redis、zookeeper、數(shù)據(jù)庫)

專家給出了解決辦法,如下圖,看起來就是MVCC,給鎖帶上token,token就是version的概念,每次操作鎖完成,token都會加1,在處理共享資源的時候帶上token,只有指定版本的token能夠處理共享資源。

淺談分布式鎖的幾種使用方式(redis、zookeeper、數(shù)據(jù)庫)

然后專家還說到了算法依賴本地時間,而且redis在處理key過期的時候,依賴gettimeofday方法獲得時間,而不是monotonic clock,這也會帶來時間的不準確。比如一下場景,兩個客戶端client 1和client 2,5個redis節(jié)點nodes (A, B, C, D and E)。

1、client 1從A、B、C成功獲取鎖,從D、E獲取鎖網(wǎng)絡(luò)超時。

2、節(jié)點C的時鐘不準確,導(dǎo)致鎖超時。

3、client 2從C、D、E成功獲取鎖,從A、B獲取鎖網(wǎng)絡(luò)超時。

4、這樣client 1和client 2都獲得了鎖。

總結(jié)專家關(guān)于Redlock不可用的兩點:

1、GC等場景可能隨時發(fā)生,并導(dǎo)致在客戶端獲取了鎖,在處理中超時,導(dǎo)致另外的客戶端獲取了鎖。專家還給出了使用自增token的解決方法。

2、算法依賴本地時間,會出現(xiàn)時鐘不準,導(dǎo)致2個客戶端同時獲得鎖的情況。
所以專家給出的結(jié)論是,只有在有界的網(wǎng)絡(luò)延遲、有界的程序中斷、有界的時鐘錯誤范圍,Redlock才能正常工作,但是這三種場景的邊界又是無法確認的,所以專家不建議使用Redlock。對于正確性要求高的場景,專家推薦了Zookeeper,關(guān)于使用Zookeeper作為分布式鎖后面再討論。

Redis作者的回應(yīng)

redis作者看到這個專家的文章后,寫了一篇博客予以回應(yīng)。作者很客氣的感謝了專家,然后表達出了對專家觀點的不認同。

I asked for an analysis in the original Redlock specification here: http://redis.io/topics/distlock. So thank you Martin. However I don't agree with the analysis.

redis作者關(guān)于使用token解決鎖超時問題可以概括成下面五點:

觀點1,使用分布式鎖一般是在,你沒有其他方式去控制共享資源了,專家使用token來保證對共享資源的處理,那么就不需要分布式鎖了。

觀點2,對于token的生成,為保證不同客戶端獲得的token的可靠性,生成token的服務(wù)還是需要分布式鎖保證服務(wù)的可靠性。

觀點3,對于專家說的自增的token的方式,redis作者認為完全沒必要,每個客戶端可以生成唯一的uuid作為token,給共享資源設(shè)置為只有該uuid的客戶端才能處理的狀態(tài),這樣其他客戶端就無法處理該共享資源,直到獲得鎖的客戶端釋放鎖。

觀點4,redis作者認為,對于token是有序的,并不能解決專家提出的GC問題,如上圖所示,如果token 34的客戶端寫入過程中發(fā)送GC導(dǎo)致鎖超時,另外的客戶端可能獲得token 35的鎖,并再次開始寫入,導(dǎo)致鎖沖突。所以token的有序并不能跟共享資源結(jié)合起來。

觀點5,redis作者認為,大部分場景下,分布式鎖用來處理非事務(wù)場景下的更新問題。作者意思應(yīng)該是有些場景很難結(jié)合token處理共享資源,所以得依賴鎖去鎖定資源并進行處理。

專家說到的另一個時鐘問題,redis作者也給出了解釋。客戶端實際獲得的鎖的時間是默認的超時時間,減去獲取鎖所花費的時間,如果獲取鎖花費時間過長導(dǎo)致超過了鎖的默認超時間,那么此時客戶端并不能獲取到鎖,不會存在專家提出的例子。

個人感覺

第一個問題我概括為,在一個客戶端獲取了分布式鎖后,在客戶端的處理過程中,可能出現(xiàn)鎖超時釋放的情況,這里說的處理中除了GC等非抗力外,程序流程未處理完也是可能發(fā)生的。之前在說到數(shù)據(jù)庫鎖設(shè)置的超時時間2分鐘,如果出現(xiàn)某個任務(wù)占用某個訂單鎖超過2分鐘,那么另一個交易中心就可以獲得這把訂單鎖,從而兩個交易中心同時處理同一個訂單。正常情況,任務(wù)當然秒級處理完成,可是有時候,加入某個rpc請求設(shè)置的超時時間過長,一個任務(wù)中有多個這樣的超時請求,那么,很可能就出現(xiàn)超過自動解鎖時間了。當初我們的交易模塊是用C++寫的,不存在GC,如果用java寫,中間還可能出現(xiàn)Full GC,那么鎖超時解鎖后,自己客戶端無法感知,是件非常嚴重的事情。我覺得這不是鎖本身的問題,上面說到的任何一個分布式鎖,只要自帶了超時釋放的特性,都會出現(xiàn)這樣的問題。如果使用鎖的超時功能,那么客戶端一定得設(shè)置獲取鎖超時后,采取相應(yīng)的處理,而不是繼續(xù)處理共享資源。Redlock的算法,在客戶端獲取鎖后,會返回客戶端能占用的鎖時間,客戶端必須處理該時間,讓任務(wù)在超過該時間后停止下來。

第二個問題,自然就是分布式專家沒有理解Redlock。Redlock有個關(guān)鍵的特性是,獲取鎖的時間是鎖默認超時的總時間減去獲取鎖所花費的時間,這樣客戶端處理的時間就是一個相對時間,就跟本地時間無關(guān)了。

由此看來,Redlock的正確性是能得到很好的保證的。仔細分析Redlock,相比于一個節(jié)點的redis,Redlock提供的最主要的特性是可靠性更高,這在有些場景下是很重要的特性。但是我覺得Redlock為了實現(xiàn)可靠性,卻花費了過大的代價。

首先必須部署5個節(jié)點才能讓Redlock的可靠性更強。

然后需要請求5個節(jié)點才能獲取到鎖,通過Future的方式,先并發(fā)向5個節(jié)點請求,再一起獲得響應(yīng)結(jié)果,能縮短響應(yīng)時間,不過還是比單節(jié)點redis鎖要耗費更多時間。

然后由于必須獲取到5個節(jié)點中的3個以上,所以可能出現(xiàn)獲取鎖沖突,即大家都獲得了1-2把鎖,結(jié)果誰也不能獲取到鎖,這個問題,redis作者借鑒了raft算法的精髓,通過沖突后在隨機時間開始,可以大大降低沖突時間,但是這問題并不能很好的避免,特別是在第一次獲取鎖的時候,所以獲取鎖的時間成本增加了。

如果5個節(jié)點有2個宕機,此時鎖的可用性會極大降低,首先必須等待這兩個宕機節(jié)點的結(jié)果超時才能返回,另外只有3個節(jié)點,客戶端必須獲取到這全部3個節(jié)點的鎖才能擁有鎖,難度也加大了。

如果出現(xiàn)網(wǎng)絡(luò)分區(qū),那么可能出現(xiàn)客戶端永遠也無法獲取鎖的情況。

分析了這么多原因,我覺得Redlock的問題,最關(guān)鍵的一點在于Redlock需要客戶端去保證寫入的一致性,后端5個節(jié)點完全獨立,所有的客戶端都得操作這5個節(jié)點。如果5個節(jié)點有一個leader,客戶端只要從leader獲取鎖,其他節(jié)點能同步leader的數(shù)據(jù),這樣,分區(qū)、超時、沖突等問題都不會存在。所以為了保證分布式鎖的正確性,我覺得使用強一致性的分布式協(xié)調(diào)服務(wù)能更好的解決問題。

問題又來了,失效時間我設(shè)置多長時間為好?如何設(shè)置的失效時間太短,方法沒等執(zhí)行完,鎖就自動釋放了,那么就會產(chǎn)生并發(fā)問題。如果設(shè)置的時間太長,其他獲取鎖的線程就可能要平白的多等一段時間。

這個問題使用數(shù)據(jù)庫實現(xiàn)分布式鎖同樣存在。

對于這個問題目前主流的做法是每獲得一個鎖時,只設(shè)置一個很短的超時時間,同時起一個線程在每次快要到超時時間時去刷新鎖的超時時間。在釋放鎖的同時結(jié)束這個線程。如redis官方的分布式鎖組件redisson,就是用的這種方案。

使用緩存實現(xiàn)分布式鎖的優(yōu)點

性能好。

使用緩存實現(xiàn)分布式鎖的缺點

實現(xiàn)過于負責(zé),需要考慮的因素太多。

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

基于zookeeper臨時有序節(jié)點可以實現(xiàn)的分布式鎖。

大致思想即為:每個客戶端對某個方法加鎖時,在zookeeper上的與該方法對應(yīng)的指定節(jié)點的目錄下,生成一個唯一的瞬時有序節(jié)點。 判斷是否獲取鎖的方式很簡單,只需要判斷有序節(jié)點中序號最小的一個。 當釋放鎖的時候,只需將這個瞬時節(jié)點刪除即可。同時,其可以避免服務(wù)宕機導(dǎo)致的鎖無法釋放,而產(chǎn)生的死鎖問題。

來看下Zookeeper能不能解決前面提到的問題。

  • 鎖無法釋放?使用Zookeeper可以有效的解決鎖無法釋放的問題,因為在創(chuàng)建鎖的時候,客戶端會在ZK中創(chuàng)建一個臨時節(jié)點,一旦客戶端獲取到鎖之后突然掛掉(Session連接斷開),那么這個臨時節(jié)點就會自動刪除掉。其他客戶端就可以再次獲得鎖。
  • 非阻塞鎖?使用Zookeeper可以實現(xiàn)阻塞的鎖,客戶端可以通過在ZK中創(chuàng)建順序節(jié)點,并且在節(jié)點上綁定監(jiān)聽器,一旦節(jié)點有變化,Zookeeper會通知客戶端,客戶端可以檢查自己創(chuàng)建的節(jié)點是不是當前所有節(jié)點中序號最小的,如果是,那么自己就獲取到鎖,便可以執(zhí)行業(yè)務(wù)邏輯了。
  • 不可重入?使用Zookeeper也可以有效的解決不可重入的問題,客戶端在創(chuàng)建節(jié)點的時候,把當前客戶端的主機信息和線程信息直接寫入到節(jié)點中,下次想要獲取鎖的時候和當前最小的節(jié)點中的數(shù)據(jù)比對一下就可以了。如果和自己的信息一樣,那么自己直接獲取到鎖,如果不一樣就再創(chuàng)建一個臨時的順序節(jié)點,參與排隊。
  • 單點問題?使用Zookeeper可以有效的解決單點問題,ZK是集群部署的,只要集群中有半數(shù)以上的機器存活,就可以對外提供服務(wù)。
  • 公平問題?使用Zookeeper可以解決公平鎖問題,客戶端在ZK中創(chuàng)建的臨時節(jié)點是有序的,每次鎖被釋放時,ZK可以通知最小節(jié)點來獲取鎖,保證了公平。

問題又來了,我們知道Zookeeper需要集群部署,會不會出現(xiàn)Redis集群那樣的數(shù)據(jù)同步問題呢?

Zookeeper是一個保證了弱一致性即最終一致性的分布式組件。

Zookeeper采用稱為Quorum Based Protocol的數(shù)據(jù)同步協(xié)議。假如Zookeeper集群有N臺Zookeeper服務(wù)器(N通常取奇數(shù),3臺能夠滿足數(shù)據(jù)可靠性同時有很高讀寫性能,5臺在數(shù)據(jù)可靠性和讀寫性能方面平衡最好),那么用戶的一個寫操作,首先同步到N/2 + 1臺服務(wù)器上,然后返回給用戶,提示用戶寫成功。基于Quorum Based Protocol的數(shù)據(jù)同步協(xié)議決定了Zookeeper能夠支持什么強度的一致性。

在分布式環(huán)境下,滿足強一致性的數(shù)據(jù)儲存基本不存在,它要求在更新一個節(jié)點的數(shù)據(jù),需要同步更新所有的節(jié)點。這種同步策略出現(xiàn)在主從同步復(fù)制的數(shù)據(jù)庫中。但是這種同步策略,對寫性能的影響太大而很少見于實踐。因為Zookeeper是同步寫N/2+1個節(jié)點,還有N/2個節(jié)點沒有同步更新,所以Zookeeper不是強一致性的。

用戶的數(shù)據(jù)更新操作,不保證后續(xù)的讀操作能夠讀到更新后的值,但是最終會呈現(xiàn)一致性。犧牲一致性,并不是完全不管數(shù)據(jù)的一致性,否則數(shù)據(jù)是混亂的,那么系統(tǒng)可用性再高分布式再好也沒有了價值。犧牲一致性,只是不再要求關(guān)系型數(shù)據(jù)庫中的強一致性,而是只要系統(tǒng)能達到最終一致性即可。

Zookeeper是否滿足因果一致性,需要看客戶端的編程方式。

  • 不滿足因果一致性的做法
  • A進程向Zookeeper的/z寫入一個數(shù)據(jù),成功返回
  • A進程通知B進程,A已經(jīng)修改了/z的數(shù)據(jù)
  • B讀取Zookeeper的/z的數(shù)據(jù)
  • 由于B連接的Zookeeper的服務(wù)器有可能還沒有得到A寫入數(shù)據(jù)的更新,那么B將讀不到A寫入的數(shù)據(jù)

滿足因果一致性的做法

  • B進程監(jiān)聽Zookeeper上/z的數(shù)據(jù)變化
  • A進程向Zookeeper的/z寫入一個數(shù)據(jù),成功返回前,Zookeeper需要調(diào)用注冊在/z上的監(jiān)聽器,Leader將數(shù)據(jù)變化的通知告訴B
  • B進程的事件響應(yīng)方法得到響應(yīng)后,去取變化的數(shù)據(jù),那么B一定能夠得到變化的值
  • 這里的因果一致性提現(xiàn)在Leader和B之間的因果一致性,也就是是Leader通知了數(shù)據(jù)有變化

第二種事件監(jiān)聽機制也是對Zookeeper進行正確編程應(yīng)該使用的方法,所以,Zookeeper應(yīng)該是滿足因果一致性的

所以我們在基于Zookeeper實現(xiàn)分布式鎖的時候,應(yīng)該使用滿足因果一致性的做法,即等待鎖的線程都監(jiān)聽Zookeeper上鎖的變化,在鎖被釋放的時候,Zookeeper會將鎖變化的通知告訴滿足公平鎖條件的等待線程。

可以直接使用zookeeper第三方庫客戶端,這個客戶端中封裝了一個可重入的鎖服務(wù)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { 
  try {   
    return interProcessMutex.acquire(timeout, unit); 
  } catch (Exception e) {   
    e.printStackTrace(); 
  
  return true;
}
 
public boolean unlock() { 
  try {   
    interProcessMutex.release(); 
  } catch (Throwable e) {   
    log.error(e.getMessage(), e); 
  } finally {   
    executorService.schedule(new Cleaner(client, path), delayTimeForClean, TimeUnit.MILLISECONDS); 
  
  return true;
}

使用ZK實現(xiàn)的分布式鎖好像完全符合了本文開頭我們對一個分布式鎖的所有期望。但是,其實并不是,Zookeeper實現(xiàn)的分布式鎖其實存在一個缺點,那就是性能上可能并沒有緩存服務(wù)那么高。因為每次在創(chuàng)建鎖和釋放鎖的過程中,都要動態(tài)創(chuàng)建、銷毀瞬時節(jié)點來實現(xiàn)鎖功能。ZK中創(chuàng)建和刪除節(jié)點只能通過Leader服務(wù)器來執(zhí)行,然后將數(shù)據(jù)同不到所有的Follower機器上。

使用Zookeeper實現(xiàn)分布式鎖的優(yōu)點

有效的解決單點問題,不可重入問題,非阻塞問題以及鎖無法釋放的問題。實現(xiàn)起來較為簡單。

使用Zookeeper實現(xiàn)分布式鎖的缺點

性能上不如使用緩存實現(xiàn)分布式鎖。 需要對ZK的原理有所了解。

三種方案的比較從理解的難易程度角度(從低到高)

數(shù)據(jù)庫 > 緩存 > Zookeeper

從實現(xiàn)的復(fù)雜性角度(從低到高)

Zookeeper > 緩存 > 數(shù)據(jù)庫

從性能角度(從高到低)

緩存 > Zookeeper >= 數(shù)據(jù)庫

從可靠性角度(從高到低)

Zookeeper > 緩存 > 數(shù)據(jù)庫\

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。

原文鏈接:https://blog.csdn.net/u010963948/article/details/79006572

延伸 · 閱讀

精彩推薦
  • Redis詳解Redis復(fù)制原理

    詳解Redis復(fù)制原理

    與大多數(shù)db一樣,Redis也提供了復(fù)制機制,以滿足故障恢復(fù)和負載均衡等需求。復(fù)制也是Redis高可用的基礎(chǔ),哨兵和集群都是建立在復(fù)制基礎(chǔ)上實現(xiàn)高可用的...

    李留廣10222021-08-09
  • Redisredis中如何使用lua腳本讓你的靈活性提高5個逼格詳解

    redis中如何使用lua腳本讓你的靈活性提高5個逼格詳解

    這篇文章主要給大家介紹了關(guān)于redis中如何使用lua腳本讓你的靈活性提高5個逼格的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具...

    一線碼農(nóng)5812019-11-18
  • RedisRedis全量復(fù)制與部分復(fù)制示例詳解

    Redis全量復(fù)制與部分復(fù)制示例詳解

    這篇文章主要給大家介紹了關(guān)于Redis全量復(fù)制與部分復(fù)制的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用Redis爬蟲具有一定的參考學(xué)習(xí)...

    豆子先生5052019-11-27
  • RedisRedis 事務(wù)知識點相關(guān)總結(jié)

    Redis 事務(wù)知識點相關(guān)總結(jié)

    這篇文章主要介紹了Redis 事務(wù)相關(guān)總結(jié),幫助大家更好的理解和學(xué)習(xí)使用Redis,感興趣的朋友可以了解下...

    AsiaYe8232021-07-28
  • RedisRedis的配置、啟動、操作和關(guān)閉方法

    Redis的配置、啟動、操作和關(guān)閉方法

    今天小編就為大家分享一篇Redis的配置、啟動、操作和關(guān)閉方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    大道化簡5312019-11-14
  • Redisredis實現(xiàn)排行榜功能

    redis實現(xiàn)排行榜功能

    排行榜在很多地方都能使用到,redis的zset可以很方便地用來實現(xiàn)排行榜功能,本文就來簡單的介紹一下如何使用,具有一定的參考價值,感興趣的小伙伴們...

    乘月歸5022021-08-05
  • Redisredis 交集、并集、差集的具體使用

    redis 交集、并集、差集的具體使用

    這篇文章主要介紹了redis 交集、并集、差集的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友...

    xiaojin21cen10152021-07-27
  • RedisRedis如何實現(xiàn)數(shù)據(jù)庫讀寫分離詳解

    Redis如何實現(xiàn)數(shù)據(jù)庫讀寫分離詳解

    Redis的主從架構(gòu),能幫助我們實現(xiàn)讀多,寫少的情況,下面這篇文章主要給大家介紹了關(guān)于Redis如何實現(xiàn)數(shù)據(jù)庫讀寫分離的相關(guān)資料,文中通過示例代碼介紹...

    羅兵漂流記6092019-11-11
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 久久国产精品久久久久久久久久 | 亚洲a网| 色噜噜狠狠狠综合曰曰曰88av | 九九re热 | 色花av| 亚洲精品99 | 久久久久国产精品免费免费搜索 | 国产精品视频一二三区 | 亚洲免费成人av | 国产激情视频 | 国产精品一卡二卡三卡 | 中文字幕乱码一区二区三区 | 国产精品久久久久久亚洲调教 | 日韩精品一区二区三区在线播放 | 国产在线观看一区二区 | 中文字幕视频 | 久久亚 | 国产97色在线 | 亚洲 | 在线播放中文字幕 | av中文字幕在线 | 日本中文字幕免费 | 亚洲毛片 | 超碰人人干人人 | 中文字幕在线观看日本 | 一区自拍 | 日本精a在线观看 | 免费一级 国产 | 国产免费黄色 | 电影在线观看免费 | 在线欧美视频 | 久久先锋 | 成人综合视频网 | 久久久久久久久久久久99 | 一区二区三区日本 | 欧美日韩亚洲一区二区 | 国内在线精品 | 精品乱码一区二区三四区 | 乱视频在线观看 | 免费岛国视频 | 亚洲精品一区二区三区在线观看 | 日韩精品三区 |