我們在 Redis持久化機制你學會了嗎?學習了AOF和RDB,如果Redis當機,他們分別通過回放日誌和重新讀入RDB檔案的方式恢復資料,從而提高可靠性。我們今天來想這麼一個問題,假如我們只部署了一臺Redis例項,如果這個例項當機了,那麼它在恢復期間,是無法提供資料存取請求的,這樣就會使得服務中斷。
Redis採用了增加副本的方式,將一份資料同時儲存在多個例項上來避免這種情況發生的。即使有一個例項出現故障,在這個例項恢復期間,其它例項也能對外提供服務,不會影響業務的使用。那多個例項是如何保證資料的一致性呢?資料的讀寫操作可以發給所有的例項嗎?實際上,Redis提供了主從模式,以保證資料副本的一致。主從庫之間採用的是讀寫分離的方式。
對於讀操作來說,主、從庫都可以服務。但是對於寫操作來說,首先是要到主庫執行,然後,主庫再將寫操作同步給從庫。
那為什麼要採用讀寫分離呢?如果不管是主庫還是從庫都能接受客戶端的寫操作,它帶來的直接問題就是:如果客戶端對同一個key進行了2次修改操作,並且每次修改都分到不同的例項上,那麼這2個例項上的副本就不一致了。而主從庫模式一旦採用了讀寫分離,所有資料的修改只會在主庫上進行。主庫有了最新的資料後,會同步給從庫,這樣,主從庫的資料就是一致的。
接下來我們來看一下主從同步是如何完成的?我們首先來看一下主從庫之間的第一次同步是如何進行的。當我們啟動多個redis例項時,它們之間的相互關係就可以通過replicaof命令的形式形成主庫和從庫的關係,之後會按照三個階段完成資料的第一次同步。
replicaof 127.0.0.1 6379
第一階段是主從庫建立連線、協商同步的過程,主要是為了全量複製做準備。在這一步,從庫和主庫建立連線,並告訴主庫即將進行資料同步,主庫確認回覆後,主庫和從庫之間就可以開始同步了。
具體來說,從庫給主庫傳送psync命令,表示要進行資料同步,主庫根據這個命令引數來啟動複製。psync命令包含了主庫的runID和複製進度offset兩個引數。
-
runID:每個Redis例項啟動時會自動生成一個隨機ID來唯一標識這個例項。當從庫和主庫第一次複製時,因為不知道主庫的runID,所以複製為 ?
-
offset:此時設定為-1,表示第一次複製。
主庫收到psync命令後,會用FULLRESYNC響應命令帶上兩個引數:主庫runID和主庫目前的複製進度offset,返回給從庫。從庫收到響應後,會記錄下這兩個引數。這裡有個地方需要注意,FULLRESYNC 響應表示第一次複製採用的是全量複製,也就是說,主庫會把當前所有的資料都複製給從庫。
第二階段是主庫將所有資料同步給從庫。從庫收到資料後,在本地完成資料載入。這個過程依賴於記憶體快照生成的RDB檔案。
具體來說,主庫執行bgsave命令,生成RDB檔案,接著將檔案發給從庫。從庫收到RDB檔案後,會清空當前的資料庫,然後載入RDB檔案。這是因為從庫在通過執行replicaof命令開始和主庫進行同步之前,有可能儲存了
其他的資料。為了避免之前資料的影響,從庫需要把當前資料庫清空。在主庫將資料同步給從庫的過程中,主庫不會被阻塞,仍然可以正常接收請求。
否則,Redis 的服務就被中斷了。但是,這些請求中的寫操作並沒有記錄到剛剛生成的 RDB 檔案中。為了保證主從庫的資料一致性,主庫會在記憶體中用專門的 replication buffer,記錄 RDB 檔案生成後收到的所有寫操作。
最後第三階段,主庫會把第二階段執行過程中收到的命令再傳送給從庫。具體操作是,當主庫完成RDB檔案傳送後,就會把此時replication buffer中記錄的操作傳送給從庫,從庫接下來再去執行這些操作。這樣一來,主從庫就實現同步了。
到這裡,我們就瞭解了主從庫間通過全量複製實現資料同步的過程了。一旦主從庫完成了全量複製,他們之間就會一直維護一個網路連線,主庫會通過這個連線將後續收到的命令同步給從庫,這個過程也稱為基於長連線的命令傳播,可以避免頻繁建立連線的開銷。
但是,在主從傳播命令的過程中,如果出現網路斷開,那主從庫之間就無法進行命令傳播了,從庫自然也就無法和主庫保持一致了。接下來,我們就來聊一聊網路斷開連線後該怎麼辦。
在網路斷開連線後,Redis主從庫會採用增量複製的方式繼續同步。那麼,增量複製時,主從庫之間具體是怎麼保證同步的呢?這裡的奧妙就在repl_backlog_buffer這個環形快取區。
主庫除了會把接收到的寫命令寫入replication buffer傳送給從庫外,同時也會把這些操作命令也寫入 repl_backlog_buffer 這個緩衝區。repl_backlog_buffer是一個環形緩衝區,主庫會記錄自己寫到的位置,從庫則會記錄自己讀到的位置。剛開始的時候,主庫和從庫的寫位置在一起,這算是他們的起始位置。隨著主庫不斷接收新的操作,它在緩衝區中的寫位置會逐步偏離起始位置。我們通常用偏移量來衡量這個偏移距離的大小,對主庫來說,對應的偏移量就是 master_repl_offset。主庫接收的新寫操作越多,這個值就會越大。同樣,從庫在複製完寫操作命令後,它在緩衝區中的讀位置也開始逐步偏移剛才的起始位置,此時,從庫已複製的偏移量 slave_repl_offset 也在不斷增加。正常情況下,這兩個偏移量基本相等。
主從庫的連線恢復之後,從庫首先會給主庫傳送 psync 命令,並把自己當前的 slave_repl_offset 發給主庫,主庫會判斷自己的 master_repl_offset 和 slave_repl_offset 之間的差距。在網路斷連階段,主庫可能會收到新的寫操作命令。所以,一般來說,master_repl_offset 會大於 slave_repl_offset。
此時,主庫只用把 master_repl_offset 和 slave_repl_offset 之間的命令操作同步給從庫就行。如上圖所示,主庫和從庫之間差了b、c、d,增量複製時,主庫只要把他們同步給從庫就行了。
我們藉助一張圖來回顧一下增量複製。
需要注意的一點,因為repl_backlog_buffer是一個環形緩衝區,所以在緩衝區寫滿後,主庫會繼續寫入,此時,就會覆蓋掉之前寫入的操作。如果從庫的讀取速度比較慢,就有可能導致從庫還未讀取的操作被主庫新寫的操作覆蓋了,這會導致主從庫間的資料不一致,從而進行全量複製了。
因此,我們要想辦法避免這一情況,一般而言,我們可以調整repl_backlog_size這個引數來設定快取區的大小。如果它配置得過小,在增量複製階段,可能會導致從庫的複製進度趕不上主庫,進而導致從庫重新進行全量複製。所以,通過調大這個引數,可以減少從庫在網路斷連時全量複製的風險。
Redis的主從複製就分享到這裡。更多硬核知識,請關注公眾號"程式設計師學長"。