概述
?Redis作為常用的緩存中間件,因其高性能,豐富的數據結構,使用簡單等,常被用在需要一定高性能的To C業務場景中,如「秒殺場景」「用戶信息中心」「帖子」「群聊」等等大家常見的業務場景中,以提高服務的響應速度,降低接口RT,同時也可以降低對DB層的壓力,以提高整個服務集群的可靠性與穩定性等,提升用戶體驗。當然,Redis還有很多的應用場景,如分布式鎖,限流等等的應用場景,后續會逐一進行討論分析。
?To C的高流量場景往往反映著該服務所對應的業務域具有較高的重要性,對服務集群的高性能與高可靠性都具有較高的要求,Redis作為常用的緩存中間件嵌入在服務中來抗大流量,其自生就應該具備一定的高可用性。
?從Redis誕生至今,其高可用架構也在不斷的演進中,以滿足不同業務體量下服務的性能與可用性需求。從最初的主從架構,到基于Sentinel(哨兵模式)的高可用架構,再到目前被廣泛使用的集群架構,Redis都在不斷發展演進。每一種結構都存在其對應的優缺點,也對應著應用場景,本文將著重介紹Redis不同的高可用架構,對應的優缺點,以及每種架構的實現原理,應用場景等,其高性能的實現原理不在本文的討論范圍內(想必大家也都了解Redis為何性能好的原因)。
?希望本文對想了解Redis高可用架構的同學有一定的幫助,最后也歡迎大家指出其中的問題,一起討論。
?內容說明:
? 1. 本文將按照Redis演進的時間線,逐一的介紹不同的架構。分別為:「基本的主從架構」「改進的主從架構」「基于Sentinel(哨兵模式)的高可用架構」「集群模式」。
? 2. 因整體內容較多,本文將分階段進行完善。
?閱讀說明:中間涉及到一些分布式相關知識,有一定分布式基礎的可更好理解相關內容。
主從架構
基本的主從架構
??主從架構模式:部署多臺redis節點,其中只有一臺節點是主節點(master),其他的節點都是從節點(slave),也叫備份節點(replica)。只有master節點提供數據的事務性操作(增刪改),slave節點只提供讀操作。所有slave節點的數據都是從master節點同步過來的。
問題與缺點
??上圖只是最簡單的一種主從結構方式,所有的slave節點都掛在master節點上,這樣做的好處是slave節點與master節點的數據延遲較小;缺點是如果slave節點數量很多,master同步一次數據的耗時就很長,影響master節點的性能。
改進的主從架構
主從從架構
??當slave過多時,可以將主從架構調整為主從從的拓撲結構,以減少過多的slave節點數據同步(全量同步/增加同步)帶來的cpu/網絡等的開銷,降低master節點的壓力;
問題與缺點
??一主多從變為樹樁結構,由于層級深度,導致深度越高的slave與最頂層master間數據同步延遲較大,數據一致性變差,所以最多不要超過三層。
主要使用場景
- 讀多寫少 - 讀寫分離 - 有一定的并發量
主從同步機制
全量同步(快照同步)
觸發場景
- 集群中加入新的節點
- 主從節點偏移量差距超過一定的閾值
- 潛在的原因:
- 網絡問題
- 從節點離線較長后再次連上集群,導致主從數據偏移量相差過大
- 主節點寫入數據的速度大于主從同步的數據等場景
- 閾值:由redis buffer 緩沖區的大小決定
- 潛在的原因:
同步原理
- 從節點啟動之后會與主節點建立socket長連接,然后向主節點發送psync指令
- 主節點收到從節點的psync指令之后,會執行BGsave命令生成新的rbd持久化文件,在生成過程中處理的客戶端寫操作命令會放在repl緩沖區。
- 主節點將生成的rbd文件發送給從節點,從節點清空老數據,加載新的rbd文件
- 主節點將生成的repl緩沖區的指令發送給從節點,從節點將執行該命令,將數據寫入到內存
- 主節點通過socket長連接把修改命令發送給從節點,保證主從一致
全量同步開銷
??主節點:生成RDB文件會占用內存、硬盤資源,網絡傳輸RDB的時候會占用一定的網絡帶寬資源
從節點:清空數據,若數據量大,需要消耗一定的時間,加載RDB也需要一定的時間
命令傳播(增量同步)
??在master節點和slave節點同步完成后,主服務器的每次更新都會發送相應的命令到從服務器。從服務器執行對應操作,將數據庫狀態更新到主服務器一致的狀態。
1.支持斷點續傳 - 通過 master的redis buffer緩沖區 / master節點offset / slave節點的offset實現
無盤復制
??通常,全量復制需要在磁盤上創建RDB文件,然后加載到內存中,Redis支持無盤復制,生成的RDB文件不保存到磁盤而是直接通過網絡發送給從節點。無盤復制適用于主節點所在機器磁盤性能較差但網絡寬帶較充裕的場景。需要注意的是,無盤復制目前依然處于實驗階段。(類似零拷貝)
問題與缺點
- 主從架構的問題在于所有的寫入操作都在master節點上,實際上是一個具有單點問題的架構;若master節點出現異常(節點下線等導致的不可用等),則整個集群將不可用
- 主從切換需要人工干預(無選舉機制),同時需要變更客戶端連接地址
基于Sentinel(哨兵模式)的高可用架構
出現背景
Sentinel架構解決redis主從架構人工干預的問題。
Redis官方文檔
https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/
簡介
??Redis Sentinel 是 Redis 的高可用實現方案。Sentinel不是一個單獨的進程,而是有多個哨兵服務組成的分布式系統。Sentinel集群獨立于 Redis 集群,哨兵之間彼此建立連接,共同監控、管理所有的 Redis 節點。哨兵間使用流言協議(gossip protocols)進行消息傳播,使用投票協議(agreement protocols)決定是否執行自動故障遷移和選擇新的主節點。
作用
- 監 控:監控所有 Redis 節點的狀態。
- 故障轉移:當哨兵發現主節點下線時,會在所有從節點中選擇一個作為新的主節點,并將所有其他節點的 Master 指向新的主節點。同時已下線的原主節點也會被降級為從節點,并修改配置將 原Master 指向新的主節點,等到它重新上線時就會自動以從節點進行工作。
- 通 知:當哨兵選舉了新的主節點之后,可以通過 API 向客戶端進行通知。
基本原理
??Sentinel服務負責持續監控主從節點的健康狀況,當主節點掛掉時,自動選擇一個最優的從節點切換為主節點。客戶端來連接集群時,會首先連接 sentinel,通過 sentinel 來查詢主節點的地址,然后再去連接主節點進行數據交互。當主節點發生故障時,客戶端會重新向 sentinel 要地址,sentinel 會將最新的主節點地址告訴客戶端。如此應用程序將無需重啟即可自動完成節點切換。
從庫發現
??對于哨兵的配置,我們只需要配置主庫的信息,哨兵在連接主庫之后,會調用 INFO
命令獲取主庫的信息,再從中解析出連接主庫的從庫信息,再以此和其他從庫建立連接進行監控。
??哨兵對所有節點都會每隔 10s 發送一次 INFO
命令,從各節點獲取 Redis 集群實時的拓撲圖信息。如果新節點加入,哨兵就會去監控新的節點。
**INFO
**命令信息
# Replication
role:master
connected_slaves:2
slave0:ip=172.25.0.102,port=6379,state=online,offset=258369,lag=1
slave1:ip=172.25.0.103,port=6379,state=online,offset=258508,lag=0
master_failover_state:no-failover
master_replid:a4a6a7f3b2e15d9a43c01d4ba6c842539e582d6a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:258508
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:258508
發布/訂閱機制
??哨兵們在連接同一個主庫之后,是通過發布/訂閱(pub/sub)模式來發現彼此的存在的。
發布/訂閱(pub/sub)是一種消息通信模式,主要的目的是解耦消息發布者和消息訂閱者之間的耦合。Redis 作為一個 pub/sub server,在訂閱者和發布者之間起到了消息路由的功能。訂閱者可以通過 subscribe 和 psubscribe 命令從 Redis 訂閱自己感興趣的消息類型,Redis 將消息類型稱為頻道(channel)。當發布者通過 publish 命令向 Redis 發送特定類型的消息時,該頻道的全部訂閱者都會收到此消息。這里消息的傳遞是多對多的。一個 client 可以訂閱多個 channel,也可以向多個 channel 發送消息。
??在哨兵模式下,哨兵們會在每個 Redis 服務上創建并訂閱一個名為 sentinel:hello
的頻道,哨兵們就是通過它來相互發現,實現相互通信的。
??訂閱后,每個哨兵每隔 2 秒都會向 hello
頻道發布一條攜帶自身信息的 hello 信息,這樣哨兵就能知道其他哨兵的狀態、監控的主節點和是否有新的哨兵加入:
//Hello頻道消息
127.0.0.1:6371> subscribe __sentinel__:hello
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "__sentinel__:hello"
3) (integer) 1
1) "message"
2) "__sentinel__:hello"
3) "172.25.0.202,26379,5134e342cc62ac76494c140b66b7fda80340e3a8,0,mymaster,172.25.0.101,6379,0"
1) "message"
2) "__sentinel__:hello"
3) "172.25.0.203,26379,5f5ce54a6f22f71c7d273cfb9eb14377b103d4ad,0,mymaster,172.25.0.101,6379,0"
1) "message"
2) "__sentinel__:hello"
3) "172.25.0.201,26379,4fa3486dfbaca9abc62b2976e821d18e697ab2db,0,mymaster,172.25.0.101,6379,0"
監控
??哨兵在對 Redis 節點建立 TCP 連接之后,會周期性地發送 PING
命令給節點(默認是 1s),以此判斷節點是否正常。如果在 down-after-millisenconds
時間內沒有收到節點的響應,它就認為這個節點掉線了。
主觀下線
??當哨兵發現與自己連接的其他節點斷開連接,它就會將該節點標記為主觀下線(+sdown
),包括主節點、從節點或者其他哨兵都可以標記為 sdown
狀態。
//從節點主觀下線
1:X 19 Mar 2024 13:26:29.837 # +sdown slave 172.25.0.103:6379 172.25.0.103 6379 @ mymaster 172.25.0.101 6379#
//哨兵主觀下線
1:X 19 Mar 2024 13:19:19.799 # +sdown sentinel 5134e342cc62ac76494c140b66b7fda80340e3a8 172.25.0.202 26379 @ mymaster 172.25.0.101 6379#
//主節點主觀下線
1:X 19 Mar 2024 13:24:06.612 # +sdown master mymaster 172.25.0.101 6379
??當該節點重新連接之后,哨兵會取消對它的主觀下線標記,操作是 -sdown
。
1:X 19 Mar 2024 13:20:04.811 # -sdown sentinel 5134e342cc62ac76494c140b66b7fda80340e3a8 172.25.0.202 26379 @ mymaster 172.25.0.101 6379
??如果哨兵判斷從節點或者其他哨兵節點主觀下線,哨兵并不會執行其他操作。如果是主節點主觀下線,哨兵就要采取措施,確定主節點是否真的宕機,并執行故障轉移。
客觀下線
??哨兵確認主節點是否真的宕機這一步成為客觀下線確認,如果主節點真的宕機了,哨兵就會將主節點標記為客觀下線(+odown
)狀態。
1:X 19 Mar 2024 13:24:06.612 # +sdown master mymaster 172.25.0.101 6379
1:X 19 Mar 2024 13:24:06.685 # +odown master mymaster 172.25.0.101 6379 #quorum 2/2
??要判斷主節點是否客觀下線,需要與其他哨兵達成共識,如果大多數哨兵認為主節點主觀下線了,哨兵才能確認主節點客觀下線。達成共識的方式就是發起一輪投票,如果票數超過哨兵節點數的一半,并且大于等于 quorum
設置的數量,就是投票成功。否則哨兵就不能說主節點客觀下線了。
quorum
是法定人數的意思,該信息在哨兵配置信息中進行配置:
# sentinel.conf
sentinel monitor <master-name> <ip> <redis-port> <quorum>
客觀下線投票過程
- 當哨兵發現主節點下線,標記主節點為
sdown
狀態。 - 哨兵向其他哨兵發送
SENTINEL is-master-down-by-addr
命令,詢問其他哨兵該主節點是否已下線。 - 其他哨兵在收到投票請求之后,會檢查本地主緩存中主節點的狀態并進行回復(
1
表示下線,0
表示正常)。 - 發起的詢問的哨兵在接收到回復之后,會累加“下線”的得票數。
- 當下線的票數大于一半哨兵數量并且不小于
quorum
時,就會將主節點標記為odown
狀態。并開始準備故障轉移。 - 發起投票的哨兵有一個投票倒計時,倒計時結束如果票數仍然不夠的話,則放棄本次客觀線下投票。并嘗試繼續與主節點建立連接。
【注意】
1.當哨兵把主節點標記為 odown 時,并不會通知其他哨兵,因為這樣自己才更有機會進行故障轉移。2.如果多個哨兵在同一段時間內發現主節點下線,那么每個發現的哨兵都會發起投票,投票的結果只是讓發起投票的哨兵能夠確認主節點是否下線,并不會與其他哨兵共享。因此這個下線確認的動作是多個節點同時發起進行的。
故障轉移
??哨兵在將主節點標記為 odown
狀態之后,就會馬上開始嘗試故障轉移了。
??故障轉移主要由 sentinelFailoverStateMachineZ(sentinelRedisInstance)
函數負責。該函數由一個狀態機組成,共有五個狀態,標志著故障轉移共分為五個大步驟:
| SENTINEL_FAILOVER_STATE | desc | invoke
|:------------------------|:---------------|:-------------------------------------------------------|
| `WAIT_START` | Leader 選舉 | sentinelFailoverWaitStart(sentinelRedisInstance) |
| `SELECT_SLAVE` | Master 選取 | sentinelFailoverSelectSlave(sentinelRedisInstance) |
| `SEND_SLAVEOF_NOONE` | Slave 身份去除 | sentinelFailoverSendSlaveOfNoOne(sentinelRedisInstance) |
| `WAIT_PROMOTION` | 提升 Master | sentinelFailoverWaitPromotion(sentinelRedisInstance) |
| `RECONF_SLAVES` | 配置從節點 | sentinelFailoverReconfNextSlave(sentinelRedisInstance) |
詳見:https://www.jianshu.com/p/178d0b10809c
整體架構
集群模式(Redis Cluster)
節點負載均衡
一致性哈希
數據分布算法
虛擬節點機制
集群請求重定向
MOVED 錯誤
ASK 錯誤
Goosip協議
說明:集群節點通信機制