the secret of redis cluster
redis 幾乎是我們在日常使用快取中最常見的選擇了,它有著良好的設計,高效率的API,簡單的協議。但是同時也有著明裡暗裡的一些坑的存在,我們需要在使用的時候小心的去規避掉這些坑。下面是我要向大家介紹的,redis(cluster)快取裡的中的一些慣用法。
建立叢集
建立一個 redis 叢集很容易,但是也很困難。我們在建立叢集的時候,總是會想如何去快速的建立,然而 redis 的節點分佈,實際上是要有著自己的一定的分佈準則的。
我們知道,在 redis 的叢集裡,承擔實際的快取責任的是主節點,從節點一般情況下是不對外提供服務的。
當主節點掛掉的時候,從節點自動的會被提升為主節點,開始承擔壓力。
因此, redis cluster 首先要滿足的一點是:主從節點不能出現在一臺機器上。
這個也很好理解,這樣做是為了讓當主掛掉的時候還能有對應的從節點可用。
那麼,我把主節點都放到一臺機器上,而從節點都放到另一臺機器上可行麼。
理論上其實是可行的,但是在實際的操作中,你會發現這個叢集實際上是有著巨大的風險的。
為什麼這麼說呢,因為我們在使用 redis 過程中,由於客戶端訪問量過大擊垮 redis 的情況其實並不少見,甚至可能導致 redis master 疲於應對客戶端的請求從而導致無法及時響應主從的同步資訊。我們知道,redis 本身是單執行緒的,一旦該執行緒過於繁忙就可能會出現這種情況。被叢集的其他節點認為是斷鏈然後提升其從到主,繼續服務。
但是如果遇到了上面那種節點分佈,一臺叢集上的主節點因為過熱而發生了崩潰,甚至有可能導致整臺機器的崩潰。這樣,其上所有的主節點都會被瞬間切換到對應的另一臺機器上。我們知道,主從切換的時候其實是 redis 叢集的一種不穩定的中間狀態,客戶端可能會因為叢集的抖動而產生報復性的請求反彈,這樣壓力瞬間又會壓垮這一臺機器。然後兩臺機器來回雪崩,最終導致整個叢集的不可用。
為了避免上述情況,我們應該講 redis 的主從節點儘量的打散,保證主從的分佈都是均勻的。讓一臺機器掛掉之後的壓力可以均勻的擴散到整個叢集的所有節點上,這樣可以避免有少數節點宕機造成的雪崩式反應。
另外我們需要注意的是,redis 本身是通過選舉
來進行主從切換的,如果當要求選舉的時候掛掉了超過一半的主節點數,這時候也是不可選舉的。結合我們常見的 一主一從模式,我們最終可以得出結論:
redis cluster 的主從分佈必須滿足:
- 主從節點儘量平均分散,防止壓力瞬間集中造成雪崩
- 任何一臺宿主機上不能分佈超過一半的主節點數。
開啟主從讀寫分離
redis 為了保證效率,採用的是主從非同步複製的同步方式。這樣做造成的後果自然是顯而易見的:在持續寫入的情況下,從庫必然會和主庫不一致。redis認為這部分不一致是可以接受的,實際在我們使用的時候也是這樣的。但是,由於服務都在主庫上,每一個主庫實際上已經成為了瓶頸。尤其當某個 key 高熱,但寫少讀多的的情況下,根本無法擴充套件。
針對這種情況,我們其實可以大膽的將讀命令轉發向從庫。
因為主從協議的限制,我們往往不能轉發寫請求到從庫,但是讀請求是百無禁忌的。只要你的業務能接受讀上面短暫的不一致,那麼這種情況不失為一種解決高熱key的辦法。畢竟從節點的個數理論上是可以無限擴充套件的。
那麼就有人會問了,如果業務上出現了寫多讀少的高熱key怎麼辦呢?
這個嘛,解決這個問題其實是有兩種思路。
方法一
一旦某個key因為寫操作而過熱,那麼其實在其客戶端的上也是過熱的。因此,嘗試在客戶端上進行 cell 合併是一個非常好的選擇。這裡可以參考 anna 的論文,ofollow,noindex">http://db.cs.berkeley.edu/jmh/papers/anna_ieee18.pdf 。簡單來說,在服務端延遲處理一些pipe寫請求,並將這些pipe請求裡可以合併的寫請求都合併,可以合併,並插入到一個合適的位置裡。這樣,多個寫其實就已經變成了單個寫。這樣一來能大幅度降低寫操作的數量,也就降低了高熱的key的可能性。
方法二
解決掉寫出這個高熱 key 的人,責令其改正。這在很多場景下是一個更行之有效的辦法。
畢竟我們做中介軟體的,不能任由業務程式碼瞎搞胡搞。要用快取,也得按照基本法來。
關於 key 的分析
redis 自帶的 redis-cli 就有針對某個節點的大 key 分析工具。
root:/# redis-cli --bigkeys # Scanning the entire keyspace to find biggest keys as well as # average sizes per key type.You can use -i 0.1 to sleep 0.1 sec # per 100 SCAN commands (not usually needed). [00.00%] Biggest string found so far 'key-7862' with 8 bytes [28.16%] Biggest zsetfound so far 'cov' with 2 members -------- summary ------- Sampled 10002 keys in the keyspace! Total key length in bytes is 78894 (avg len 7.89) Biggest string found 'key-7862' has 8 bytes Biggestzset found 'cov' has 2 members 10001 strings with 78894 bytes (99.99% of keys, avg size 7.89) 0 lists with 0 items (00.00% of keys, avg size 0.00) 0 sets with 0 members (00.00% of keys, avg size 0.00) 0 hashs with 0 fields (00.00% of keys, avg size 0.00) 1 zsets with 2 members (00.01% of keys, avg size 2.00) 0 streams with 0 entries (00.00% of keys, avg size 0.00)
我們可以稍微利用一下這個大 key 工具。但是,實際對於一個在用的叢集節點來說,這樣做是非常危險的。我們無法知道 redis-cli 這個指令碼的效率,而且當 key 個數偏大的時候分析的時間可能相當的漫長。因此,我們採取一種曲線救國的方案。
偽從分析
我們知道,redis 實際上是通過 psync 命令進行主從協議同步的。因此,我們可以用我們自己寫的 tcp 客戶端,偽裝自己是一個從節點,發一個 psync 命令。
然後主節點的 redis 會先 fork 然後將完整的記憶體全量輸出成 rdb 檔案給我們,等待傳輸網完畢之後再將增量傳輸給我們。
這樣,我們就利用了 psync 命令實際上進行了一次開銷非常低的全量資料 dump 。吶,dump 的資料都有了你還有啥不能分析的,你可以繼續把自己當從節點掛載在上面進行流式分析,也可以就此打住進行大key的離線分析。都是可以的。
需要明確的是,redis 的 rdb 檔案裡實際上是包含有 key expire 資訊的。因此,我們除了可以分析出 key 的大小來之外,我們還可以分析出哪些 key 是沒加 ttl 的。
另外需要明確的是,哪些 key 的是大 key 呢?
這其實是看用法的。
redis 官方有個粗略的標準: 一次操作的返回值在 1kb 以上的,都叫大 key 。
這樣我們就知道了,其實就是看這個kv在rdb裡的大小。
對於這種大 key,我們需要堅決予以避免。這其中一個很重要的原因,是因為我們在遷移資料的時候,過大的 key 可能會導致 migrate 命令的時間過長從而造成 redis 節點的假死。
大 key 拆分
發現了大 key 之後,我們還要治理。治理的方式也很簡單。一般是採取按照業務規律拆散的方法來治理。
- 對於 string 型別的單 key 過大,這個時候請寫這個 key 的人原地女裝謝謝。
- HASH、SET、LIST 這樣的結構過大,則按照 key 字首進行拆分
- ZSET 過大,則按照分段進行拆分。
- hyperloglog 過大,兄嘚你是怎麼做到的,我想學一下。
關於熱 key 的分析
熱 key 不像大 key 那樣顯而易見,但是對於叢集的危害是有過之而無不及的。
對於熱 key,上面有簡單敘述怎麼治理。但是,怎麼發現其實才是熱 key 問題的核心。我們通常採用的辦法,是取樣法。
比如,在客戶端,代理中介軟體上進行埋點取樣,在 redis 機器上利用 tcpdump/tcpcopy 之類的工具進行抓包然後分析。但無論怎樣,熱 key 的出現到發現總歸是有一定的延遲的。這個過程中我們能做的就是儘量的提早發現熱 key,並將其採用適當的方法處理掉。避免對叢集造成更大的威脅。
更重要的是,提升業務開發對熱 key 問題的重視程度,多科普,多預防。建立大家拆 key 的思維,才是正確的選擇。
redis 槽位遷移
槽位遷移是 redis 官方提供的一套基於 redis 叢集的資料遷移擴容方案。
其基本步驟就是將所有的 key 分成 16384 個槽位,然後每個槽位進行遷移的時候,首先在老的節點上拒絕新 key 的錄入和不存在的 key 的查詢,統統的把這些請求都重定向到新節點上。然後再將老節點上存在的 key 一個一個的通過 migrate 命令遷移到新的節點上去。其中在遷移的過程中,這個 key 對新老節點都是被暫時鎖住的,所有與之相關的任何操作都會被卡住直到遷移完成。
這其中就有個效率問題的事情的,結合上文,我們發現,一旦 key 過大的時候,migrate 鎖 key 花費的時間其實會相當的長。再結合 redis 的新的 migrate 指令可以同時遷移多個key,被鎖key的機率大大增加。因此,如何解決大 key 問題,是每一個 redis 從業人員必須要面對的難題。
另外還有要說的是,雖然 redis 官方已經很盡職盡責的加速遷移的速度了,但是遷移過程對 redis 叢集的整體損害還是比較大的,時間和很長。因此,我們發明了一種邪魔外道的解決方案:
主從同步方案。
這其中,我們利用了上文中說的 psync 命令,將一個叢集與另一個叢集,通過工具進行單向同步。
然後在斷開同步的瞬間,通過代理中介軟體將叢集都指向新的叢集。這樣的話,叢集中間只會有切換瞬間的少許不一致的key。
但是,這裡要注意的是。由於你本身就是在使用 redis cluster 的主從模式,因此,你就得相容部分key的不一致的情況,因為主從切換時有發生,業務上本身就應該已經做了針對性的容錯。這樣,即使我們出現瞬間的不一致也是應該可以被容錯掉的。打不了,針對不能不一致的key我們可以進行key的事後清除即可。
總結
關於 redis 的內容說了不少了,但是在叢集使用的過程中遇到我們遇到的坑遠遠比這個還要多的多,希望與大家交流共勉。