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