分布式系統(tǒng)之Redis主從架構(gòu)
單機(jī)的 redis,能夠承載的 QPS 大概就在上萬到幾萬不等。對(duì)于緩存來說,一般都是用來支撐讀高并發(fā)的。因此架構(gòu)做成主從(master-slave)架構(gòu),一主多從,主負(fù)責(zé)寫,并且將數(shù)據(jù)復(fù)制到其它的 slave 節(jié)點(diǎn),從節(jié)點(diǎn)負(fù)責(zé)讀。所有的讀請(qǐng)求全部走從節(jié)點(diǎn)。這樣也可以很輕松實(shí)現(xiàn)水平擴(kuò)容,支撐讀高并發(fā)。

redis replication -> 主從架構(gòu) -> 讀寫分離 -> 水平擴(kuò)容支撐讀高并發(fā)
redis replication 的核心機(jī)制
- redis 采用異步方式復(fù)制數(shù)據(jù)到 slave 節(jié)點(diǎn),不過 redis2.8 開始,slave node 會(huì)周期性地確認(rèn)自己每次復(fù)制的數(shù)據(jù)量;
- 一個(gè) master node 是可以配置多個(gè) slave node 的;
- slave node 也可以連接其他的 slave node;
- slave node 做復(fù)制的時(shí)候,不會(huì) block master node 的正常工作;
- slave node 在做復(fù)制的時(shí)候,也不會(huì) block 對(duì)自己的查詢操作,它會(huì)用舊的數(shù)據(jù)集來提供服務(wù);但是復(fù)制完成的時(shí)候,需要?jiǎng)h除舊數(shù)據(jù)集,加載新數(shù)據(jù)集,這個(gè)時(shí)候就會(huì)暫停對(duì)外服務(wù)了;
slave node 主要用來進(jìn)行橫向擴(kuò)容,做讀寫分離,擴(kuò)容的 slave node 可以提高讀的吞吐量。
注意,如果采用了主從架構(gòu),那么建議必須開啟 master node 的持久化,不建議用 slave node 作為 master node 的數(shù)據(jù)熱備,因?yàn)槟菢拥脑挘绻汴P(guān)掉 master 的持久化,可能在 master 宕機(jī)重啟的時(shí)候數(shù)據(jù)是空的,然后可能一經(jīng)過復(fù)制, slave node 的數(shù)據(jù)也丟了。
另外,master 的各種備份方案,也需要做。萬一本地的所有文件丟失了,從備份中挑選一份 rdb 去恢復(fù) master,這樣才能確保啟動(dòng)的時(shí)候,是有數(shù)據(jù)的,即使采用了后續(xù)講解的高可用機(jī)制,slave node 可以自動(dòng)接管 master node,但也可能 sentinel 還沒檢測(cè)到 master failure,master node 就自動(dòng)重啟了,還是可能導(dǎo)致上面所有的 slave node 數(shù)據(jù)被清空。
redis 主從復(fù)制的核心原理
當(dāng)啟動(dòng)一個(gè) slave node 的時(shí)候,它會(huì)發(fā)送一個(gè) PSYNC 命令給 master node。
如果這是 slave node 初次連接到 master node,那么會(huì)觸發(fā)一次 full resynchronization 全量復(fù)制。此時(shí) master 會(huì)啟動(dòng)一個(gè)后臺(tái)線程,開始生成一份 RDB 快照文件,同時(shí)還會(huì)將從客戶端 client 新收到的所有寫命令緩存在內(nèi)存中。RDB 文件生成完畢后, master 會(huì)將這個(gè) RDB 發(fā)送給 slave,slave 會(huì)先寫入本地磁盤,然后再?gòu)谋镜卮疟P加載到內(nèi)存中,接著 master 會(huì)將內(nèi)存中緩存的寫命令發(fā)送到 slave,slave 也會(huì)同步這些數(shù)據(jù)。slave node 如果跟 master node 有網(wǎng)絡(luò)故障,斷開了連接,會(huì)自動(dòng)重連,連接之后 master node 僅會(huì)復(fù)制給 slave 部分缺少的數(shù)據(jù)。

