客戶端實際上是距離key」最近」的地方,由於Redis命令就是從客戶端發出的,例如在客戶端設置全局字典(key和調用次數),每次調用Redis命令時,使用這個字典進行記錄,以下所示。redis
1緩存 2網絡 3數據結構 4架構 5併發 6運維 7異步 8分佈式 9高併發 10 11 12 |
public static final AtomicLongMap<String> ATOMIC_LONG_MAP = AtomicLongMap.create(); String get(String key) { counterKey(key); //ignore } String set(String key, String value) { counterKey(key); //ignore } void counterKey(String key) { ATOMIC_LONG_MAP.incrementAndGet(key); } |
爲了減小對客戶端代碼的侵入,能夠在Redis客戶端的關鍵部分進行計數,例如Jedis的Connection類中的sendCommand方法是全部命令執行的樞紐。
1 2 3 4 5 6 7 |
public Connection sendCommand(final ProtocolCommand cmd, final byte[]... args) { //從參數中獲取key String key = analysis(args); //計數 counterKey(key); //ignore } |
同時爲了防止ATOMIC_LONG_MAP過大,能夠對其進行按期清理。
1 2 3 |
public void scheduleCleanMap() { ERROR_NAME_VALUE_MAP.clear(); } |
使用客戶端進行熱點key的統計很是容易實現,可是同時問題也很是多:
(1). 沒法預知key的個數,存在內存泄露的危險。
(2). 對於客戶端代碼有侵入,各個語言的客戶端都須要維護此邏輯,維護成本較高。
(3). 只能瞭解當前客戶端的熱點key,沒法實現規模化運維統計。
固然除了使用本地字典計數外,還可使用其餘存儲來完成異步計數,從而解決本地內存泄露問題。可是一樣會存在第(2)(3)個問題。
像Twemproxy、Codis這些基於代理的Redis分佈式架構,全部客戶端的請求都是經過代理端完成的,以下圖所示。此架構是最適合作熱點key統計的,由於代理是全部Redis客戶端和服務端的橋樑。但並非全部Redis都是採用此種架構。
使用monitor命令統計熱點key是不少開發和運維人員首先想到,monitor命令能夠監控到Redis執行的全部命令,下面爲一次monitor命令執行後部分結果。
1 2 3 4 5 6 7 8 |
1477638175.920489 [0 10.16.xx.183:54465] "GET" "tab:relate:kp:162818" 1477638175.925794 [0 10.10.xx.14:35334] "HGETALL" "rf:v1:84083217_83727736" 1477638175.938106 [0 10.16.xx.180:60413] "GET" "tab:relate:kp:900" 1477638175.939651 [0 10.16.xx.183:54320] "GET" "tab:relate:kp:15907" ... 1477638175.962519 [0 10.10.xx.14:35334] "GET" "tab:relate:kp:3079" 1477638175.963216 [0 10.10.xx.14:35334] "GET" "tab:relate:kp:3079" 1477638175.964395 [0 10.10.xx.204:57395] "HGETALL" "rf:v1:80547158_83076533" |
如上圖所示,利用monitor的結果就能夠統計出一段時間內的熱點key排行榜,命令排行榜,客戶端分佈等數據,例以下面的僞代碼統計了最近10萬條命令中的熱點key。
1 2 3 4 5 6 7 8 9 10 |
//獲取10萬條命令 List<String> keyList = redis.monitor(100000); //存入到字典中,分別是key和對應的次數 AtomicLongMap<String> ATOMIC_LONG_MAP = AtomicLongMap.create(); //統計 for (String command : commandList) { ATOMIC_LONG_MAP.incrementAndGet(key); } //後續統計和分析熱點key statHotKey(ATOMIC_LONG_MAP); |
Facebook開源的redis-faina 正是利用上述原理使用Python語言實現的,例以下面獲取最近10萬條命令的熱點key、熱點命令、耗時分佈等數據。爲了減小網絡開銷以及加快輸出緩衝區的消費速度,monitor儘量在本機執行。
redis-cli -p 6380 monitor | head -n 100000 | ./redis-faina.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
Overall Stats ======================================== Lines Processed 50000 Commands/Sec 900.48 Top Prefixes ======================================== tab 27565 (55.13%) rf 15111 (30.22%) ugc 2051 (4.10%) ... Top Keys ======================================== tab:relate:kp:9350 2110 (4.22%) tab:relate:kp:15907 1594 (3.19%) ... Top Commands ======================================== GET 25700 (51.40%) HGETALL 15111 (30.22%) ... Command Time (microsecs) ======================================== Median 622.75 75% 1504.0 90% 2820.0 99% 6798.0 |
此種方法會有兩個問題:
(1) 本書屢次強調monitor命令在高併發條件下,會存在內存暴增和影響Redis性能的隱患,因此此種方法適合在短期內使用。
(2) 只能統計一個Redis節點的熱點key,對於Redis集羣須要進行彙總統計。
第四章咱們介紹過,Redis客戶端使用TCP協議與服務端進行交互,通訊協議採用的是RESP。若是站在機器的角度,能夠經過對機器上全部Redis端口的TCP數據包進行抓取完成熱點key的統計,以下圖所示。
此種方法對於Redis客戶端和服務端來講毫無侵入,是比較完美的方案,可是依然存在兩個問題:
(1) 須要必定的開發成本,可是一些開源方案實現了該功能,例如ELK(ElasticSearch Logstash Kibana)體系下的packetbeat[2] 插件,能夠實現對Redis、MySQL等衆多主流服務的數據包抓取、分析、報表展現。
(2) 因爲是以機器爲單位進行統計,要想了解一個集羣的熱點key,須要進行後期彙總。
最後經過下給出上述四種方案的特色。
方案 | 優勢 | 缺點 |
---|---|---|
客戶端 | 實現簡單 | 內存泄露隱患 維護成本高 只能統計單個客戶端 |
代理 | 代理是客戶端和服務端的橋樑,實現最方便最系統 | 增長代理端的開發部署成本 |
服務端 | 實現簡單 | monitor自己的使用成本和危害,只能短期使用 只能統計單個Redis節點 |
機器TCP流量 | 對於客戶端和服務端無侵入和影響 | 須要專業的運維團隊開發,而且增長了機器的部署成本 |
最後咱們給出三種解決熱點key問題的思路,具體選用那種要根據具體業務場景來決定。
(1) 拆分複雜數據結構: 若是當前key的類型是一個二級數據結構,例如哈希類型。若是該哈希元素個數較多,能夠考慮將當前hash進行拆分,這樣該熱點key能夠拆分爲若干個新的key分佈到不一樣Redis節點上,從而減輕壓力。
(2) 遷移熱點key:以Redis Cluster爲例,能夠將熱點key所在的slot單獨遷移到一個新的Redis節點上,但此操做會增長運維成本。
(3) 本地緩存加通知機制:能夠將熱點key放在業務端的本地緩存中,由於是在業務端的本地內存中,處理能力要高出Redis數十倍,但當數據更新時,此種模式會形成各個業務端和Redis數據不一致,一般會使用發佈訂閱機制來解決相似問題。