分布式數據緩存系統的設計與實現

隨著現代企業互聯網應用的規模越來越大,系統面對大量併發請求的處理能力越來越重要,在整個應用系統架構中,數據庫層的訪問速度會成為整個應用系統的瓶頸。數據緩存技術能夠緩存數據庫的查詢結果,當下次訪問同樣數據時,可以直接從緩存中取,有效地降低了數據庫層的訪問負載量,提高了系統性能。但是,隨著網絡用戶的不斷增加,一臺服務器的性能根本無法滿足大量的併發請求,這種情況下就要使用服務器集群,而在集群環境下的緩存就是分佈式緩存。

近年來出現了不少緩存產品,如 Memcached,JBoss Cache,OSCache,Ehcache 等,但是這些分佈式緩存產品在數據冗餘備份和失敗恢復方面存在一定的不足。Memcached 並不支持冗餘備份,服務器端所有節點的緩存內容是互異的,如果一個節點出現故障宕機,那麼該節點保存的所有數據也就沒有了,所以 Memcached 存在單點失效問題。JBoss Cache 支持兩種冗餘策略:全局複製和 Buddy 複製,全局複製將數據複製給集群中所有節點,保證在失敗轉移時可以轉移到集群中任何一個節點,但它限制了系統的伸縮性;Buddy 複製則挑選特定節點擔當備份數據節點,但作為冗餘備份的節點是通過其 xml 文件設置的,當備份節點失效時,就無法啟動新的節點作為失效節點。OSCache 提供的集群功能是非常有限的,無法讓緩存中數據在各個節點間複製。Ehcache 是一個 Java 語言開發的簡單緩存系統,沒有提供冗餘備份和失敗恢復功能。

因此,設計並實現一種具有數據冗餘備份和失敗恢復功能的分佈式數據緩存系統就很有意義了。該研究課題來源於中航信信天游電子客票驗真官網項目。

1 系統應用架構

在現代企業應用分層架構中,數據緩存層通常以中間件的形式存在於數據庫層的前面。信天游電子客票驗真官網的分層架構見圖 1 所示。本緩存系統部署在應用服務器集群上,即在應用程序層緩存數據,第一次請求時訪問數據庫層查詢數據,同時將數據緩存起來,將來有相同請求時,直接從緩存系統中返回數據,減小了數據庫層的壓力,有效提高了系統的整體性能。

分佈式數據緩存系統的設計與實現

圖 1 應用架構圖

2 緩存框架設計

本文設計的分佈式緩存系統採用 Peer-To-Peer 的拓撲結構,集群中每個緩存節點都知道其它所有節點的存在,每個節點可以與任一節點進行通信,這種結構具有單跳數據分發,低延時的特點。本緩存系統作為信天游電子客票驗真官網的數據緩存層,功能是完成對數據庫查詢結果的緩存,減少客戶請求的處理時間,降低數據庫端的負載,同時還要具有數據冗餘備份和失敗恢復的功能。緩存系統主要由以下模塊構成:緩存管理模塊、複製緩存模塊、分佈式緩存模塊、數據分佈模塊、替換算法模塊、緩存同步模塊、緩存通信及可靠性服務模塊。緩存系統設計框架見圖 2。

分佈式數據緩存系統的設計與實現

圖 2 緩存系統設計框架圖

3 系統主要模塊設計

本系統使用 Java 語言開發,採用模塊化設計思想和基於面向對象的設計方法,具有較好的可擴展性。下面介紹主要模塊的設計及實現。

3.1 緩存管理模塊

緩存管理模塊在本系統中由 CacheManager 類來實現,它是整個緩存系統的入口點,負責初始化系統的所有配置信息,建立和管理緩存對象,以及協調各模塊間工作。初始化過程如下:首先構造 CacheManager 對象,調用 init()方法,該方法先調用 ConfigurationFactory類的 parseConfiguration()方法構造一個 Configuration 類對象,然後用 Configuration 對象構造一 個 ConfigurationHelper 類 對 象 , 最 後 以 該 ConfigurationHelper 對 象 為 參 數 , 調 用CacheManager 類的 configure()方法完成所有配置信息的初始化。

3.2 緩存數據分佈與同步

本文設計的是分佈式數據緩存系統,必然緩存要工作在集群中的多臺機器上,那麼緩存數據如何在集群中的機器節點間分佈就很重要,它保證了緩存數據在集群上的正確性。當集群中某個節點的緩存數據過期或有數據更新時,如何保證所有節點間的緩存數據同步,這就需要有效的同步機制來保證緩存節點間的數據相同。本文設計的緩存系統實現了兩種分佈式緩存:複製緩存和分佈式緩存。