主從復(fù)制的斷點(diǎn)續(xù)傳
從 redis2.8 開始,就支持主從復(fù)制的斷點(diǎn)續(xù)傳,如果主從復(fù)制過程中,網(wǎng)絡(luò)連接斷掉了,那么可以接著上次復(fù)制的地方,繼續(xù)復(fù)制下去,而不是從頭開始復(fù)制一份。
master node 會(huì)在內(nèi)存中維護(hù)一個(gè) backlog,master 和 slave 都會(huì)保存一個(gè) replica offset 還有一個(gè) master run id,offset 就是保存在 backlog 中的。如果 master 和 slave 網(wǎng)絡(luò)連接斷掉了,slave 會(huì)讓 master 從上次 replica offset 開始繼續(xù)復(fù)制,如果沒有找到對(duì)應(yīng)的 offset,那么就會(huì)執(zhí)行一次 resynchronization。
如果根據(jù) host+ip 定位 master node,是不靠譜的,如果 master node 重啟或者數(shù)據(jù)出現(xiàn)了變化,那么 slave node 應(yīng)該根據(jù)不同的 run id 區(qū)分。
無磁盤化復(fù)制
master 在內(nèi)存中直接創(chuàng)建 RDB,然后發(fā)送給 slave,不會(huì)在自己本地落地磁盤了。只需要在配置文件中開啟 repl-diskless-sync yes 即可。
- repl-diskless-sync yes# 等待 5s 后再開始復(fù)制,因?yàn)橐雀?nbsp;slave 重新連接過來repl-diskless-sync-delay 5
過期 key 處理
slave 不會(huì)過期 key,只會(huì)等待 master 過期 key。如果 master 過期了一個(gè) key,或者通過 LRU 淘汰了一個(gè) key,那么會(huì)模擬一條 del 命令發(fā)送給 slave。
復(fù)制的完整流程
slave node 啟動(dòng)時(shí),會(huì)在自己本地保存 master node 的信息,包括 master node 的host和ip,但是復(fù)制流程沒開始。
slave node 內(nèi)部有個(gè)定時(shí)任務(wù),每秒檢查是否有新的 master node 要連接和復(fù)制,如果發(fā)現(xiàn),就跟 master node 建立 socket 網(wǎng)絡(luò)連接。然后 slave node 發(fā)送 ping 命令給 master node。如果 master 設(shè)置了 requirepass,那么 slave node 必須發(fā)送 masterauth 的口令過去進(jìn)行認(rèn)證。master node 第一次執(zhí)行全量復(fù)制,將所有數(shù)據(jù)發(fā)給 slave node。而在后續(xù),master node 持續(xù)將寫命令,異步復(fù)制給 slave node。

全量復(fù)制
- master 執(zhí)行 bgsave ,在本地生成一份 rdb 快照文件。
- master node 將 rdb 快照文件發(fā)送給 slave node,如果 rdb 復(fù)制時(shí)間超過 60秒(repl-timeout),那么 slave node 就會(huì)認(rèn)為復(fù)制失敗,可以適當(dāng)調(diào)大這個(gè)參數(shù)(對(duì)于千兆網(wǎng)卡的機(jī)器,一般每秒傳輸 100MB,6G 文件,很可能超過 60s)
- master node 在生成 rdb 時(shí),會(huì)將所有新的寫命令緩存在內(nèi)存中,在 slave node 保存了 rdb 之后,再將新的寫命令復(fù)制給 slave node。
- 如果在復(fù)制期間,內(nèi)存緩沖區(qū)持續(xù)消耗超過 64MB,或者一次性超過 256MB,那么停止復(fù)制,復(fù)制失敗。
- client-output-buffer-limit slave 256MB 64MB 60
- slave node 接收到 rdb 之后,清空自己的舊數(shù)據(jù),然后重新加載 rdb 到自己的內(nèi)存中,同時(shí)基于舊的數(shù)據(jù)版本對(duì)外提供服務(wù)。
- 如果 slave node 開啟了 AOF,那么會(huì)立即執(zhí)行 BGREWRITEAOF,重寫 AOF。
增量復(fù)制
- 如果全量復(fù)制過程中,master-slave 網(wǎng)絡(luò)連接斷掉,那么 slave 重新連接 master 時(shí),會(huì)觸發(fā)增量復(fù)制。
- master 直接從自己的 backlog 中獲取部分丟失的數(shù)據(jù),發(fā)送給 slave node,默認(rèn) backlog 就是 1MB。
- master 就是根據(jù) slave 發(fā)送的 psync 中的 offset 來從 backlog 中獲取數(shù)據(jù)的。
heartbeat
主從節(jié)點(diǎn)互相都會(huì)發(fā)送 heartbeat 信息。
master 默認(rèn)每隔 10秒 發(fā)送一次 heartbeat,slave node 每隔 1秒 發(fā)送一個(gè) heartbeat。
異步復(fù)制
master 每次接收到寫命令之后,先在內(nèi)部寫入數(shù)據(jù),然后異步發(fā)送給 slave node。
redis 如何才能做到高可用
如果系統(tǒng)在 365 天內(nèi),有 99.99% 的時(shí)間,都是可以嘩嘩對(duì)外提供服務(wù)的,那么就說系統(tǒng)是高可用的。
一個(gè) slave 掛掉了,是不會(huì)影響可用性的,還有其它的 slave 在提供相同數(shù)據(jù)下的相同的對(duì)外的查詢服務(wù)。
但是,如果 master node 死掉了,會(huì)怎么樣?沒法寫數(shù)據(jù)了,寫緩存的時(shí)候,全部失效了。slave node 還有什么用呢,沒有 master 給它們復(fù)制數(shù)據(jù)了,系統(tǒng)相當(dāng)于不可用了。
redis 的高可用架構(gòu),叫做 failover 故障轉(zhuǎn)移,也可以叫做主備切換。
master node 在故障時(shí),自動(dòng)檢測(cè),并且將某個(gè) slave node 自動(dòng)切換為 master node 的過程,叫做主備切換。這個(gè)過程,實(shí)現(xiàn)了 redis 的主從架構(gòu)下的高可用。