探索Redis設計與實現12:淺析Redis主從複製
摘要
早期的RDBMS被設計為執行在單個CPU之上,讀寫操作都由經單個數據庫例項完成,複製技術使得資料庫的讀寫操作可以分散在運行於不同CPU之上的獨立伺服器上,Redis作為一個開源的、優秀的key-value快取及持久化儲存解決方案,也提供了複製功能,本文主要介紹Redis的複製原理及特性。
Redis複製概論
資料庫複製指的是發生在不同資料庫例項之間,單向的資訊傳播的行為,通常由被複制方和複製方組成,被複制方和複製方之間建立網路連線,複製方式通常為被複制方主動將資料傳送到複製方,複製方接收到資料儲存在當前例項,最終目的是為了保證雙方的資料一致、同步。
複製示意圖
Redis複製方式
Redis的複製方式有兩種,一種是主(master)-從(slave)模式,一種是從(slave)-從(slave)模式,因此Redis的複製拓撲圖會豐富一些,可以像星型拓撲,也可以像個有向無環:
Redis叢集複製結構圖
通過配置多個Redis例項獨立執行、定向複製,形成Redis叢集,master負責寫、slave負責讀。
複製優點
通過配置多個Redis例項,資料備份在不同的例項上,主庫專注寫請求,從庫負責讀請求,這樣的好處主要體現在下面幾個方面:
1、高可用性
在一個Redis叢集中,如果master宕機,slave可以介入並取代master的位置,因此對於整個Redis服務來說不至於提供不了服務,這樣使得整個Redis服務足夠安全。
2、高效能
在一個Redis叢集中,master負責寫請求,slave負責讀請求,這麼做一方面通過將讀請求分散到其他機器從而大大減少了master伺服器的壓力,另一方面slave專注於提供讀服務從而提高了響應和讀取速度。
3、水平擴充套件性
通過增加slave機器可以橫向(水平)擴充套件Redis服務的整個查詢服務的能力。
複製缺點
複製提供了高可用性的解決方案,但同時引入了分散式計算的複雜度問題,認為有兩個核心問題:
- 資料一致性問題,如何保證master伺服器寫入的資料能夠及時同步到slave機器上。
- 程式設計複雜,如何在客戶端提供讀寫分離的實現方案,通過客戶端實現將讀寫請求分別路由到master和slave例項上。
上面兩個問題,尤其是第一個問題是Redis服務實現一直在演變,致力於解決的一個問題。
複製實時性和資料一致性矛盾
Redis提供了提高資料一致性的解決方案,本文後面會進行介紹,一致性程度的增加雖然使得我能夠更信任資料,但是更好的一致性方案通常伴隨著效能的損失,從而減少了吞吐量和服務能力。然而我們希望系統的效能達到最優,則必須要犧牲一致性的程度,因此Redis的複製實時性和資料一致性是存在矛盾的。
Redis複製原理及特性
slave指向master
舉個例子,我們有四臺redis例項,M1,R1、R2、R3,其中M1為master,R1、R2、R3分別為三臺slave redis例項。在M1啟動如下:
./redis-server ../redis8000.conf --port 8000
下面分別為R1、R2、R3的啟動命令:
./redis-server ../redis8001.conf --port 8001 --slaveof 127.0.0.1 8000
./redis-server ../redis8002.conf --port 8002 --slaveof 127.0.0.1 8000
./redis-server ../redis8003.conf --port 8003 --slaveof 127.0.0.1 8000
這樣,我們就成功的啟動了四臺Redis例項,master例項的服務埠為8000,R1、R2、R3的服務埠分別為8001、8002、8003,叢集圖如下:
Redis叢集複製拓撲
上面的命令在slave啟動的時候就指定了master機器,我們也可以在slave執行的時候通過slaveof命令來指定master機器。
複製過程
Redis複製主要由SYNC命令實現,複製過程如下圖:
Redis複製過程
上圖為Redis複製工作過程:
- slave向master傳送sync命令。
- master開啟子程序來講dataset寫入rdb檔案,同時將子程序完成之前接收到的寫命令快取起來。
- 子程序寫完,父程序得知,開始將RDB檔案傳送給slave。
- master傳送完RDB檔案,將快取的命令也發給slave。
- master增量的把寫命令發給slave。
值得注意的是,當slave跟master的連線斷開時,slave可以自動的重新連線master,在redis2.8版本之前,每當slave程序掛掉重新連線master的時候都會開始新的一輪全量複製。如果master同時接收到多個slave的同步請求,則master只需要備份一次RDB檔案。
增量複製
上面複製過程介紹的最後提到,slave和master斷開了、當slave和master重新連線上之後需要全量複製,這個策略是很不友好的,從Redis2.8開始,Redis提供了增量複製的機制:
增量複製機制
master除了備份RDB檔案之外還會維護者一個環形佇列,以及環形佇列的寫索引和slave同步的全域性offset,環形佇列用於儲存最新的操作資料,當slave和maste斷開重連之後,會把slave維護的offset,也就是上一次同步到哪裡的這個值告訴master,同時會告訴master上次和當前slave連線的master的runid,滿足下面兩個條件,Redis不會全量複製:
- slave傳遞的run id和master的run id一致。
- master在環形佇列上可以找到對呀offset的值。
滿足上面兩個條件,Redis就不會全量複製,這樣的好處是大大的提高的效能,不做無效的功。
增量複製是由psync命令實現的,slave可以通過psync命令來讓Redis進行增量複製,當然最終是否能夠增量複製取決於環形佇列的大小和slave的斷線時間長短和重連的這個master是否是之前的master。
環形佇列大小配置引數:
repl-backlog-size 1mb
Redis同時也提供了當沒有slave需要同步的時候,多久可以釋放環形佇列:
repl-backlog-ttl 3600
免持久化複製
免持久化機制官方叫做Diskless Replication,前面基於RDB檔案寫磁碟的方式可以看出,Redis必須要先將RDB檔案寫入磁碟,才進行網路傳輸,那麼為什麼不能直接通過網路把RDB檔案傳送給slave呢?免持久化複製就是做這個事情的,而且在Redis2.8.18版本開始支援,當然目前還是實驗階段。
值得注意的是,一旦基於Diskless Replication的複製傳送開始,新的slave請求需要等待這次傳輸完畢才能夠得到服務。
是否開啟Diskless Replication的開關配置為:
repo-diskless-sync no
為了讓後續的slave能夠儘量趕上本次複製,Redis提供了一個引數配置指定複製開始的時間延遲:
repl-diskless-sync-delay 5
slave只讀模式
自從Redis2.6版本開始,支援對slave的只讀模式的配置,預設對slave的配置也是隻讀。只讀模式的slave將會拒絕客戶端的寫請求,從而避免因為從slave寫入而導致的資料不一致問題。
半同步複製
和MySQL複製策略有點類似,Redis複製本身是非同步的,但也提供了半同步的複製策略,半同步複製策略在Redis複製中的語義是這樣的:
允許使用者給出這樣的配置:在maste接受寫操作的時候,只有當一定時間間隔內,至少有N臺slave線上,否則寫入無效。
上面功能的實現基於Redis下面特性:
- Redis slaves每秒鐘會ping一次master,告訴master當前slave複製到哪裡了。
- Redis master會記住每個slave複製到哪裡了。
我們可以通過下面配置來指定時間間隔和N這個值:
min-slaves-to-write <number of slaves>
min-slaves-max-lag <number of seconds>
當配置了上面兩個引數之後,一旦對於一個寫操作沒有滿足上面的兩個條件,則master會報錯,並且將本次寫操作視為無效。這有點像CAP理論中的“C”,即一致性實現,雖然半同步策略不能夠完全保證master和slave的資料一致性,但是相對減少了不一致性的視窗期。
總結
本文在理解Redis複製概念和複製的優缺點的基礎之上介紹了當前Redis複製工作原理以及主要特性,希望能夠幫助大家。