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