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




































