在分布式系統中,想必我們經常會看到鎖的應用來保證操作的原子性,使用較簡單的例如物件鎖,單一鎖等等,再高階一點的例如讀寫鎖等等。但是不論是單一鎖或者讀寫鎖,在使用上都具有一定的互斥性。這裡的互斥性指的是當某個鎖持有者持有當前的鎖之後,其它執行緒必須進行阻塞等待操作。這種行為在具有很高workload的系統中,代價還是比較高的。從更深層次來看待這種鎖,它是一種單一粒度,較為粗粒度的鎖設計模式。那麼在實際的應用中,我們是否能夠將這種單一鎖進行優化呢,使之用起來能夠更為的高效。本文筆者將要講述的將是粗粒度鎖的細粒度化改造,改造的實現方式為分級鎖的實現。
為什麼我們這麼強調鎖的細粒度化改造呢?相比於粗粒度鎖,它在使用上能夠帶給系統怎樣的幫助呢?
說到這裡我們不得不談到粗粒度鎖在使用上的一些弊端,典型的一點比如它會阻塞一些毫無關聯的請求操作的處理。比如某個儲存系統在根目錄下有a,b兩個目錄,為了保證系統處理請求操作的原子性,我們用鎖來做其中的控制。如果我用1個鎖來做,則會有一下兩種情況發生:
但其實上面的場景系統在持有鎖的情況去保護a目錄的併發修改卻同樣block住了b目錄的操作,這其實是可以避免的,我們完全可以讓這2個目錄的相關操作併發地執行,然後再用2個對應鎖去保證這2個目錄空間下的請求操作。這樣的話,系統的請求吞吐量將會上公升很多。
在上面的例子中從乙個全域性單一鎖到2個命名空間目錄單獨鎖的拆分,就是鎖細粒化改造的乙個簡單例子。下面本文將要講述的分級鎖的設計部分也是採用了上述的思路,但是額外多了部分的優化改造,使之更適用於實際系統的使用。
本節將要介紹的分級鎖的主要特點在於它包含有多個層級的鎖,在這裡我們以兩級鎖為例,在此鎖內,包含有2個級別鎖:
在分布鎖中,遵守以下規則:
在操作開始前,必須先申請得到top鎖來準備獲取child鎖,在獲取得到child鎖之後,可以再釋放top鎖。這裡的child鎖,可以理解為就是每個分割槽鎖。這裡top鎖的目的是為了保證獲取各個分割槽鎖的原子性。分級鎖原型定義如下:
/**
* latchlock controls two hierarchical read/write locks:
* the toplock and the childlock.
* typically an operation starts with the toplock already acquired.
* to acquire child lock latchlock will
* first acquire the childlock, and then release the toplock.
*/public
abstract
class
latchlock
public
void
readunlock()
public
void
writelock()
public
void
writeunlock()
}
在分級鎖中,儘管top鎖會是同乙個,但是假設我們獲取的不同的child鎖,其實不會收到top鎖其它執行緒持有的情況。因為其它child鎖被lock之後,top鎖就釋放了,這樣的話其它分級鎖的child鎖的獲取就不會受到影響了。
在這裡top鎖扮演的還是之前全域性同一鎖的角色,但是所鎖住的物件是每個分割槽的例項而不是每乙個具體的操作了。
這裡我們以典型的hdfs fsn全域性單一鎖為例作為top鎖的分級鎖實現:
public
class
inodemaplock
extends
latchlock
private
inodemaplock
(reentrantreadwritelock childlock)
@override
protected
boolean
isreadtoplocked()
@override
protected
boolean
iswritetoplocked()
@override
protected
void
readtopdunlock()
@override
protected
void
writetopunlock()
@override
protected
boolean
hasreadchildlock()
@override
protected
void
readchildlock()
, {}", thread.currentthread().getid(), thread.currentthread().getname());
this
.childlock.
readlock()
.lock()
; namesystem.
getfslock()
.addchildlock
(this);
// log.info("readchildlock: done");
}@override
protected
void
readchildunlock()
, {}", thread.currentthread().getid(), thread.currentthread().getname());
this
.childlock.
readlock()
.unlock()
;// log.info("readchildunlock: done");
}@override
protected
boolean
haswritechildlock()
@override
protected
void
writechildlock()
, {}", thread.currentthread().getid(), thread.currentthread().getname());
this
.childlock.
writelock()
.lock()
; namesystem.
getfslock()
.addchildlock
(this);
// log.info("writechildlock: done");
}@override
protected
void
writechildunlock()
, {}", thread.currentthread().getid(), thread.currentthread().getname());
this
.childlock.
writelock()
.unlock()
;// log.info("writechildunlock: done");
}@override
protected latchlock
clone()
}
在使用分級鎖時,如果遇到可能需要獲取多分割槽(child)鎖時,則要進行多個分割槽child鎖的獲取,之後再釋放top鎖,操作方法如下:
/**
* 獲取多child鎖,keys為write操作涉及到的相關分割槽例項
*/public
void
latchwritelock
(k keys)
assert plock != null :
"plock is null"
; plock.
writetopunlock()
;}
在上面的例子中,遵循的規則如下:
每個partition對應乙個partition鎖(就是本文提到的分級鎖),每個partition鎖包含child鎖和top鎖,top鎖是所有partition鎖共用的乙個鎖,child鎖則是每個partition獨有的。所以我們可看到,分級鎖在多partition情況下可以很好地得到運用。[1]. . namenode fine-grained locking via metadata partitioning
細粒度鎖的實現之分級鎖的設計實現
在分布式系統中,想必我們經常會看到鎖的應用來保證操作的原子性,使用較簡單的例如物件鎖,單一鎖等等,再高階一點的例如讀寫鎖等等。但是不論是單一鎖或者讀寫鎖,在使用上都具有一定的互斥性。這裡的互斥性指的是當某個鎖持有者持有當前的鎖之後,其它執行緒必須進行阻塞等待操作。這種行為在具有很高workload的...
zookeeper分布式鎖的設計思路與實現
分布式鎖有多種實現方式,比如通過資料庫,redis都可以實現,作為分布式協同工具zookeeper也有著標準的實現方式.設計思路 每個客戶端往 locks下建立臨時有序節點 locks lock 建立成功之後 locks下面會有每個客戶端對應的節點,如 locks lock 0000000001 客...
讀 寫鎖的實現和應用(高併發狀態下的map實現)
程式中涉及到對一些共享資源的讀和寫操作,且寫操作沒有讀操作那麼頻繁。在沒有寫操作的時候,兩個執行緒同時讀乙個資源沒有任何問題,所以應該允許多個執行緒能在同時讀取共享資源。但是如果有乙個執行緒想去寫這些共享資源,就不應該再有其它執行緒對該資源進行讀或寫 譯者注 也就是說 讀 讀能共存,讀 寫不能共存,...