redis深度歷險(xiǎn)分為兩個(gè)部分,單機(jī)Redis和分布式Redis。
“專(zhuān)業(yè)、務(wù)實(shí)、高效、創(chuàng)新、把客戶(hù)的事當(dāng)成自己的事”是我們每一個(gè)人一直以來(lái)堅(jiān)持追求的企業(yè)文化。 創(chuàng)新互聯(lián)是您可以信賴(lài)的網(wǎng)站建設(shè)服務(wù)商、專(zhuān)業(yè)的互聯(lián)網(wǎng)服務(wù)提供商! 專(zhuān)注于成都做網(wǎng)站、成都網(wǎng)站建設(shè)、軟件開(kāi)發(fā)、設(shè)計(jì)服務(wù)業(yè)務(wù)。我們始終堅(jiān)持以客戶(hù)需求為導(dǎo)向,結(jié)合用戶(hù)體驗(yàn)與視覺(jué)傳達(dá),提供有針對(duì)性的項(xiàng)目解決方案,提供專(zhuān)業(yè)性的建議,創(chuàng)新互聯(lián)建站將不斷地超越自我,追逐市場(chǎng),引領(lǐng)市場(chǎng)!
本文為分布式Redis深度歷險(xiǎn)系列的第一篇,主要內(nèi)容為Redis的復(fù)制功能。
Redis的復(fù)制功能的作用和大多數(shù)分布式存儲(chǔ)系統(tǒng)一樣,就是為了支持主從設(shè)計(jì),主從設(shè)計(jì)的好處有以下幾點(diǎn):
讀寫(xiě)分離,提高讀寫(xiě)性能
數(shù)據(jù)備份,減少數(shù)據(jù)丟失的風(fēng)險(xiǎn)
高可用,避免單點(diǎn)故障
Redis的復(fù)制主要分為同步和命令傳播兩個(gè)步驟:
同步可以理解為全量,是將主服務(wù)器某一時(shí)刻的所有數(shù)據(jù)全部同步到從服務(wù)器。
命令傳播可以理解為增量,當(dāng)主服務(wù)器數(shù)據(jù)被修改時(shí),主服務(wù)器向從服務(wù)器發(fā)送對(duì)應(yīng)的數(shù)據(jù)修改命令。
同步分為以下幾個(gè)步驟:
1.從服務(wù)器向主服務(wù)器發(fā)送SYNC
命令(執(zhí)行SLAVE OF
命令的第一步也會(huì)執(zhí)行SYNC
)
2.主服務(wù)器在收到從服務(wù)器命令時(shí),會(huì)執(zhí)行BGSAVE
,也就是新開(kāi)一個(gè)子進(jìn)程將內(nèi)存中的數(shù)據(jù)保存到RDB文件中。同時(shí)使用一個(gè)內(nèi)存緩沖區(qū)記錄從現(xiàn)在開(kāi)始執(zhí)行的寫(xiě)命令,該內(nèi)存緩沖區(qū)的作用就是記錄RDB文件生成期間的增量。
3.向從服務(wù)器發(fā)送RDB文件
4.將緩沖區(qū)中的寫(xiě)命令發(fā)送給從服務(wù)器
同步可以分為兩種情況,一種是從服務(wù)器第一次連接主服務(wù)器,另一種是從服務(wù)與主服務(wù)器的網(wǎng)絡(luò)鏈接斷開(kāi)了,重新連上主服務(wù)器并重新同步。
命令傳播實(shí)現(xiàn)邏輯比較簡(jiǎn)單,當(dāng)主服務(wù)器執(zhí)行了寫(xiě)命令后,為了保證從服務(wù)器與主服務(wù)器數(shù)據(jù)的一致性,主服務(wù)器會(huì)將寫(xiě)命令發(fā)送給從服務(wù)器,從服務(wù)器執(zhí)行完收到的寫(xiě)命令后其數(shù)據(jù)就能和主服務(wù)器保持一致了(當(dāng)然會(huì)有延時(shí)),注意,從服務(wù)器對(duì)于客戶(hù)端來(lái)說(shuō)是只讀的,因此從服務(wù)器的所有數(shù)據(jù)都是來(lái)自于主服務(wù)器的同步or命令傳播。
假設(shè)Redis主從服務(wù)器之間的網(wǎng)絡(luò)環(huán)境不太可靠,我們來(lái)看看上述復(fù)制方法會(huì)出現(xiàn)什么問(wèn)題。假設(shè)有主服務(wù)器A和從服務(wù)器B,主服務(wù)器中目前存在1-10000共一萬(wàn)條數(shù)據(jù)。
1.初始連接,從服務(wù)器第一次從主服務(wù)器同步數(shù)據(jù),同步完成后,從服務(wù)器也有1-10000共一萬(wàn)條數(shù)據(jù)。
2.主服務(wù)器新增10001,10002兩條數(shù)據(jù)
3.通過(guò)命令傳播,從服務(wù)器也新增10001,10002兩條數(shù)據(jù)
4.這時(shí)候主從服務(wù)器之間的網(wǎng)絡(luò)斷開(kāi)
5.主服務(wù)器新增數(shù)據(jù)10003,因?yàn)榫W(wǎng)絡(luò)斷開(kāi),所以從服務(wù)器感受不到數(shù)據(jù)變化
6.網(wǎng)絡(luò)恢復(fù),從服務(wù)器重新連接上主服務(wù)器,并發(fā)送SYNC命令,進(jìn)行同步操作
7.主服務(wù)器將所有數(shù)據(jù)發(fā)送給從服務(wù)器(1-10003)
從上述步驟中可以看到,當(dāng)從服務(wù)器重新連接上主服務(wù)器時(shí),會(huì)重新進(jìn)行全量同步,造成大量不必要的IO開(kāi)銷(xiāo),如果網(wǎng)絡(luò)環(huán)境不穩(wěn)定時(shí),會(huì)導(dǎo)致主服務(wù)器一直將內(nèi)存中的數(shù)據(jù)寫(xiě)到磁盤(pán)再發(fā)送給從服務(wù)器。
為了解決老版復(fù)制問(wèn)題,Redis2.8對(duì)于復(fù)制功能進(jìn)行了優(yōu)化。實(shí)現(xiàn)如下:
1.主服務(wù)器會(huì)維護(hù)一個(gè)偏移量,每次向服務(wù)器傳播N個(gè)字節(jié)的數(shù)據(jù)時(shí),該偏移量就會(huì)加上N,比如說(shuō)一開(kāi)始是0,接受到一條set key1 value1
后,其偏移量就為13(真實(shí)偏移可能不是13,只是舉個(gè)例子)。//這里可能要看下代碼確認(rèn)
2.從服務(wù)器也維護(hù)一個(gè)偏移量,當(dāng)從服務(wù)器收到到主服務(wù)器的N個(gè)字節(jié)數(shù)據(jù)時(shí),該偏移量會(huì)加上N。
3.主服務(wù)器維護(hù)一個(gè)固定大小的緩沖區(qū),每次接受到客戶(hù)端寫(xiě)命令后,都會(huì)將對(duì)應(yīng)命令
往這個(gè)緩沖區(qū)寫(xiě)入。當(dāng)寫(xiě)入內(nèi)容超出固定大小后,會(huì)覆蓋原來(lái)的數(shù)據(jù)。
4.主服務(wù)器有一個(gè)唯一id
5.從服務(wù)器連接上主服務(wù)時(shí),會(huì)向主服務(wù)器發(fā)送上一次連接的主服務(wù)器的id以及偏移量,這里又分幾種情況:
如果從服務(wù)器沒(méi)傳id或者id與當(dāng)前主服務(wù)器不匹配,那主服務(wù)器將傳送全量數(shù)據(jù)
如果從服務(wù)器的offset在緩沖區(qū)中不能找到(落后太多導(dǎo)致緩沖區(qū)已經(jīng)被新數(shù)據(jù)覆蓋了),那也會(huì)進(jìn)行全量同步
如果offset能在緩沖區(qū)找到,則主服務(wù)從offset開(kāi)始,將緩沖區(qū)的數(shù)據(jù)依次發(fā)送給從服務(wù)器。(有做pipeline的優(yōu)化嗎)
以上就是新版復(fù)制的大致思路,要注意的是,主服務(wù)器緩沖區(qū)的大小設(shè)置很關(guān)鍵,如果設(shè)置的太大會(huì)導(dǎo)致空間浪費(fèi),如果太小會(huì)導(dǎo)致網(wǎng)絡(luò)環(huán)境不好時(shí),其退化為老版復(fù)制。
之前我就踩過(guò)這樣的坑:在上云時(shí),redis集群在兩個(gè)不同機(jī)房,主從之前網(wǎng)絡(luò)環(huán)境不太穩(wěn)定,而redis機(jī)器上存儲(chǔ)的value比較大,很容易就將緩沖區(qū)占滿(mǎn)導(dǎo)致每次全量同步,形成惡性循環(huán),從服務(wù)器落后不可讀,主服務(wù)器不可寫(xiě)(當(dāng)從Redis落后太多時(shí),主Redis將拒絕寫(xiě)入,具體參數(shù)可以配置的,下文還會(huì)提到)
所以建議將緩沖區(qū)大小設(shè)置為平均重連間隔*每秒寫(xiě)入數(shù)據(jù)量*2
從服務(wù)器默認(rèn)會(huì)每秒一次的頻率向主服務(wù)器發(fā)送心跳:REPLCONF A?K
,
replication_offset代表從服務(wù)器當(dāng)前的復(fù)制偏移量。
心跳有三個(gè)作用:
1.檢測(cè)主從服務(wù)器的網(wǎng)絡(luò)連接
2.實(shí)現(xiàn)min-slaves功能
3.檢測(cè)命令丟失
主服務(wù)器會(huì)記錄從服務(wù)器上次發(fā)送心跳是什么時(shí)間,根據(jù)這個(gè)時(shí)間,我們能知道主從服務(wù)器之間的連接是不是出現(xiàn)了故障
Redis為了保證數(shù)據(jù)的安全性,可以配置當(dāng)從服務(wù)器小于min-slaves-to-write
個(gè)或者min-slaves-to-write
個(gè)從服務(wù)器的延遲都大于等于min-slaves-max-lag
時(shí),主服務(wù)器拒絕寫(xiě)。
主從之間的復(fù)制,其實(shí)是以主服務(wù)器作為從服務(wù)器的客戶(hù)端來(lái)實(shí)現(xiàn)的(在Redis中,所有服務(wù)器之間的數(shù)據(jù)傳遞都是以該種方式)。假設(shè)主服務(wù)器向從服務(wù)器發(fā)送一條寫(xiě)命令,但網(wǎng)絡(luò)出現(xiàn)異常,從服務(wù)器并沒(méi)有收到該命令。
這就會(huì)導(dǎo)致數(shù)據(jù)不一致的狀態(tài)(你可能想主服務(wù)器發(fā)送命令時(shí),如果從沒(méi)返回失敗,進(jìn)行重發(fā)不就好了嗎?如果說(shuō)從成功執(zhí)行了命令,但是再回復(fù)主的時(shí)候出現(xiàn)了問(wèn)題,那主如果重發(fā)就會(huì)造成數(shù)據(jù)異常了)。所以主服務(wù)器會(huì)根據(jù)心跳信息來(lái)決定要發(fā)送的數(shù)據(jù)??磦€(gè)例子:
初始,主服務(wù)器和從服務(wù)器偏移量都是100。
主服務(wù)器收到客戶(hù)端的寫(xiě)命令,將偏移量改成110,同時(shí)向從服務(wù)器發(fā)送寫(xiě)命令,但因網(wǎng)絡(luò)原因,從服務(wù)器并沒(méi)有收到,其偏移量仍然是100。主服務(wù)器根據(jù)心跳發(fā)現(xiàn)從服務(wù)器的偏移量是100落后于自己,所以會(huì)將100-110的數(shù)據(jù)進(jìn)行重發(fā)。
?
看到這里,你可能對(duì)于上述方案的正確性感到質(zhì)疑:在從服務(wù)器接收到100-110的數(shù)據(jù)前,它發(fā)送心跳包告訴主服務(wù)器自己當(dāng)前偏移為100,然后接收到了100-110的數(shù)據(jù)。這時(shí)下個(gè)心跳還沒(méi)發(fā)出,主服務(wù)器認(rèn)為從服務(wù)器落后于自己,再次發(fā)送100-110的數(shù)據(jù),導(dǎo)致從服務(wù)器再次寫(xiě)入100-110的數(shù)據(jù),導(dǎo)致數(shù)據(jù)異常!
?
如果你有想到這個(gè)問(wèn)題,說(shuō)明你是有在認(rèn)真思考了~
其實(shí)是不存在這種情況的,原因是redis是單線(xiàn)程的!記住單線(xiàn)程三個(gè)字,再回頭看一遍問(wèn)題描述,相信你能想明白~
?