redis高可靠之redis主從模式
上兩章筆記分享了和相關的筆記, 本文筆者將繼續介紹redis高可靠相關內容, 如有不足指出歡迎留言指正.
本文還是採用AQ方式記錄, 廢話不多說, 開始上菜.
Q: 文章的標題為redis高可靠性, 那Redis 具有高可靠性具體是什麼意思?
A : 高可靠至少有兩層含義:
- 資料儘量少丟失;
- 服務儘量少斷;
上一章提到了AOF和RDB的持久化方式保證了前者; 對於後者redis的做法是增加冗餘的副本; 將一份資料同時儲存在多個例項上。即使有一個例項出現了故障,需要過一段時間才能恢復,其他例項也可以對外提供服務,不會影響業務使用。
redis提供了主從庫模式,以保證資料副本的一致,主從庫之間採用的是讀寫分離的方式。
- 讀操作: 主庫、從庫都可以接收;
- 寫操作: 首先執行到主庫, 然後主庫將資料同步到從庫;
Q: redis為什麼要採用主從模式?
A: 其實這個問題不僅僅是redis的問題, 很多大型網際網路公司都會採用主從模式, 甚至是異地多活, 這裡除了考慮單機效能瓶外, 更多的會考慮到容災機制, 這個說來話長, 後面可以單獨分享.
言歸正傳, 從redis單機效能出發:
- redis單機效能存在瓶頸, 讀qps大約在11w/s, 寫大約在8w/s, 對於像雙十大促場景肯定是扛不住的;
- redis單機儲存資料能力是有限的.
Q: 主從庫關係如何建立?
A : 當啟動多個例項時, 在所有的從庫中執行replicaof
replicaof 主庫ip 主庫對應埠號
通過replicaof命令即可建立主庫和從庫之間關係;
Q: redis主從模式如何保證資料的一致性?
A : 首先, redis第一次主從同步時採用RDB全量複製的方式, 後面將採用增量方式;
Q: 主從庫間如何進行第一次同步?
A : 首先上圖, 圖片轉載至
主從資料同步主要分為3階段:
第一階段
從庫執行
replicaof 172.16.19.3 6379
命令後向主庫傳送建立連線請求
psync ? -1
命令分為3個部分,
- 第一部分為psync表示請求資料同步;
- “? ”表示主庫的runId, 由於第一次並不知道主庫runId, 因此用“?”表示, 第三個表示偏移量offset;
- “-1” 表示第一次複製.
主庫收到 psync 命令後,會用FULLRESYNC響應命令帶上兩個引數:主庫 runID 和主庫目前的複製進度 offset,返回給從庫。從庫收到響應後,會記錄下這兩個引數。
- FULLRESYNC命令表示全量複製, 主庫會當前所有的資料複製給從庫;
第二階段
主庫將所有資料同步給從庫。從庫收到資料後,在本地完成資料載入。這個過程依賴於記憶體快照生成的 RDB 檔案。
主庫執行bgsave命名, 生成RDB檔案, 接著將檔案發給從庫. 從庫接收到 RDB 檔案後,會先清空當前資料庫,然後載入 RDB 檔案。這是因為從庫在通過 replicaof 命令開始和主庫同步前,可能儲存了其他資料。為了避免之前資料的影響,從庫需要先把當前資料庫清空。
第三階段
主庫與從庫進行資料進行同步時, 會有新的請求過來修改主庫當前的資料, 此時主庫並不會阻塞, 依然可以接受訪問, 但是新的請求不會記錄在剛才生成的RDB檔案中, 此時主從的資料就會不一致. 為了保證主從庫的資料一致性,主庫會在記憶體中用專門的 replication buffer,記錄 RDB 檔案生成後收到的所有寫操作。
第三個階段,主庫會把第二階段執行過程中新收到的寫命令,再發送給從庫。具體的操作是,當主庫完成 RDB 檔案傳送後,就會把此時 replication buffer 中的修改操作發給從庫,從庫再重新執行這些操作。這樣一來,主從庫就實現同步了。
建立連線後, 為了避免網路連線開銷,由於主庫和從庫間採用的是長連線, 之後主庫每次寫操作都會同步給從庫, 這樣主從的資料就可以保持基本一致了;
Q: 筆者, 小菜有一個問題, 主庫和每一個從庫間第一次建立聯絡時, 都需要進行全量複製, 對於主庫來說, 生成 RDB 檔案和傳輸 RDB 檔案, 都是耗時的操作, 如果從庫的數量很多, 就會導致主庫忙於 fork 子程序生成 RDB 檔案,進行資料全量同步。fork 這個操作會阻塞主執行緒處理正常請求,從而導致主庫響應應用程式的請求速度變慢。傳輸 RDB 檔案也會佔用主庫的網路頻寬,同樣會給主庫的資源使用帶來壓力。有沒有什麼方法可以分擔主庫的壓力呢?
A: 其實是有的, “主 - 從 - 從” 模式; 剛才介紹的主從庫模式中,所有的從庫都是和主庫連線,所有的全量複製也都是和主庫進行的。現在,可以通過“主 - 從 - 從”模式將主庫生成 RDB 和傳輸 RDB 的壓力,以級聯的方式分散到從庫上。 具體部署如圖:
這樣一來,這些從庫就會知道,在進行同步時,不用再和主庫進行互動了,只要和級聯的從庫進行寫操作同步就行了,這就可以減輕主庫上的壓力.
小結:
一旦主從庫完成了全量複製,它們之間就會一直維護一個網路連線,主庫會通過這個連線將後續陸續收到的命令操作再同步給從庫,這個過程也稱為基於長連線的命令傳播,可以避免頻繁建立連線的開銷。
Q: 筆者, 小菜覺得長連線的命令傳播方式確實很贊, 但是如果主從庫之間斷網了, 怎麼辦呢?
A: 在 Redis 2.8 之前,如果主從庫在命令傳播時出現了網路閃斷,那麼,從庫就會和主庫重新進行一次全量複製,開銷非常大。
從 Redis 2.8 開始,網路斷了之後,主從庫會採用增量複製的方式繼續同步。聽名字大概就可以猜到它和全量複製的不同:全量複製是同步所有資料,而增量複製只會把主從庫網路斷連期間主庫收到的命令,同步給從庫。
Q: 筆者, 增量複製時, 主從庫之間具體是怎麼保持同步的呢?
A: 主庫會把斷連期間收到的寫操作命令,寫入 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 之間的命令操作同步給從庫就行。
具體如下圖:
為了更清楚理解網路正常連線資料同步以及網路斷鏈後增量資料同步, 下面首先說一下, redis內部存在的兩個快取機制:
repl_backlog_buffer和replication buffer
-
repl_backlog_buffer: 一個環形緩衝區, 只要有從庫存在,這個repl_backlog_buffer就會存在。主庫的所有寫命令除了傳播給從庫之外,都會在這個repl_backlog_buffer中記錄一份,快取起來,只有預先快取了這些命令,當從庫斷連後,從庫重新發送psync $master_runid o f f s e t , 主 庫 才 能 通 過 offset,主庫才能通過 offset,主庫才能通過offset在repl_backlog_buffer中找到從庫斷開的位置,只發送$offset之後的增量資料給從庫即可。repl_backlog_buffer是為了從庫斷開之後,如何找到主從差異資料而設計的環形緩衝區,從而避免全量同步帶來的效能開銷。如果從庫斷開時間太久,repl_backlog_buffer環形緩衝區被主庫的寫命令覆蓋了,那麼從庫連上主庫後只能乖乖地進行一次全量同步,所以repl_backlog_buffer配置儘量大一些,可以降低主從斷開後全量同步的概率。而在repl_backlog_buffer中找主從差異的資料後,如何發給從庫呢?這就用到了replication buffer。
-
replication buffer:Redis和客戶端通訊也好,和從庫通訊也好,Redis都需要給分配一個記憶體buffer進行資料互動,客戶端是一個client,從庫也是一個client,我們每個client連上Redis後,Redis都會分配一個client buffer,所有資料互動都是通過這個buffer進行的:Redis先把資料寫到這個buffer中,然後再把buffer中的資料發到client socket中再通過網路傳送出去,這樣就完成了資料互動。所以主從在增量同步時,從庫作為一個client,也會分配一個buffer,只不過這個buffer專門用來傳播使用者的寫命令到從庫,保證主從資料一致,我們通常把它叫做replication buffer。
-
再延伸一下,既然有這個記憶體buffer存在,那麼這個buffer有沒有限制呢?如果主從在傳播命令時,因為某些原因從庫處理得非常慢,那麼主庫上的這個buffer就會持續增長,消耗大量的記憶體資源,甚至OOM。所以Redis提供了client-output-buffer-limit引數限制這個buffer的大小,如果超過限制,主庫會強制斷開這個client的連線,也就是說從庫處理慢導致主庫記憶體buffer的積壓達到限制後,主庫會強制斷開從庫的連線,此時主從複製會中斷,中斷後如果從庫再次發起複製請求,那麼此時可能會導致惡性迴圈,引發複製風暴,這種情況需要格外注意。
Q: 筆者, repl_backlog_buffer 是一個環形緩衝區,所以在緩衝區寫滿後,主庫會繼續寫入,此時,就會覆蓋掉之前寫入的操作。如果從庫的讀取速度比較慢,就有可能導致從庫還未讀取的操作被主庫新寫的操作覆蓋了,這會導致主從庫間的資料不一致。
A : 這個問題很好, 我們需要儘量避免這一情況,一般而言,可以調整 repl_backlog_size 這個引數。這個引數和所需的緩衝空間大小有關。緩衝空間的計算公式是:緩衝空間大小 = 主庫寫入命令速度 * 操作大小 - 主從庫間網路傳輸命令速度 * 操作大小。在實際應用中,考慮到可能存在一些突發的請求壓力,我們通常需要把這個緩衝空間擴大一倍,即 repl_backlog_size = 緩衝空間大小 * 2,這也就是 repl_backlog_size 的最終值。
舉個例子,如果主庫每秒寫入 2000 個操作,每個操作的大小為 2KB,網路每秒能傳輸 1000 個操作,那麼,有 1000 個操作需要緩衝起來,這就至少需要 2MB 的緩衝空間。否則,新寫的命令就會覆蓋掉舊操作了。為了應對可能的突發壓力,我們最終把 repl_backlog_size 設為 4MB。
這樣一來,增量複製時主從庫的資料不一致風險就降低了。不過,如果併發請求量非常大,連兩倍的緩衝空間都存不下新操作請求的話,此時,主從庫資料仍然可能不一致。針對這種情況,一方面,你可以根據 Redis 所在伺服器的記憶體資源再適當增加 repl_backlog_size 值,比如說設定成緩衝空間大小的 4 倍,另一方面,你可以考慮使用切片叢集來分擔單個主庫的請求壓力。
小結
主從複製的基本原理:
- 全量複製: 第一次同步時採用增量複製, 建議增量複製時一個 Redis 例項的資料庫不要太大, 幾GB即可, 這樣可以減少RDB生成, 傳輸, 重新載入時的開銷;
- 基於長連線的命令傳播:主從庫正常執行後的常規同步階段。在這個階段中,主從庫之間通過命令傳播實現同步。
- 增量複製: 當主從網路斷鏈時, 採用增量複製模式, 需要注意的是 repl_backlog_size 這個配置引數。如果它配置得過小,在增量複製階段,可能會導致從庫的複製進度趕不上主庫,進而導致從庫重新進行全量複製。所以,通過調大這個引數,可以減少從庫在網路斷連時全量複製的風險。