摘要:基于一致性哈希的分布式內存鍵值存儲。失效未經請求與數據轉移就斷開了和的連接則需要及時通知。可見編譯模式并沒有比混合模式效果好,因為即使是不熱點的代碼也要編譯,反而浪費時間,所以一般還是選擇默認的混合模式較好。
Consistent Hashing based Key-Value Memory Storage
基于一致性哈希的分布式內存鍵值存儲——CHKV。
目前的定位就是作為 Cache,DataBase 的功能先不考慮。
NameNode : 維護 DataNode節點 列表,用心跳檢測 DataNode(一般被動,被動失效時主動詢問三次),節點增減等系統信息變化時調整數據并通知 Client;
DataNode : 存儲具體的數據,向 NameNode 主動發起心跳并采用請求響應的方式來實現上下線,便于 NameNode 發起挪動數據指令,實際挪動操作由 DataNode 自行完成;
Client : 負責向 NameNode 請求 DataNode 相關信息并監聽其變化,操縱數據時直接向對應 DataNode 發起請求就行,
目前支持set,get,delete,keys,expire幾個操作;
NameNode 失效則整個系統不可用。
若當成內存數據庫使用,則要注意持久化,而且只要有一個 DataNode 失效(未經請求與數據轉移就下線了)整個系統就不可對外服務;
若當成內存緩存使用,則 DataNode 失效只是失去了一部分緩存,系統仍然可用。
DataNode 失效(未經請求與數據轉移就斷開了和 NameNode 的連接)則 NameNode 需要及時通知 Client。
客戶 要使用 CHKV 就必須使用 Client 庫或者自己依據協議(兼容redis)實現,可以是多種語言的API。
當然也可以把 Client 當做 Proxy,使得 CHKV 內部結構對 客戶 透明,亦即有如下兩種方式:
方式1:
用戶直接使用Client庫 || || || || || NameNode || || || || DataNode DataNode DataNode DataNode ......
方式2:
用戶通過Proxy訪問 || Client庫構建的Proxy || || || || || NameNode || || || || DataNode DataNode DataNode DataNode ......分析
要想實現高可用有兩點: NameNode 要主從雙機熱備,避免單點失效;每個 DataNode 可以做成主從復制甚至集群。
各個組件之間的連接情況:
NameNode 要保持和 N 個 Client 的TCP長連接,但是只有在集群發生變化時才有交互,所以使用IO多路復用負載就不大
NameNode 要和 M 個 DataNode 保持心跳,TCP請求響應式,負載與 M 和心跳間隔秒數 interval 有關
DataNode 與 Client 是TCP請求響應式操作,Client 請求完畢后保留與該 DataNode TCP連接一段時間,以備后續訪問復用連接,連接采取自動過期策略,類似于LRU
DataNode 與 NameNode 保持心跳
Client 與 NameNode 保持TCP長連接
Client 與 DataNode TCP請求響應式操作
如下圖所示,有4個連接:其中1、2要主動心跳來保持連接;3保持連接以備復用并可以自動超時斷開,再次使用時重連;4完成數據轉移后就斷開連接。
NameNode || || 1、心跳請求響應|| ||2、監聽長連接 || 3、數據請求響應 || DataNodes ========== Clients || || || 4、數據轉移,可復用3
開發優先級:3、1、4、2
代碼結構
NameNode : 實現 NameNode 功能
handler : handler
res : 資源,如常量,命令工廠
service : 服務,含Client管理,DataNode管理
DataNode : 實現 DataNode 功能
command : 處理客戶端各個命令的具體命令對象
job : 一些的任務如心跳、數據遷移
handler : 處理連接的handler
service : 服務,含定時任務管理,數據請求管理
Client : 實現 Client 功能
handler : handler
Client : 暴露給用戶的命令管理
Connection : 發出網絡請求
Common : 實現一些公共的功能,上面三個模塊依賴于此模塊
command : 命令抽象類
model : 一些公用的pojo,如請求響應對象
util : 一些工具類
helper : 輔助腳本
使用方法DataNode 運行起來就可以直接使用 redis-cli 連接,如redis-cli -h 127.0.0.1 -p 10100,并進行set、get、del等操作;
注意:要首先運行 NameNode,然后可以通過JVM參數的方式調整端口,在同一臺機器上運行多個 DataNode,
若要在不同機器上運行 DataNode 也可以直接修改配置文件。
新的 DataNode 可以直接上線,NameNode 會自動通知下一個節點轉移相應數據給新節點;DataNode 若要下線,
則可以通過 telnet DataNode 節點的下線監聽端口(TCP監聽) 如 telnet 127.0.0.1 6666 ,
并發送 k 字符即可,待下線的DataNode收到命令 k 后會自動把數據全部轉移給下一個 DataNode
然后提示進程pid,用戶就可以關閉該DataNode進程了,如 Linux: kill -s 9 23456,Windows:taskkill /pid 23456
NameNode 和 DataNode 啟動后就可以使用 Client 了,代碼示例如下:
Client 代碼示例在此,關鍵如下:
try(Client client = new Client("192.168.0.136","10102")){// 支持自動關閉 logger.debug(client.set("192.168.0.136:10099","123456")+""); logger.debug(client.get("192.168.0.136:10099")+""); logger.debug(client.set("112","23")+""); logger.debug(client.del("1321")+""); logger.debug(client.del("112")+""); }壓力測試
在本機開啟1個 NameNode 和1個 DataNode 直接壓測,4次
redis-benchmark -h 127.0.0.1 -p 10100 -c 100 -t set -q
SET: 5006.76 requests per second
SET: 5056.43 requests per second
SET: 5063.55 requests per second
SET: 5123.74.55 requests per second
把以上2個節點日志級別都調整為 info(實際上 DataNode 節點才會影響 qps),重啟
redis-benchmark -h 127.0.0.1 -p 10100 -c 100 -t set -q
SET: 62421.97 requests per second
SET: 87260.03 requests per second
SET: 92592.59 requests per second
SET: 94517.96 requests per second
可見日志對qps影響很大,是 幾k 與 幾十k 的不同數量級的概念,若把級別改成 error,平均qps還能提升 幾k,所以生產環境一定要注意日志級別。
此外觀察,不重啟并且每次壓測間隔都很小的話,qps一般會從 65k 附近開始,經過1、2次的 88k 左右,最終穩定在 98k 附近,數十次測試,最低 62.4k,最高101.2k。
重啟的話,qps就會重復上述變化過程,這應該是和內存分配等初始化工作有關,第1次壓測有大量的初始化,而后面就沒了,所以第一次qps都比較低;還可能與 JIT 有關,所以 Java 的性能測試嚴格上來說要忽略掉最初的幾個樣本才對。
經觀察,DataNode進程啟動后,內存消耗在59M附近,第1次壓測飆升到134M然后穩定到112M,第2次上升到133M然后穩定到116M,后面每次壓測內存都是先增加幾M然后減小更多,最終穩定在76M。
在本機運行一個redis-server進程,然后壓測一下
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -t set -q
SET: 129032.27 requests per second
SET: 124533.27 requests per second
SET: 130208.34 requests per second
SET: 132450.33 requests per second
經數十次測試,qps 穩定在 128k 附近,最高 132.3k ,最低 122.7k 可見CHKV的單個 DataNode 目前性能還比不過單個 redis。
DataNode 經過重構后,現在的壓測結果如下
redis-benchmark -h 127.0.0.1 -p 10100 -c 100 -t set -q
SET: 78554.59 requests per second
SET: 114285.71 requests per second
SET: 119047.63 requests per second
SET: 123628.14 requests per second
經過多次測試,qps 穩定在 125k 附近,最高 131.9k ,最低 78.6k(這是啟動后第一次壓測的特例,后期穩定時最低是 114.3k),可見重構后
單個 DataNode 和單個 redis-server 的 qps 差距已經很小了,優化效果還是比較明顯的。
主要優化兩個:去掉多帶帶的 BusinessHandler 的多帶帶邏輯線程,因為沒有耗時操作,直接在IO線程操作反而能省掉切換時間;
DataNode 通過 public static volatile Map
第一條對比明顯,很容易直接測試,第二條沒直接測,只是分析。
然后通過 -Xint 或者 -Djava.compiler=NONE 關閉 JIT 使用 解釋模式,再壓測試試。
redis-benchmark -h 127.0.0.1 -p 10100 -c 100 -t set -q
SET: 16105.65 requests per second
SET: 16244.31 requests per second
SET: 16183.85 requests per second
SET: 16170.76 requests per second
可見關閉 JIT 后 qps 降低了 7倍多,而且每次差別不大(即使是第一次),這也能說明上面(默認是混合模式)第一次壓測的 qps 比后面低了那么多的原因確實和 JIT 有關。
通過 -Xcomp 使用 編譯模式 ,啟動會很慢。
redis-benchmark -h 127.0.0.1 -p 10100 -c 100 -t set -q
SET: 83612.04 requests per second
SET: 117647.05 requests per second
SET: 121802.68 requests per second
SET: 120048.02 requests per second
可見 編譯模式 并沒有比 混合模式 效果好,因為即使是不熱點的代碼也要編譯,反而浪費時間,所以一般還是選擇默認的 混合模式 較好。
然后來驗證線程數、客戶端操作與 qps 的關系,實驗機器是 4 core、8 processor,我把 DataNode 的 DataManager 中 workerGroup的線程數依次減少從 8 調到為 1 (之前的測試都是 4 ),
發現 qps 先升后降,在值為 2 的時候達到最大值,超過了redis,下面是數據
redis-benchmark -h 127.0.0.1 -p 10100 -c 100 -t set -q
SET: 93283.04 requests per second
SET: 141043.05 requests per second
SET: 145560.68 requests per second
SET: 145384.02 requests per second
經數十次測試,qps 穩定在 142k 附近,最高 150.6k ,穩定后最低 137.2k。
Netty 本身使用了IO多路復用,在客戶端操作都比較輕量(壓測這個 set 也確實比較輕量)時選擇線程數較少是合理的,
因為這時候線程切換的代價超過了多線程帶來的好處,這樣我們也能理解 redis 單線程設計的初衷了,
單線程雖然有些極端,但是如果考慮 面向快速輕量操作的客戶端 和 單線程的安全與簡潔特性,也是最佳的選擇。
但是如果客戶端操作不是輕量級的,比如我們把 set 數據大小調為500bytes,再對 CKHV 不同的 workerGroup線程數進行壓測
2 redis-benchmark -h 127.0.0.1 -p 10100 -c 100 -t set -d 500 -q
SET: 80450.52 requests per second
SET: 102459.02 requests per second
SET: 108813.92 requests per second
SET: 99206.34 requests per second
3 redis-benchmark -h 127.0.0.1 -p 10100 -c 100 -t set -d 500 -q
SET: 92592.59 requests per second
SET: 133868.81 requests per second
SET: 133868.81 requests per second
SET: 135685.22 requests per second
4 redis-benchmark -h 127.0.0.1 -p 10100 -c 100 -t set -d 500 -q
SET: 72046.11 requests per second
SET: 106723.59 requests per second
SET: 114810.56 requests per second
SET: 119047.63 requests per second
可見這個時候4、3個線程qps都大于2個線程,符合驗證,但是4的qps又比3少,說明線程太多反而不好,
然而把數據大小調到900byte時,4個線程又比3個線程的qps大了,
所以這個參數真的要針對不同的應用場景做出不同的調整,總結起來就是輕量快速的操作適宜線程 適當少,重量慢速操作適宜線程 適當多。
水平有限,目前項目的問題還很多,可以改進的地方還很多,先列個清單:
高可用性保證
斷線重連
DataNode遷移數據的正確性保障
對于WeakReference的支持
更多數據類型
更多操作
完整的校驗機制
等等......
全部代碼在Github上,歡迎 star,歡迎 issue,歡迎 fork,歡迎 pull request......
總之就是歡迎大家和我一起完善這個項目,一起進步。
戳此看原文,來自MageekChiu
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69355.html
摘要:余數分布式算法就是根據服務器臺數的余數進行分散。余數分布式算法由于保存鍵的服務器會發生巨大變化,而影響緩存的命中率,但中,只有在上增加服務器的地點逆時針方向的第一臺服務器上的鍵會受到影響。 WHAT is Memcache? Free & open source, high-performance, distributed memory object caching system, g...
摘要:從代碼上看字典也是在哈希表基礎上再抽象了一層而已。在中,哈希表實際上就是數組鏈表的形式來構建的。后,在哈希沖突時是將新的節點添加到鏈表的表尾。在對哈希表進行擴展或者收縮操作時,過程并不是一次性地完成的,而是漸進式地完成的。 前言 只有光頭才能變強 showImg(https://segmentfault.com/img/remote/1460000016837794); 最近在學Red...
摘要:本文節選自深入淺出分布式基礎架構數據庫篇。與在數據結構與算法查找樹一節中我們介紹了的基本概念與實現,這里我們繼續來分析下為何相較于紅黑樹等二叉查找樹會更適合于作為數據庫索引的實 showImg(https://segmentfault.com/img/remote/1460000018453572?w=1280&h=554); 本文節選自深入淺出分布式基礎架構-數據庫篇 https:/...
閱讀 2001·2019-08-29 16:27
閱讀 1370·2019-08-29 16:14
閱讀 3372·2019-08-29 14:18
閱讀 3455·2019-08-29 13:56
閱讀 1252·2019-08-29 11:13
閱讀 2118·2019-08-28 18:19
閱讀 3439·2019-08-27 10:57
閱讀 2273·2019-08-26 11:39