這些年背過(guò)的面試題:Redis 高可用篇

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


























