【Redis】《Redis 开发与运维》笔记-Chapter10-集群
十、集群
1、概述
Redis Cluster是Redis 的分布式解決方案,在3.0版本正式推出,有效地解決了Redis分布式方面的需求。
Redis在3.0版本之前分布式方案一般有兩種:
- 客戶端分區方案,優點是分區邏輯可控,缺點是需要自己處理數據路由、高可用、故障轉移等問題。
- 代理方案,優點是簡化客戶端分布式邏輯和升級維護便利,缺點是加重架構部署復雜度和性能損耗。
現在官方為我們提供了專有的集群方案: Redis Cluster,它非常優雅地解決了Redis集群方面的問題。
2、數據分布理論
- 分布式數據庫首先要解決把整個數據集按照分區規則映射到多個節點的問題,即把數據集劃分到多個節點上,每個節點負責整體數據的一個子集。
- 常見的分區規則有哈希分區和順序分區兩種。
- Redis Cluster采用哈希分區規則。
| 哈希分區 | - 離散度好 - 數據分布業務無關 - 無法順序訪問 | Redis Cluster Cassandra Dynamo |
| 順序訪問 | - 離散度易傾斜 - 數據分布業務相關 - 可順序訪問 | Bigtable HBase Hypertable |
常見的哈希分區規則有幾種:
1)節點取余分區
- 使用特定的數據,如Redis的鍵或用戶ID,再根據節點數量N使用公式:hash(key)%N計算出哈希值,用來決定數據映射到哪一個節點上。
- 缺點:當節點數量變化時,如擴容或收縮節點,數據節點映射關系需要重新計算,會導致數據的重新遷移。
- 優點:簡單性,常用于數據庫的分庫分表規則,一般采用預分區的方式,提前根據數據量規劃好分區數,比如劃分為512或1024張表,保證可支撐未來一段時間的數據量,再根據負載情況將表遷移到其他數據庫中。
- 擴容時通常采用翻倍擴容,避免數據映射全部被打亂導致全量遷移的情況。
2)一致性哈希分區
- 一致性哈希分區(Distributed Hash Table)實現思路是為系統中每個節點分配一個token,范圍一般在0~2^32,這些token構成一個哈希環。數據讀寫執行節點查找操作時,先根據key計算hash值,然后順時針找到第一個大于等于該哈希值的token節點。
- 相比節點取余最大的好處在于加入和刪除節點只影響哈希環中相鄰的節點,對其他節點無影響。
- 一致性哈希分區存在幾個問題:
- 加減節點會造成哈希環中部分數據無法命中,需要手動處理或者忽略這部分數據,因此一致性哈希常用于緩存場景。
- 當使用少量節點時,節點變化將大范圍影響哈希環中數據映射,因此這種方式不適合少量數據節點的分布式方案。
- 普通的一致性哈希分區在增減節點時需要增加一倍或減去一半節點才能保證數據和負載的均衡。
- 正因為一致性哈希分區的這些缺點,一些分布式系統采用虛擬槽對一致性哈希進行改進,比如Dynamo系統。
3)虛擬槽分區
- 虛擬槽分區巧妙地使用了哈希空間,使用分散度良好的哈希函數把所有數據映射到一個固定范圍的整數集合中,整數定義為槽(slot)。這個范圍一般遠遠大于節點數,比如Redis Cluster槽范圍是0~16383。
- 槽是集群內數據管理和遷移的基本單位。采用大范圍槽的主要目的是為了方便數據拆分和集群擴展。每個節點會負責一定數量的槽。
- Redis Cluster就是采用虛擬槽分區。
3、Redis數據分區
數據分區是分布式存儲的核心。
Redis Cluser采用虛擬槽分區,所有的鍵根據哈希函數映射到0~16383整數槽內,計算公式:slot=CRC16(key)&16383。每一個節點負責維護一部分槽以及槽所映射的鍵值數據。
Redis虛擬槽分區的特點:
- 解耦數據和節點之間的關系,簡化了節點擴容和收縮難度。
- 節點自身維護槽的映射關系,不需要客戶端或者代理服務維護槽分區元數據。
- 支持節點、槽、鍵之間的映射查詢,用于數據路由、在線伸縮等場景。
4、集群功能限制
Redis集群相對單機在功能上存在一些限制,限制如下:
- key批量操作支持有限。如mset、mget,目前只支持具有相同slot值的key執行批量操作。對于映射為不同slot值的key由于執行mget、mget等操作可能存在于多個節點上因此不被支持。
- key事務操作支持有限。同理只支持多key在同一節點上的事務操作,當多個key分布在不同的節點上時無法使用事務功能。
- key作為數據分區的最小粒度,因此不能將一個大的鍵值對象如hash、list等映射到不同的節點。
- 不支持多數據庫空間。單機下的Redis可以支持16個數據庫,集群模式下只能使用一個數據庫空間,即db0。
- 復制結構只支持一層,從節點只能復制主節點,不支持嵌套樹狀復制結構。
5、搭建集群
搭建集群工作需要以下三個步驟:
- 準備節點。
- 節點握手。
- 分配槽。
6、搭建集群步驟一:準備節點
集群相關配置如下,其他配置和單機模式一致即可,配置文件命名規則redis-{port}.conf #節點端口 port 6379 # 開啟集群模式 cluster enabled yes # 節點超時時間,單位毫秒 cluster-node-timeout 15000 # 集群內部配置文件 cluster config file "nodes 6379.conf"#cat data/nodes-6379.conf cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 co vars currentEpoch 0 lastVoteEpoch 0127.0.0.1:6380> cluster nodes // 每個節點目前只能識別出自己的節點信息(要通過節點握手才能建立聯系)。 8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 myself,master - 0 0 0 co- Redis集群一般由多個節點組成,節點數量至少為6個才能保證組成完整高可用的集群。
- 每個節點需要開啟配置cluster-enabled yes,讓Redis運行在集群模式下。
- 建議為集群內所有節點統一目錄,一般劃分三個目錄:conf、data、log,分別存放配置、數據和日志相關文件。把6個節點配置統一放在conf目錄下。
- 第一次啟動時如果沒有集群配置文件,它會自動創建一份,文件名稱采用cluster-config-file參數項控制,建議采用node-{port}.conf格式定義,通過使用端口號區分不同節點,防止同一機器下多個 節點彼此覆蓋,造成集群信息異常。
- 如果啟動時存在集群配置文件,節點會使用配置文件內容初始化集群信息。
- 集群模式的Redis除了原有的配置文件之外又加了一份集群配置文件。當集群內節點信息發生變化,如添加節點、節點下線、故障轉移等。節點會自動保存集群狀態到配置文件中。
- 需要注意的是,Redis自動維護集群配置文件,不要手動修改,防止節點重啟時產生集群信息錯亂。
- 集群配置文件內容記錄了集群初始狀態,這里最重要的是節點ID,它是一個40位16進制字符串,用于唯一標識集群內一個節點,之后很多集群操作都要借助于節點ID來完成。需要注意是,節點ID不同于運行ID。節點ID在集群初始化時只創建一次,節點重啟時會加載集群配置文件進行重用,而Redis的運行ID每次重啟都會變化。
7、搭建集群步驟二:節點握手
- 節點握手是指一批運行在集群模式下的節點通過Gossip協議彼此通信,達到感知對方的過程。
- 節點握手是集群彼此通信的第一步,由客戶端發起命令:cluster meet {ip} {port}
- cluster meet命令是一個異步命令,執行之后立刻返回。內部發起與目標節點進行握手通信。
- cluster meet 127.0.0.1 6380讓節點6379和6380節點進行握手通信:
- 節點6379本地創建6380節點信息對象,并發送meet消息。
- 節點6380接受到meet消息后,保存6379節點信息并回復pong消息。
- 之后節點6379和6380彼此定期通過ping/pong消息進行正常的節點通信。
- 這里的meet、ping、pong消息是Gossip協議通信的載體,它的主要作用是節點彼此交換狀態數據信息。
- 對節點6379和6380分別執行cluster nodes命令,可以看到它們彼此已經感知到對方的存在。
- 我們只需要在集群內任意節點上執行cluster meet命令加入新節點,握手狀態會通過消息在集群內傳播,這樣其他節點會自動發現新節點并發起握手 流程。
- 節點建立握手之后集群還不能正常工作,這時集群處于下線狀態,所有的數據讀寫都被禁止。
- 通過cluster info命令可以獲取集群當前狀態。
- 被分配的槽(cluster_slots_assigned)是0 ,由于目前所有的槽沒有分配到節點,因此集群無法完成槽到節點的映射。只有當16384個槽全部分配給節點后,集群才進入在線狀態。
8、搭建集群步驟三:分配槽
// 利用bash特性批量設置槽(slots),命令如下: redis-cli -h 127.0.0.1 p 6379 cluster addslots {0 . . .5461} redis-cli -h 127.0.0.1 -p 6380 cluster addslots {5462 . . .10922} redis-cli -h 127.0.0.1 -p 6381 cluster addslots {10923 . . .16383}// 執行cluster info查看集群狀態 127.0.0.1 :6379> cluster info cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:6 cluster_size:3 cluster_current_epoch:5 cluster_my_epoch:0 cluster_stats_messages_sent:4874 cluster_stats_messages_received:4726- Redis集群把所有的數據映射到16384個槽中。每個key會映射為一個固定的槽,只有當節點分配了槽,才能響應和這些槽關聯的鍵命令。
- 通過cluster addslots命令為節點分配槽。
- 當前集群狀態是OK,集群進入在線狀態。
- 所有的槽都已經分配給節點,執行cluster nodes命令可以看到節點和槽的分配關系。
- 作為一個完整的集群,每個負責處理槽的節點應該具有從節點,保證當它出現故障時可以自動進行故障轉移。
- 集群模式下,Reids節點角色分為主節點和從節點。首次啟動的節點和被分配槽的節點都是主節點,從節點負責復制主節點槽信息和相關的數據。
- 使用cluster replicate {nodeId}命令讓一個節點成為從節點。其中命令執行必須在對應的從節點上執行,nodeId是要復制主節點的節點ID。
- 通過cluster nodes命令查看集群狀態和復制關系。
- Redis官方提供了redis-trib.rb工具方便我們快速搭建集群。
9、用redis-trib.rb搭建集群
redis-trib.rb是采用Ruby實現的Redis集群管理工具。內部通過Cluster相關命令幫我們簡化集群創建、檢查、槽遷移和均衡等常見運維操作,使用之前需要安裝Ruby依賴環境。
1)Ruby環境準備
1、安裝Ruby: -- 下載ruby wget https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz -- 安裝ruby tar xvf ruby-2.3.1.tar.gz ./configure -prefix=/usr/local/ruby make make install cd /usr/local/ruby sudo cp bin/ruby /usr/local/bin sudo cp bin/gem /usr/local/bin2、安裝rubygem redis依賴: wget http://rubygems.org/downloads/redis-3.3.0.gem gem install -l redis-3.3.0.gem gem list --check redis gem3、安裝redis-trib.rb: sudo cp /{redis_home}/src/redis-trib.rb /usr/local/bin4、執行redis-trib.rb命令確認環境是否正確: # redis-trib.rbUsage: redis-trib <command> <options> <arguments . . .> create host1:port1 . . . hostN:portN --replicas <arg> check host:port info host:port fix host:port --timeout <arg> reshard host:port --from <arg> --to <arg> --slots <arg> --yes --timeout <arg> --pipeline <arg> . . .忽略 . . .- 從redis-trib.rb的提示信息可以看出,它提供了集群創建、檢查、修復、均衡等命令行工具。
- 使用redis-trib.rb create命令可快速搭建集群。
2)準備節點
// 準備好節點配置并啟動: redis-server conf/redis-6481.conf redis-server conf/redis-6482.conf redis-server conf/redis-6483.conf redis-server conf/redis-6484.conf redis-server conf/redis-6485.conf redis-server conf/redis-6486.conf3)創建集群
// 使用redis-trib.rb create命令完成節點握手和槽分配過程,命令如下: redis-trib.rb create --replicas 1 127.0.0.1:6481 127.0.0.1:6482 127.0.0.1:6483 127.0.0.1:6484 127.0.0.1:6485 127.0.0.1:6486- –replicas參數指定集群中每個主節點配備幾個從節點,這里設置為1。
- 如果部署節點使用不同的IP地址,redis-trib.rb會盡可能保證主從節點不分配在同一機器下。
- 節點列表順序用于確定主從角色,先主節點之后是從節點。
- 需要注意給redis-trib.rb的節點地址必須是不包含任何槽/數據的節點,否則會拒絕創建集群。
4)集群完整性檢查
redis-trib.rb check 127.0.0.1:6481- 集群完整性指所有的槽都分配到存活的主節點上,只要16384個槽中有一個沒有分配給節點則表示集群不完整。
- 可以使用redis-trib.rb check命令檢測創建的集群是否成功,check命令只需要給出集群中任意一個節點地址就可以完成整個集群的檢查工作。
10、節點通信流程
- 在分布式存儲中需要提供維護節點元數據信息的機制,所謂元數據是指:節點負責哪些數據,是否出現故障等狀態信息。
- 常見的元數據維護方式分為:集中式和P2P方式。Redis集群采用P2P的Gossip(流言)協議,Gossip協議工作原理就是節點彼此不斷通信交換信息,一段時間后所有的節點都會知道集群完整的信息。
- 通信過程說明:
- 集群中的每個節點都會單獨開辟一個TCP通道,用于節點之間彼此通信,通信端口號在基礎端口上加10000。
- 每個節點在固定周期內通過特定規則選擇幾個節點發送ping消息。
- 接收到ping消息的節點用pong消息作為響應。
- 集群中每個節點通過一定規則挑選要通信的節點,每個節點可能知道全部節點,也可能僅知道部分節點,只要這些節點彼此可以正常通信,最終它們會達到一致的狀態。當節點出故障、新節點加入、主從角色變化、槽信息變更等事件發生時,通過不斷的ping/pong消息通信,經過一段時間后所有的節點都會知道整個集群全部節點的最新狀態,從而達到集群狀態同步的目的。
11、Gossip消息
- Gossip協議的主要職責就是信息交換。信息交換的載體就是節點彼此發送的Gossip消息。
- 常用的Gossip消息可分為: ping消息、pong消息、meet消息、fail消息等。
- meet消息:用于通知新節點加入。消息發送者通知接收者加入到當前集群,meet消息通信正常完成后,接收節點會加入到集群中并進行周期性的ping、pong消息交換。
- ping消息:集群內交換最頻繁的消息,集群內每個節點每秒向多個其他節點發送ping消息,用于檢測節點是否在線和交換彼此狀態信息。ping消息發送封裝了自身節點和部分其他節點的狀態數據。
- pong消息:當接收到ping、meet消息時,作為響應消息回復給發送方確認消息正常通信。pong消息內部封裝了自身狀態數據。節點也可以向集群內廣播自身的pong消息來通知整個集群對自身狀態進行更新。
- fail消息:當節點判定集群內另一個節點下線時,會向集群內廣播一個fail消息,其他節點接收到fail消息之后把對應節點更新為下線狀態。
- 所有的消息格式劃分為:消息頭和消息體。消息頭包含發送節點自身狀態數據,接收節點根據消息頭就可以獲取到發送節點的相關數據。
- 集群內所有的消息都采用相同的消息頭結構clusterMsg,它包含了發送節點關鍵信息,如節點id 、槽映射、節點標識(主從角色,是否下線)等。消息體在Redis內部采用clusterMsgData結構聲明。
- 消息體clusterMsgData定義發送消息的數據,其中ping、meet、pong都采用cluster MsgDataGossip數組作為消息體數據,實際消息類型使用消息頭的type屬性區分。每個消息體包含該節點的多個clusterMsgDataGossip結構數據,用于信息交換。
- 當接收到ping、meet消息時,接收節點會解析消息內容并根據自身的識別情況做出相應處理。
12、節點選擇
- Redis集群內節點通信采用固定頻率(定時任務每秒執行10次)。因此節點每次選擇需要通信的節點列表變得非常重要。通信節點選擇過多雖然可以做到信息及時交換但成本過高。節點選擇過少會降低集群內所有節點彼此信息交換頻率,從而影響故障判定、新節點發現等需求的速度。因此Redis集群的Gossip協議需要兼顧信息交換實時性和成本開銷。
- 消息交換的成本主要體現在單位時間選擇發送消息的節點數量和每個消息攜帶的數據量。
1)選擇發送消息的節點數量
- 集群內每個節點維護定時任務默認每秒執行10次,每秒會隨機選取5個節點找出最久沒有通信的節點發送ping消息,用于保證Gossip信息交換的隨機性。
- 每100毫秒都會掃描本地節點列表,如果發現節點最近一次接受pong消息的時間大于cluster_node_timeout/2 ,則立刻發送ping消息,防止該節點信息太長時間未更新。
- 根據以上規則得出每個節點每秒需要發送ping消息的數量= 1+10*num(node.pong_received>cluster_node_timeout/2),因此cluster_node_timeout參數對消息發送的節點數量影響非常大。當我們的帶寬資源緊張時,可以適當調大這個參數,如從默認15秒改為30秒來降低帶寬占用率。過度調大cluster_node_timeout會影響消息交換的頻率從而影響故障轉移、槽信息更新、新節點發現的速度。因此需要根據業務容忍度和資源消耗進行平衡。
- 同時整個集群消息總交換量也跟節點數成正比。
2)消息數據量
- 每個ping消息的數據量體現在消息頭和消息體中,其中消息頭主要占用空間的字段是myslots[CLUSTER_SLOTS/8],占用2KB,這塊空間占用相對固定。消息體會攜帶一定數量的其他節點信息用于信息交換。
- 消息體攜帶數據量跟集群的節點數息息相關,更大的集群每次消息通信的成本也就更高。
13、集群伸縮原理
- Redis集群可以實現對節點的靈活上下線控制。其中原理可抽象為槽和對應數據在不同節點之間靈活移動。
- 如果希望加入1個節點實現集群擴容時,需要通過相關命令把一部分槽和數據遷移給新節點,因此每個節點負責的槽和數據相比之前變少了,從而達到了集群擴容的目的。
14、擴容集群
1)準備新節點
- 需要提前準備好新節點并運行在集群模式下,新節點建議跟集群內的節點配置保持一致,便于管理統一。
2)加入集群
- 新節點依然采用cluster meet命令加入到現有集群中。在集群內任意節點執行cluster meet命令讓6385和6386節點加入進來。
- 集群內新舊節點經過一段時間的ping/pong消息通信之后,所有節點會發現新節點并將它們的狀態保存到本地。
- 新節點剛開始都是主節點狀態,但是由于沒有負責的槽,所以不能接受任何讀寫操作。對于新節點的后續操作我們一般有兩種選擇:
- 為它遷移槽和數據實現擴容。
- 作為其他主節點的從節點負責故障轉移。
- redis-trib.rb工具也實現了為現有集群添加新節點的命令,還實現了直接添加為從節點的支持,命令見下方。內部同樣采用cluster meet命令實現加入集群功能。
- 正式環境建議使用redis-trib.rb add-node命令加入新節點,該命令內部會執行新節點狀態檢查,如果新節點已經加入其他集群或者包含數據,則放棄集群加入操作并打印相關信息。如果我們手動執行cluster meet命令加入已經存在于其他集群的節點,會造成被加入節點的集群合并到現有集群的情況,從而造成數據丟失和錯亂,后果非常嚴重,線上謹慎操作。
3)遷移槽和數據
- 加入集群后需要為新節點遷移槽和相關數據,槽在遷移過程中集群可以正常提供讀寫服務,遷移過程是集群擴容最核心的環節。
- 槽遷移計劃:槽是Redis集群管理數據的基本單位,首先需要為新節點制定槽的遷移計劃,確定原有節點的哪些槽需要遷移到新節點。遷移計劃需要確保每個節點負責相似數量的槽,從而保證各節點的數據均勻。
- 遷移數據:數據遷移過程是逐個槽進行的,每個槽數據遷移的流程說明如下:
- 對目標節點發送cluster setslot {slot} importing {sourceNodeId}命令,讓目標節點準備導入槽的數據。
- 對源節點發送cluster setslot {slot} migrating {targetNodeId}命令,讓源節點準備遷出槽的數據。
- 源節點循環執行cluster getkeysinslot {slot} {count}命令,獲取count個屬于槽{slot}的鍵。
- 在源節點上執行migrate {targetIp}{targetPort}""0{timeout}keys {keys…}命令,把獲取的鍵通過流水線(pipeline)機制批量遷移到目標節點,批量遷移版本的migrate命令在Redis3.0.6以上版本提供,之前的migrate命令只能單個鍵遷移。對于大量key的場景,批量鍵遷移將極大降低節點之間網絡IO次數。
- 重復執行步驟3)和步驟4)直到槽下所有的鍵值數據遷移到目標節點。
- 向集群內所有主節點發送cluster setslot {slot} node {targetNodeId}命令,通知槽分配給目標節點。為了保證槽節點映射變更及時傳播,需要遍歷發送給所有主節點更新被遷移的槽指向新節點。
- 添加從節點:使用cluster replicate {masterNodeId}命令為主節點添加對應從節點,注意在集群模式下slaveof添加從節點操作不再支持。從節點內部除了對主節點發起全量復制之外,還需要更新本地節點的集群相關狀態。
實際操作槽遷移過程時肯定涉及大量槽并且每個槽對應非常多的鍵。因此redis-trib提供了槽重分片功能,命令如下:
redis-trib.rb reshard host:port --from <arg> --to <arg> --slots <arg> --yes --t <arg> --pipeline <arg>
參數說明:
- host:port:必傳參數,集群內任意節點地址,用來獲取整個集群信息。
- –from:制定源節點的id,如果有多個源節點,使用逗號分隔,如果是all源節點變為集群內所有主節點,在遷移過程中提示用戶輸入。
- –to:需要遷移的目標節點的id,目標節點只能填寫一個,在遷移過程中提示用戶輸入。
- –slots:需要遷移槽的總數量,在遷移過程中提示用戶輸入。
- –yes:當打印出reshard執行計劃時,是否需要用戶輸入yes確認后再執行reshard。
- –timeout:控制每次migrate操作的超時時間,默認為60000毫秒。
- –pipeline:控制每次批量遷移鍵的數量,默認為10。
reshard命令簡化了數據遷移的工作量,其內部針對每個槽的數據遷移同樣使用之前的流程。
由于槽用于hash運算本身順序沒有意義,因此無須強制要求節點負責槽的順序性。遷移之后建議使用redis-trib.rb rebalance命令檢查節點之間槽的均衡性。
15、收縮集群
安全下線節點的流程說明:
- 首先需要確定下線節點是否有負責的槽,如果是,需要把槽遷移到其他節點,保證節點下線后整個集群槽節點映射的完整性。
- 當下線節點不再負責槽或者本身是從節點時,就可以通知集群內其他節點忘記下線節點,當所有的節點忘記該節點后可以正常關閉。
1)下線遷移槽
- 下線節點需要把自己負責的槽遷移到其他節點,原理與之前節點擴容的遷移槽過程一致。
- 收縮正好和擴容遷移方向相反。
- 下線節點槽遷出完成后,剩下的步驟需要讓集群忘記該節點。
2)忘記節點
- 由于集群內的節點不停地通過Gossip消息彼此交換節點狀態,因此需要通過一種健壯的機制讓集群內所有節點忘記下線的節點。也就是說讓其他節點不再與要下線節點進行Gossip消息交換。Redis提供了cluster forget{downNodeId}命令實現該功能。
- 當節點接收到cluster forget{down NodeId}命令后,會把nodeId指定的節點加入到禁用列表中,在禁用列表內的節點不再發送Gossip消息。
- 禁用列表有效期是60秒,超過60秒節點會再次參與消息交換。也就是說當第一次forget命令發出后,我們有60秒的時間讓集群內的所有節點忘記下線節點。
- 線上操作不建議直接使用cluster forget命令下線節點,需要跟大量節點命令交互,實際操作起來過于繁瑣并且容易遺漏forget節點。建議使用redis-trib.rb del-node {host:port}{downNodeId}命令。
- 當下線主節點具有從節點時需要把該從節點指向到其他主節點,因此對于主從節點都下線的情況,建議先下線從節點再下線主節點,防止不必要的全量復制。
16、請求路由——請求重定向
- Redis集群對客戶端通信協議做了比較大的修改,為了追求性能最大化,并沒有采用代理的方式而是采用客戶端直連節點的方式。因此對于希望從單機切換到集群環境的應用需要修改客戶端代碼。
- 在集群模式下,Redis接收任何鍵相關命令時首先計算鍵對應的槽,再根據槽找出所對應的節點,如果節點是自身,則處理鍵命令;否則回復MOVED重定向錯誤,通知客戶端請求正確的節點。這個過程稱為MOVED重定向。
- 可以借助cluster keyslot{key}命令返回key所對應的槽。
- 重定向信息包含了鍵所對應的槽以及負責該槽的節點地址,根據這些信息客戶端就可以向正確的節點發起請求。
- 使用redis-cli命令時,可以加入-c參數支持自動重定向,簡化手動發起重定向操作。
- redis-cli自動幫我們連接到正確的節點執行命令,這個過程是在redis-cli內部維護,實質上是client端接到MOVED信息之后再次發起請求,并不在Redis節點中完成請求轉發。
- 節點對于不屬于它的鍵命令只回復重定向響應,并不負責轉發。
- 正因為集群模式下把解析發起重定向的過程放到客戶端完成,所以集群客戶端協議相對于單機有了很大的變化。
- 鍵命令執行步驟主要分兩步:計算槽,查找槽所對應的節點。
1)計算槽
- Redis首先需要計算鍵所對應的槽。根據鍵的有效部分使用CRC16函數計算出散列值,再取對16383的余數,使每個鍵都可以映射到0~16383槽范圍內。
- 如果鍵內容包含{和}大括號字符,則計算槽的有效部分是括號內的內容;否則采用鍵的全內容計算槽。其中鍵內部使用大括號包含的內容又叫做hash_tag,它提供不同的鍵可以具備相同slot的功能,常用于Redis IO優化。例如在集群模式下使用mget等命令優化批量調用時,鍵列表必須具有相同的slot,否則會報錯。這時可以利用hash_tag讓不同的鍵具有相同的slot達到優化的目的。
- Pipeline同樣可以受益于hash_tag,由于Pipeline只能向一個節點批量發送執行命令,而相同slot必然會對應到唯一的節點,降低了集群使用Pipeline的門檻。
2)槽節點查找
- Redis計算得到鍵對應的槽后,需要查找槽所對應的節點。集群內通過消息交換每個節點都會知道所有節點的槽信息,內部保存在clusterState結構中。
- 根據MOVED重定向機制,客戶端可以隨機連接集群內任一Redis獲取鍵所在節點,這種客戶端又叫Dummy(傀儡)客戶端,它優點是代碼實現簡單,對客戶端協議影響較小,只需要根據重定向信息再次發送請求即可。但是它的弊端很明顯,每次執行鍵命令前都要到Redis上進行重定向才能找到要執行命令的節點,額外增加了IO開銷,這不是Redis集群高效的使用方式。正因為如此通常集群客戶端都采用另一種實現:Smart(智能)客戶端。
17、請求路由——Smart客戶端
1)Smart客戶端原理
- 大多數開發語言的Redis客戶端都采用Smart客戶端支持集群協議。
- Smart客戶端通過在內部維護slot→node的映射關系,本地就可實現鍵到節點的查找,從而保證IO效率的最大化,而MOVED重定向負責協助Smart客戶端更新slot→node映射。
- Smart客戶端操作集群的流程:略。
2)Smart客戶端——JedisCluster
- Redis Cluster雖然提供了分布式的特性,但是有些命令或者操作,諸如keys、flushall、刪除指定模式的鍵,需要遍歷所有節點才可以完成。
- Redis Cluster中,由于key分布到各個節點上,會造成無法實現mget、mset等功能。但是可以利用CRC16算法計算出key對應的slot,以及Smart客戶端保存了slot和節點對應關系的特性,將屬于同一個Redis節點的key進行歸檔,然后分別對每個節點對應的子key列表執行mget或者pipeline操作。
- Lua和事務需要所操作的key,必須在一個節點上,不過Redis Cluster提供了hashtag,如果開發人員確實要使用Lua或者事務,可以將所要操作的key使用一個hashtag。
18、請求路由——ASK重定向
1)客戶端ASK重定向流程
- Redis集群支持在線遷移槽(slot)和數據來完成水平伸縮,當slot對應的數據從源節點到目標節點遷移過程中,客戶端需要做到智能識別,保證鍵命令可正常執行。
- 當一個slot數據從源節點遷移到目標節點時,期間可能出現一部分數據在源節點,而另一部分在目標節點的情況時,客戶端鍵命令執行流程將發生變化:
- 客戶端根據本地slots緩存發送命令到源節點,如果存在鍵對象則直接執行并返回結果給客戶端。
- 如果鍵對象不存在,則可能存在于目標節點,這時源節點會回復ASK重定向異常。格式如下:(error)ASK{slot}{targetIP} :{targetPort}。
- 客戶端從ASK重定向異常提取出目標節點信息,發送asking命令到目標節點打開客戶端連接標識,再執行鍵命令。如果存在則執行,不存在則返回不存在信息。
- ASK與MOVED雖然都是對客戶端的重定向控制,但是有著本質區別。ASK重定向說明集群正在進行slot數據遷移,客戶端無法知道什么時候遷移完成,因此只能是臨時性的重定向,客戶端不會更新slots緩存。但是MOVED重定向說明鍵對應的槽已經明確指定到新的節點,因此需要更新slots緩存。
2)節點內部處理
- 為了支持ASK重定向,源節點和目標節點在內部的clusterState結構中維護當前正在遷移的槽信息,用于識別槽遷移情況。
- 節點每次接收到鍵命令時,都會根據clusterState內的遷移屬性進行命令處理,如下所示:
- 如果鍵所在的槽由當前節點負責,但鍵不存在則查找migrating_slots_to數組查看槽是否正在遷出,如果是返回ASK重定向。
- 如果客戶端發送asking命令打開了CLIENT_ASKING標識,則該客戶端下次發送鍵命令時查找importing_slots_from數組獲取clusterNode ,如果指向自身則執行命令。
- 需要注意的是,asking命令是一次性命令,每次執行完后客戶端標識都會修改回原狀態,因此每次客戶端接收到ASK重定向后都需要發送asking命令。
- 批量操作。ASK重定向對單鍵命令支持得很完善,但是,在開發中我們經常使用批量操作,如mget或pipeline。當槽處于遷移狀態時,批量操作會受到影響。
- 當在集群環境下使用mget、mset等批量操作時,slot遷移數據期間由于鍵列表無法保證在同一節點,會導致大量錯誤。
- 使用smart客戶端批量操作集群時,需要評估mget/mset、Pipeline等方式在slot遷移場景下的容錯性,防止集群遷移造成大量錯誤和數據丟失的情況。
- 集群環境下對于使用批量操作的場景,建議優先使用Pipeline方式,在客戶端實現對ASK重定向的正確處理,這樣既可以受益于批量操作的IO優化,又可以兼容slot遷移場景。
19、故障轉移之故障發現
- Redis集群內節點通過ping/pong消息實現節點通信,消息不但可以傳播節點槽信息,還可以傳播其他狀態如:主從狀態、節點故障等。因此故障發現也是通過消息傳播機制實現的,主要環節包括:主觀下線(pfail)和客觀下線(fail)。
- 主觀下線:指某個節點認為另一個節點不可用,即下線狀態,這個狀態并不是最終的故障判定,只能代表一個節點的意見,可能存在誤判情況。
- 客觀下線:指標記一個節點真正的下線,集群內多個節點都認為該節點不可用,從而達成共識的結果。如果是持有槽的主節點故障,需要為該節點進行故障轉移。
- 主觀下線:集群中每個節點都會定期向其他節點發送ping消息,接收節點回復pong消息作為響應。如果在cluster-node-timeout時間內通信一直失敗,則發送節點會認為接收節點存在故障,把接收節點標記為主觀下線(pfail)狀態。流程說明如下:
- 節點a發送ping消息給節點b,如果通信正常將接收到pong消息,節點a更新最近一次與節點b的通信時間。
- 如果節點a與節點b通信出現問題則斷開連接,下次會進行重連。如果一直通信失敗,則節點a記錄的與節點b最后通信時間將無法更新。
- 節點a內的定時任務檢測到與節點b最后通信時間超高cluster-node-timeout時,更新本地對節點b的狀態為主觀下線(pfail)。
- 主觀下線簡單來講就是,當cluster-note-timeout時間內某節點無法與另一個節點順利完成ping消息通信時,則將該節點標記為主觀下線狀態。每個節點內的cluster State結構都需要保存其他節點信息,用于從自身視角判斷其他節點的狀態。結構關鍵屬性中最重要的屬性是flags,用于標示該節點對應狀態。
- 當某個節點判斷另一個節點主觀下線后,相應的節點狀態會跟隨消息在集群內傳播。ping/pong消息的消息體會攜帶集群1/10的其他節點狀態數據,當接受節點發現消息體中含有主觀下線的節點狀態時,會在本地找到故障節點的ClusterNode結構,保存到下線報告鏈表中。通過Gossip消息傳播,集群內節點不斷收集到故障節點的下線報告。當半數以上持有槽的主節點都標記某個節點是主觀下線時,觸發客觀下線流程。流程說明如下:
- 當消息體內含有其他節點的pfail狀態會判斷發送節點的狀態,如果發送節點是主節點則對報告的pfail狀態處理,從節點則忽略。
- 找到pfail對應的節點結構,更新clusterNode內部下線報告鏈表。
- 根據更新后的下線報告鏈表告嘗試進行客觀下線。
- 為什么必須是負責槽的主節點參與故障發現決策?因為集群模式下只有處理槽的主節點才負責讀寫請求和集群槽等關鍵信息維護,而從節點只進行主節點數據和狀態信息的復制。
- 為什么半數以上處理槽的主節點?必須半數以上是為了應對網絡分區等原因造成的集群分割情況,被分割的小集群因為無法完成從主觀下線到客觀下線這一關鍵過程,從而防止小集群完成故障轉移之后繼續對外提供服務。
- 每個節點ClusterNode結構中都會存在一個下線鏈表結構,保存了其他主節點針對當前節點的下線報告。下線報告中保存了報告故障的節點結構和最近收到下線報告的時間,當接收到fail狀態時,會維護對應節點的下線上報鏈表。每個下線報告都存在有效期,每次在嘗試觸發客觀下線時,都會檢測下線報告是否過期,對于過期的下線報告將被刪除。如果在cluster-node-time2的時間內該下線報告沒有得到更新則過期并刪除。下線報告的有效期限是server.cluster_node_timeout2 ,主要是針對故障誤報的情況。
- 如果在cluster-node-time*2時間內無法收集到一半以上槽節點的下線報告,那么之前的下線報告將會過期,也就是說主觀下線上報的速度追趕不上下線報告過期的速度,那么故障節點將永遠無法被標記為客觀下線從而導致故障轉移失敗。因此不建議將cluster-node-time設置得過小。
- 集群中的節點每次接收到其他節點的pfail狀態,都會嘗試觸發客觀下線,流程說明如下:
- 首先統計有效的下線報告數量,如果小于集群內持有槽的主節點總數的一半則退出。
- 當下線報告大于槽主節點數量一半時,標記對應故障節點為客觀下線狀態。
- 向集群廣播一條fail消息,通知所有的節點將故障節點標記為客觀下線,fail消息的消息體只包含故障節點的ID。
- 廣播fail消息是客觀下線的最后一步,它承擔著非常重要的職責:
- 通知集群內所有的節點標記故障節點為客觀下線狀態并立刻生效。
- 通知故障節點的從節點觸發故障轉移流程。
- 網絡分區會導致分割后的小集群無法收到大集群的fail消息,因此如果故障節點所有的從節點都在小集群內將導致無法完成后續故障轉移,因此部署主從結構時需要根據自身機房/機架拓撲結構,降低主從被分區的可能性。
20、故障轉移之故障恢復
- 故障節點變為客觀下線后,如果下線節點是持有槽的主節點則需要在它的從節點中選出一個替換它,從而保證集群的高可用。下線主節點的所有從 節點承擔故障恢復的義務,當從節點通過內部定時任務發現自身復制的主節點進入客觀下線時,將會觸發故障恢復流程。流程說明如下:
- 資格檢查:每個從節點都要檢查最后與主節點斷線時間,判斷是否有資格替換故障的主節點。如果從節點與主節點斷線時間超過cluster-node-time*cluster-slave-validity-factor,則當前從節點不具備故障轉移資格。參數cluster-slave-validity-factor用于從節點的有效因子,默認為10。
- 準備選舉時間:當從節點符合故障轉移資格后,更新觸發故障選舉的時間,只有到達該時間后才能執行后續流程。之所以采用延遲觸發機制,主要是通過對多個從節點使用不同的延遲選舉時間來支持優先級問題。復制偏移量越大說明從節點延遲越低,那么它應該具有更高的優先級來替換故障主節點。所有的從節點中復制偏移量最大的將提前觸發故障選舉流程。
- 發起選舉:當從節點定時任務檢測到達故障選舉時間(failover_auth_time)到達后,發起選舉流程如下:
- 更新配置紀元:配置紀元是一個只增不減的整數,每個主節點自身維護一個配置紀元(clusterNode.configEpoch)標示當前主節點的版本,所有主節點的配置紀元都不相等,從節點會復制主節點的配置紀元。整個集群又維護一個全局的配置紀元(clusterState.current Epoch),用于記錄集群內所有主節點配置紀元的最大版本。
- 廣播選舉消息:在集群內廣播選舉消息(FAILOVER_AUTH_REQUEST),并記錄已發送過消息的狀態,保證該從節點在一個配置紀元內只能發起一次選舉。消息內容如同ping消息只是將type類型變為FAILOVER_AUTH_REQUEST。
- 選舉投票:只有持有槽的主節點才會處理故障選舉消息(FAILOVER_AUTH_REQUEST),因為每個持有槽的節點在一個配置紀元內都有唯一的一張選票,當接到第一個請求投票的從節點消息時回復FAILOVER_AUTH_ACK消息作為投票,之后相同配置紀元內其他從節點的選舉消息將忽略。
- 替換主節點:當從節點收集到足夠的選票之后,觸發替換主節點操作:
- 當前從節點取消復制變為主節點。
- 執行clusterDelSlot操作撤銷故障主節點負責的槽,并執行clusterAddSlot把這些槽委派給自己。
- 向集群廣播自己的pong消息,通知集群內所有的節點當前從節點變為主節點并接管了故障主節點的槽信息。
- 執行cluster info命令可以查看配置紀元信息。
- 配置紀元會跟隨ping/pong消息在集群內傳播,當發送方與接收方都是主節點且配置紀元相等時代表出現了沖突,nodeId更大的一方會遞增全局配置紀元并賦值給當前節點來區分沖突。
- 配置紀元的主要作用:
- 標示集群內每個主節點的不同版本和當前集群最大的版本。
- 每次集群發生重要事件時,這里的重要事件指出現新的主節點(新加入的或者由從節點轉換而來),從節點競爭選舉。都會遞增集群全局的配置紀元并賦值給相關主節點,用于記錄這一關鍵事件。
- 主節點具有更大的配置紀元代表了更新的集群狀態,因此當節點間進行ping/pong消息交換時,如出現slots等關鍵信息不一致時,以配置紀元更大的一方為準,防止過時的消息狀態污染集群。
- 配置紀元的應用場景有:
- 新節點加入。
- 槽節點映射沖突檢測。
- 從節點投票選舉沖突檢測。
- 之前在通過cluster setslot命令修改槽節點映射時,需要確保執行請求的主節點本地配置紀元(configEpoch)是最大值,否則修改后的槽信息在消息傳播中不會被擁有更高的配置紀元的節點采納。由于Gossip通信機制無法準確知道當前最大的配置紀元在哪個節點,因此在槽遷移任務最后的cluster setslot {slot} node {nodeId}命令需要在全部主節點中執行一遍。
- 從節點每次發起投票時都會自增集群的全局配置紀元,并單獨保存在clusterState.failover_auth_epoch變量中用于標識本次從節點發起選舉的版本。
- 投票過程其實是一個領導者選舉的過程,如集群內有N個持有槽的主節點代表有N張選票。由于在每個配置紀元內持有槽的主節點只能投票給一個從節點,因此只能有一個從節點獲得N/2+ 1的選票,保證能夠找出唯一的從節點。
- Redis集群沒有直接使用從節點進行領導者選舉,主要因為從節點數必須大于等于3個才能保證湊夠N/2+1個節點,將導致從節點資源浪費。使用集群內所有持有槽的主節點進行領導者選舉,即使只有一個從節點也可以完成選舉過程。當從節點收集到N/2+ 1個持有槽的主節點投票時,從節點可以執行替換主節點操作。
- 故障主節點也算在投票數內,假設集群內節點規模是3主3從,其中有2個主節點部署在一臺機器上,當這臺機器宕機時,由于從節點無法收集到3/2+1個主節點選票將導致故障轉移失敗。這個問題也適用于故障發現環節。因此部署集群時所有主節點最少需要部署在3臺物理機上才能避免單點問題。
- 投票作廢:每個配置紀元代表了一次選舉周期,如果在開始投票之后的cluster-node-timeout*2時間內從節點沒有獲取足夠數量的投票,則本次選舉作廢。從節點對配置紀元自增并發起下一輪投票,直到選舉成功為止。
21、故障轉移之故障轉移時間
- 估算出故障轉移時間:
- 主觀下線(pfail)識別時間=cluster-node-timeout。
- 主觀下線狀態消息傳播時間<=cluster-node-timeout/2。消息通信機制對超過cluster-node-timeout/2未通信節點會發起ping消息,消息體在選擇包含哪些節點時會優先選取下線狀態節點,所以通常這段時間內能夠收集到半數以上主節點的pfail報告從而完成故障發現。
- 從節點轉移時間<=1000毫秒。由于存在延遲發起選舉機制,偏移量最大的從節點會最多延遲1秒發起選舉。通常第一次選舉就會成功,所以從節點執行轉移時間在1秒以內。
根據以上分析可以預估出故障轉移時間,如下:
failover-time (毫秒) ≤ cluster-node-timeout + cluster-node-timeout/2 + 1000
因此,故障轉移時間跟cluster-node-timeout參數息息相關,默認15秒。配置時可以根據業務容忍度做出適當調整,但不是越小越好。
22、集群運維——集群完整性
- 為了保證集群完整性,默認情況下當集群16384個槽任何一個沒有指派到節點時整個集群不可用。執行任何鍵命令返回(error)CLUSTERDOWN Hash slot not served錯誤。
- 當持有槽的主節點下線時,從故障發現到自動完成轉移期間整個集群是不可用狀態,對于大多數業務無法容忍這種情況, 因此建議將參數cluster-require-full-coverage配置為no,當主節點故障時只影響它負責槽的相關命令執行,不會影響其他主節點的可用性。
23、集群運維——帶寬消耗
- 集群內Gossip消息通信本身會消耗帶寬,官方建議集群最大規模在1000以內,也是出于對消息通信成本的考慮,因此單集群不適合部署超大規模的節點。
- 節點間消息通信對帶寬的消耗體現在以下幾個方面:
- 消息發送頻率:跟cluster-node-timeout密切相關,當節點發現與其他節點最后通信時間超過cluster-node-timeout/2時會直接發送ping消息。
- 消息數據量:每個消息主要的數據占用包含:slots槽數組(2KB空間)和整個集群1/10的狀態數據(10個節點狀態數據約1KB)。
- 節點部署的機器規模:機器帶寬的上線是固定的,因此相同規模的集群分布的機器越多每臺機器劃分的節點越均勻,則集群內整體的可用帶寬越高。
- 集群帶寬消耗主要分為:讀寫命令消耗+Gossip消息消耗。因此搭建Redis集群時需要根據業務數據規模和消息通信成本做出合理規劃:
- 在滿足業務需要的情況下盡量避免大集群。同一個系統可以針對不同業務場景拆分使用多套集群。這樣每個集群既滿足伸縮性和故障轉移要求,還可以規避大規模集群的弊端。
- 適度提高cluster-node-timeout降低消息發送頻率,同時cluster-node-timeout還影響故障轉移的速度,因此需要根據自身業務場景兼顧二者的平衡。
- 如果條件允許集群盡量均勻部署在更多機器上。避免集中部署,如集群有60個節點,集中部署在3臺機器上每臺部署20個節點,這時機器帶寬消耗將非常嚴重。
24、集群運維——Pub/Sub廣播問題
- Redis在2.0版本提供了Pub/Sub(發布/訂閱)功能,用于針對頻道實現消息的發布和訂閱。但是在集群模式下內部實現對所有的publish命令都會向所有的節點進行廣播,造成每條publish數據都會在集群內所有節點傳播一次,加重帶寬負擔。
- 當頻繁應用 Pub/Sub功能時應該避免在大量節點的集群內使用,否則會嚴重消耗集群內網絡帶寬。針對這種情況建議使用sentinel結構專門用于Pub/Sub功能,從而規避這一問題。
25、集群運維——集群傾斜
- 集群傾斜指不同節點之間數據量和請求量出現明顯差異。
1)數據傾斜
- 數據傾斜主要分為以下幾種:
- 節點和槽分配嚴重不均。
- 針對每個節點分配的槽不均的情況,可以使用redis-trib.rb info {host:ip}進行定位,會列舉出每個節點負責的槽和鍵總量以及每個槽平均鍵數量。
- 當節點對應槽數量不均勻時,可以使用redis-trib.rb rebalance命令進行平衡。
- 不同槽對應鍵數量差異過大。
- 鍵通過CRC16哈希函數映射到槽上,正常情況下槽內鍵數量會相對均勻。但當大量使用hash_tag時,會產生不同 的鍵映射到同一個槽的情況。特別是選擇作為hash_tag的數據離散度較差時,將加速槽內鍵數量傾斜情況。
- 通過命令:cluster countkeysinslot{slot}可以獲取槽對應的鍵數量,識別出哪些槽映射了過多的鍵。再通過命令cluster getkeysinslot{slot}{count}循環迭代出槽下所有的鍵。從而發現過度使用hash_tag的鍵。
- 集合對象包含大量元素。
- 對于大集合對象的識別可以使用redis-cli-- bigkeys命令識別。找出大集合之后可以根據業務場景進行拆分。
- 同時集群槽數據遷移是對鍵執行migrate操作完成,過大的鍵集合如幾百兆,容易造成migrate命令超時導致數據遷移失敗。
- 內存相關配置不一致。
- 內存相關配置指hash-max-ziplist-value、set-max-intset-entries等壓縮數據結構配置。當集群大量使用hash、set等數據結構時,如果內存壓縮數據結構配置不一致,極端情況下會相差數倍的內存,從而造成節點內存量傾斜。
2)請求傾斜
- 集群內特定節點請求量/流量過大將導致節點之間負載不均,影響集群均衡和運維成本。常出現在熱點鍵場景,當鍵命令消耗較低時如小對象的get、set、incr等,即使請求量差異較大一般也不會產生負載嚴重不均。
- 但是當熱點鍵對應高算法復雜度的命令或者是大對象操作如hgetall、smembers等,會導致對應節點負載過高的情況。避免方式如下:
- 合理設計鍵,熱點大集合對象做拆分或使用hmget替代hgetall避免整體讀取。
- 不要使用熱鍵作為hash_tag,避免映射到同一槽。
- 對于一致性要求不高的場景,客戶端可使用本地緩存減少熱鍵調用。
26、集群運維——集群讀寫分離
1)只讀連接
- 集群模式下從節點不接受任何讀寫請求,發送過來的鍵命令會重定向到負責槽的主節點上(其中包括它的主節點)。
- 當需要使用從節點分擔主節點讀壓力時,可以使用readonly命令打開客戶端連接只讀狀態。之前的復制配置slave-read-only在集群模式下無效。當開啟只讀狀態時,從節點接收讀命令處理流程變為:如果對應的槽屬于自己正在復制的主節點則直接執行讀命令,否則返回重定向信息。
- readonly命令是連接級別生效,因此每次新建連接時都需要執行readonly開啟只讀狀態。執行readwrite命令可以關閉連接只讀狀態。
2)讀寫分離
- 針對從節點故障問題,客戶端需要維護可用節點列表,集群提供了cluster slaves {nodeId}命令,返回nodeId對應主節點下所有從節點信息,數據格式同cluster nodes。解析從節點列表信息,排除fail狀態節點,這樣客戶端對從節點的故障判定可以委托給集群處理,簡化維護可用從節點列表難度。
- 集群模式下讀寫分離涉及對客戶端修改如下:
- 維護每個主節點可用從節點列表。
- 針對讀命令維護請求節點路由。
- 從節點新建連接開啟readonly狀態。
- 集群模式下讀寫分離成本比較高,可以直接擴展主節點數量提高集群性能,一般不建議集群模式下做讀寫分離。
- 集群讀寫分離有時用于特殊業務場景如:
- 利用復制的最終一致性使用多個從節點做跨機房部署降低讀命令網絡延遲。
- 主節點故障轉移時間過長,業務端把讀請求路由給從節點保證讀操作可用。
- 以上場景也可以在不同機房獨立部署Redis集群解決,通過客戶端多寫來維護,讀命令直接請求到最近機房的Redis集群,或者當一個集群節點故障時客戶端轉向另一個集群。
27、集群運維——手動故障轉移
- Redis集群提供了手動故障轉移功能:指定從節點發起轉移流程,主從節點角色進行切換,從節點變為新的主節點對外提供服務,舊的主節點變為它的從節點。
- 在從節點上執行cluster failover命令發起轉移流程,默認情況下轉移期間客戶端請求會有短暫的阻塞,但不會丟失數據,流程如下:
- 從節點通知主節點停止處理所有客戶端請求。
- 主節點發送對應從節點延遲復制的數據。
- 從節點接收處理復制延遲的數據,直到主從復制偏移量一致為止,保證復制數據不丟失。
- 從節點立刻發起投票選舉(這里不需要延遲觸發選舉)。選舉成功后斷開復制變為新的主節點,之后向集群廣播主節點pong消息。
- 舊主節點接受到消息后更新自身配置變為從節點,解除所有客戶端請求阻塞,這些請求會被重定向到新主節點上執行。
- 舊主節點變為從節點后,向新的主節點發起全量復制流程。
- 主從節點轉移后,新的從節點由于之前沒有緩存主節點信息無法使用部分復制功能,所以會發起全量復制,當節點包含大量數據時會嚴重消耗CPU和網絡資源,線上不要頻繁操作。Redis4.0的Psync2將有效改善這一問題。
- 手動故障轉移的應用場景主要如下:
- 主節點遷移:運維Redis集群過程中經常遇到調整節點部署的問題,如節點所在的老機器替換到新機器等。由于從節點默認不響應請求可以安全下線關閉,但直接下線主節點會導致故障自動轉移期間主節點無法對外提供服務,影響線上業務的穩定性。這時可以使用手動故障轉移,把要下線的主節點安全的替換為從節點后,再做下線操作操作。
- 強制故障轉移。當自動故障轉移失敗時,只要故障的主節點有存活的從節點就可以通過手動轉移故障強制讓從節點替換故障的主節點,保證集群的可用性。
- 自動故障轉移失敗的場景有:
- 主節點和它的所有從節點同時故障。這個問題需要通過調整節點機器部署拓撲做規避,保證主從節點不在同一機器/機架上。除非機房內大面積故障,否則兩臺機器/機架同時故障概率很低。
- 所有從節點與主節點復制斷線時間超過cluster-slave-validity-factor*cluster-node-tineout+repl-ping-slave-period,導致從節點被判定為沒有故障轉移資格,手動故障轉移從節點不做中斷超時檢查。
- 由于網絡不穩定等問題,故障發現或故障選舉時間無法在cluster-node-timeout*2內完成,流程會不斷重試,最終從節點復制中斷時間超時,失去故障轉移資格無法完成轉移。
- 集群內超過一半以上的主節點同時故障。
- 根據以上情況,cluster failover命令提供了兩個參數force/takeover提供支持:、
- cluster failover force——用于當主節點宕機且無法自動完成故障轉移情況。從節點接到cluster failover force請求時,從節點直接發起選舉,不再跟主節點確認復制偏移量(從節點復制延遲的數據會丟失),當從節點選舉成功后替換為新的主節點并廣播集群配置。
- cluster failover takeover——用于集群內超過一半以上主節點故障的場景,因為從節點無法收到半數以上主節點投票,所以無法完成選舉過程。可以執行cluster failover takeover強制轉移,接到命令的從節點不再進行選舉流程而是直接更新本地配置紀元并替換主節點。takeover故障轉移由于沒有通過領導者選舉發起故障轉移,會導致配置紀元存在沖突的可能。當沖突發生時,集群會以nodeId字典序更大的一方配置為準。因此要小心集群分區后,手動執行takeover導致的集群沖突問題。
- 在集群可以自動完成故障轉移的情況下,不要使用cluster failover takeover強制干擾集群選舉機制,該操作主要用于半數以上主節點故障時采取的強制措施,請慎用。
- 手動故障轉移時,在滿足當前需求的情況下建議優先級:cluster failver>cluster failover force>cluster failover takeover。
28、集群運維——數據遷移
- 應用Redis集群時,常需要把單機Redis數據遷移到集群環境。redis-trib.rb工具提供了導入功能,用于數據從單機向集群環境遷移的場景。
redis-trib .rb import host :port --from <arg> --copy --replace - redis-trib.rb import命令內部采用批量scan和migrate 的方式遷移數據。這種遷移方式存在以下缺點:
- 遷移只能從單機節點向集群環境導入數據。
- 不支持在線遷移數據,遷移數據時應用方必須停寫,無法平滑遷移數據。
- 遷移過程中途如果出現超時等錯誤,不支持斷點續傳只能重新全量導入。
- 使用單線程進行數據遷移,大數據量遷移速度過慢。
- 唯品會開發的redis-migrate-tool(https://github.com/vipshop/redis-migrate-tool),該工具可滿足大多數Redis遷移需求,特點如下:
- 支持單機、Twemproxy、Redis Cluster 、RDB/AOF等多種類型的數據遷移。
- 工具模擬成從節點基于復制流遷移數據,從而支持在線遷移數據,業務方不需要停寫。
- 采用多線程加速數據遷移過程且提供數據校驗和查看遷移狀態等功能。
總結
以上是生活随笔為你收集整理的【Redis】《Redis 开发与运维》笔记-Chapter10-集群的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通过指针便利图像元素
- 下一篇: Win7系统桌面设置便签与备忘录的方法