Apache頂級專案8-Geode原始碼深度分析
Apache頂級專案介紹之Geode。Apache頂級專案介紹之8,我們重新恢復Apache頂級專案系列,較之前介紹系列,我們本文直入程式碼分析,原因有二,Geode即上文我們分析的Gemfire的開源版本,其二被逼無奈,閱讀原始碼查詢問題。
1. Geode
2016年11月21日,Apache 軟體基金會(ASF)宣佈 Apache® Geode™ 已從 Apache 孵化器畢業成為頂級專案(TLP),表明該專案的社群和產品已根據 ASF 的精英流程和原則得到良好管理。
Apache Geode 是一個數據管理平臺,提供實時的、一致的、貫穿整個雲架構地訪問資料關鍵型應用,最初由 GemStone Systems 公司開發,商標為 GemFire。 此項技術初期被廣泛應用在金融領域,用於華爾街交易平臺,作為事務性、 低延時的資料引擎。2015年4月將程式碼提交給 Apache 孵化器作為孵化專案。目前 Apache Geode 有超過600家大中型企業級使用者, 主要是必須滿足低延時和24x7 高可靠要求的,高可擴充套件的關鍵業務應用系統。
2. 適用場景
可以參考上文,分散式快取利器-Gemfire.
4個主要使用場景:
-高可用性的分散式快取
-網格計算
-事件通知和處理(CEP類似)
-交易處理(Transaction),採用最終一致性
Geode 池化了伺服器上的記憶體, CPU, 網路資源, 和本地磁碟,跨多個程序來管理應用物件和行為. 它使用了動態資料複製和分割槽技術來實現高可用, 高效能, 高可擴充套件性, 和容錯. 另外, 對於一個分散式資料容器, Apache Geode 是一個基於記憶體的資料管理系統, 提供了可靠的非同步事件通知和可靠的訊息投遞.
3. 資料結構原始碼
如上文所說,系統目前遇到一個分散式系統異常複雜之難題,難到不遍歷原始碼無法解開謎題的地步。注意,筆者寫此文時,此難題尚無答案,我們希望當此文釋出時,已有解藥。
另外,強烈建議看官先自行了解Gemfire功能以及部分原理,否則此文較為吃力。
3.1 Region
進入正題,先來看一下核心類Region。
Region繼承Java的ConcurrentMap,實現了分散式K,V的HashMap,並提供了高階的事務,Persistence,分割槽等分散式功能。介面繼承Map的get, put無須多說,看幾個比較重要的。
Geode的Region可以像樹形一樣,形成一種Hierarchy結構巢狀,使用"/"來分隔多個子Region.所以有向上獲取父親,自然也有向下導航獲取子女:getSubregion(String path)
RegionDistributedLock
第一個分散式鎖,全域性Region。註釋寫很到位,其中提到當region建立時會自動開啟全域性region鎖,所有對region級別的操作,如invalidateRegion, destroyRegion也可能會自動用到region級別鎖,這個麼可以理解,畢竟是重量級操作。
getDistributedLock(key)
提供了更細粒度的region級別分散式鎖控制。
然而,除非必要,所有分散式操作不建議用如此之重的鎖,否則後患無窮。
public void writeToDisk();
當然,支援持久化到磁碟,分散式的一種表現形式,相對於普通的HashMap。
public void becomeLockGrantor();
不贅述,如註釋所描述,LockService的擔保,前提當前region必須是Global型別,有點類似對於分散式全域性變數加鎖,所以每當呼叫此方法,執行緒將阻塞直到擔保被轉移到當前member。
超級重量級方法,請自保。註釋強調了,相互呼叫可能造成死鎖,說的比較委婉,back and force。
public void registerInterest(K key);
後續很多分散式,多個member互動的入口之一。
註釋提到用於向CacheServer提交註冊一個感興趣的key,後續還有按照正則表示式註冊,或者註冊所有key。其註冊目的是用於被通知,類似ems queue的subscription功能,或者回調通知。
public List getInterestList();
返回當前region已經註冊的所有感興趣key。
對了,每個類程式碼小於500行都是騙人的,隨便看看這些開原始碼,少則1000,多則6-7千行程式碼。
3.2 AbstractRegion
AbstractRegion抽象類,主要封裝實現了關於RegionAttributes, AttributesMutator, and some no-brainer method implementations。
其中,主要cache操作的讀寫則由上面loader與writer代理實現。Usually there will be only one CacheWriter in the distributed system.所以,通常整個分散式系統只有一個cache writer,其它特殊情況暫時不提。
截獲entry更新事件,其中entryEvent包含了整個操作的資訊,如KeyInfo, EventID, newValue, oldValue, Operation, originated member等。BTW, 這個看起來普通的EntryEvent類也有2856行,此處省略無數。。。
protected Set asyncEventQueueIds;
隱隱覺得,其分散式的訊息事件處理會放到queue中,這裡用了set結構,我們繼續看下去。
protected booleanenableSubscriptionConflation;
這個引數對於高頻寫region非常有用,主要是同一個key高速寫,類似實時報價。這個引數效果類似於Leaky Bucke。
漏桶演算法(Leaky Bucket)是網路世界中流量整形(Traffic Shaping)或速率限制(Rate Limiting)時經常使用的一種演算法,它的主要目的是控制資料注入到網路的速率,平滑網路上的突發流量。漏桶演算法提供了一種機制,通過它,突發流量可以被整形以便為網路提供一個穩定的流量。漏桶可以看作是一個帶有常量服務時間的單伺服器佇列,如果漏桶(包快取)溢位,那麼資料包會被丟棄。
3.3 Regions UML
當我們越加深入,發現程式碼越來越複雜,很多類少則幾千,多則上萬行,類也有上千萬個,對於大型開源專案,還是來一張類圖吧,掌握整體巨集觀很重要。
可以看到LocalRegion繼承自AbstractRegion,最重要的是內部封裝了Region的資料結構。
瞭解HashMap內部資料結構的朋友應該知道,其內部為陣列+連結串列組合的二維空間;同理,Geode的Region內部資料結構則由RegionMap來抽象,持有真正的資料,而究其內部則是內建了一個ConcurrentHashMap,想想讓我們來設計的話也大概如此吧。
3.4 AbstractRegionMap
可以看到裡面內建一個CustomEntryConcurrentHashMap map;應該是這個map, 這是什麼神器?
如果你看到了Doug Lea的大名,不要驚奇,難道Doug也為Gemfire/Geode操刀?非也。
Gemfire/Geode當年直接copy Doug老人家的ConcurrentHashMap, 你可以去比較一下原始碼,當然,GemStone也進行了一些定製化,如讀鎖等。
瞭解完其真實內部儲存資料結構後,心中略有一二,而然這也是預期之中,我們繼續看如何實現分散式的精髓。
3.5 DistributedRegion
我們還是先上UML圖吧。
DistributinRegion代表了分散式的ReplicatedRegion, 而其中則是靠CacheDistributionAdvisor來幫忙跟蹤保持資料同步的。
3.6 Partitioned Regions
Partitioned Region為PatitionedRegion的例項化,內部則基於key的hash code來檢測定位Bucket。
PartitionedRegion,Geode中的核心分散式結構。通過hash演算法(可以自定義)來把key對映到相應的bucket從而達到分散式儲存,並提供routing訊息到相應的bucket。
PartionedRegion中的PartitionedRegionDataStore則實際負責分散式管理bucket,以及儲存。
PRHARedundancyProvider
類如其名:
主要提供針對Reduancy Copy的管理,包括:
bucket建立,以及node的管理。
當建立並配bucket的時候,其內部創建出BucketRegion的例項, 來看一下BucketRegion中經典的virtualPut:
註釋很好,
如果當前是Primary, 則op locally, 之後分發操作op到secondaries及bridge server,同時cache listerner同步其結果;
反之,則首先op locally,之後更新local bridge server, gateway
來個序列圖吧:
4. 分散式管理
Geode的分散式訊息通訊分為主要兩大類,一類是Peer to Peer,主要使用了InternalDistributedMember。
4.1 Peer to Peer 訊息機制
InternalDistributedMember封裝了分散式member的資訊,當需要傳送p2p訊息時,需要用該物件表明目標。
DM提供了獲取所有當前cluster中的peers列表,並通過listerners來獲取動態資訊,而其底層則是通過DistributionAdvisor實現,統一的模式,所以真正籌謀畫冊的都是幕僚。DM主要封裝了相關的網路通訊資訊,包含netMember, dcPort(direct channel port), vmPid(member machine's process id)等,當然實現特定內部序列化介面(DataSerializableFixedID)是必不可少的。
而訊息則是DistributionMessage封裝,包含了上述InternalDistributedMember資訊的sender與destination,以及多個recipients的InternalDistrbutedMember[]陣列,還有萬惡的用於direct ack的ReplySender等。
整個訊息通訊過程大概是先建立DistributionMessage, 設定recipient(可以多個),之後呼叫(DistrbutManager DM) DM.putOutgoing(message)。
如果需要不但傳送訊息,而且還要確認訊息回覆response,則要使用著名的ReplyProcessor21, 如果你看到過Gemfire/Geode的ThreadDump, 你就明白我說什麼了。
還是來個序列圖吧,一圖抵千言。
最後在遠端則是通用的處理:
最後,遠端返回replyMessage,並使用相同的unique id,sender收到訊息後,找到ReplyProcessor,並wakeup thread waitForRepliesUninterrupibly()。
4.2 Client / Server 通訊
Client/Server通訊機制與p2p略有不同。Server端封裝了command,引入智囊團BridgeServerImpl, AcceptorImpl, Client端則封裝了AbstractOp。
Server:
Acceptorimpl主要監聽socket埠,接受客戶端的連結。而其中又通過區分max-connections, max-threads而分為使用dedicated thread還是selector模式的NIO處理。
Client:
客戶端則使用PoolImpl來管理連線connections,client region則持有serverRegionProxy以便實現呼叫。
值得注意的是,客戶端可以通過subscribe events於特定/多個region或者通過內建的continuous queries,這樣當伺服器端有資料更新則client端可以受到更新的通知event。
5. 總結
好吧,時間,篇幅受限,我們先到此。
公眾號:技術極客TechBooster