3.2.1 複製緩存(Replicated Cache

複製緩存就是集群中每一臺機器節點的緩存內容都是一致的,每個節點都有一份緩存數據的完整備份。當複製緩存中有數據更新時,並不需要數據分佈算法,直接把數據更新到本地機器的複製緩存中,然後調用通信模塊把更新消息發送給集群中其它節點,保證所有節點緩存數據同步。更新過程見圖 3。

分佈式數據緩存系統的設計與實現

圖 3 複製緩存更新過程

當從複製緩存中查詢數據時,直接從本地機器的緩存中查找數據,如果存在,直接返回;如果不存在,即從本地數據庫中查詢數據,把查詢結果放入本地緩存,返回查詢結果,同時調用緩存通信模塊把更新消息發送給集群中其它節點。查詢過程見圖 4。

分佈式數據緩存系統的設計與實現

圖 4 複製緩存查詢過程

複製緩存採用的同步機制[3]是 TTL 模式和客戶端失效模式。客戶端失效模式是指當本地複製緩存有數據更新時,調用通信模塊向集群中其它節點發送更新消息,保證所有節點緩存數據同步,客戶端失效模式是通過給複製緩存註冊監聽器的方式實現的。TTL 模式是指,在向緩存中插入數據對象時,給該對象一個生存週期(TTL),當從緩存中取對象時,首先查看它的 TTL 是否過期,如果過期,便刪除,再從本地數據庫中查詢數據,把數據重新放入緩存;後臺會啟動一個線程不斷地檢測本地緩存中的對象是否過期,如果過期,就從緩存中刪除數據,最後還要調用通信模塊把更新消息或失效消息發送給集群中其它節點。複製緩存由 ReplicatedCache 類實現,該類提供了操作複製緩存的所有 API 接口。

3.2.2 分佈式緩存(Distributed Cache

分佈式緩存使用強大的數據分佈算法把緩存數據分佈到集群中的各個節點上,每個節點都保存一部分數據,整個緩存數據分散到了集群中的每個節點上,同時把備份數據分佈到不同的節點上,保證系統的可靠性。首先討論分佈式緩存使用的分佈式算法:一致性哈希算法(Consistent Hashing)[4]。它是一種哈希算法,將一個所給數據映射為一個 32 位的哈希值,也就是 0 至 2^32 的數值空間,將這個空間看作為一個首尾相接的圓,見圖 5 所示。緩存數據分佈過程:首先求出集群中所有機器節點的哈希值,分佈式緩存使用每個機器的 IP 地址作為哈希函數的鍵,將計算出的所有哈希值映射到 0 至 2^32 的圓上;然後使用相同的哈希函數求緩存數據鍵的哈希值,並映射到圓上,從鍵映射到的位置開始順時針查找,將數據保存在找到的第一個緩存節點上;如果超過 2^32 還找不到節點,就把緩存數據保存在第一個節點上。同時,將緩存節點順時針方向的下一個節點作為數據的備份節點。見圖 5所示,object1 映射到的位置超過 2^32 還未找到緩存節點,就把 object1 保存在第一個節點nodeA 上,nodeB 作為備份節點。

分佈式數據緩存系統的設計與實現

圖 5 一致性哈希算法(1)

分佈式數據緩存系統的設計與實現

圖 6 一致性哈希算法(2)

如果分佈式緩存系統中添加了一臺新機器,假設為 nodeD,並映射到圖 5 中 nodeC 和nodeA 之間,見圖 6 所示,則原來映射到 nodeA 的 object1 現在要重新分佈在 nodeD 上,只有 nodeC 和 nodeD 之間的數據要重新分佈。從上面可以看出,一致性哈希算法最大限度地抑制了鍵的重新分佈,增加了緩存系統的可靠性。向分佈式緩存中插入數據時,先根據一致性哈希算法找到保存該數據的節點及其備份節點,再調用通信模塊將該數據保存到機器節點及備份節點中。更新和刪除的處理流程同數據插入的過程。從分佈式緩存中查詢數據時,先調用一致性哈希算法找到存儲該數據的節點,然後調用緩存通信模塊去讀取數據,如果節點緩存中存在數據,調用通信模塊直接返回結果;如果不存在,則從本地數據庫中查詢數據,把查詢結果更新給節點緩存及備份緩存中,返回結果。查詢處理過程見圖 7 所示。

分佈式數據緩存系統的設計與實現

圖 7 分佈式緩存查詢過程

分佈式緩存採用的同步機制也是 TTL 模式和客戶端失效模式,但不完全同於複製緩存的同步機制。TTL 模式,亦即在向緩存中插入數據對象時,給對象一個生存週期(TTL),當查詢對象時,首先查看它的 TTL 是否過期,如果過期,就從本地數據庫中查詢數據,更新緩存及備份節點中的數據對象;同樣後臺會啟動一個線程來檢測本地緩存中的對象是否過期,如果過期,直接從緩存中刪除,把這個失效消息發送給備份節點。客戶端失效模式是指,當分佈式緩存中有數據更新或刪除時,先根據一致性哈希算法找到保存該數據的節點及備份節點,然後調用通信模塊把該消息發送給數據節點及備份節點,實現緩存數據更新或刪除。分佈式緩存由 DistributedCache 類實現,該類提供了操作分佈式緩存的所有 API 接口。

3.3 替換算法模塊

緩存替換算法指出當緩存空間滿時如何選擇緩存中被替換對象。本緩存系統實現了三種傳統的替換算法[5]:先進先出算法(FIFO)、最近最少使用算法(LRU)、最不經常使用算法(LFU)。FIFO,該算法進行數據替換時,選擇最早放入緩存的對象替換。該算法由類 FIFOPolicy實現,每、次替換時,從緩存中選擇更新時間或者創建時間最早的對象換出緩存。LRU,該算法進行數據替換時,選擇最近最少使用的對象替換出緩存,很適合具有高局部性的數據訪問模式,但對順序訪問模式表現的很糟糕。該算法由類 LRUPolicy 實現,每次從緩存中選擇上一次被訪問時間最早的對象換出緩存。LFU,該算法選擇緩存中被最少訪問的對象進行替換,適合具有不相關訪問模型的應用。

該算法由類 LFUPolicy 實現,每次從緩存中選擇被訪問次數最少的對象換出緩存。

3.4 緩存通信模塊

一套通信協議,用於緩存節點間的通信。通信模塊的調用是通過給複製緩存和分佈式緩存註冊監聽器的方式實現的。通信模塊主要由類JGroupsManager實現,複製緩存發送消息過程如下:

1) 假設本地緩存中有數據更新了,這時產生一個數據更新的事件,將該事件告知監聽器 JgroupsReplicator 類;

2) 以更新的對象為參數,調用 JgroupsReplicator 類的 notifyElementUpdated()方法,該方法將更新消息封裝成一個 EventMessage 類對象;

