Redis作為一個(gè)分布式的存儲(chǔ)服務(wù),集群的工作模式幾乎是標(biāo)配邪蛔,Redis目前的主從模式也是大多人使用的模型急黎,那Redis的主機(jī)和從機(jī)是如何保證數(shù)據(jù)同步一致的呢?
RDB 與 AOF 文件
Redis本身侧到,數(shù)據(jù)的持久化有兩種形式勃教,一種是AOF,另一種就是RDB文件的格式匠抗,作為主從同步的模型故源,Redis采用的就是RDB的文件格式。這里簡(jiǎn)單介紹一下RDB 和 AOF汞贸。
AOF
這種存儲(chǔ)模式非常高效绳军,他所存儲(chǔ)的是Redis本身執(zhí)行的所有寫命令,比如說set key value
, 所有的寫相關(guān)的命令矢腻,都會(huì)被加載到AOF里面门驾。
但是有兩點(diǎn)點(diǎn)要注意一下:
- 寫入文件的過程不是實(shí)時(shí)的,畢竟寫入文件是有性能消耗的多柑,所以會(huì)有一個(gè)AOF緩沖區(qū)來存儲(chǔ)最近的操作奶是,然后定時(shí)寫入文件。
- 對(duì)于一些命令竣灌,比如說反復(fù)對(duì)list的操作诫隅,結(jié)果集會(huì)越來越大,極端的例子是我們for循環(huán)一組數(shù)據(jù)帐偎,逐一添加到list里面,對(duì)于AOF來說蛔屹,就是幾千條命令削樊,所以AOF會(huì)有一個(gè)重寫的過程,具體的內(nèi)容在這里不介紹了,可以參閱官網(wǎng)和相關(guān)書籍漫贞。
RDB
相較于AOF的快照行為甸箱,RDB是對(duì)于內(nèi)存中的數(shù)據(jù),直接進(jìn)行一次壓縮和文件寫入的過程迅脐,這種形式還原數(shù)據(jù)對(duì)于Redis來說芍殖,比AOF要快很多,但是對(duì)于生成來講谴蔑,可能性能就不盡如人意了豌骏,他是將數(shù)據(jù),按照對(duì)應(yīng)的類型(string隐锭,list窃躲,set,map钦睡,zset)按照特定的格式蒂窒,存入文件里面,添加一些版本號(hào)和唯一標(biāo)示荞怒,來識(shí)別RDB文件洒琢。
生成的命令有兩種,一個(gè)是SAVE
褐桌,一個(gè)是BGSAVE
, 區(qū)別就是前者是整個(gè)Redis不工作來生成數(shù)據(jù)衰抑,后者是開啟另一個(gè)線程來處理,不影響Redis的正常工作撩嚼。但同樣的是RDB文件生成的時(shí)候停士,性能低下,注定他不能實(shí)時(shí)存儲(chǔ)完丽,通常情況下都是設(shè)置一些條件觸發(fā)生成操作恋技。這里篇幅有限,以后有機(jī)會(huì)再慢慢介紹逻族。
兩者對(duì)比
首先第一點(diǎn)蜻底,AOF存儲(chǔ)的是操作,而RDB存儲(chǔ)的是實(shí)際的數(shù)據(jù)庫(kù)內(nèi)容聘鳞,所以注定的是存儲(chǔ)上薄辅,AOF肯定更加便捷,RDB更加耗費(fèi)性能抠璃。但相反的是站楚,對(duì)于大量數(shù)據(jù),RDB還原的速度就快很多了搏嗡,而AOF由于包含了大量的命令窿春,需要逐一執(zhí)行拉一,整個(gè)過程對(duì)于Redis而言,肯定不如RDB來得好旧乞。
數(shù)據(jù)丟失的問題上蔚润,RDB由于不是實(shí)時(shí)的,所以注定的在宕機(jī)的時(shí)候尺栖,會(huì)有一段時(shí)間空隙嫡纠,數(shù)據(jù)會(huì)產(chǎn)生丟失的情況,而AOF采用的是緩沖區(qū)的形式延赌,后臺(tái)線程進(jìn)行AOF重寫除盏,所以緩沖區(qū)也有可能因?yàn)殄礄C(jī)而存在丟失的問題,但是對(duì)比于RDB皮胡,肯定丟失的情況更加少痴颊。
主從同步的基本原理
聊了文件存儲(chǔ)之后,回到主題上面屡贺,Redis的主從模式采用的是RDB文件同步的方式蠢棱,因?yàn)镽edis的服務(wù)端,數(shù)據(jù)量有可能非常的大甩栈,所以從性能考慮泻仙,沒有采用AOF快照來同步。
過程大概如下:
- 從機(jī)上線量没,主動(dòng)鏈接主機(jī)玉转,發(fā)送
SYNC
命令 - 主機(jī)接到命令后,執(zhí)行
BGSAVE
命令殴蹄,生成RDB文件 - 主機(jī)向從機(jī)發(fā)送RDB文件究抓,開始同步數(shù)據(jù)
- 同步之后,主機(jī)將最近的更新袭灯,采用命令的形式同步到從機(jī)上面刺下。
舊版復(fù)制的缺陷
上述的過程對(duì)于一個(gè)從機(jī),新加入到集群里面的時(shí)候稽荧,比較合適橘茉,但是如果因?yàn)閿嗑€重連,則需要重新復(fù)制主機(jī)上所有的內(nèi)容姨丈,主機(jī)也因此要進(jìn)行一次全量級(jí)的RDB畅卓,比較耗費(fèi)性能。
在2.8版本之后蟋恬,采用了一個(gè)新的增量復(fù)制的過程翁潘,流程如下:
- 前面的
sync
的過程還是一樣,沒有差異 - 當(dāng)從機(jī)斷線重連之后歼争,會(huì)發(fā)送
PSYNC
,要求增量同步唐础,并包括一個(gè)offset - 主機(jī)根據(jù)offset的位置箱歧,對(duì)之后的數(shù)據(jù)進(jìn)行一次增量同步,到從機(jī)上面
這里面的offset是由主機(jī)和從機(jī)共同維護(hù)的一膨,相當(dāng)于一個(gè)樂觀鎖,描述了兩方的版本差異洒沦。
那決定能否增量同步的主要因素豹绪,包括如下三個(gè):
- 是否具備偏移量,與主機(jī)進(jìn)行對(duì)比
- 主服務(wù)器的復(fù)制積壓緩沖區(qū)(Replication backlog)
- 從服務(wù)器的ID
第一條不難理解申眼,重點(diǎn)解釋一下第二和第三條
復(fù)制積壓緩沖區(qū)
Redis每分鐘都會(huì)處理很多的數(shù)據(jù)瞒津,不可能一直把更新的操作存起來,等待從機(jī)上線在傳輸括尸,AOF也不是都在內(nèi)存中一直保存巷蚪,所以Redis有一個(gè)緩沖區(qū),采用隊(duì)列的模式來存儲(chǔ)這些寫操作濒翻。
所有的更新操作以隊(duì)列的形式放入里面屁柏,內(nèi)存大小默認(rèn)是1MB,超過1MB之后有送,前面進(jìn)入隊(duì)列的寫操作就會(huì)被移除淌喻。
所以當(dāng)從機(jī)上線之后,如果offset與主機(jī)版本差距的內(nèi)容還在緩沖區(qū)內(nèi)雀摘,則可以從緩沖區(qū)進(jìn)行增量同步裸删。否則依然還是全量同步(RDB),這里就好比你的機(jī)器宕機(jī)了一天阵赠,在上線涯塔,你不能要求我把這一天的數(shù)據(jù)都給你吧,你直接全量同步得了清蚀。
這里我就想到了一個(gè)問題匕荸,如果我們反復(fù)對(duì)Redis更新特別大的K-V的話,超過1M轧铁,會(huì)使得這個(gè)緩沖區(qū)失效每聪,因?yàn)槊看蔚母露汲^這個(gè)緩沖區(qū)大小了,所以同步操作對(duì)于從機(jī)來說齿风,都是全量同步药薯,如果太頻繁的話,則會(huì)產(chǎn)生很大的問題救斑。
當(dāng)然這個(gè)緩沖區(qū)的大小也是可以設(shè)置調(diào)整的餓童本,可以根據(jù)需求配置。
從服務(wù)器ID
這個(gè)不難理解脸候,因?yàn)镽edis有一個(gè)Slot的概念穷娱,每個(gè)機(jī)器負(fù)責(zé)一部分的Key绑蔫,所以如果你之前不是我的從機(jī),那你內(nèi)存內(nèi)的數(shù)據(jù)肯定都不是我的值泵额,就不能只看offset了配深,還要看一下你之前是不是我的從機(jī),這里主機(jī)會(huì)維護(hù)從機(jī)的唯一ID嫁盲,來校驗(yàn)篓叶。
整個(gè)復(fù)制流程
1. 設(shè)置端口號(hào)進(jìn)行連接
SLAVEOF master_ip master_port
命令,或者通過配置文件配置均可羞秤。
2. 建立套接字連接
建立連接之后缸托,從服務(wù)器會(huì)負(fù)責(zé)處理后續(xù)的RDB文件和寫命令,主服務(wù)器將從服務(wù)器作為一個(gè)客戶端進(jìn)行響應(yīng)
3. 發(fā)送PING請(qǐng)求
連接之后瘾蛋,通過PING請(qǐng)求檢查相互的狀態(tài)俐镐,類似心跳校測(cè)的內(nèi)容。
4. 身份驗(yàn)證
這里需要主服務(wù)器和從服務(wù)器都設(shè)置校驗(yàn)選項(xiàng)哺哼,然后校驗(yàn)密碼佩抹。
- 如果兩方都沒有設(shè)置,則這一步可以忽略
- 如果從服務(wù)器設(shè)置了幸斥,主服務(wù)沒有設(shè)置匹摇,則會(huì)返回
no password is set
- 如果都設(shè)置了,則會(huì)進(jìn)行驗(yàn)證
- 如果主服務(wù)器設(shè)置了甲葬,但是從服務(wù)器沒有設(shè)置廊勃,則會(huì)返回
NOAUTH
5. 發(fā)送端口
由于從服務(wù)類似于客戶端,等待接受信息经窖,所以需要告知主服務(wù)器自己接受所用的端口號(hào)坡垫,方便未來接受RDB和寫命令。
6. 同步
這個(gè)時(shí)候就進(jìn)入到我們上面描述的同步機(jī)制里面了画侣,SYNC
or PSYNC
等等的內(nèi)容冰悠。
7. 命令傳播
后續(xù)的寫命令,都會(huì)通過上面配置的端口號(hào)不停傳輸配乱。
其他內(nèi)容
心跳檢測(cè)
進(jìn)行了套接字連接溉卓,執(zhí)行過PING
命令之后,雖然保證了連接搬泥,但是后續(xù)的寫同步操作桑寨,依然需要實(shí)時(shí)連接。所以從服務(wù)器會(huì)定期發(fā)送自己的offset到主服務(wù)器上忿檩,檢測(cè)是否需要同步寫操作尉尾。
檢測(cè)從服務(wù)狀態(tài)
主服務(wù)器在一個(gè)時(shí)間周期內(nèi)沒有收到從服務(wù)器上述的心跳檢測(cè)內(nèi)容,則察覺從服務(wù)器可能異常了燥透,所以這個(gè)時(shí)候會(huì)需要查看從服務(wù)器的信息沙咏。
Redis會(huì)配置一些拒絕寫操作的內(nèi)容辨图,在一些特定情況下會(huì)認(rèn)為寫操作不安全,這里的不安全是通過配置決定的:
- min-slaves-to-write 3
- min-slaves-max-lag 10
以上的信息表示從節(jié)點(diǎn)不能少于3個(gè)肢藐,或者3個(gè)從服務(wù)延遲不能超過10秒故河。否則拒絕寫操作,只執(zhí)行讀操作吆豹。
命令丟失
由于定期的心跳檢測(cè)的存在忧勿,所以定期檢測(cè)版本,發(fā)現(xiàn)消息丟失這種內(nèi)容就自動(dòng)實(shí)現(xiàn)了瞻讽,可以通過offset準(zhǔn)確的了解到從服務(wù)器哪些信息沒有接受到,及時(shí)的作出響應(yīng)熏挎。
感謝
以上內(nèi)容參考Redis官方網(wǎng)站速勇,以及《Redis的設(shè)計(jì)與實(shí)現(xiàn)》一書,如有紕漏坎拐,歡迎指出烦磁。