Redis 集羣之 Redis Cluster 原理及安裝部署

主從架構和哨兵架構的缺點

我們知道 Redis 主從架構和哨兵架構可以通過擴容從節點增加 QPS,但是如果需要緩存的數據有上百個 G 的話,在主從架構和哨兵架構下因爲是讀寫分離,主節點寫入數據,從節點從主節點複製數據,此時單機是無法存儲這個麼數據了呢。那如果才能解決這個問題呢?如何才能突破單機的瓶頸呢。Redis 官方給出的答案就是 —— Redis Cluster。

Redis Cluster 介紹與原理

可以支撐 N 個 master node ,每個 master node 可以掛載多個 slave node。如果某個 master 節點掛掉,可以自動的把它其中的一個 slave node 升級爲 master node,從而實現了高可用。實現了讀寫分離的架構,在 master 節點寫,在 slave 節點讀。
其主要應用於海量數據、高併發、高可用的場景,如果只有幾個 G 的數據需要緩存的話,單機完全可以應付,使用 主從架構 + 哨兵架構即可以滿足需要。

提供內置的高可用支持,部分 master node 不可用的情況下,還可以繼續工作。

Redis Cluster 的數據分片算法

對於主從架構,只有一個 master 節點,只要往這一個節點上寫數據就可以呢。現在 Redis Cluster 有多個 master 節點,那是如何把數據分配到不同的 master node 上,並儘量使每個節點保存的數據均衡呢?

Redis Cluster 使用了 hash slot 算法來實現數據分片。

不同與一般的普通的 hash 算法及一致性 hash 算法, hash slot 算法 有固定的 16384 個 hash slot,其會對需要存入 redis 的每個 key 計算 CRC16 值,然後對 16384 取模,從而獲得 key 對應的 hash slot 值。

Redis Cluster 中每個 master node 會持有一部分的 slot ,比如有 3 個 master node ,每個 master node 會持有大概 5000 多個的 hash slot。每增加一個 master node,就把一部分的 hash slot 移動到新增的節點,如果刪除某一個 master node,就把它的 hash slot 移動到其它的 master node 上。

同時,我們可以對指定的數據,通過 hash tag 可以讓它們走同一個 hash slot。

跳轉

當客戶端向一個錯誤的節點發出了指令後,該節點會發現指令的 key 所在的槽位並不屬於自己管理時,會向客戶端發送一個特殊的跳轉指令,並會攜帶目標操作的節點地址,告訴客戶端應該去連接這個節點以獲取數據。

worker-01:7000> get key1
(error) MOVED 9189 192.168.56.102:7000
worker-01:7000> 

看上面的示例,會提示報錯,MOVED指令的第一個參數 9189 是 key 對應的槽位,後面是目標的節點地址。客戶端在收到 MOVED 指令後,要立即糾正本地的槽位映射表,後續所有的 key 將使用新的槽位映射表。

在我們使用 redis-cli 操作時,我肯定不希望每次還需要自己去重新連接新的節點去獲得 key 對應的值。可以通過在連接的時候增加 -c 可以實現自動定位到新的節點去獲得相應的值。

[root@bogon src]# ./redis-cli -c -h  worker-01 -p 7000
worker-01:7000> get key1
-> Redirected to slot [9189] located at 192.168.56.102:7000
"nihao"
192.168.56.102:7000>

遷移

Redis Cluster 提供了工具 redis-trib 可以讓運維人員手動調整槽位的分配情況,可以通過組合各種原生的 Redis Cluster 指令來實現。

Redis 遷移的最小單位是槽,Redis 一個槽一個槽地進行遷移,當一個槽正在遷移時,這個槽就處於中間過渡狀態。Redis-trib 首先會在源節點和目標節點設置好中間過渡狀態,然後一次性獲取源節點槽位的所有 key 列表,再挨個 key 進行遷移。每個 key 的遷移過程是以源節點作爲目標節點的 “客戶端”,源節點對當前的 key 執行 dump 指令得到序列化內容,然後通過 “客戶端” 向目標節點發送 restore 指令攜帶序列化內容作爲參數,目標節點再進行反序列化就可以將內容恢復到目標節點的內存中,然後返回 “客戶端” OK,源節點“客戶端”收到後再把當前節點的 key 刪除掉就完成了單個 key 的遷移的全過程。

大致流程就是:從源節點獲得內容->存到目標節點->從源節點刪除內容。

值得注意的是:這個過程是一個同步過程,在目標節點執行 restore 指令到源節點刪除 key 之間,源節點的主線程會處於阻塞狀態,直到 key 被成功刪除。

如果遷移過程出現了網絡故障,整個槽遷移只進行了一半,這時這兩個中間節點依舊處於中間過渡狀態,待下次遷移工具重新連上時,會提示用戶繼續進行遷移。

如果每個 key 的內容很小,遷移會很快,幾乎不會影響客戶端的正常訪問。如果 key 的內容很大,因爲遷移是阻塞的,會同時造成源節點和目標節點卡頓,影響集羣的穩定性。所以在集羣環境下,業務邏輯要儘可能避免產生很大的 key。

在遷移過程中,客戶端的訪問的流程會發生很大的變化 。

首先,新舊兩個節點對應的槽位都存大部分 key 數據。客戶端先嚐試訪問舊節點,如果對應的數據還在舊節點裏面,那麼舊節點正常處理。如果對應的數據不在舊的節點裏面,那麼有兩種可能,要麼該數據在新節點裏面,要麼根本不存在。舊節點不知道是哪種情況,所以它會向客戶端返回一個 ask targetNodeAddr 的重定向指令,客戶端收到這個重定向指令後,先去目標節點執行一個不帶任何參數的 asking 指令,然後在目標節點再重新執行原先的操作指令。

爲什麼需要執行這個不帶參數的 asking 指令呢?

在遷移未完成之前,按理說這個槽位還是不歸新節點管理的,如果這個時候向目標節點發送該槽位的指令,節點是不認的,它會向客戶端返回一個 moved 重定向指令告訴它去源節點去執行。如此就形成了重定向循環。asking 指令的目標就是打開目標節點的選項,告訴它下一條指令不能不處理,而要當作自己的槽位來處理。

從以上過程可以看出,遷移是會影響服務效率的,同樣的指令在正常情況下一個 ttl 就能完成,而在遷移過程下需要 3 個 ttl 才能搞定。

容錯

Redis Cluster 可以爲每個主節點設置若干個從節點,當主節點發生故障時,會自動將一個從節點提升爲主節點。如果某個主節點沒有從節點時,那麼發生故障時,整個集羣將完全處於不可用狀態。 當然,Redis 也提供了一個參數 cluster-node-require-full-coverage 可以允許部分節點發生故障時,其它節點還可以繼續對外訪問。

網絡抖動問題

生產環境有很多因素可能出現突然間網絡中斷的情況,然後很快又恢復。Redis Cluster 提供了一個參數 cluster-node-timeout,表示當某個節點持續 timeout 的時間無法連接時,纔會被認爲出現了故障,才需要進行主備切換。

可能下線與確認下線

Redis Cluster 是去中心化的,一個節點認爲某個節點失聯並不代表所有的節點都認爲失聯了,所以集羣會有一個協商的過程,只有當大部分的節點都認定某個節點失聯了,纔會認爲該節點需要進行主從切換來進行容錯。

Redis Cluster 通過 Gossip 協議來廣播自己的狀態及改變對整個集羣的認知。當某個節點失聯了(PFail,即可能下線),它會將這條消息向整個集羣廣播,其它節點就可能收到這條失聯信息。如果收到某個節點的失聯的節點數量(PFail Count)大於達到集羣的大多數,就可以標記這個失聯節點爲確認下線狀態(Fail),然後廣播整個集羣,強迫其它節點也接受該節點已經下線的事實。並立即對失聯節點進行主從切換。

集羣變更通知

當服務器節點發生變更時,客戶端應該立即得到通知以實時刷新自己節點關係表。分二種情況:

  • 目標節點掛掉後,客戶端會拋出一個 ConnectionError,緊接着會隨機挑一個節點進行重試。這時被重試的節點會通過 MOVED 指令告知目標槽位被分配到新的節點地址。

  • 運維手動修改了集羣信息,將主節點切換到其它節點,並將舊的節點移出集羣。這時打到舊的節點的指令會收到一個 ClusterDown 的錯誤,告知當前節點所在集羣不可用(當前節點已被刪除,它不再屬於之前的集羣)。這時客戶端會關閉所有的連接,清空槽位映射關係表,然後向上層拋錯,待一條指令過來時,就會重新嘗試初始化節點信息。

槽位遷移感知

如果 Redis Cluster 中某個槽位正在遷移或者已經遷移完畢,那麼客戶端如何能感知到這種變化呢?客戶端保存了槽位和節點的映射關係表,它需要及時得到更新,纔可以正常的將指令發到正確的節點中。

這裏需要用到我們之前提到的二個指令,MOVED 和 ASKING。

MOVED 指令用來糾正槽位的。如果我們將指令發送到錯誤的節點,該節點會發現對應的槽位不歸自己管理,就會將目標節點的地址隨同 MOVED 指令回覆到客戶端並通知客戶端去目標節點去訪問。這個時候客戶端就會刷新自己的槽位關係表,然後重試指令,後續所有打在該槽位的指令都會轉到目標節點。

ASKING 指令 和 MOVED 指令不一樣,這是用來臨時糾正槽位的。如果當前槽位正處於遷移中,指令會先被髮送到槽位所在的舊節點。如果舊節點存在數據,那就直接返回結果。如果不存在數據,那麼數據可能真的不存在,也可能在遷移目標節點上,所以舊的節點會通知客戶端去新的節點嘗試拿數據,看看新節點有沒有數據。這時,就時就會給客戶端返回一個 asking error 並攜帶上目標節點的地址。客戶端收到這個 asking error 後,就會去目標節點嘗試。客戶端不會刷新刷新槽位映射關係表,因爲它只是臨時糾正該指令的槽位信息,不影響後續指令。

  • 重試二次

MOVED 和 ASKING 指令都是重試指令,客戶端會因爲這兩個指令多重試一次。那有沒有可能會重試二次呢?

是存在,如果一條指令被髮送到錯誤的節點,這個節點會先給你一個 MOVED 錯誤告知你去另外的節點重試,所以客戶端就去另外節點重試了,結果剛好這個時候運維人員要對這個槽位進行遷移操作,於是給客戶端回覆了一個 ASKING 指令告知客戶端去目標節點去重試指令,所以這個時間會重試了二次。

  • 重試多次

重試多次也是存在的,所以一般都會設置一個最大重試次數,超過次數則向上拋出異常。

安裝部署

對於 5.0 之前的版本來說,需要安裝 ruby 才能安裝 redis cluster 集羣,但是從 5.0 之後就不需要了。

我們這理準備安裝一個 3 master node 和 3 slave node 的一個集羣。分別部署在 3 臺機器上,即每臺機器有 2 個節點。

準備

需要準備好三臺服務器,其 IP 映射分別是:

192.168.56.101 worker-01        worker-01.joyxj.com
192.168.56.102 worker-02        worker-02.joyxj.com
192.168.56.103 worker-03        worker-03.joyxj.com

同時下載好最新的 Redis,筆者下載是 redis-5.0.5,放在目錄 /opt/tools,這個大家可以隨意。

配置文件

  1. 新建目錄 /etc/redis/cluster 目錄用於存在配置文件。

  2. 拷貝配置文件到目錄下 /etc/redis/cluster,由於我們需要在一臺服務器上啓動二個 redis 實例,所以拷貝配置文件二次,並且重命名爲 7000.conf7001.conf。後面我們會用 70007001 端口啓動 reids 實例。

cp /opt/tools/redis-5.0.5/redis.conf /etc/redis/cluster/7000.conf
cp /opt/tools/redis-5.0.5/redis.conf /etc/redis/cluster/7001.conf

分別配置這二個文件:

7000.conf

port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
dir /var/redis/7000
daemonize yes
# 按實際服務器 配置,也可以直接配置 IP
bind worker-01

7001.conf

port 7001
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
dir /var/redis/7001
daemonize yes
# 按實際服務器 配置,也可以直接配置 IP
bind worker-01

三臺服務器都需要按照上面操作。

啓動實例

[root@bogon redis-5.0.5]# /opt/tools/redis-5.0.5/src/redis-server  /etc/redis/cluster/7000.conf
[root@bogon redis-5.0.5]# /opt/tools/redis-5.0.5/src/redis-server  /etc/redis/cluster/7001.conf

三臺服務器都啓動實例。

創建集羣

[root@bogon redis-5.0.5]#  /opt/tools/redis-5.0.5/src/redis-cli --cluster create 192.168.56.101:7000 192.168.56.101:7001 \
192.168.56.102:7000 192.168.56.102:7001 192.168.56.103:7000 192.168.56.103:7001 \
--cluster-replicas 1

這裏好像只能使用 IP 地址。

nodes.conf

在配置文件中我們配置了一個 cluster-config-file nodes.conf 參數,我們來看下這個文件寫了什麼內容。
我們選擇其中的一個文件,找到所在的文件目錄 var/redis/7000/nodes.conf

fe507ba7dcfe613293bfa0553d19cd34cfccbd86 192.168.56.102:7000@17000 master - 0 1564327716585 3 connected 5461-10922
229c88ce83db7ac97e0b5887dcf2f642d34d9483 192.168.56.101:7001@17001 master - 0 1564327716000 7 connected 10923-16383
93e319527a0d2d6eb89151f3586ca1d182abf6c8 192.168.56.102:7001@17001 slave c29dc2a0bf90de3e01f6a8d89288088bb1a2ad69 0 1564327717594 4 connected
d2a50194481c0b08956bf56e9692bec654842cc6 192.168.56.103:7000@17000 slave 229c88ce83db7ac97e0b5887dcf2f642d34d9483 0 1564327717391 7 connected
dd4c8528f2f83af7e1e7f02fbe634ae3973733ce 192.168.56.103:7001@17001 slave fe507ba7dcfe613293bfa0553d19cd34cfccbd86 0 1564327717594 6 connected
c29dc2a0bf90de3e01f6a8d89288088bb1a2ad69 192.168.56.101:7000@17000 myself,master - 0 1564327716000 1 connected 0-5460
vars currentEpoch 7 lastVoteEpoch 7

文件中寫入了每個節點是 master node 還是 slave node ,以及是否連接,同時對於 master 節點還記錄該節點記錄哪些槽的數據。

查看集羣狀態。

# 用 redis-cli 工具,隨便連接上一臺服務器
redis-cli -h worker-01 -p 7000
worker-01:7000> cluster nodes

可以用過 cluster help 查看更多命令。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章