3) 以 EventMessage 類對象為參數,調用 JGroupsManager 的 send()方法,該方法將EventMessage 類對象封裝成 JGroupsMessage 類對象;

4) 最後調用 JGroupsManager 的成員變量 NotificationBus 類對象的 sendNotification()方法將消息發送給集群中的其它節點。

複製緩存接收消息過程:

1) 緩存系統初始化後,生成的 JGroupsManager 類對象根據配置文件信息,會監聽本地機器的某端口號,JGroupsManager 類實現了 org.jgroups.blocks.NotificationBus.

Consumer 接口;

2) 當此端口號有消息傳來時,調用 JGroupsManager 類的 handleNotification()方法,將收到的消息轉化為 JGroupsMessage 類對象;

3) 以 JGroupsMessage 類對象為參數,再調用 JGroupsManager 類的 handleJGroupNotification()方法,該方法從 JGroupsMessage 類對象中提取出複製緩存相關信息,

包括名稱,事件的類型,數據對象的鍵和值;

4) 根據名稱找到複製緩存對象,執行消息傳遞過來的事件。分佈式緩存與複製緩存的通信過程稍有不同,但是底層實現上都調用 JGroupsManager類,這裡不予討論。

3.5 可靠性服務模塊

可靠性服務模塊功能是提供緩存節點間的數據遷移和失敗恢復,當有新緩存節點加入集群或有節點退出集群時,可靠性服務模塊會被動態的調用,可靠性服務由 Coordinator 類實現。先討論複製緩存的可靠性服務,複製緩存集群中有一個緩存節點退出或失效時,不必做任何處理,因為集群中的所有節點都保存了整個緩存的一份副本。複製緩存集群中有一個新緩存節點加入時,調用 Coordinator 類的處理流程:

1) 新加入節點的 CacheManager 類初始化工作完成後,啟動協調者 Coordinator,使Coordinator 調用通信模塊 JGroupsManager 類向集群中的其它所有節點發送一條消

息,該消息通知其它節點我是新加入的節點,新加入節點是知道集群中其它節點的;

2) 所有節點收到這條消息後,先把新節點的信息加入到配置信息中,然後回覆新節點一條同類型的消息,新節點通信模塊 JGroupsManager 類收到這條消息後,解析該

消息,發現是回覆消息,把該消息傳給 Coordinator,以後再收到同類型消息,不作處理;

3) Coordinator 解析回覆消息,向第一個回覆消息的節點發送遷移數據的請求;

4) 收到遷移數據請求的節點啟動協調者 Coordinator 類準備向新節點遷移緩存中的數據,協調者間隔性的向新節點發送緩存中的數據,直到所有緩存數據都遷移為止,

最後發送一條遷移數據完成的消息;

