主從復制是怎么實現的?
- 如果服務器發生了宕機,由于數據恢復是需要點時間,那么這個期間是無法服務新的請求的;
- 如果這臺服務器的硬盤出現了故障,可能數據就都丟失了。
要避免這種單點故障,最好的辦法是將數據備份到其他服務器上,讓這些服務器也可以對外提供服務,這樣即使有一臺服務器出現了故障,其他服務器依然可以繼續提供服務。
主機更新后根據配置和策略,自動同步到備機的mater/slaver機制,Master以寫為主,Slave以讀為主。
多臺服務器要保存同一份數據,這里問題就來了。
這些服務器之間的數據如何保持一致性呢?數據的讀寫操作是否每臺服務器都可以處理?
Redis 提供了主從復制模式,來避免上述的問題。
這個模式可以保證多臺服務器的數據一致性,且主從服務器之間采用的是「讀寫分離」的方式。
主服務器可以進行讀寫操作,當發生寫操作時自動將寫操作同步給從服務器,而從服務器一般是只讀,并接受主服務器同步過來寫操作命令,然后執行這條命令。
也就是說,所有的數據修改只在主服務器上進行,然后將最新的數據同步給從服務器,這樣就使得主從服務器的數據是一致的。
同步這兩個字說的簡單,但是這個同步過程并沒有想象中那么簡單,要考慮的事情不是一兩個。
我們先來看看,主從服務器間的第一次同步是如何工作的?
第一次同步
多臺服務器之間要通過什么方式來確定誰是主服務器,或者誰是從服務器呢?
我們可以使用 replicaof(Redis 5.0 之前使用 slaveof)命令形成主服務器和從服務器的關系。
比如,現在有服務器 A 和 服務器 B,我們在服務器 B 上執行下面這條命令:
# 服務器 B 執行這條命令
replicaof <服務器 A 的 IP 地址> <服務器 A 的 Redis 端口號>
接著,服務器 B 就會變成服務器 A 的「從服務器」,然后與主服務器進行第一次同步。
主從服務器間的第一次同步的過程可分為三個階段:
- 第一階段是建立連接、協商同步;
- 第二階段是主服務器同步數據給從服務器;
- 第三階段是主服務器發送新寫操作命令給從服務器。
為了讓你更清楚了解這三個階段,我畫了一張圖。
接下來,我在具體介紹每一個階段都做了什么。
:::info
第一階段:建立鏈接、協商同步
:::
執行了 replicaof 命令后,從服務器就會給主服務器發送 psync 命令,表示要進行數據同步。
psync 命令包含兩個參數,分別是主服務器的 runID 和復制進度 offset。
- runID,每個 Redis 服務器在啟動時都會自動生產一個隨機的 ID 來唯一標識自己。當從服務器和主服務器第一次同步時,因為不知道主服務器的 run ID,所以將其設置為 “?”。
- offset,表示復制的進度,第一次同步時,其值為 -1。
主服務器收到 psync 命令后,會用 FULLRESYNC 作為響應命令返回給對方。
并且這個響應命令會帶上兩個參數:主服務器的 runID 和主服務器目前的復制進度 offset。從服務器收到響應后,會記錄這兩個值。
FULLRESYNC 響應命令的意圖是采用全量復制的方式,也就是主服務器會把所有的數據都同步給從服務器。
所以,第一階段的工作時為了全量復制做準備。
那具體怎么全量同步呀呢?我們可以往下看第二階段。
:::info
第二階段:主服務器同步數據給從服務器
:::
接著,主服務器會執行 bgsave 命令來生成 RDB 文件,然后把文件發送給從服務器。
從服務器收到 RDB 文件后,會先清空當前的數據,然后載入 RDB 文件。
這里有一點要注意,主服務器生成 RDB 這個過程是不會阻塞主線程的,因為 bgsave 命令是產生了一個子進程來做生成 RDB 文件的工作,是異步工作的,這樣 Redis 依然可以正常處理命令。
但是,這期間的寫操作命令并沒有記錄到剛剛生成的 RDB 文件中,這時主從服務器間的數據就不一致了。
那么為了保證主從服務器的數據一致性,主服務器在下面這三個時間間隙中將收到的寫操作命令,寫入到 replication buffer 緩沖區里:
- 主服務器生成 RDB 文件期間;
- 主服務器發送 RDB 文件給從服務器期間;
- 「從服務器」加載 RDB 文件期間;
:::info
第三階段:主服務器發送新寫操作命令給從服務器
:::
在主服務器生成的 RDB 文件發送完,從服務器收到 RDB 文件后,丟棄所有舊數據,將 RDB 數據載入到內存。完成 RDB 的載入后,會回復一個確認消息給主服務器。
接著,主服務器將 replication buffer 緩沖區里所記錄的寫操作命令發送給從服務器,從服務器執行來自主服務器 replication buffer 緩沖區里發來的命令,這時主從服務器的數據就一致了。
至此,主從服務器的第一次同步的工作就完成了。
命令傳播
主從服務器在完成第一次同步后,雙方之間就會維護一個 TCP 連接。
后續主服務器可以通過這個連接繼續將寫操作命令傳播給從服務器,然后從服務器執行該命令,使得與主服務器的數據庫狀態相同。
而且這個連接是長連接的,目的是避免頻繁的 TCP 連接和斷開帶來的性能開銷。
上面的這個過程被稱為基于長連接的命令傳播,通過這種方式來保證第一次同步后的主從服務器的數據一致性。
分攤主服務器的壓力
在前面的分析中,我們可以知道主從服務器在第一次數據同步的過程中,主服務器會做兩件耗時的操作:生成 RDB 文件和傳輸 RDB 文件。
主服務器是可以有多個從服務器的,如果從服務器數量非常多,而且都與主服務器進行全量同步的話,就會帶來兩個問題:
- 由于是通過 bgsave 命令來生成 RDB 文件的,那么主服務器就會忙于使用 fork() 創建子進程,如果主服務器的內存數據非大,在執行 fork() 函數時是會阻塞主線程的,從而使得 Redis 無法正常處理請求;
- 傳輸 RDB 文件會占用主服務器的網絡帶寬,會對主服務器響應命令請求產生影響。
這種情況就好像,剛創業的公司,由于人不多,所以員工都歸老板一個人管,但是隨著公司的發展,人員的擴充,老板慢慢就無法承擔全部員工的管理工作了。
要解決這個問題,老板就需要設立經理職位,由經理管理多名普通員工,然后老板只需要管理經理就好。
Redis 也是一樣的,從服務器可以有自己的從服務器,我們可以把擁有從服務器的從服務器當作經理角色,它不僅可以接收主服務器的同步數據,自己也可以同時作為主服務器的形式將數據同步給從服務器,組織形式如下圖:
通過這種方式,主服務器生成 RDB 和傳輸 RDB 的壓力可以分攤到充當經理角色的從服務器。
那具體怎么做到的呢?
其實很簡單,我們在「從服務器」上執行下面這條命令,使其作為目標服務器的從服務器:
replicaof <目標服務器的IP> 6379
此時如果目標服務器本身也是「從服務器」,那么該目標服務器就會成為「經理」的角色,不僅可以接受主服務器同步的數據,也會把數據同步給自己旗下的從服務器,從而減輕主服務器的負擔。
增量復制
主從服務器在完成第一次同步后,就會基于長連接進行命令傳播。
可是,網絡總是不按套路出牌的嘛,說延遲就延遲,說斷開就斷開。
如果主從服務器間的網絡連接斷開了,那么就無法進行命令傳播了,這時從服務器的數據就沒辦法和主服務器保持一致了,客戶端就可能從「從服務器」讀到舊的數據。
那么問題來了,如果此時斷開的網絡,又恢復正常了,要怎么繼續保證主從服務器的數據一致性呢?
在 Redis 2.8 之前,如果主從服務器在命令同步時出現了網絡斷開又恢復的情況,從服務器就會和主服務器重新進行一次全量復制,很明顯這樣的開銷太大了,必須要改進一波。
所以,從 Redis 2.8 開始,網絡斷開又恢復后,從主從服務器會采用增量復制的方式繼續同步,也就是只會把網絡斷開期間主服務器接收到的寫操作命令,同步給從服務器。
網絡恢復后的增量復制過程如下圖:
主要有三個步驟:
- 從服務器在恢復網絡后,會發送 psync 命令給主服務器,此時的 psync 命令里的 offset 參數不是 -1;
- 主服務器收到該命令后,然后用 CONTINUE 響應命令告訴從服務器接下來采用增量復制的方式同步數據;
- 然后主服務將主從服務器斷線期間,所執行的寫命令發送給從服務器,然后從服務器執行這些命令。
那么關鍵的問題來了,主服務器怎么知道要將哪些增量數據發送給從服務器呢?
答案藏在這兩個東西里:
- repl_backlog_buffer,是一個「環形」緩沖區,用于主從服務器斷連后,從中找到差異的數據;
- replication offset,標記上面那個緩沖區的同步進度,主從服務器都有各自的偏移量,主服務器使用 master_repl_offset 來記錄自己「寫」到的位置,從服務器使用 slave_repl_offset 來記錄自己「讀」到的位置。
那 repl_backlog_buffer 緩沖區是什么時候寫入的呢?
在主服務器進行命令傳播時,不僅會將寫命令發送給從服務器,還會將寫命令寫入到 repl_backlog_buffer 緩沖區里,因此 這個緩沖區里會保存著最近傳播的寫命令。
網絡斷開后,當從服務器重新連上主服務器時,從服務器會通過 psync 命令將自己的復制偏移量 slave_repl_offset 發送給主服務器,主服務器根據自己的 master_repl_offset 和 slave_repl_offset 之間的差距,然后來決定對從服務器執行哪種同步操作:
- 如果判斷出從服務器要讀取的數據還在 repl_backlog_buffer 緩沖區里,那么主服務器將采用增量同步的方式;
- 相反,如果判斷出從服務器要讀取的數據已經不存在 repl_backlog_buffer 緩沖區里,那么主服務器將采用全量同步的方式。
當主服務器在 repl_backlog_buffer 中找到主從服務器差異(增量)的數據后,就會將增量的數據寫入到 replication buffer 緩沖區,這個緩沖區我們前面也提到過,它是緩存將要傳播給從服務器的命令。
repl_backlog_buffer 緩行緩沖區的默認大小是 1M,并且由于它是一個環形緩沖區,所以當緩沖區寫滿后,主服務器繼續寫入的話,就會覆蓋之前的數據。因此,當主服務器的寫入速度遠超于從服務器的讀取速度,緩沖區的數據一下就會被覆蓋。
那么在網絡恢復時,如果從服務器想讀的數據已經被覆蓋了,主服務器就會采用全量同步,這個方式比增量同步的性能損耗要大很多。
因此,為了避免在網絡恢復時,主服務器頻繁地使用全量同步的方式,我們應該調整下 repl_backlog_buffer 緩沖區大小,盡可能的大一些,減少出現從服務器要讀取的數據被覆蓋的概率,從而使得主服務器采用增量同步的方式。
那 repl_backlog_buffer 緩沖區具體要調整到多大呢?
repl_backlog_buffer 最小的大小可以根據這面這個公式估算。
- second 為從服務器斷線后重新連接上主服務器所需的平均 時間(以秒計算)。
- write_size_per_second 則是主服務器平均每秒產生的寫命令數據量大小。
舉個例子,如果主服務器平均每秒產生 1 MB 的寫命令,而從服務器斷線之后平均要 5 秒才能重新連接主服務器。
那么 repl_backlog_buffer 大小就不能低于 5 MB,否則新寫地命令就會覆蓋舊數據了。
當然,為了應對一些突發的情況,可以將 repl_backlog_buffer 的大小設置為此基礎上的 2 倍,也就是 10 MB。
關于 repl_backlog_buffer 大小修改的方法,只需要修改配置文件里下面這個參數項的值就可以。
repl-backlog-size 1mb
Redis 主從復制的面試題
Redis 主從節點是長連接還是短連接?
長連接
怎么判斷 Redis 某個節點是否正常工作?
Redis 判斷節點是否正常工作,基本都是通過互相的 ping-pong 心跳檢測機制,如果有一半以上的節點去 ping 一個節點的時候沒有 pong 回應,集群就會認為這個節點掛掉了,會斷開與這個節點的連接。
Redis 主從節點發送的心態間隔是不一樣的,而且作用也有一點區別:
- Redis 主節點默認每隔 10 秒對從節點發送 ping 命令,判斷從節點的存活性和連接狀態,可通過參數repl-ping-slave-period控制發送頻率。
- Redis 從節點每隔 1 秒發送 replconf ack{offset} 命令,給主節點上報自身當前的復制偏移量,目的是為了:
- 實時監測主從節點網絡狀態;
- 上報自身復制偏移量, 檢查復制數據是否丟失, 如果從節點數據丟失, 再從主節點的復制緩沖區中拉取丟失數據。
主從復制架構中,過期key如何處理?
主節點處理了一個key或者通過淘汰算法淘汰了一個key,這個時間主節點模擬一條del命令發送給從節點,從節點收到該命令后,就進行刪除key的操作。
Redis 是同步復制還是異步復制?
Redis 主節點每次收到寫命令之后,先寫到內部的緩沖區,然后異步發送給從節點。
主從復制中兩個Buffer(replication buffer,repl backlog buffer)有什么區別?
replication buffer 、repl backlog buffer 區別如下:
- 出現的階段不一樣:
- repl backlog buffer 是在增量復制階段出現,一個主節點只分配一個 repl backlog buffer;
- replication buffer 是在全量復制階段和增量復制階段都會出現,主節點會給每個新連接的從節點,分配一個 replication buffer;
- 這兩個 Buffer 都有大小限制的,當緩沖區滿了之后,發生的事情不一樣:
- 當 repl backlog buffer 滿了,因為是環形結構,會直接覆蓋起始位置數據;
- 當 replication buffer 滿了,會導致連接斷開,刪除緩存,從節點重新連接,重新開始全量復制。
為什么出現主從數據不一致?
主從數據不一致,就是指客戶端從從節點中讀取到的值和主節點中的最新值并不一致。
之所以會出現主從數據不一致的現象,是因為主從節點間的命令復制是異步進行的,所以無法實現強一致性保證(主從數據時時刻刻保持一致)。
具體來說,在主從節點命令傳播階段,主節點收到新的寫命令后,會發送給從節點。但是,主節點并不會等到從節點實際執行完命令后,再把結果返回給客戶端,而是主節點自己在本地執行完命令后,就會向客戶端返回結果了。如果從節點還沒有執行主節點同步過來的命令,主從節點間的數據就不一致了。
如何應對主從數據不一致?
第一種方法,盡量保證主從節點間的網絡連接狀況良好,避免主從節點在不同的機房。
第二種方法,可以開發一個外部程序來監控主從節點間的復制進度。具體做法:
- Redis 的 INFO replication 命令可以查看主節點接收寫命令的進度信息(master_repl_offset)和從節點復制寫命令的進度信息(slave_repl_offset),所以,我們就可以開發一個監控程序,先用 INFO replication 命令查到主、從節點的進度,然后,我們用 master_repl_offset 減去 slave_repl_offset,這樣就能得到從節點和主節點間的復制進度差值了。
- 如果某個從節點的進度差值大于我們預設的閾值,我們可以讓客戶端不再和這個從節點連接進行數據讀取,這樣就可以減少讀到不一致數據的情況。不過,為了避免出現客戶端和所有從節點都不能連接的情況,我們需要把復制進度差值的閾值設置得大一些。
主從切換如何減少數據丟失?
主從切換過程中,產生數據丟失的情況有兩種:
- 異步復制同步丟失
- 集群產生腦裂數據丟失
我們不可能保證數據完全不丟失,只能做到使得盡量少的數據丟失。
異步復制同步丟失
對于 Redis 主節點與從節點之間的數據復制,是異步復制的,當客戶端發送寫請求給主節點的時候,客戶端會返回 ok,接著主節點將寫請求異步同步給各個從節點,但是如果此時主節點還沒來得及同步給從節點時發生了斷電,那么主節點內存中的數據會丟失。
:::info
減少異步復制的數據丟失的方案
:::
Redis 配置里有一個參數 min-slaves-max-lag,表示一旦所有的從節點數據復制和同步的延遲都超過了 min-slaves-max-lag 定義的值,那么主節點就會拒絕接收任何請求。
假設將 min-slaves-max-lag 配置為 10s 后,根據目前 master->slave 的復制速度,如果數據同步完成所需要時間超過10s,就會認為 master 未來宕機后損失的數據會很多,master 就拒絕寫入新請求。這樣就能將 master 和 slave 數據差控制在10s內,即使 master 宕機也只是這未復制的 10s 數據。
那么對于客戶端,當客戶端發現 master 不可寫后,我們可以采取降級措施,將數據暫時寫入本地緩存和磁盤中,在一段時間(等 master 恢復正常)后重新寫入 master 來保證數據不丟失,也可以將數據寫入 kafka 消息隊列,等 master 恢復正常,再隔一段時間去消費 kafka 中的數據,讓將數據重新寫入 master
集群產生腦裂數據丟失
先來理解集群的腦裂現象,這就好比一個人有兩個大腦,那么到底受誰控制呢?
那么在 Redis 中,集群腦裂產生數據丟失的現象是怎樣的呢?
在 Redis 主從架構中,部署方式一般是「一主多從」,主節點提供寫操作,從節點提供讀操作。
如果主節點的網絡突然發生了問題,它與所有的從節點都失聯了,但是此時的主節點和客戶端的網絡是正常的,這個客戶端并不知道 Redis 內部已經出現了問題,還在照樣的向這個失聯的主節點寫數據(過程A),此時這些數據被主節點緩存到了緩沖區里,因為主從節點之間的網絡問題,這些數據都是無法同步給從節點的。
這時,哨兵也發現主節點失聯了,它就認為主節點掛了(但實際上主節點正常運行,只是網絡出問題了),于是哨兵就會在從節點中選舉出一個 leeder 作為主節點,這時集群就有兩個主節點了 —— 腦裂出現了。
這時候網絡突然好了,哨兵因為之前已經選舉出一個新主節點了,它就會把舊主節點降級為從節點(A),然后從節點(A)會向新主節點請求數據同步,因為第一次同步是全量同步的方式,此時的從節點(A)會清空掉自己本地的數據,然后再做全量同步。所以,之前客戶端在過程 A 寫入的數據就會丟失了,也就是集群產生腦裂數據丟失的問題。
總結一句話就是:由于網絡問題,集群節點之間失去聯系。主從數據不同步;重新平衡選舉,產生兩個主服務。等網絡恢復,舊主節點會降級為從節點,再與新主節點進行同步復制的時候,由于會從節點會清空自己的緩沖區,所以導致之前客戶端寫入的數據丟失了。
:::info
減少腦裂的數據丟失的方案
:::
TODO ?? 這里的復制和同步延遲如何實現???
當主節點發現「從節點下線的數量太多」,或者「網絡延遲太大」的時候,那么主節點會禁止寫操作,直接把錯誤返回給客戶端。
在 Redis 的配置文件中有兩個參數我們可以設置:
- min-slaves-to-write x,主節點必須要有至少 x 個從節點連接,如果小于這個數,主節點會禁止寫數據。
- min-slaves-max-lag x,主從數據復制和同步的延遲不能超過 x 秒,如果主從同步的延遲超過 x 秒,主節點會禁止寫數據。
我們可以把 min-slaves-to-write 和 min-slaves-max-lag 這兩個配置項搭配起來使用,分別給它們設置一定的閾值,假設為 N 和 T。
這兩個配置項組合后的要求是,主節點連接的從節點中至少有 N 個從節點,「并且」主節點進行數據復制時的 ACK 消息延遲不能超過 T 秒,否則,主節點就不會再接收客戶端的寫請求了。
即使原主節點是假故障,它在假故障期間也無法響應哨兵心跳,也不能和從節點進行同步,自然也就無法和從節點進行 ACK 確認了。這樣一來,min-slaves-to-write 和 min-slaves-max-lag 的組合要求就無法得到滿足,原主節點就會被限制接收客戶端寫請求,客戶端也就不能在原主節點中寫入新數據了。
等到新主節點上線時,就只有新主節點能接收和處理客戶端請求,此時,新寫的數據會被直接寫到新主節點中。而原主節點會被哨兵降為從節點,即使它的數據被清空了,也不會有新數據丟失。我再來給你舉個例子。
假設我們將 min-slaves-to-write 設置為 1,把 min-slaves-max-lag 設置為 12s,把哨兵的 down-after-milliseconds 設置為 10s,主節點因為某些原因卡住了 15s,導致哨兵判斷主節點客觀下線,開始進行主從切換。同時,因為原主節點卡住了 15s,沒有一個從節點能和原主節點在 12s 內進行數據復制,原主節點也無法接收客戶端請求了。這樣一來,主從切換完成后,也只有新主節點能接收請求,不會發生腦裂,也就不會發生數據丟失的問題了。
主從如何做到故障自動切換?
主節點掛了 ,從節點是無法自動升級為主節點的,這個過程需要人工處理,在此期間 Redis 無法對外提供寫操作。
此時,Redis 哨兵機制就登場了,哨兵在發現主節點出現故障時,由哨兵自動完成故障發現和故障轉移,并通知給應用方,從而實現高可用性。
如何實現 “斷開重連后接收歷史消息”?
在 Redis 中實現 “斷開重連后接收歷史消息”,核心是解決消息持久化和消費位置記錄兩個問題。Redis 的原生數據結構中,Stream是最適合此場景的方案(Redis 5.0 + 引入),它專為消息隊列設計,天然支持消息持久化、消費偏移量記錄和重連后歷史消息回溯。
- 消息持久化:消息一旦寫入 Stream,會被持久化(可配合 RDB/AOF 確保重啟不丟失)。
- 消費偏移量:支持消費者組(Consumer Group),自動記錄每個消費者的最后消費位置(
<font style="color:#000000;">last_delivered_id</font>
)。 - 歷史消息回溯:消費者重連后,可從上次記錄的偏移量繼續讀取未消費的歷史消息。
關鍵配置:確保消息不丟失
- 持久化配置:開啟 Redis 的 AOF(Append Only File)持久化,確保消息寫入磁盤:conf
appendonly yes # 開啟AOF
appendfsync everysec # 每秒同步一次(平衡性能和安全性)
- 消費者組偏移量持久化:消費者組的偏移量會被 Redis 自動持久化,重啟后依然有效。
相比 Redis 的其他結構(如 List 的 BRPOP),Stream 無需手動維護偏移量,且提供更完善的消息確認、回溯和持久化機制,是最優解。
Redis Cluster
Redis Cluster 的 “ASK 錯誤” 和 “MOVED 錯誤” 有什么區別?客戶端如何處理?
在 Redis Cluster 中,<font style="color:#000000;">ASK</font>
錯誤和 <font style="color:#000000;">MOVED</font>
錯誤都與槽位(slot)的重定位有關,但它們的觸發場景、含義和處理方式有本質區別。
區別
維度 | **<font style="color:rgb(0, 0, 0);">MOVED</font>** 錯誤 | **<font style="color:rgb(0, 0, 0);">ASK</font>** 錯誤 |
---|---|---|
觸發場景 | 槽位已永久遷移到新節點 | 槽位正在遷移過程中(臨時狀態) |
含義 | 告知客戶端:該槽位的新主節點是 X | 告知客戶端:該槽位正在遷移,臨時向 X 節點查詢 |
槽位歸屬 | 槽位已完全屬于新節點 | 槽位仍屬于原節點(遷移未完成) |
持久化 | 集群會更新槽位映射表(配置紀元遞增) | 不更新集群映射表(臨時狀態) |
兩者介紹
<font style="color:rgb(0, 0, 0);">MOVED</font>
錯誤
當 Redis Cluster 中的槽位完成永久遷移后(如集群擴容 / 縮容時,槽位從節點 A 遷移到節點 B 并完成),若客戶端仍向原節點 A 發送該槽位的請求,節點 A 會返回 <font style="color:rgba(0, 0, 0, 0.85) !important;">MOVED</font>
錯誤,格式如下:
MOVED <slot> <new_node_ip>:<new_node_port>
示例:
客戶端向節點 127.0.0.1:6379 發送屬于槽位 10086 的命令,但該槽位已遷移到 127.0.0.1:6380,節點 6379 會返回:
MOVED 10086 127.0.0.1:6380
<font style="color:rgb(0, 0, 0);">ASK</font>
錯誤
當槽位正在遷移過程中(部分數據已從原節點 A 遷移到新節點 B,但未完全遷移完畢),若客戶端向原節點 A 發送該槽位的請求:
- 若數據仍在 A 中,A 直接返回結果;
- 若數據已遷移到 B 中,A 會返回
<font style="color:rgb(0, 0, 0);">ASK</font>
錯誤,格式如下:
ASK <slot> <new_node_ip>:<new_node_port>
示例:
槽位 10086 正在從 6379 遷移到 6380,客戶端向 6379 查詢已遷移到 6380 的 key,6379 會返回:
ASK 10086 127.0.0.1:6380
客戶端處理方式
處理 <font style="color:rgb(0, 0, 0);">MOVED</font>
錯誤
<font style="color:rgba(0, 0, 0, 0.85) !important;">MOVED</font>
表示槽位已永久遷移,客戶端需要更新本地的槽位映射表,并將后續請求直接發送到新節點:
- 解析
<font style="color:rgb(0, 0, 0);">MOVED</font>
錯誤中的新節點地址和槽位; - 更新客戶端本地緩存的「槽位 - 節點」映射關系(將該槽位綁定到新節點);
- 重新向新節點發送原命令。
注意:客戶端無需每次查詢集群狀態,只需通過 <font style="color:rgba(0, 0, 0, 0.85) !important;">MOVED</font>
錯誤逐步更新本地映射,最終與集群一致。
處理 <font style="color:rgb(0, 0, 0);">ASK</font>
錯誤
<font style="color:rgba(0, 0, 0, 0.85) !important;">ASK</font>
表示槽位臨時遷移,客戶端無需更新本地映射,只需臨時向新節點查詢:
- 解析
<font style="color:rgb(0, 0, 0);">ASK</font>
錯誤中的臨時節點地址; - 先向該臨時節點發送
<font style="color:rgb(0, 0, 0);">ASKING</font>
命令(告知節點允許處理該槽位的臨時請求); - 再向該臨時節點重新發送原命令;
- 后續請求仍按本地原映射發送(因為槽位最終歸屬未變)。
示例流程:
客戶端 → 原節點(6379)請求 key → 收到 ASK 10086 6380
客戶端 → 向 6380 發送 ASKING 命令
客戶端 → 向 6380 重新發送原命令 → 得到結果
總結
<font style="color:rgb(0, 0, 0);">MOVED</font>
是永久重定向,客戶端需更新本地槽位映射;<font style="color:rgb(0, 0, 0);">ASK</font>
是臨時重定向,客戶端僅臨時向新節點查詢,不更新映射。
Redis Cluster 集群中,每個節點至少需要配置幾個從節點才能保證高可用?
Redis Cluster 要求至少有 3 個主節點才能正常工作。為每個主節點配置 1 個從節點,即形成 3 主 3 從的結構,這樣當某個主節點發生故障時,其對應的從節點可以自動升級為主節點,繼續提供服務,從而保證集群的高可用性。
Redis Cluster 的 “槽位遷移” 是什么場景下觸發的?遷移過程中數據是否可用?
在 Redis Cluster(Redis 集群)中,槽位(Slot)遷移是核心的動態擴容 / 縮容機制,用于在集群節點間重新分配數據存儲的 “責任范圍”。
Redis Cluster 將所有數據映射到 16384 個槽位(Slot 0 ~ 16383),每個主節點負責一部分槽位的讀寫。當集群的 “槽位 - 節點” 對應關系需要調整時,就會觸發槽位遷移,具體場景包括:
- 集群擴容(新增主節點)
- 當原集群性能不足,需要新增主節點分擔負載時,需將已有主節點的部分槽位遷移到新主節點,確保槽位均勻分配。
- 集群縮容(下線主節點)
- 當集群資源過剩或某個主節點需下線(如硬件故障、節點退役)時,需將該主節點負責的所有槽位遷移到其他正常主節點,再移除下線節點。
- 槽位均衡(手動觸發)
- 若集群因異常(如節點故障后恢復、手動調整失誤)導致槽位分配不均(如某主節點負責 10000 個槽位,其他節點僅負責 2000 個),可通過手動命令觸發槽位遷移,實現負載均衡。
- 主節點故障后的簡介間接遷移
- 當主節點故障且無可用從節點時(極端情況),若通過
<font style="color:rgba(0, 0, 0, 0.85);">CLUSTER FAILOVER FORCE</font>
等命令手動指定新主節點,需先將故障主節點的槽位遷移到新主節點,再完成故障轉移。
- 當主節點故障且無可用從節點時(極端情況),若通過
槽位遷移過程中數據是否可用?
結論:槽位遷移過程中,數據完全可用,讀寫操作不會中斷。
Redis 采用 “增量遷移 + 原子化槽位切換” 的設計,確保遷移期間的可用性,具體流程如下:
:::info
移前的準備:標記 “遷移中槽位”
:::
發起遷移的節點(通常是 <font style="color:rgba(0, 0, 0, 0.85) !important;">redis-cli --cluster</font>
工具或管理節點)會先向源主節點(待遷出槽位的主節點)發送 <font style="color:rgba(0, 0, 0, 0.85) !important;">CLUSTER SETSLOT <slot> MIGRATING <target-node-id></font>
命令,標記該槽位進入 “遷移中” 狀態;
同時向目標主節點(接收槽位的主節點)發送 <font style="color:rgba(0, 0, 0, 0.85) !important;">CLUSTER SETSLOT <slot> IMPORTING <source-node-id></font>
命令,標記該槽位進入 “接收中” 狀態。
:::info
遷移過程:增量復制 + 分批遷移
:::
源主節點會將 “遷移中槽位” 的所有鍵值對分批(默認每批 16 個鍵)遷移到目標主節點,流程如下:
- 源主節點掃描該槽位的鍵,生成鍵列表;
- 向目標主節點發送
<font style="color:rgb(0, 0, 0);">MIGRATE</font>
命令,批量傳輸鍵值對(傳輸期間源節點仍可處理該槽位的讀寫); - 目標主節點接收并存儲鍵值對,完成后向源節點返回確認;
- 重復步驟 1~3,直到該槽位的所有鍵遷移完成。
Redis Cluster 支持跨節點的事務操作嗎?為什么?
Redis Cluster(Redis 集群)不支持跨節點的事務操作,即無法在一個事務中操作位于不同節點的鍵。
核心原因:數據分片與事務原子性的沖突。
Redis Cluster 采用哈希槽(Hash Slot) 機制將數據分片存儲:所有鍵通過哈希計算分配到 16384 個槽位,每個槽位由特定主節點負責,不同槽位可能分布在不同節點上。而 Redis 的事務(<font style="color:rgba(0, 0, 0, 0.85);">MULTI</font>
/<font style="color:rgba(0, 0, 0, 0.85);">EXEC</font>
)本質是 “單個節點上的原子操作序列”,無法跨節點協調。
- 事務的單節點原子性限制(不同節點的命令隊列相互獨立),無法保證原子性
- 跨節點事務的難點(跨節點事務需要引入分布式事務 2PC兩階段提交)
- Cluster協議的路由限制
為什么要有哨兵?
為什么要有哨兵機制?
在 Redis 的主從架構中,由于主從模式是讀寫分離的,如果主節點(master)掛了,那么將沒有主節點來服務客戶端的寫操作請求,也沒有主節點給從節點(slave)進行數據同步了。
這時如果要恢復服務的話,需要人工介入,選擇一個「從節點」切換為「主節點」,然后讓其他從節點指向新的主節點,同時還需要通知上游那些連接 Redis 主節點的客戶端,將其配置中的主節點 IP 地址更新為「新主節點」的 IP 地址。
這樣也不太“智能”了,要是有一個節點能監控「主節點」的狀態,當發現主節點掛了 ,它自動將一個「從節點」切換為「主節點」的話,那么可以節省我們很多事情啊!
Redis 在 2.8 版本以后提供的哨兵(Sentinel)機制,它的作用是實現主從節點故障轉移。它會監測主節點是否存活,如果發現主節點掛了,它就會選舉一個從節點切換為主節點,并且把新主節點的相關信息通知給從節點和客戶端。
哨兵機制是如何工作的?
哨兵其實是一個運行在特殊模式下的 Redis 進程,所以它也是一個節點。從“哨兵”這個名字也可以看得出來,它相當于是“觀察者節點”,觀察的對象是主從節點。
當然,它不僅僅是觀察那么簡單,在它觀察到有異常的狀況下,會做出一些“動作”,來修復異常狀態。
哨兵節點主要負責三件事情:監控、選主、通知。
所以,我們重點要學習這三件事情:
- 哨兵節點是如何監控節點的?又是如何判斷主節點是否真的故障了?
- 根據什么規則選擇一個從節點切換為主節點?
- 怎么把新主節點的相關信息通知給從節點和客戶端呢?
如何判斷主節點真的故障了?
哨兵會每隔 1 秒給所有主從節點發送 PING 命令,當主從節點收到 PING 命令后,會發送一個響應命令給哨兵,這樣就可以判斷它們是否在正常運行。
如果主節點或者從節點沒有在規定的時間內響應哨兵的 PING 命令,哨兵就會將它們標記為「主觀下線」。這個「規定的時間」是配置項 down-after-milliseconds 參數設定的,單位是毫秒。
:::info
主觀下線?難道還有客觀下線?
:::
是的沒錯,客觀下線只適用于主節點。
之所以針對「主節點」設計「主觀下線」和「客觀下線」兩個狀態,是因為有可能「主節點」其實并沒有故障,可能只是因為主節點的系統壓力比較大或者網絡發送了擁塞,導致主節點沒有在規定時間內響應哨兵的 PING 命令。
所以,為了減少誤判的情況,哨兵在部署的時候不會只部署一個節點,而是用多個節點部署成哨兵集群(最少需要三臺機器來部署哨兵集群),通過多個哨兵節點一起判斷,就可以就可以避免單個哨兵因為自身網絡狀況不好,而誤判主節點下線的情況。同時,多個哨兵的網絡同時不穩定的概率較小,由它們一起做決策,誤判率也能降低。
:::info
具體是怎么判定主節點為「客觀下線」的呢?
:::
當一個哨兵判斷主節點為「主觀下線」后,就會向其他哨兵發起命令,其他哨兵收到這個命令后,就會根據自身和主節點的網絡狀況,做出贊成投票或者拒絕投票的響應。
當這個哨兵的贊同票數達到哨兵配置文件中的 quorum 配置項設定的值后,這時主節點就會被該哨兵標記為「客觀下線」。
例如,現在有 3 個哨兵,quorum 配置的是 2,那么一個哨兵需要 2 張贊成票,就可以標記主節點為“客觀下線”了。這 2 張贊成票包括哨兵自己的一張贊成票和另外兩個哨兵的贊成票。
PS:quorum 的值一般設置為哨兵個數的二分之一加1,例如 3 個哨兵就設置 2。
哨兵判斷完主節點客觀下線后,哨兵就要開始在多個「從節點」中,選出一個從節點來做新主節點。
由哪個哨兵進行主從故障轉移?
前面說過,為了更加“客觀”的判斷主節點故障了,一般不會只由單個哨兵的檢測結果來判斷,而是多個哨兵一起判斷,這樣可以減少誤判概率,所以哨兵是以哨兵集群的方式存在的。
問題來了,由哨兵集群中的哪個節點進行主從故障轉移呢?
所以這時候,還需要在哨兵集群中選出一個 leader,讓 leader 來執行主從切換。
選舉 leader 的過程其實是一個投票的過程,在投票開始前,肯定得有個「候選者」。
那誰來作為候選者呢?
哪個哨兵節點判斷主節點為「客觀下線」,這個哨兵節點就是候選者,所謂的候選者就是想當 Leader 的哨兵。
舉個例子,假設有三個哨兵。當哨兵 B 先判斷到主節點「主觀下線后」,就會給其他實例發送 is-master-down-by-addr 命令。接著,其他哨兵會根據自己和主節點的網絡連接情況,做出贊成投票或者拒絕投票的響應。
當哨兵 B 收到贊成票數達到哨兵配置文件中的 quorum 配置項設定的值后,就會將主節點標記為「客觀下線」,此時的哨兵 B 就是一個Leader 候選者。
候選者如何選舉成為Leader?
候選者會向其他哨兵發送命令,表明希望成為 Leader 來執行主從切換,并讓所有其他哨兵對它進行投票。
每個哨兵只有一次投票機會,如果用完后就不能參與投票了,可以投給自己或投給別人,但是只有候選者才能把票投給自己。
那么在投票過程中,任何一個「候選者」,要滿足兩個條件:
- 第一,拿到半數以上的贊成票;
- 第二,拿到的票數同時還需要大于等于哨兵配置文件中的 quorum 值。
舉個例子,假設哨兵節點有 3 個,quorum 設置為 2,那么任何一個想成為 Leader 的哨兵只要拿到 2 張贊成票,就可以選舉成功了。如果沒有滿足條件,就需要重新進行選舉。
這時候有的同學就會問了,如果某個時間點,剛好有兩個哨兵節點判斷到主節點為客觀下線,那這時不就有兩個候選者了?這時該如何決定誰是 Leader 呢?
每位候選者都會先給自己投一票,然后向其他哨兵發起投票請求。如果投票者先收到「候選者 A」的投票請求,就會先投票給它,如果投票者用完投票機會后,收到「候選者 B」的投票請求后,就會拒絕投票。這時,候選者 A 先滿足了上面的那兩個條件,所以「候選者 A」就會被選舉為 Leader。
為什么哨兵節點至少要3個?
如果哨兵集群中只有 2 個哨兵節點,此時如果一個哨兵想要成功成為 Leader,必須獲得 2 票,而不是 1 票。
所以,如果哨兵集群中有個哨兵掛掉了,那么就只剩一個哨兵了,如果這個哨兵想要成為 Leader,這時票數就沒辦法達到 2 票,就無法成功成為 Leader,這時是無法進行主從節點切換的。
因此,通常我們至少會配置 3 個哨兵節點。這時,如果哨兵集群中有個哨兵掛掉了,那么還剩下兩個個哨兵,如果這個哨兵想要成為 Leader,這時還是有機會達到 2 票的,所以還是可以選舉成功的,不會導致無法進行主從節點切換。
當然,你要問,如果 3 個哨兵節點,掛了 2 個怎么辦?這個時候得人為介入了,或者增加多一點哨兵節點。
再說一個問題,Redis 1 主 4 從,5 個哨兵 ,quorum 設置為 3,如果 2 個哨兵故障,當主節點宕機時,哨兵能否判斷主節點“客觀下線”?主從能否自動切換?
- 哨兵集群可以判定主節點“客觀下線”。哨兵集群還剩下 3 個哨兵,當一個哨兵判斷主節點“主觀下線”后,詢問另外 2 個哨兵后,有可能能拿到 3 張贊同票,這時就達到了 quorum 的值,因此,哨兵集群可以判定主節點為“客觀下線”。
- 哨兵集群可以完成主從切換。當有個哨兵標記主節點為「客觀下線」后,就會進行選舉 Leader 的過程,因為此時哨兵集群還剩下 3 個哨兵,那么還是可以拿到半數以上(5/2+1=3)的票,而且也達到了 quorum 值,滿足了選舉 Leader 的兩個條件, 所以就能選舉成功,因此哨兵集群可以完成主從切換。
如果 quorum 設置為 2 ,并且如果有 3 個哨兵故障的話。此時哨兵集群還是可以判定主節點為“客觀下線”,但是哨兵不能完成主從切換了,大家可以自己推演下。
如果 quorum 設置為 3,并且如果有 3 個哨兵故障的話,哨兵集群即不能判定主節點為“客觀下線”,也不能完成主從切換了。
可以看到,quorum 為 2 的時候,并且如果有 3 個哨兵故障的話,雖然可以判定主節點為“客觀下線”,但是不能完成主從切換,這樣感覺「判定主節點為客觀下線」這件事情白做了一樣,既然這樣,還不如不要做,quorum 為 3 的時候,就可以避免這種無用功。
所以,quorum 的值建議設置為哨兵個數的二分之一加1,例如 3 個哨兵就設置 2,5 個哨兵設置為 3,而且哨兵節點的數量應該是奇數。
主從故障轉移的過程是怎么的?
在哨兵集群中通過投票的方式,選舉出了哨兵 leader 后,就可以進行主從故障轉移的過程了,如下圖:
主從故障轉移操作包含以下四個步驟:
- 第一步:選出新主節點
- 第二步:讓已下線主節點屬下的所有「從節點」修改復制目標,修改為復制「新主節點」;
- 第三步:將新主節點的 IP 地址和信息,通過「發布者/訂閱者機制」通知給客戶端;
- 第四步:繼續監視舊主節點,當這個舊主節點重新上線時,將它設置為新主節點的從節點;
步驟一:選出新主節點
故障轉移操作第一步要做的就是在已下線主節點屬下的所有「從節點」中,挑選出一個狀態良好、數據完整的從節點,然后向這個「從節點」發送 SLAVEOF no one 命令,將這個「從節點」轉換為「主節點」。
那么多「從節點」,到底選擇哪個從節點作為新主節點的?
隨機的方式好嗎?隨機的方式,實現起來很簡單,但是如果選到一個網絡狀態不好的從節點作為新主節點,那么可能在將來不久又要做一次主從故障遷移。
所以,我們首先要把網絡狀態不好的從節點給過濾掉。首先把已經下線的從節點過濾掉,然后把以往網絡連接狀態不好的從節點也給過濾掉。
怎么判斷從節點之前的網絡連接狀態不好呢?
Redis 有個叫 down-after-milliseconds * 10 配置項,其down-after-milliseconds 是主從節點斷連的最大連接超時時間。如果在 down-after-milliseconds 毫秒內,主從節點都沒有通過網絡聯系上,我們就可以認為主從節點斷連了。如果發生斷連的次數超過了 10 次,就說明這個從節點的網絡狀況不好,不適合作為新主節點。
至此,我們就把網絡狀態不好的從節點過濾掉了,接下來要對所有從節點進行三輪考察:優先級、復制進度、ID 號。在進行每一輪考察的時候,哪個從節點優先勝出,就選擇其作為新主節點。
- 第一輪考察:哨兵首先會根據從節點的優先級來進行排序,優先級越小排名越靠前,
- 第二輪考察:如果優先級相同,則查看復制的下標,哪個從「主節點」接收的復制數據多,哪個就靠前。
- 第三輪考察:如果優先級和下標都相同,就選擇從節點 ID 較小的那個。
:::info
第一輪考察:優先級最高的從節點勝出
:::
Redis 有個叫 slave-priority 配置項,可以給從節點設置優先級。
每一臺從節點的服務器配置不一定是相同的,我們可以根據服務器性能配置來設置從節點的優先級。
比如,如果 「 A 從節點」的物理內存是所有從節點中最大的, 那么我們可以把「 A 從節點」的優先級設置成最高。這樣當哨兵進行第一輪考慮的時候,優先級最高的 A 從節點就會優先勝出,于是就會成為新主節點。
:::info
第二輪考察:復制進度最靠前的從節點勝出
:::
如果在第一輪考察中,發現優先級最高的從節點有兩個,那么就會進行第二輪考察,比較兩個從節點哪個復制進度。
什么是復制進度?主從架構中,主節點會將寫操作同步給從節點,在這個過程中,主節點會用 master_repl_offset 記錄當前的最新寫操作在 repl_backlog_buffer 中的位置(如下圖中的「主服務器已經寫入的數據」的位置),而從節點會用 slave_repl_offset 這個值記錄當前的復制進度(如下圖中的「從服務器要讀的位置」的位置)。
如果某個從節點的 slave_repl_offset 最接近 master_repl_offset,說明它的復制進度是最靠前的,于是就可以將它選為新主節點。
:::info
第三輪考察:ID 號小的從節點勝出
:::
如果在第二輪考察中,發現有兩個從節點優先級和復制進度都是一樣的,那么就會進行第三輪考察,比較兩個從節點的 ID 號,ID 號小的從節點勝出。
什么是 ID 號?每個從節點都有一個編號,這個編號就是 ID 號,是用來唯一標識從節點的。
到這里,選主的事情終于結束了。簡單給大家總結下:
在選舉出從節點后,哨兵 leader 向被選中的從節點發送 SLAVEOF no one 命令,讓這個從節點解除從節點的身份,將其變為新主節點。
如下圖,哨兵 leader 向被選中的從節點 server2 發送 SLAVEOF no one 命令,將該從節點升級為新主節點。
在發送 SLAVEOF no one 命令之后,哨兵 leader 會以每秒一次的頻率向被升級的從節點發送 INFO 命令(沒進行故障轉移之前,INFO 命令的頻率是每十秒一次),并觀察命令回復中的角色信息,當被升級節點的角色信息從原來的 slave 變為 master 時,哨兵 leader 就知道被選中的從節點已經順利升級為主節點了。
如下圖,選中的從節點 server2 升級成了新主節點:
步驟二:將從節點指向新主節點
當新主節點出現之后,哨兵 leader 下一步要做的就是,讓已下線主節點屬下的所有「從節點」指向「新主節點」,這一動作可以通過向「從節點」發送 SLAVEOF 命令來實現。
如下圖,哨兵 leader 向所有從節點(server3和server4)發送 SLAVEOF ,讓它們成為新主節點的從節點。
所有從節點指向新主節點后的拓撲圖如下:
步驟三:通知客戶的主節點已更換
經過前面一系列的操作后,哨兵集群終于完成主從切換的工作,那么新主節點的信息要如何通知給客戶端呢?
這主要通過 Redis 的發布者/訂閱者機制來實現的。每個哨兵節點提供發布者/訂閱者機制,客戶端可以從哨兵訂閱消息。
哨兵提供的消息訂閱頻道有很多,不同頻道包含了主從節點切換過程中的不同關鍵事件,幾個常見的事件如下:
客戶端和哨兵建立連接后,客戶端會訂閱哨兵提供的頻道。主從切換完成后,哨兵就會向 +switch-master 頻道發布新主節點的 IP 地址和端口的消息,這個時候客戶端就可以收到這條信息,然后用這里面的新主節點的 IP 地址和端口進行通信了。
通過發布者/訂閱者機制機制,有了這些事件通知,客戶端不僅可以在主從切換后得到新主節點的連接信息,還可以監控到主從節點切換過程中發生的各個重要事件。這樣,客戶端就可以知道主從切換進行到哪一步了,有助于了解切換進度。
將舊主節點切換為從節點
故障轉移操作最后要做的是,繼續監視舊主節點,當舊主節點重新上線時,哨兵集群就會向它發送 SLAVEOF 命令,讓它成為新主節點的從節點,如下圖:
哨兵集群是如何組成的?
在我第一次搭建哨兵集群的時候,當時覺得很詫異。因為在配置哨兵的信息時,竟然只需要填下面這幾個參數,設置主節點名字、主節點的 IP 地址和端口號以及 quorum 值。
sentinel monitor <master-name> <ip> <redis-port> <quorum>
不需要填其他哨兵節點的信息,我就好奇它們是如何感知對方的,又是如何組成哨兵集群的?
后面才了解到,哨兵節點之間是通過 Redis 的發布者/訂閱者機制來相互發現的。
在主從集群中,主節點上有一個名為sentinel:hello的頻道,不同哨兵就是通過它來相互發現,實現互相通信的。
在下圖中,哨兵 A 把自己的 IP 地址和端口的信息發布到__sentinel__:hello 頻道上,哨兵 B 和 C 訂閱了該頻道。那么此時,哨兵 B 和 C 就可以從這個頻道直接獲取哨兵 A 的 IP 地址和端口號。然后,哨兵 B、C 可以和哨兵 A 建立網絡連接。
通過這個方式,哨兵 B 和 C 也可以建立網絡連接,這樣一來,哨兵集群就形成了。
哨兵集群會對「從節點」的運行狀態進行監控,那哨兵集群如何知道「從節點」的信息?
主節點知道所有「從節點」的信息,所以哨兵會每 10 秒一次的頻率向主節點發送 INFO 命令來獲取所有「從節點」的信息。
如下圖所示,哨兵 B 給主節點發送 INFO 命令,主節點接受到這個命令后,就會把從節點列表返回給哨兵。接著,哨兵就可以根據從節點列表中的連接信息,和每個從節點建立連接,并在這個連接上持續地對從節點進行監控。哨兵 A 和 C 可以通過相同的方法和從節點建立連接。
正式通過 Redis 的發布者/訂閱者機制,哨兵之間可以相互感知,然后組成集群,同時,哨兵又通過 INFO 命令,在主節點里獲得了所有從節點連接信息,于是就能和從節點建立連接,并進行監控了。
哨兵集群中,為什么建議部署奇數個哨兵節點?
- 避免投票平局
- 哨兵集群在進行主節點故障檢測以及新主節點選舉等操作時,需要通過投票來決定。若節點數量為偶數,可能會出現投票平局的情況,導致無法達成共識,從而影響故障轉移的進行。
- 提高故障轉移效率
- 奇數個哨兵節點有助于更快地確定多數派,減少決策時間
- 資源利用更合理
- 在保證相同容錯能力的情況下,奇數個節點比偶數個節點更節省資源。
哨兵的 “quorum” 參數有什么作用?如何設置合理的值?
<font style="color:#000000;">quorum</font>
定義了判定主節點 “客觀下線(ODOWN)” 所需的最少哨兵節點數量。<font style="color:#000000;">quorum</font>
是哨兵集群對 “主節點是否真的故障” 達成共識的 “最低票數門檻”,目的是避免因單個哨兵的誤判(如網絡抖動)觸發不必要的故障轉移。
:::info
如何設置合理的quorum值?
:::
<font style="color:#000000;">quorum = (哨兵總數 // 2) + 1</font>
(多數派原則),
主從復制、哨兵、Cluster 三種架構分別適用于什么業務規模?
在 Redis 的三種核心架構(主從復制、哨兵、Cluster)中,其適用的業務規模差異主要由數據量、并發量、可用性要求、擴容需求四大核心因素決定。
主從復制
主從復制是 Redis 最基礎的高可用架構,核心邏輯是1 個 Master 節點寫入數據,N 個 Slave 節點同步 Master 數據并提供讀服務,本身不具備 “自動故障轉移” 能力(需手動切換 Master)。
- 讀寫分離:Master 負責寫操作,Slave 分擔讀操作,解決單節點 “讀多寫少” 場景下的性能瓶頸。
- 數據冗余:Slave 節點是 Master 的副本,避免單點數據丟失風險(如 Master 宕機后可從 Slave 恢復數據)。
- 無自動故障轉移:Master 宕機后,需手動將某個 Slave 升級為新 Master,期間寫服務不可用。
- 橫向擴展有限:Slave 節點數量建議不超過 5 個(過多 Slave 會增加 Master 的同步壓力,導致延遲),且所有節點存儲全量數據(無法突破單節點存儲上限)。
適用于中小規模,允許短時間(分鐘級)寫服務不可用(如手動切換 Master 的時間),或可接受 “讀服務正常、寫服務暫停” 的臨時狀態。
哨兵模式(Sentinel)
哨兵架構是在 “主從復制” 基礎上增加了哨兵節點(Sentinel Node) ,核心能力是監控主從節點狀態、自動故障轉移、通知客戶端,解決了主從復制 “手動切換 Master” 的痛點。
- 自動故障轉移:當哨兵集群檢測到 Master 宕機后,會自動選舉 1 個 Slave 升級為新 Master,并更新其他 Slave 的同步目標,整個過程無需人工干預(通常秒級完成)。
- 高可用保障:哨兵節點本身需部署 3 個及以上(避免哨兵單點故障),確保 “監控服務” 不中斷。
- 讀寫分離延續:保留主從復制的 “讀多寫少” 優化能力,Slave 仍可分擔讀壓力。
- 存儲瓶頸未解決:所有節點(Master+Slave)仍存儲全量數據,無法突破單節點的存儲上限(如數據量達到 50GB,單節點內存無法承載)。
適用于中大規模業務,數據量20GB、并發量讀1w~2w,可用性高。
應用場景:
- 電商的用戶購物車、訂單列表(數據量中等,讀并發高,寫服務中斷會直接影響用戶體驗)。
- 在線教育的課程報名、學習進度存儲(用戶數據重要,需高可用,且讀多寫少)。
- 支付系統的非核心記賬數據(如交易流水查詢,讀并發高,需避免寫服務中斷導致數據丟失)。
Cluster(集群)
Cluster 是 Redis 官方推出的分布式集群架構,核心邏輯是將數據分片(Sharding)到多個 “主從節點組” ,每個主節點(Master)負責一部分哈希槽(共 16384 個槽),Slave 節點僅作為對應 Master 的副本,同時支持 “自動故障轉移”。
- 數據分片:突破單節點存儲上限 —— 數據按 “哈希槽” 分配到不同 Master,每個 Master 僅存儲部分數據(如 3 個 Master 節點,每個負責~5461 個槽,總存儲量可線性擴展)。
- 并發擴展:寫并發可線性提升 —— 多個 Master 節點同時處理寫請求(如 5 個 Master,寫并發可提升至單節點的 5 倍左右)。
- 自動故障轉移:每個 Master 對應 1~N 個 Slave,當 Master 宕機時,其 Slave 會自動升級為新 Master,保障分片數據的可用性。
- 復雜度較高:需管理哈希槽分配、跨節點數據遷移(如擴容時添加 Master 需重新分配槽)、客戶端路由(客戶端需支持 Cluster 協議,自動定位數據所在的 Master)。
適用于大規模/超大規模業務,數據量支持20GB以上,并發讀支持5w以上,可用性極高。
總結
架構類型 | 適用數據量 | 適用并發量(讀 / 寫) | 可用性(寫服務) | 核心痛點 | 典型業務規模 |
---|---|---|---|---|---|
主從復制 | ≤10GB(單節點) | 讀≤1 萬 QPS / 寫≤2000 QPS | 低(手動切換,分鐘級) | 無自動故障轉移 | 中小規模(初創公司、內部系統) |
哨兵(Sentinel) | ≤20GB(單節點) | 讀≤5 萬 QPS / 寫≤3000 QPS | 高(自動切換,秒級) | 無數據分片(存儲瓶頸) | 中大規模(成長型企業、核心業務) |
Cluster(集群) | ≥20GB(分布式) | 讀≥5 萬 QPS / 寫≥3000 QPS | 極高(分片故障自動恢復) | 架構復雜(槽管理、路由) | 大規模 / 超大規模(頭部企業、核心交易) |