說起分布式的概念,首當其沖就是CAP理論,即滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance)。但是CAP理論告訴我們,任何系統只能滿足其中兩個,所以都要求去做取舍。那么人們常說的一般都是,需要犧牲一致性來保證系統的高可用性,只要保證系統的最終一致性,並且允許的時間差值能夠被接受就行。
對於這個,本人的體會就是訂單系統,對於訂單系統來說,用戶端的一致性需要保證強一致性,但是對於后台或者商家來說的話,這個訂單的狀態只要保證最終一致性就行,時間差值在可接受范圍內就OK。
1.單機的情況下:
單機情況要解決共享資源的訪問很容易,Java的API提供了很豐富的解決方案,常見的諸如synchronize,lock,volatile,c.u.t包等等,很多,但是這是在單機情況下,因為只有一個JVM在運行我們的代碼。
2.多機的情況下:
這個時候就會出現一套代碼出現在多個JVM中,請求落在哪一個上面是隨機的。這個時候上面提到的基於Java的API提供的一些解決機制就沒法滿足要求,它只能解決當前機器中能保證順序訪問共享資源,但是不能保證其他機器。

那么對於多機的情況怎么去解決這個問題呢,其實很簡單,只要保證互斥就行了,原理和單機是一樣的,找到一個互斥點就行。那么這個互斥點就必須在大家共有的一個環境中。
那么我所了解到現在分布式鎖有三種實現方案。
1.基於數據庫。
2.基於緩存環境,redis,memcache等。
3.基於zookeeper。
方案的實現注意點
1.首先保證在分布式的環境中,同一個方法只能被同一個服務器上的一個線程執行。
2.鎖要可重入,嚴重一點的場景不能獲取鎖之后如果需要再次獲取時發現不能獲取了,造成死鎖。
3.鎖要可阻塞。這一般只要保證有個超時時間就行。
4.高可用的加鎖和釋放鎖功能。
5.加鎖和釋放鎖的性能要好。
一、基於數據庫的實現方式
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 可重入問題。
二、基於緩存實現(redis,memchache等常見緩存框架)
基於redis的實現可以參考我之前的一篇文章
主要存在的問題:
1. 重入的問題沒有解決。其實在筆者項目中,不存在需要現場重入的場景,基本都是在方法外面用redis的加鎖包住,finally后釋放。
2.redis中如何保證鎖的容錯性。需要注意加鎖成功,但是設置失效時間時宕機的場景,保證不出現死鎖。文章里有解決方案。
1.3 基於zk的實現
相比較穩定性而言,zk鎖無疑是最好的實現方式,但是zk鎖的實現依靠一個zk平台,它的理解程度也比較復雜,包括它是怎么保證多節點數據的一致性,怎么對外提供穩定的服務等等。復雜程度也最高,但是最穩定。
想比較而言,采用redis實現分布式鎖還是比較好的。個人意見。