5) 新節點的協調者不斷地接收來自遷移節點的緩存數據,放入本地緩存中,這一過程中不向其它節點發送更新緩存數據消息,直到緩存數據遷移完成。分佈式緩存的數據分佈方法完全不同於複製緩存,所以它們的數據遷移和失敗恢復的處理過程不同。分佈式緩存集群中加入一個新緩存節點時,調用 Coordinator 類的處理過程如

下:

1) 新加入節點的 CacheManager 類初始化後,調用一致性哈希算法找到自己映射在圓上的位置,設新節點用 nodeE 表示,映射在圓上的位置見圖 8 所示,只需遷移 nodeB上映射在 nodeA 和 nodeE 之間的緩存數據到新節點。啟動協調者 Coordinator,調用通信模塊 JGroupsManager 類向集群中的其它所有節點發送一條消息,該消息通

知其它節點我是新加入的節點,然後再向 nodeB 節點發送一條遷移數據的請求;

2) 所有節點收到這條消息後,都把新節點的信息加入到配置信息中,nodeB 節點收到遷移數據的請求後,啟動 Coordinator,準備向 nodeE 遷移數據;

3) nodeB 節點 Coordinator 到自己緩存中取數據,先判斷數據映射在圓上的位置,如果是在 nodeE 節點的範圍,就把該數據對象封裝成消息發給 nodeE 節點,同時把

該數據放入自己的備份緩存中,並從本地緩存中刪除(B 節點的備份節點保存的這部分數據也被刪除),直至所有緩存數據檢測完,這一過程間歇性的完成,防止造成網絡擁塞;

4) nodeB 節點 Coordinator 下一步取本地備份緩存中的數據,檢測數據應該保存的位置,如果是 nodeE 節點的數據,則不作處理,否則將備份數據發給 nodeE 節點,通知它這是備份數據,nodeE 節點把數據放入自己的備份緩存中,nodeB 節點同時從本地備份緩存中刪除這些數據。該過程也要間歇地完成,直到所有備份數據檢測完,發送一條遷移完成的消息;

5) 至此,整個的數據遷移工作就完成了。

分佈式數據緩存系統的設計與實現

圖 8 分佈式緩存數據遷移

分佈式數據緩存系統的設計與實現

圖 9 分佈式緩存失敗恢復

見圖 9 所示,假設分佈式緩存集群中有 5 個節點,現在 nodeB 節點突然宕機了,失敗恢復的處理過程如下:

1) 假設 nodeA 節點查詢一條數據,該數據位於 nodeB 節點上,nodeA 向 nodeB 發送一條請求消息等待一段時間沒有收到任何回覆,重複向 nodeB 發送兩次請求,如果仍收不到回覆,便向備份節點 nodeC 請求數據,nodeC 發送數據給 nodeA 節點,nodeA 收到數據後通知 nodeC 節點 nodeB 節點好像已失效;

2) nodeC 節點向 nodeB 節點發送一條詢問消息,如果收到回覆消息,說明 nodeB 節點正常;如果等待一段時間後,又重複發了幾次詢問消息,都收不到回覆消息,就

確定 nodeB 節點失效了;

3) nodeC 節點向所有其它節點發送 nodeB 失效消息,所有節點從配置信息中刪除nodeB 節點的信息;

4) nodeC 節點協調者 Coordinator 取本地備份緩存中的數據,放入本地緩存中保存,同時更新 nodeC 節點的備份節點緩存,並從本地備份緩存中刪除數據,直至處理

完畢;

5) nodeC 節點協調者 Coordinator 向逆時針方向的 nodeE 節點發送備份數據的請求,nodeE 收到消息後,查看自己的備份節點是否是 nodeC,如果是,就啟動 Coordinator

向 nodeC 節點發送備份數據,否則不做處理;

6) nodeE 節點向 nodeC 節點備份數據完成後,整個的失敗恢復工作就完成了。

4 結論

經實驗測試,本文實現的兩種不同分佈模式的緩存:複製緩存和分佈式緩存,都具有數據冗餘備份和失敗恢復功能,並有著良好的穩定性。複製緩存適於在節點較少的集群中使用,而分佈式緩存適於應用在大規模的集群服務器中。目前,該緩存系統已經應用在信天游電子客票驗真官網的數據緩存層,系統運行穩定,達到了實際使用的需求。但是,系統實現的分佈式緩存存在一個明顯缺點,就是當系統執行數據冗餘備份或失敗恢復功能時,網絡中的數據傳輸量明顯增大,對網絡帶寬的要求很高。本文下一步的改進工作包括:

1) 研究策略使系統在不繁忙時或網絡帶寬充裕時,執行數據冗餘備份和失敗恢復。

2) 考慮在分佈式緩存的本地加入 Near Cache,減小網絡傳輸時間。

3) 考慮把 AOP 功能加入本緩存系統中,實現智能緩存系統,同時抽象出緩存框架供其他應用程序使用。


分享到:


相關文章: