一、什么是 Redis Cluster
Redis 是在內存中保存數據的,而我們的電腦一般內存都不大,這也意味着 Redis 不適合存儲大數據,適合存儲大數據的是 Hadoop 生態系統的 Hbase 或者是 MogoDB 。Redis 更適合處理高並發,一台設備的存儲能力是有限的但是多台設備協同合作,就可以內存增大很多倍,這時就需要集群。
Redis 集群搭建的方式有很多種,但從 redis 3.0 版本之后,支持 redis-cluster 集群,它是 Redis 官方提供的解決方案,Redis Cluster 采用的是 無中心架構 ,每個節點保存數據和整個集群狀態,每個節點都和其他節點有所連接。Redis Cluster的基本原理可以從數據分片、數據遷移、集群通訊、故障檢測以及故障轉移等方面進行了解,其架構如下:
客戶端與 redis 節點直連,不需要中間件 proxy 層,客戶端不需要連接集群所有節點,連接集群匯中任何一個節點即可。所有的 redis 節點彼此互聯(PING-PONG 機制),內部使用二進制協議優化傳輸速度和帶寬。
二、分布式存儲機制-槽
【1】Redis Cluster 在設計中沒有使用一致性哈希(Consistency Hashing),而是使用數據分片(Sharding)引入哈希槽
【2】Redis Cluster 把所有的節點映射到 [0-16383] slot 槽上,cluster 負責維護 node<->slot<->value 三者之間的關系。
【3】Redis 集群中內置了 16384 個哈希槽,當需要在 Redis 集群中放置一個 key-value 時,redis 先將 key 使用 CRC16 算法算出一個結果,然后把結果對 16384 求余數,這樣每個 key 都會對應一個編號 0-16383 之間的哈希槽,redis 會根據節點數量大致均等的將哈希槽映射到不同的節點上。集群之間通過一種特殊的二進制協議交互集群信息。
例如,當有三個節點時,槽分布的值如下:
節點1: 0-5460
節點2: 5461-10921
節點3: 10922-16383
【4】Redis Cluster 允許用戶強制把某個 key 掛在特定槽位上。通過在 key 字符串里面嵌入 tag 標記,這就可以強制 key 所掛的槽位等於 tag 所在的槽位。
【5】客戶端為了可以直接定位某個具體的 key 所在的節點,需要緩存槽位相關信息,從而實現快速定位。同時因為客戶端與服務端的槽位可能不一致,還需要糾正機制來實現槽位信息的校驗調整。
【6】Redis Cluster 的每個節點會將集群的配置信息持久化到配置文件中,所以必須確保配置文件可寫,而且盡量不要依靠人工修改配置文件。
【7】ClusterNode 數據結構中的 slots和 numslots屬性記錄了節點負責處理哪些槽。其中,slot屬性是一個二進制位數組(bitarray),其長度為16384/8=2048 Byte,共包含16384個二進制位。集群中的 Master節點用bit(0和1)來標識是否擁有某個槽。比如,對於編號為1的槽,Master只要判斷序列第二位(索引從0開始)的值是不是1即可,時間復雜度為O(1)。
三、容錯機制投票
【1】選舉過程是集群中所有 master 節點參與,如果半數以上 master 節點與故障節點通信超過設置的(cluster-node-timeout),認為該節點故障,自動觸發故障轉移操作。故障節點的從節點自動升級為主節點。
【2】如果集群任意 master 掛掉,且當前 master 沒有 slave。集群進入 fail 狀態,也可以理解成集群的 slot 映射[0-16383]找不到對應的槽時進入 fail 狀態。
四、搭建Redis-Cluster
准備工作:准備六台 Redis 服務器,三台 Master 主機,三台 Slave 備機(我們因條件問題,下面六台服務器均出自一台計算機,只是端口不一致(從7001-7006))
【1】下載 Redis 的源碼文件,進行解壓(tar -zxvf redis-3.0.0.tar.gz),進入解碼目錄,對 C語言開發的 Redis 進行編譯[make],編譯完成后創建安裝的目錄(/usr/local/redis-cluster/redis-1 等),執行命令進行安裝(make install PREFIX=/usr/local/redis-cluster/redis-1 等)
【2】復制配置文件,將 /redis-3.0.0/redis.conf 復制到 redis下的 bin目錄下(cp redis.conf /usr/local/redis-cluster/redis-1/bin)
【3】修改六台服務器中的bin/redis.conf 配置文件,將配置集群字段前的注釋去掉: # cluster-enabled yes 並修改端口(45行:port 7001)
【4】安裝 Ruby 環境:
【5】網上下載 redis-3.0.0.gem
,執行 gem install redis-3.0.0.gem
安裝。
【6】使用 ruby 腳本搭建集群:進入 Redis 源碼目錄中的 src 目錄,執行如下命令:需要更換 IP 地址
出現如下情況時表示集群成功:如圖所示,7001至7003為主機,7004至7006為備機:
五、客戶端連接 RedisCluster
進入可以連接 Redis 的客戶端文件中,例如:我在 Windows 環境下使用的是 redis-cli.exe 可執行文件。通過運行如下命令:進入 Redis 集群(-h:連接的主機地址、-p:連接的端口、-c:表示集群環境,不寫表示連接的單機。Redis只需連接一個節點即可進入集群環境,可以通過 quit 命令退出 Redis 客戶端連接。通過客戶端關閉Redis服務:./redis-cli -h 地址 -p 端口 shutdown)。
■ -MOVED:前面有個減號,表示該指令是一個錯誤消息。客戶端在收到 MOVED 指令后,要立即糾正本地的槽位映射表。后續所有key 將使用新的槽位映射;
■ 3999:key 對應的槽位編號;
■ 127.0.0.1:8283:目標節點地址;
六、通過 SpringDataRedis 連接 Redis 集群:主要是兩個配置文件
redis-cluster-config.properties(主要用於配置可變的服務器地址和端口)
applicationContext-redis-cluster.xml(從 properties 獲取可變的參數作為屬性傳入集群類(redis-clusterConfiguration)中)
七、查詢集群狀態信息
通過cluster info可查詢集群狀態信息:
八、添加主節點
【1】集群創建成功后,向集群創建添加 master 節點,准備一個 Redis 節點(7007 端口)並修改配置文件,隨后將其啟動成功,再新打開一個窗口,進入存放 redis-trib.rb 文件的目錄下:執行如下命令:
【2】在客戶端執行:cluster nodes 查看集群節點:會發現新添加的集群節點 7007(作為master卻沒有槽數<最后面的x-x>)
【3】hash 槽從新分配:添加完主節點需要對主節點進行hash
槽分配這樣該主節才可以存儲數據。redis
集群有16384
個槽,集群中的每個 master
結點分配一些槽,通過查看集群結點可以看到槽占用情況。開始分配:執行如下命令
☛ 出現上述的:How many slots do you want to move(是詢問你需要分配的槽大小:我們就輸入1000<輸入1000
表示要分配1000
個槽>)
☛ 出現上述情況:詢問需要分配槽的節點id:我們就輸入7007的節點ID(就是剛才執行 cluster nodes 返回的第一行數據)
☛ 輸入源節點 id:槽將從源節點中拿,分配后的槽在源節點就不存在了。輸入 all 將從所有源節點開始拿,done 取消分配。
☛ 輸入 yes
開始移動槽到目標結點 id
:
【4】重新查看節點信息: 7007的槽(0-332 5461-5794 10923-11255)是來自其他三個master節點的部分槽。
九、添加從節點
集群創建成功后,可以向集群中插入一個 slave 從節點(准備一個 7008 端口的 Redis並將其啟動成功,我們將其配置為 7007 的從節點),打開新的窗口,進入存放 redis-trib.rb 文件的目錄下,執行如下命令(格式為:./redis-trib.rb add-node --slave --master-id 主節點id 添加節點的ip和端口 集群中已存在節點ip和端口):其中主節點ID,通過 cluster nodes 查詢獲取。
通過客戶端程序查看集群節點信息:cluster nodes 得知 7008 為 7007 的 slave 節點
十、刪除節點
【1】我們將剛添加的 7008 從節點進行刪除,命令如下:刪除后通過 cluster nodes 查看發現 7008 成功移除
【2】接着主節點 7007 會發現不能刪除,因為其占有 hash 槽,需要將槽分配給其他節點,方能刪除:
【3】將 7007 的槽分配給 7001(參考 八【3】 的槽重新分配:重點修改內容如下:)
【4】重新執行刪除 7007 節點的命令:通過 cluster nodes 會發現 7007 節點以被刪除
十一、遷移
RedisCluster 提供了工具 redis-trib 可以讓運維人員手動調整槽位的分配情況。Redis 數據遷移是槽,Reids 是一個一個槽進行遷移,當一個槽正在遷移時,這個槽就處於中間過渡狀態。如下圖:槽的源節點的狀態為 migrating(遷移),在目標節點的狀態為 importin(導入)表示數據正在從源節點流向目標節點。
遷移過程中,如果每個 key 的內容都很小,migrate 指令會執行得很快,他就不會影響客戶端的正常訪問。如果 key 的內容很大,因為 migrate 指令時阻塞指令,會同時導致源節點和目標節點卡頓,影響集群的穩定性。所以在集群環境下,業務邏輯要盡可能避免產生很大的key。
遷移過程中,客戶端訪問流程的變化: 首先新舊兩個節點對應的槽位都存在部分 key 數據。客戶端先嘗試訪問舊節點。如果對應的數據還在舊節點里面,那么舊節點正常處理。那么對應的節點不在舊節點中,那么有兩種可能,不存在或者在新節點中。此時舊節點會向客戶端發送 -ASK targetNodeAddr 的重定向指令。客戶端收到指令后,先去目標節點執行一個不帶任何參數的 ASKING 指令,然后在目標節點再重新執行原先的操作指令。
為什么需要執行一個不帶參數的 ASKING 指令:在遷移未完成之前,按理說這個槽位還是不歸新節點管理的,如果這個時候向目標節點發送該槽位的指令,節點是不認的,它會向客戶端返回一個 -MOVED 重定向指令告訴它去源節點執行。如此就會形成重定向循環。ASKING 指令的目標就是打開目標節點的選項,告訴它下一條指令不能不理,要當成自己的槽位來處理。從以上過程可以看出,遷移會影響服務效率的,同樣的指令在正常情況下一個ttl 就能完成,而在遷移情況下需要3個ttl 才能搞定。
十二、容錯
Redis Cluster 可以為每個主節點設置若干個從節點,當主節點發生故障時,集群會自動將其中某個從節點提升為主節點。如果某個主節點沒有從節點,那么當它發生故障時,集群將完全處於不可用狀態。不過 Redis 也提供了一個參數 cluster-require-full-converage 可以允許部分節點發生故障,其他節點還可以繼續提供對外訪問。
十三、網絡抖動
網絡抖動:突然間部分連接變得不可訪問,然后又很快恢復正常。為解決這個問題,Redis Cluster 提供了一種選項 cluster-node-timeout,表示當某個節點持續 timeout的時間失聯時,才可以認定該節點出現故障,需要進行主從切換。如果沒有這個選項,網絡抖動導致主從頻繁切換。cluster-slave-validity-factor 配置系數,等於零時,主從切換是不會抗拒網絡抖動的。如果這個系數大於1,他就成了主從切換的松弛系數。
【PFail 與 Fail】:只有大多數節點都認定某個節點掛了,集群才認為該節點需要進行主從切換來容錯。Redis 節點采用 Gossip 協議來廣播自己的狀態以及對整個集群的認知。比如一個節點發現某個節點失聯了(PFail:Possibly Fail),它會將這條信息整個集群廣播,其他節點就可以收到這個節點的失聯信息。如果收到了某個節點失聯的節點數量(PFail Count)已經達到了集群的大多數,就可以標記該失聯節點為確定下線狀態(Fail),然后向整個集群廣播,強迫其他節點頁接受該節點已經下線的事實,並立即對該失聯節點進行主從切換。