提及分佈式的概念,首當其衝就是CAP理論,即知足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance)。可是CAP理論告訴咱們,任何系統只能知足其中兩個,因此都要求去作取捨。那麼人們常說的通常都是,須要犧牲一致性來保證系統的高可用性,只要保證系統的最終一致性,而且容許的時間差值可以被接受就行。html
對於這個,本人的體會就是訂單系統,對於訂單系統來講,用戶端的一致性須要保證強一致性,可是對於後臺或者商家來講的話,這個訂單的狀態只要保證最終一致性就行,時間差值在可接受範圍內就OK。redis
1.單機的狀況下:數據庫
單機狀況要解決共享資源的訪問很容易,Java的API提供了很豐富的解決方案,常見的諸如synchronize,lock,volatile,c.u.t包等等,不少,可是這是在單機狀況下,由於只有一個JVM在運行咱們的代碼。緩存
2.多機的狀況下:服務器
這個時候就會出現一套代碼出如今多個JVM中,請求落在哪個上面是隨機的。這個時候上面提到的基於Java的API提供的一些解決機制就無法知足要求,它只能解決當前機器中能保證順序訪問共享資源,可是不能保證其餘機器。框架
那麼對於多機的狀況怎麼去解決這個問題呢,其實很簡單,只要保證互斥就好了,原理和單機是同樣的,找到一個互斥點就行。那麼這個互斥點就必須在你們共有的一個環境中。分佈式
那麼我所瞭解到如今分佈式鎖有三種實現方案。post
1.基於數據庫。性能
2.基於緩存環境,redis,memcache等。測試
3.基於zookeeper。
方案的實現注意點
1.首先保證在分佈式的環境中,同一個方法只能被同一個服務器上的一個線程執行。
2.鎖要可重入,嚴重一點的場景不能獲取鎖以後若是須要再次獲取時發現不能獲取了,形成死鎖。
3.鎖要可阻塞。這通常只要保證有個超時時間就行。
4.高可用的加鎖和釋放鎖功能。
5.加鎖和釋放鎖的性能要好。
1、基於數據庫的實現方式
1.1 基於數據庫表獲取
此時這張表相似一個公共資源池,每一個線程都要來這邊獲取條件,看能不能獲取到當前方法的鎖。
1.1.1 獲取鎖時,只要執行insert語句insert into lock_table("method_name","time");
1.1.2 釋放鎖時,執行對應的delete語句就行。
一個簡單的分佈式鎖就實現了,可是裏面會存在不少問題,由於這只是一個初步方案,須要不斷改進。
可能出現的問題
1.2.1 這個表中沒有設計失效時間,一旦出現加鎖成功可是解鎖失敗的狀況,會出現其餘線程沒法獲取到鎖。
1.2.2 這把鎖不是可重入的,同一個線程在沒有釋放以前沒法再insert。
1.2.3 這把鎖不是阻塞的,這邊阻塞的意思就是有加鎖時間限制,在這個時間內不斷去嘗試,相似Java裏面的自旋。超過期間就失敗。出現這個問題的緣由和1.2.2一致。
1.2.4 最後一點也是要考慮的,它的可用性怎麼樣?並很差,一旦數據庫掛了,就不能使用了。
針對的解決方案
1.2.1 -->
1.2.1.1能夠存在一個定時任務,可是要注意斷定失效的時間點的把握,既不能過短也不能太長。
1.2.1.2 代碼中在加鎖時能夠先判斷當前記錄是否是已經超過最大容許時間,超過了說明已經失效了,先手動釋放鎖,再加鎖
1.2.2 -->
重入的需求能夠加入一個字段記錄當前JVM的機器標識和線程標識,再次獲取時判斷一下就行。
1.2.3 -->
阻塞的問題很簡單了,代碼裏執行while循環,設置一個容許最大時間,超過了,直接失敗就是了。
1.2.4 -->
單機的問題更好解決了,上兩臺,互爲準備,隨時備份,搞定。
1.2 基於排他鎖的實現
通常就能夠理解爲加上寫鎖,致使其餘事務不能加寫鎖,只能讀而已。就是人們常說的select * for update。
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;
}
public void unlock(){
connection.commit();
}
可解決問題:
1.2.1 它是阻塞的嗎?是的,由於經過排它鎖測試這一篇張,能夠看見,當你使用了select * for update時,其餘想要獲取鎖的事務讀不出數據,一直阻塞在那兒。
1.2.2 宕機?宕機以後就自動釋放了。無法解決的問題:
1.2.3 單點問題
1.2.4 可重入問題。
2、基於緩存實現(redis,memchache等常見緩存框架)
基於redis的實現能夠參考我以前的一篇文章
主要存在的問題:
1. 重入的問題沒有解決。其實在筆者項目中,不存在須要現場重入的場景,基本都是在方法外面用redis的加鎖包住,finally後釋放。
2.redis中如何保證鎖的容錯性。須要注意加鎖成功,可是設置失效時間時宕機的場景,保證不出現死鎖。文章裏有解決方案。
1.3 基於zk的實現
相比較穩定性而言,zk鎖無疑是最好的實現方式,可是zk鎖的實現依靠一個zk平臺,它的理解程度也比較複雜,包括它是怎麼保證多節點數據的一致性,怎麼對外提供穩定的服務等等。複雜程度也最高,可是最穩定。
想比較而言,採用redis實現分佈式鎖仍是比較好的。我的意見。