我們一起聊聊如何編寫一個 Java memcached 客戶端
非常喜歡讀開源項目,每次讀源碼,都會覺得自己修煉某種武學功法,期待修煉完成后,可以大殺四方。
從2012年開始,陸續閱讀了 Cobar、Druid、Xmemcached、RocketMQ、MetaQ、Canal、ShardingJdbc、Sofa-Jraft 等開源項目。
這篇文章,聊聊筆者閱讀 Xmemcached 源碼的心得體會,希望對大家有所幫助。
圖片
1、Xmemcached 簡介
XMemcached 是一個 Java memcached 客戶端。
可能有的同學對 Memcached 并不熟悉,它和 Redis 一樣都是分布式內存緩存系統,用于加速動態 Web 應用程序,通過減少數據庫負載來提高性能 。
筆者當時服務于一家彩票公司,公司的核心系統訂單服務、調度中心、業務網關都使用 XMemcached 操作 memcached 。
基于一個非常樸素的好奇心:“如何用 Java 編寫一個緩存客戶端 ?” 筆者花了接近兩個月的時間將 Xmemcached 源碼瀏覽了一次。
瀏覽完源碼后,筆者有三點心得:
- 設計模式
- 網絡命令編解碼
- 一致性哈希算法
2、設計模式
下圖是 Xmemcached 的使用范例:
圖片
我們可以清晰的發現流程分為三個部分 :
- 客戶端構造器通過 servers 參數創建客戶端 ;
- 通過客戶端調用 set、 get、 delete 方法 ;
- 關閉客戶端。
MemcachedClient 接口定義了基本的緩存操作方法,比如 get、set、add 、cas 方法,而 XmemcachedClient 是 MemcachedClient 的實現類。
真正創建客戶端對象 XmemcachedClient 是通過構造器 XMemcachedClientBuilder 來實現的,這是一個非常典型的設計模式:Builder 模式 。
Builder 模式是一種創建型設計模式,它允許你分步驟創建復雜對象。與直接構造對象不同,Builder 模式通過一個構建過程,逐步設置對象的不同部分,從而使對象的創建過程更加靈活和可控。
圖片
我們在配置 構造器 XMemcachedClientBuilder 時,可以配置序列化對象、命令工廠、服務器列表等配置。
最后,調用構造器的 build 方法創建 MemcachedClient 對象,見下圖:
圖片
下面是 XMemcached 的主要類的 UML 圖:
圖片
3、網絡命令編解碼
在網絡編程中,一個重要的步驟是對發送的數據包進行編碼、對接受的數據包進行解碼。
1)發送命令,進行編碼
因為 Xmemcached 使用了自研的網絡通訊框架 ,每次發送命令時,都會調用命令的 encode 方法,將命令對象轉換成 IoBuffer 對象通過網絡發送。
下圖是 TextGetOneCommand 的 encode 方法 :
圖片
2)接收命令,進行解碼
當通訊框架收到響應字節數組時,可能收到的數據包并不完整,在收到數據包時,通過 decode 方法判斷數據包是否完整,當數據完整之后,將同步請求的 countDownLatch 計數減 1 。
圖片
當筆者理解了網絡命令編解碼的技巧之后,后來用 Netty 寫了個分庫分表 proxy 輪子時,在設計 MySQL 命令包編解碼就使用了類似的技巧。
圖片
4、一致性 Hash 算法
一致性 Hash 算法非常典型的應用就是緩存集群,它能夠很大程度上(注意不是完全解決)解決余數哈希的增加服務器導致緩存失效的問題。
我們需要進行如下步驟,使用一致性哈希(Consistent Hashing)將鍵值對映射到 memcached 服務器上。
圖片
- 計算 memcached 服務器(節點)的哈希值,并將其配置到 0~2^32 的圓上。
- 用同樣的方法計算存儲數據的鍵的哈希值,并映射到圓上。
- 從鍵的哈希值對應的位置開始順時針查找,將數據保存到找到的第一個服務器上。
- 如果超過 2^32 仍然找不到服務器,就會保存到第一臺 memcached 服務器上。
下圖,我們新增一臺 memcached 服務器 node5 。
圖片
假如是我們使用余數分布式算法,保存鍵的服務器會發生很大變化從而影響緩存的命中率,但一致性哈希算法僅僅如圖中所示 node2 和 node 5 之間小部分黃色區域會有影響。
KetamaMemcachedSessionLocator.java 實現了一致性哈希算法, 使用的 Hash 算法是 KETAMA HASH 算法。
1)根據服務器列表生成 Hash 環 ,存儲容器 TreeMap
圖片
2)通過 key 得到 TreeMap 的 tailMap,然后找到 firstKey
圖片
5、寫到最后
閱讀 Xmemcached 源碼,讓筆者的眼界大大提升,比如序列化方式、構造器 Builder 設計模式、一致性哈希算法、通訊命令編解碼、failover 設計等等。
比較可惜的是,筆者當時能力有限,并沒有完全理解自研網絡框架 yanf4j 。
盡管如此,當讀完 Xmemcached 源碼后,筆者對于“如何用 Java 編寫一個緩存客戶端 ?” 這個問題,腦海里已經有了概念,當筆者對于 Netty 更加熟悉之后,這個問題也就變得不是問題了。