面試官拷打: Redis 高可用篇章中面試最常見的六個問題!
大家好,我是碼哥,今天跟大家分享幾個 Redis 高可用篇章中面試最常見的 6 個問題。
- Redis 如何實現持久化?
- Redis 高可用方案有哪些?
- 主從異步復制同步實現原理?
- sentinel 哨兵集群?
- Redis Cluster 是什么?
- 為什么集群的 slots 是 16384?
Redis 如何實現持久化?
Chaya 轉行做程序員,去大廠面試被面試官問到:“Redis 如何實現持久化?”
Chaya 心想:“好家伙,我學了碼哥的 Redis 高手心法,這不要起飛么,是時候展示真正的技術了。”
Redis 有兩個方式實現了數據持久化,他們分別是 RDB 快照和 AOF(Append Only File)。RDB 內存快照是全量持久化,AOF 做增量持久化。
RDB 內存快照
RDB內存快照
bgsave 指令會調用 glibc 的函數fork產生一個子進程用于寫入臨時 RDB 文件,快照持久化完全交給子進程來處理,完成后自動結束,父進程可以繼續處理客戶端請求,阻塞只發生在 fork 階段,時間很短,當子進程寫完新的 RDB 文件后,它會替換舊的 RDB 文件。
RDB 文件實時性不夠,宕機的時候可能會導致大量數據丟失。此外,fork 子進程屬于重量級操作,執行成本比較高,頻繁生成 RDB 文件,磁盤壓力也會過大。
AOF
AOF (Append Only File)持久化記錄的是服務器接收的每個寫操作,在服務器啟動執行重放還原數據集。由于 AOF 記錄的是一個個指令內容,故障恢復的時候需要執行每一個指令,如果日志文件太大,整個恢復過程就會非常緩慢。
AOF寫后日志
所以,還需配合 AOF 來使用。簡單來說,RDB 內存快照以一定的頻率執行,在兩次快照之間,使用 AOF 日志記錄這期間的所有寫操作。
如此一來,快照就不需要頻繁執行,避免了 fork 對主線程的性能影響,AOF 不再是全量日志,而是生成 RDB 快照時間的增量 AOF 日志
面試官:“如果機器突然掉電會怎樣?”
Chaya 假裝思考一下,說道:“取決于 AOF 配置項appendfsync寫回策略。always同步寫回可以做到數據不丟失,但是每個寫指令都需要寫入磁盤,性能最差。
everysec每秒寫回,避免了同步寫回的性能開銷,發生宕機可能有一秒位寫入磁盤的數據丟失,在性能和可靠性之間做了折中。”
這時候,面試官心想這候選人,有點東西。
Chaya 繼續補充道:“為了避免 AOF 文件體積膨脹的問題,還有一個 AOF 重寫機制對文件瘦身。在 7.0 版本還做了優化,提出了 Multi-Part AOF 機制,因為在 7.0 之前的版本中 AOF Rewrite 過程中,主進程除了把寫指令寫到 AOF 緩沖區以外,還要寫到 AOF 重寫緩沖區中。一份數據要寫兩個緩沖區,還要寫到兩個 AOF 文件,產生兩次磁盤 I/O ,太浪費了。”
Redis 高可用方案有哪些?
高可用有兩個含義:一是數據盡量不丟失,二是服務盡可能提供服務。 Redis 高可用方案嚴格意義上來說有 3 種。
- 主從復制架構,這是后兩個方案的基石。
- sentinel 哨兵集群。
- Redis Cluster 集群,極力推薦該方式。
1. 主從異步復制同步
主從異步復制架構是高可用的基石,主要分為 RDB 內存快照文件全量同步和增量同步。
全量同步
Redis master 執行 bgsave 命令生成 RDB 內存快照文件,slave 收到 RDB 內存快照文件保存到磁盤,并清空當前數據庫的數據,再加載 RDB 文件數據到內存中。最后,master 再把發送生成 RDB 文件至同步 slave 加載 RDB 期間接受到的新寫命令同步到到 slave。
Redis全量同步
增量同步
只要主從連接不中斷,就會持續進行基于長連接的命令傳播復制。在 Redis 2.8 之前,如果主從復制在命令傳播時出現了網絡閃斷,那么,slave 就會和 mater 重新進行一次全量復制,開銷非常大。
從 Redis 2.8 開始,網絡斷了重連之后,slave 會嘗試采用增量復制的方式繼續同步。
增量復制:用于網絡中斷等情況后的復制,只將中斷期間 mater 執行的寫命令發送給 slave,與全量復制相比更加高效。
圖 3-4
其中還涉及到 replication buffer 和 repl_backlog 的緩沖區的作用,說到這一塊就已經讓你脫穎而出了。
接著,你再補充在 Redis 7.0 之后,采用了共享緩沖區的設計。
Chaya 自信的補充說:“因為不管是全量復制還是增量復制,當寫請求到達 master 時,指令會分別寫入所有 slave 的 replication buffer 以及 repl_backlog_buffer。重復保存,太浪費內存了。
既然存儲內容是一樣,直接的做法就是主從復制在命令傳播時,將這些寫命令放在一個全局的復制緩沖區中,多個 slave 共享這份數據,不同 slave 引用緩沖區的不同內容,這就是共享緩沖區的核心思想。”
2. sentinel 集群
Sentinel 是 Redis 的一種運行模式,它專注于對 Redis 實例(主節點、從節點)運行狀態的監控,并能夠在主節點發生故障時通過一系列的機制實現選主及主從切換,實現自動故障轉移,確保整個 Redis 系統的可用性。
sentinel 主要做四件事情。
3-18
監控 master 和 slave 狀態,判斷是否下線。
- 每秒一次的頻率向 master 和 slave 以及其他 sentinel 發送 PING 命令,如果該節點距離最后一次響應 PING 的時間超過 down-after-milliseconds 選項所指定的值, 則這個實例會被 Sentinel 標記為主觀下線,當 master 被標記主觀下線。
- 其他正在監視這個 master 的所有 sentinel 會按照每秒一次的頻率確認 master 是否主觀下線。
- 當足夠多的 sentinel 丟認為 master 主觀下線,則標記這個 master 客觀下線。
選舉新 master,如果 master 出現故障,sentine 需要選舉一個 slave 晉升為新 master。晉升為新 master 的 slave 是有條件的,先過濾不滿足條件的,再打分排優先級。
篩選打分
slave 優先級,通過 replica-priority 100 配置,值越低,優先級越高。
復制偏移量(processed replication offset),已復制的數據量越多越好,slave_repl_offset與 master_repl_offset 差值越小。
slave runID,在優先級和復制進度都相同的情況下,runID 最小的 slave 得分最高,會被選為新主庫。
過濾掉下線、網絡異常的 slave。
過濾掉經常與 master 斷開的 slave。
執行主從切換,從 sentinel 集群中選舉一個 leader 執行故障自動切換。成為 leader 的條件是收到的贊成票大于等于 `quorum 的值且贊半數以上。
第一個判定 master 主觀下線的 sentinel 收到其他 sentinel 節點的回復并確定 master 客觀下線后,就會給其他 sentinel 節點發送命令申請成為 leader。
通知,通知其他 slave 執行 replicaof 與新的 master 同步數據,并通知客戶端與新 master 建立連接。
3-17
3. Redis Cluster
Redis Cluster 在 Redis 3.0 及以上版本提供,是一種分布式數據庫方案,通過分片(sharding)來進行數據管理(分治思想的一種實踐),并提供復制和故障轉移功能。
Redis Cluster 并沒有使用一致性哈希算法,而是將數據劃分為 16384 的 slots ,每個節點負責一部分 slots,slot 的信息存儲在每個節點中。
圖 3-25
集群 mater 節點最大上限是 16384(官方建議最大節點數為 1000 個),數據庫的每個 key 會映射到這 16384 個槽中的其中一個,每個節點可以處理 1 個或者最多 16384 個槽。
面試官:“集群各個節點之間是如何通信呢?”
通過 Gossip 協議進行通信,節點之間不斷交換信息,交換的信息包括節點出現故障、新節點加入、主從節點變更, slots 信息變更等。常用的 Gossip 消息分為 4 種,分別是:ping、pong、meet、fail。
- meet 消息:通知新節點加入。消息發送者通知接受者加入當前集群。
- ping消息:每個節點每秒向其他節點發送 ping 消息,用于檢測節點在線和交換刺激狀態信息。
- pong消息:節點接受到 ping 消息后,作為響應消息回復發送方確認正常,同時 pong 還包含了自身的狀態數據,想集群廣播 pong 消息來通知集群自身狀態進行更新。
- fail消息:節點 ping 不通謀節點后,則向集群所有節點廣播該節點掛掉的消息。
面試官:“Redis Cluster 如何實現自動故障轉移呢?”
- 故障檢測:集群中每個節點都會定期通過 Gossip 協議向其他節點發送 PING 消息,檢測各個節點的狀態(在線狀態、疑似下線狀態 PFAIL、已下線狀態 FAIL)。并通過 Gossip 協議來廣播自己的狀態以及自己對整個集群認知的改變。
- master 選舉:使用從當前故障 master 的所有 slave 選舉一個提升為 master。
- 故障轉移:取消與舊 master 的主從復制關系,將舊 master 負責的槽位信息指派到當前 master,更新 Cluster 狀態并寫入數據文件,通過 gossip 協議向集群廣播發送 CLUSTERMSG_TYPE_PONG消息,把最新的信息傳播給其他節點,其他節點收到該消息后更新自身的狀態信息或與新 master 建立主從復制關系。
面試官:“新增節點或者重新分配 slots 導致 slots 與節點之間的映射關系改變了,客戶端如何知道把請求發到哪里?”
Redis Cluster 提供了請求重定向機制解決:客戶端將請求發送到某個節點上,這個節點沒有相應的數據,該 Redis 節點會告訴客戶端將請求發送到其他的節點。
MOVED 重定向
當重新分配或者負載均衡,slots 數據已經遷移到其他節點,節點會響應一個 MOVED 錯誤指引客戶端重定向到正確的節點,并且客戶端會更新本地 slots 與節點映射關系,以便下次可以正確訪問。
GET 公眾號:碼哥字節
-MOVED 16330 172.17.18.2:6379
該響應的含義是客戶端請求的鍵值對所在的 slot 16300 已經遷移到了 172.17.18.2 這個節點上,端口是 6379。
同時,客戶端還會更新本地緩存,將該 slot 與 Redis 實例對應關系更新正確。
3-30
ASK 重定向
如果某個 slot 的數據只有部分遷移過去,沒有遷移完成,節點收到客戶端請求如果能根據 key -> slot -> node 映射關系定位到的節點存在該 key,則直接執行命令,否則就向客戶端響應 ASK 錯誤,表示該 key 所在的 slot 正在遷移到其他節點,客戶端先給目標節點發送 ASKING 命令詢問節點是否可以處理,接著才會發送操作指令。
比如客戶端請求定位到 key = “公眾號:碼哥字節” 的 slot 是 16330 由實例 172.17.18.1 負責,節點 1 如果找得到就直接執行命令,否則響應 ASK 錯誤信息,指引客戶端轉向正在遷移的目標節點 172.17.18.2,端口是 6379。
GET 公眾號:碼哥字節
-ASK 16330 172.17.18.2:6379
3-31
注意:ASK 錯誤指令并不會更新客戶端緩存的 slot 分配信息。
為什么集群的 slots 是 16384?
面試官:“CRC16 算法,產生的 hash 值有 16 bit 位,可以產生 65536(2^16)個值 ,也就是說值分布在 0 ~ 65535 之間。”
- 正常的 ping 數據包攜帶節點的完整配置,用的是一個 bitmap 數據結構,它能以冪等方式來更新配置。如果采用 16384 個插槽,占空間 2KB (16384/8);如果采用 65536 個插槽,占空間 8KB (65536/8)。
- Redis Cluster 不太可能擴展到超過 1000 個主節點,太多可能導致網絡擁堵。
- 16384 個 slot 范圍比較合適,當集群擴展到 1000 個節點時,也能確保每個 master 節點有足夠的 slot。
8KB 的心跳包看似不大,但是這個是心跳包每秒都要將本節點的信息同步給集群其他節點。比起 16384 個 slot ,header 大小增加了 4 倍,ping 消息的消息頭太大了,浪費帶寬。