我們在 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的主從復制就分享到這里。更多硬核知識,請關注公眾號"程序員學長"。