Redis主從復制是如何保證數(shù)據(jù)不丟失的?
介紹
在生產(chǎn)環(huán)境中,為了系統(tǒng)的可靠性,我們會對Redis搭建主從。這樣當一個實例發(fā)生宕機,另一個實例中還有數(shù)據(jù),還能繼續(xù)提供服務。主從庫之間采用的是讀寫分離的模式。
讀操作:主庫,從庫都可以執(zhí)行 寫操作:只能主庫上執(zhí)行,主庫將操作同步給從庫
因為主從庫都可以接收讀請求,提高了系統(tǒng)的QPS。那么主從庫之間如何進行數(shù)據(jù)同步呢?
全量復制
「我們可以通過replicaof命令或者replicaof設置來讓redis形成主從庫的關系」(redis 5.0之前使用slaveof命令)
假設現(xiàn)在有兩個實例,實例一(172.16.19.1)和實例二(172.16.19.2)
當我們在實例二上執(zhí)行如下命令后,實例二就變成了實例一的從庫,并從實例一上復制數(shù)據(jù)
- replicaof 172.16.19.1 6379
當然我們也可以在實例二的redis.conf配置文件中配置如下內(nèi)容
- replicaof 172.16.19.1 6379
整個同步過程如下圖所示
主從庫全量復制主要分為如下三個階段
- 從庫發(fā)送psync命令,此時主庫開始生成rdb文件
- 主庫將生成的rdb文件發(fā)送給從庫
- 主庫將生成rdb文件后接收到的寫命令發(fā)送給從庫
我們仔細分析一下三個過程
從庫發(fā)送psync命令,此時主庫開始生成rdb文件
從庫發(fā)送psync命令,表示要進行數(shù)據(jù)復制,psync命令包含了如下2個參數(shù)
「runID」:主庫的runID,每個redis實例啟動時都會自動生成一個隨機ID,用來唯一標識實例。當從庫第一次復制時,因為不知道主庫的runID,所以將runID設置為?「offset」:復制進度,第一次復制為-1
主庫將生成的rdb文件發(fā)送給從庫
主庫執(zhí)行bgsave命令,生成rdb文件,并且發(fā)送給從庫。從庫收到rdb文件后,會清空當前數(shù)據(jù)庫,然后加載rdb文件。因為從庫在通過replicaof命令復制前,可能保存了其他的數(shù)據(jù),為了避免之前數(shù)據(jù)的影響,需要先把從庫清空
主庫將生成rdb文件后接收到的寫命令發(fā)送給從庫
生成rdb文件后,主庫仍能執(zhí)行寫命令,這些寫命令會被放到replication buffer中。當主庫發(fā)送完rdb文件后,就會把replication buffer中的命令發(fā)給從庫,從庫執(zhí)行這些操作后。主從就是實現(xiàn)同步了。「后續(xù)正常的命令同步也是主庫將命令寫到replication buffer然后發(fā)給從庫」
增量復制
如果在主從命令傳播的過程中,出現(xiàn)了網(wǎng)絡異常應該怎么辦呢?
在Redis2.8之前,如果出現(xiàn)了網(wǎng)絡異常,從庫和主庫會進行一次增量復制,開銷非常大。在Redis2.8之后,主從庫會采用增量復制的方式進行同步。增量復制只會把主從庫斷連期間主庫接收到的命令同步給從庫
「增量同步時主從庫如何保持一致呢?」
復制偏移量
主庫和存庫都會在內(nèi)部維護一個復制偏移量 主庫每次向從庫發(fā)送n個字節(jié)的數(shù)據(jù)時,就把自己的復制偏移量加上n 從庫每次收到主庫傳來的n個字節(jié)的數(shù)據(jù)時,就把自己的復制偏移量加上n
repl_backlog_buffer(復制積壓緩沖區(qū))
repl_backlog_buffer是由主服務器維護的一個固定長度先進先出(FIFO)隊列 我們舉個例子,如果將hello字符串放入一個固定長度為3的FIFO隊列,值依次為
- [h, e, l] [e, l, l] [l, l, o]
「每次都是都是在隊尾添加值,彈出隊首」復制積壓緩沖區(qū)的構造如下
偏移量 | ... | 20 | 21 | 22 | 23 | 24 | 25 | ... |
---|---|---|---|---|---|---|---|---|
字節(jié)值 | ... | h | e | l | l | l | o | ... |
「當服務器在進行命令傳播的時候,不僅會將寫命令發(fā)送給所有從服務器,還會將寫命令入隊到復制積壓緩沖區(qū)中」
當從庫發(fā)生網(wǎng)絡中斷重新上主庫之后,會發(fā)送「psync 主庫id offset」給主庫,主庫根據(jù)復制偏移量來決定對從服務器執(zhí)行何種復制操作
如果從庫發(fā)送的主庫id與當前連接的主庫id相同,可以繼續(xù)嘗試增量復制
如果從庫發(fā)送的主庫id與當前連接的主庫id不相同,說明主服務器斷線之前復制的主服務器并不是當前連接的服務器,只能全量復制
如果offse偏移量之后的數(shù)據(jù)(即偏移量offset+1開始的數(shù)據(jù))仍然存在repl_backlog_buffer中,則把命令放到replication buffer,然后發(fā)送給從庫
如果offset偏移量之后的數(shù)據(jù)不存在repl_backlog_buffer中,則進行全量復制
replication buffer和repl_backlog_buffer
有很多小伙伴剛開始的時候分不清replication buffer和repl_backlog_buffer的作用,包括我。
其實很好理解,replication buffer其實是一個client端的緩沖區(qū),redis每次把要發(fā)送的命令放到這個緩沖區(qū)中,然后再發(fā)送。「每個客戶端一個replication buffer」
「而repl_backlog_buffer單純用作增量復制,在redis服務器中只有一個」
本文轉(zhuǎn)載自微信公眾號「Java識堂」,可以通過以下二維碼關注。轉(zhuǎn)載本文請聯(lián)系Java識堂公眾號。