redis核心技术与实战(四)高可用高扩展篇
1.《redis架構(gòu)組成》
1.redis學(xué)習(xí)維度
2.一個(gè)基本的鍵值型數(shù)據(jù)庫包括什么?
1.訪問框架
redis通過網(wǎng)絡(luò)框架進(jìn)行訪問,使得 Redis 可以作為一個(gè)基礎(chǔ)性的網(wǎng)絡(luò)服務(wù)進(jìn)行訪問,擴(kuò)大了redis應(yīng)用范圍;
過程:如果客戶端發(fā)送“put hello world ”這條指令會(huì)被包裝成一個(gè)網(wǎng)絡(luò)包,服務(wù)端收到后進(jìn)行解析,執(zhí)行指令;
問題:請(qǐng)求連接,解析,數(shù)據(jù)存取這些操作使用單線程還是多線程呢?
如果單線程,其中一個(gè)環(huán)節(jié)出錯(cuò)或阻塞就會(huì)導(dǎo)致整個(gè)流程阻塞,降低了系統(tǒng)響應(yīng)速度;
如果是多線程,那么當(dāng)有共享資源的時(shí)候,會(huì)產(chǎn)生線程競(jìng)爭(zhēng),也會(huì)影響系統(tǒng)效率,這又該怎么辦呢?
注意:Redis 又是如何做到“單線程,高性能”的呢?
2.索引模塊
redis采用哈希表作為索引,因?yàn)殒I值數(shù)據(jù)基本都是保存在內(nèi)存中的,而內(nèi)存的高性能隨機(jī)訪問特性可以很好地與哈希表 O(1) 的操作復(fù)雜度相匹配。
3.存儲(chǔ)模塊
redis提供AOF和RDB持久化數(shù)據(jù);
4.高可用集群模塊
redis提供主從架構(gòu),哨兵模式
5.高擴(kuò)展模塊
redis支持分片機(jī)制
2.《redis IO多路復(fù)用》
redis之所以那么快,除了使用hash表這樣的高效的數(shù)據(jù)結(jié)構(gòu),還因?yàn)樗腎O多路復(fù)用模型;
1.redis真的是的單線程嗎?
redis的鍵值操作以及IO處理是單線程的
其他操作時(shí)多線程的,例如:持久化,異步刪除,集群數(shù)據(jù)同步等;
2.redis為什么使用單線程,多線程不行嗎?
多線程情況下,勢(shì)必會(huì)有共享資源的爭(zhēng)奪,這就需要 引入 同步語句或者鎖來協(xié)調(diào)多線程之間的運(yùn)作,一旦引入鎖,同步語句,就會(huì)增加redis的復(fù)雜性,發(fā)生阻塞,影響性能;
3.redis 基于多路復(fù)用的IO模型
1.我們先來看看客戶端,服務(wù)端IO通信需要哪些操作?
首先服務(wù)端監(jiān)聽客戶端的請(qǐng)求(bind/listen),和客戶端建立連接(accept),接收客戶
端的數(shù)據(jù)(recv),解析(parse),鍵值操作,然后返回(send)
潛在阻塞點(diǎn)有兩個(gè):
當(dāng)redis監(jiān)聽到一個(gè)客戶端連接請(qǐng)求(accept),但是一直未能連接成功時(shí),阻塞
當(dāng)redis接收客戶端發(fā)送的請(qǐng)求數(shù)據(jù)包時(shí),一直未收到數(shù)據(jù),阻塞
這就導(dǎo)致 Redis 整個(gè)線程阻塞,無法處理其他客戶端請(qǐng)求,效率很低。不過,幸運(yùn)的是,socket 網(wǎng)絡(luò)模型本身支持非阻塞模式。
2.socket 非阻塞模式
socket 網(wǎng)絡(luò)模型的非阻塞模式主要體現(xiàn)在三個(gè)函數(shù)上;
調(diào)用socket()函數(shù)返回 主動(dòng)套接字,調(diào)用listen() 將主動(dòng)套接字轉(zhuǎn)換為 監(jiān)聽套接字,此時(shí),可以監(jiān)聽來自客戶端的連接請(qǐng)求。調(diào)用accept() 返回已連接套接字。
針對(duì)監(jiān)聽套接字,我們可以設(shè)置非阻塞模式:當(dāng) Redis 調(diào)用 accept() 但一直未有連接請(qǐng)求到達(dá)時(shí),Redis 線程可以返回處理其他操作,而不用一直等待。但是,你要注意的是,調(diào)用 accept() 時(shí),已經(jīng)存在監(jiān)聽套接字了。
雖然 Redis 線程可以不用繼續(xù)等待,但是總得有機(jī)制繼續(xù)在監(jiān)聽套接字上等待后續(xù)連接請(qǐng)求,并在有請(qǐng)求時(shí)通知 Redis。
我們也可以針對(duì)已連接套接字設(shè)置非阻塞模式:Redis 調(diào)用 recv() 后,如果已連接套接字上一直沒有數(shù)據(jù)到達(dá),Redis 線程同樣可以返回處理其他操作。我們也需要有機(jī)制繼續(xù)監(jiān)聽該已連接套接字,并在有數(shù)據(jù)達(dá)到時(shí)通知 Redis。
到此,Linux 中的 IO 多路復(fù)用機(jī)制就要登場(chǎng)了。
3.redis 基于多路復(fù)用的IO 模型
redis 的多路復(fù)用IO模型 基于 select/epoll機(jī)制實(shí)現(xiàn)的;
select/epoll支持以下兩點(diǎn):
red單線程運(yùn)行下,支持linux內(nèi)核中同時(shí)存在多個(gè) “監(jiān)聽套接字”和“連接套接字”,內(nèi)核會(huì)一直監(jiān)聽這些套接字,每當(dāng)有請(qǐng)求到達(dá),就會(huì)通知redis,處理
select/epoll機(jī)制提供 事件回調(diào)函數(shù),不同的事件對(duì)應(yīng)不同的回調(diào)函數(shù),時(shí)間發(fā)生調(diào)用對(duì)應(yīng)的回調(diào)函數(shù)
圖中的多個(gè) FD 就是剛才所說的多個(gè)套接字。Redis 網(wǎng)絡(luò)框架調(diào)用 epoll 機(jī)制,讓內(nèi)核監(jiān)聽這些套接字。此時(shí),Redis 線程不會(huì)阻塞在某一個(gè)特定的監(jiān)聽或已連接套接字上,也就是說,不會(huì)阻塞在某一個(gè)特定的客戶端請(qǐng)求處理上。
為了在請(qǐng)求到達(dá)時(shí)能通知到 Redis 線程,select/epoll 提供了基于事件的回調(diào)機(jī)制,即針對(duì)不同事件的發(fā)生,調(diào)用相應(yīng)的處理函數(shù)。
select/epoll 一旦監(jiān)測(cè)到 FD 上有請(qǐng)求到達(dá)時(shí),就會(huì)觸發(fā)相應(yīng)的事件。這些事件會(huì)被放進(jìn)一個(gè)事件隊(duì)列,Redis 單線程對(duì)該事件隊(duì)列不斷進(jìn)行處理。這樣一來,Redis 無需一直輪詢是否有請(qǐng)求實(shí)際發(fā)生,這就可以避免造成 CPU 資源浪費(fèi)。同時(shí),Redis 在對(duì)事件隊(duì)列中的事件進(jìn)行處理時(shí),會(huì)調(diào)用相應(yīng)的處理函數(shù),這就實(shí)現(xiàn)了基于事件的回調(diào)。因?yàn)?Redis 一直在對(duì)事件隊(duì)列進(jìn)行處理,所以能及時(shí)響應(yīng)客戶端請(qǐng)求,提升 Redis 的響應(yīng)性能。
例子:
假如兩個(gè)請(qǐng)求分別對(duì)應(yīng) Accept 事件和 Read 事件,Redis 分別對(duì)這兩個(gè)事件注冊(cè) accept 和 get 回調(diào)函數(shù)。當(dāng) Linux 內(nèi)核監(jiān)聽到有連接請(qǐng)求或讀數(shù)據(jù)請(qǐng)求時(shí),就會(huì)觸發(fā) Accept 事件和 Read 事件,此時(shí),內(nèi)核就會(huì)回調(diào) Redis 相應(yīng)的 accept 和 get 函數(shù)進(jìn)行處理。
4.redis 多路復(fù)用IO 模型有沒有什么性能瓶頸?
1.任何一個(gè)請(qǐng)求在server中耗時(shí),就會(huì)影響整個(gè)server的性能,后面所有請(qǐng)求都會(huì)阻塞等待
操作bigkey,分配內(nèi)存空間比較耗時(shí),同樣刪除bigkey時(shí)釋放內(nèi)存空間也比較耗時(shí)
使用復(fù)雜度過高的命令:例如SORT/SUNION/ZUNIONSTORE,或者時(shí)間復(fù)雜度O(N),但是N特別大
大量key集中過期:Redis的過期機(jī)制也是在主線程中執(zhí)行的,大量key集中過期會(huì)導(dǎo)致處理一個(gè)請(qǐng)求時(shí),耗時(shí)都在刪除過期key,耗時(shí)變長;
**淘汰策略:**淘汰策略也是在主線程執(zhí)行的,當(dāng)內(nèi)存超過Redis內(nèi)存上限后,每次寫入都需要淘汰一些key,也會(huì)造成耗時(shí)變長;
**AOF刷盤開啟always機(jī)制:**每次寫入都需要把這個(gè)操作刷到磁盤,寫磁盤的速度遠(yuǎn)比寫內(nèi)存慢,會(huì)拖慢Redis的性能;
主從全量同步生成RDB:雖然采用fork子進(jìn)程生成數(shù)據(jù)快照,但fork這一瞬間也是會(huì)阻塞整個(gè)線程的,實(shí)例越大,阻塞時(shí)間越久;
解決:一方面需要開發(fā)人員手動(dòng)處理,另一方面 Redis在4.0推出了lazy-free機(jī)制,把bigkey釋放內(nèi)存的耗時(shí)操作放在了異步線程中執(zhí)行,降低對(duì)主線程的影響。
2.并發(fā)量大時(shí),雖然使用 多路復(fù)用IO,單個(gè)線程,多個(gè)IO,當(dāng)時(shí)redis主線程處理仍然是單線程,順序執(zhí)行,無法利用計(jì)算機(jī)多個(gè)CPU(內(nèi)核)
解決:Redis在6.0推出了多線程,可以在高并發(fā)場(chǎng)景下利用CPU多核多線程讀寫客戶端數(shù)據(jù),進(jìn)一步提升server性能,當(dāng)然,只是針對(duì)客戶端的讀寫是并行的,每個(gè)命令的真正操作依舊是單線程的。
3.《持久化:AOF和RDB》
1.AOF
1.AOF寫日志
AOF 寫日志使用的是 寫后記錄日志
好處:
避免命令錯(cuò)誤,AOF日志恢復(fù)時(shí)出現(xiàn)錯(cuò)誤。redis不會(huì)檢查 命令是否正確,所以如果先記日志,后執(zhí)行命令,會(huì)導(dǎo)致記日志成功,執(zhí)行命令失敗
不會(huì)阻塞主線程。因?yàn)閷懭罩臼谴疟P操作,影響性能,最終導(dǎo)致后面請(qǐng)求耗時(shí)
AOF的缺點(diǎn):
寫日志和命令執(zhí)行都是在主線程執(zhí)行,所以記錄日志會(huì)阻塞下一個(gè)命令操作
寫后 記錄日志 ,宕機(jī)會(huì)導(dǎo)致數(shù)據(jù)丟失
*案例:set testkey testvalue,“3”表示當(dāng)前命令有三個(gè)部分,每部分都是由“$+數(shù)字”開頭,后面緊跟著具體的命令、鍵或值。這里,“數(shù)字”表示這部分中的命令、鍵或值一共有多少字節(jié)。例如,“$3 set”表示這部分有 3 個(gè)字節(jié),也就是“set”命令。
2. 三種寫回策略
AOF 配置項(xiàng) appendfsync 的三個(gè)可選值。
Always:同步寫回,同步刷盤,寫一次,記錄一次(丟失數(shù)據(jù)少,影響redis性能)
Everysec:每秒寫回,每秒執(zhí)行一次刷盤(最多丟失一秒數(shù)據(jù))
No:操作系統(tǒng)控制的寫回, 每個(gè)命令執(zhí)行完把日志寫回 AOF文件的內(nèi)存緩沖區(qū),由操作系統(tǒng)決定什么時(shí)候刷盤(速度快,但是會(huì)出現(xiàn)數(shù)據(jù)丟失嚴(yán)重的情況)
3.AOF日志文件過大問題
.文件過大帶來的問題
文件系統(tǒng)不支持太大的文件
文件過大導(dǎo)致 命令追加的耗時(shí),影響性能
文件過大, AOF日志回寫的時(shí),由于是逐條命令執(zhí)行,會(huì)導(dǎo)致系統(tǒng)啟動(dòng)過慢
4.AOF重寫
1.什么是AOF重寫?
AOF重寫時(shí),redis根據(jù)當(dāng)前數(shù)據(jù)庫的現(xiàn)狀,創(chuàng)建一個(gè)新的AOF日志文件,他會(huì)獲取redis當(dāng)前狀態(tài)所有數(shù)據(jù),原來的AOF文件每個(gè)鍵值對(duì)可能有多個(gè)命令,而重寫機(jī)制把多個(gè)命令合并成一個(gè)命令,記錄該鍵值對(duì)的當(dāng)前狀態(tài);
當(dāng)我們對(duì)一個(gè)列表先后做了 6 次修改操作后,列表的最后狀態(tài)是[“D”, “C”, “N”],此時(shí),只用 LPUSH u:list “N”, “C”, "D"這一條命令就能實(shí)現(xiàn)該數(shù)據(jù)的恢復(fù),這就節(jié)省了五條命令的空間。
2.AOF重寫是異步的嗎?
重寫時(shí),會(huì)有由主線程fork(fork會(huì)阻塞主線程)出來一個(gè)后臺(tái)子線程 bgrewriteaof,會(huì)把主線程當(dāng)狀態(tài)所有內(nèi)存復(fù)制給后臺(tái)子線程bgrewriteaof,子線程去執(zhí)行AOF重寫。
fork原理
a、fork子進(jìn)程,fork這個(gè)瞬間一定是會(huì)阻塞主線程的(注意,fork時(shí)并不會(huì)一次性拷貝所有內(nèi)存數(shù)據(jù)給子進(jìn)程,老師文章寫的是拷貝所有內(nèi)存數(shù)據(jù)給子進(jìn)程,我個(gè)人認(rèn)為是有歧義的),fork采用操作系統(tǒng)提供的寫實(shí)復(fù)制(Copy On Write)機(jī)制,就是為了避免一次性拷貝大量?jī)?nèi)存數(shù)據(jù)給子進(jìn)程造成的長時(shí)間阻塞問題,但**fork子進(jìn)程需要拷貝進(jìn)程必要的數(shù)據(jù)結(jié)構(gòu),其中有一項(xiàng)就是拷貝內(nèi)存頁表(虛擬內(nèi)存和物理內(nèi)存的映射索引表),這個(gè)拷貝過程會(huì)消耗大量CPU資源,拷貝完成之前整個(gè)進(jìn)程是會(huì)阻塞的,阻塞時(shí)間取決于整個(gè)實(shí)例的內(nèi)存大小,實(shí)例越大,內(nèi)存頁表越大,fork阻塞時(shí)間越久。拷貝內(nèi)存頁表完成后,子進(jìn)程與父進(jìn)程指向相同的內(nèi)存地址空間,也就是說此時(shí)雖然產(chǎn)生了子進(jìn)程,但是并沒有申請(qǐng)與父進(jìn)程相同的內(nèi)存大小。**那什么時(shí)候父子進(jìn)程才會(huì)真正內(nèi)存分離呢?“寫實(shí)復(fù)制”顧名思義,就是在寫發(fā)生時(shí),才真正拷貝內(nèi)存真正的數(shù)據(jù),這個(gè)過程中,父進(jìn)程也可能會(huì)產(chǎn)生阻塞的風(fēng)險(xiǎn),就是下面介紹的場(chǎng)景。
b、fork出的子進(jìn)程指向與父進(jìn)程相同的內(nèi)存地址空間,此時(shí)子進(jìn)程就可以執(zhí)行AOF重寫,把內(nèi)存中的所有數(shù)據(jù)寫入到AOF文件中。但是此時(shí)父進(jìn)程依舊是會(huì)有流量寫入的,如果父進(jìn)程操作的是一個(gè)已經(jīng)存在的key,那么這個(gè)時(shí)候父進(jìn)程就會(huì)真正拷貝這個(gè)key對(duì)應(yīng)的內(nèi)存數(shù)據(jù),申請(qǐng)新的內(nèi)存空間,這樣逐漸地,父子進(jìn)程內(nèi)存數(shù)據(jù)開始分離,父子進(jìn)程逐漸擁有各自獨(dú)立的內(nèi)存空間。因?yàn)閮?nèi)存分配是以頁為單位進(jìn)行分配的,默認(rèn)4k,如果父進(jìn)程此時(shí)操作的是一個(gè)bigkey,重新申請(qǐng)大塊內(nèi)存耗時(shí)會(huì)變長,可能會(huì)產(chǎn)阻塞風(fēng)險(xiǎn)。另外,如果操作系統(tǒng)開啟了內(nèi)存大頁機(jī)制(Huge Page,頁面大小2M),那么父進(jìn)程申請(qǐng)內(nèi)存時(shí)阻塞的概率將會(huì)大大提高,所以在Redis機(jī)器上需要關(guān)閉Huge Page機(jī)制。Redis每次fork生成RDB或AOF重寫完成后,都可以在Redis log中看到父進(jìn)程重新申請(qǐng)了多大的內(nèi)存空間。
重寫的過程總結(jié)為**“一個(gè)拷貝,兩處日志”**。
一個(gè)拷貝:主線程的內(nèi)存數(shù)據(jù)拷貝給子線程bgrewriteaof,子線程對(duì)數(shù)據(jù)進(jìn)行分析,重寫日志
兩處日志:
主線程操作的AOF日志,如果重寫期間有新的寫入操作,寫入操作放入 AOF日志緩沖區(qū)
AOF重寫日志 ,重寫期間新的寫入操作,也會(huì)放入AOF重寫日志緩沖區(qū)(等bgrewriteaof重寫日志完成,就把AOF重寫日志緩沖區(qū)的數(shù)據(jù)重寫 形成新的AOF日志文件,這時(shí)候就可以代替舊的文件了)
每次 AOF 重寫時(shí),Redis 會(huì)先執(zhí)行一個(gè)內(nèi)存拷貝,用于重寫;然后,使用兩個(gè)日志保證在重寫過程中,新寫入的數(shù)據(jù)不會(huì)丟失。而且,因?yàn)?Redis 采用額外的線程進(jìn)行數(shù)據(jù)重寫,所以,這個(gè)過程并不會(huì)阻塞主線程。
5.問題
1.AOF 日志重寫的時(shí)候,是由 bgrewriteaof 子進(jìn)程來完成的,不用主線程參與,我們今天說的非阻塞也是指子進(jìn)程的執(zhí)行不阻塞主線程。但是,你覺得,這個(gè)重寫過程有沒有其他潛在的阻塞風(fēng)險(xiǎn)呢?如果有的話,會(huì)在哪里阻塞?
fork進(jìn)程會(huì)阻塞
copy-onwrite機(jī)制,當(dāng)父進(jìn)程有數(shù)據(jù)寫入已存在的key,且為bigkey,申請(qǐng)新的內(nèi)存空間時(shí)父進(jìn)程會(huì)阻塞
2.AOF 重寫也有一個(gè)重寫日志,為什么它不共享使用 AOF 本身的日志呢?
父子進(jìn)程共享一個(gè)日志文件,出現(xiàn)資源爭(zhēng)奪的情況,阻塞父進(jìn)程
如果重寫失敗呢?那么AOF文件會(huì)被污染,無法進(jìn)行宕機(jī)后,數(shù)據(jù)回寫;新建一個(gè)重寫日志文件,重寫失敗,直接刪除就好了;
2.RDB(既可以保證可靠性,還能在宕機(jī)時(shí)實(shí)現(xiàn)快速恢復(fù))
1.redis生成RDB快照的兩個(gè)命令
save:阻塞主線程
bgsave: 創(chuàng)建一個(gè)子線程,專門生成RDB文件,避免主線程阻塞,RDB默認(rèn)使用方式
2.redis處理RDB快照,主線程怎么進(jìn)行寫操作
進(jìn)行 全量快照 時(shí),主進(jìn)程會(huì)fork出來一個(gè)子進(jìn)程,會(huì)copy內(nèi)存數(shù)據(jù)的內(nèi)存頁表地址,copy完直到父子進(jìn)程就有共享同一個(gè)內(nèi)存數(shù)據(jù)的內(nèi)存地址;
在子進(jìn)程寫RDB的時(shí)候,主進(jìn)程正常讀,寫的時(shí)候是使用操作系統(tǒng) Copy-On-Write(寫時(shí)復(fù)制)機(jī)制,會(huì)申請(qǐng)新的內(nèi)存空間存放訪問的key鍵值對(duì)數(shù)據(jù),bgsave 子進(jìn)程會(huì)把這個(gè)副本數(shù)據(jù)寫入 RDB 文件,主線程仍然寫原來的數(shù)據(jù);
3.增量快照
我們可以頻繁的執(zhí)行全量快照嗎?
注意:如果頻繁地執(zhí)行全量快照,也會(huì)帶來兩方面的開銷。
**頻繁將全量數(shù)據(jù)寫入磁盤,會(huì)給磁盤帶來很大壓力,**多個(gè)快照競(jìng)爭(zhēng)有限的磁盤帶寬,前一個(gè)快照還沒有做完,后一個(gè)又開始做了,容易造成惡性循環(huán)。
bgsave 子進(jìn)程需要通過 fork 操作從主線程創(chuàng)建出來。雖然,子進(jìn)程在創(chuàng)建后不會(huì)再阻塞主線程,但是,**fork 這個(gè)創(chuàng)建過程本身會(huì)阻塞主線程,而且主線程的內(nèi)存越大,阻塞時(shí)間越長。**如果頻繁 fork 出 bgsave 子進(jìn)程,這就會(huì)頻繁阻塞主線程了
當(dāng)我們第一次做完全量快照之后,后面T1,T2時(shí)刻做快照時(shí)只需要把修改的數(shù)據(jù)寫入快照文件就行了。
所以,我們需要申請(qǐng)額外的內(nèi)存空間來存儲(chǔ) 元數(shù)據(jù)信息;
雖然跟 AOF 相比,快照的恢復(fù)速度快,但是,快照的頻率不好把握,如果頻率太低,兩次快照間一旦宕機(jī),就可能有比較多的數(shù)據(jù)丟失。如果頻率太高,又會(huì)產(chǎn)生額外開銷
4.混合使用 AOF 日志和內(nèi)存快照
redis4.0中提出混合使用 AOF 日志和內(nèi)存快照的方案。
內(nèi)存快照以一定的頻率執(zhí)行,在兩次快照之間,使用 AOF 日志記錄這期間的所有命令操作。
好處:
避免頻繁fork對(duì)主進(jìn)程的影響
主進(jìn)程寫入時(shí)不會(huì)有額外的內(nèi)存空間開銷
AOF日志文件也不會(huì)過大
如下圖所示,T1 和 T2 時(shí)刻的修改,用 AOF 日志記錄,等到第二次做全量快照時(shí),就可以清空 AOF 日志,因?yàn)榇藭r(shí)的修改都已經(jīng)記錄到快照中了,恢復(fù)時(shí)就不再用日志了。
3.持久化方案總結(jié)
數(shù)據(jù)不能丟失時(shí),內(nèi)存快照RDB和 AOF 的混合使用是一個(gè)很好的選擇;
如果允許分鐘級(jí)別的數(shù)據(jù)丟失,可以只使用 RDB;
如果只用 AOF,優(yōu)先使用 everysec 的配置選項(xiàng),因?yàn)樗诳煽啃院托阅苤g取了一個(gè)平衡;
4.《主從數(shù)據(jù)同步》
1.讀寫分離
主庫寫數(shù)據(jù),并同步給從庫
主從庫均可讀數(shù)據(jù)
為什么讀寫分離,寫數(shù)據(jù)可以寫在從庫上嗎?
不可以,寫在所有庫上,如果某一個(gè)寫操作失敗了呢?是不是意味著主從庫數(shù)據(jù)不一致了呢?
這時(shí)候我們?yōu)榱藬?shù)據(jù)一致,就要協(xié)調(diào)主從庫寫操作要么都完成,有一個(gè)不完成就回滾,這勢(shì)必會(huì)用到鎖,以及實(shí)例之間協(xié)商是否均完成寫操作,這樣會(huì)影響redis性能;
主從庫模式,主庫有數(shù)據(jù),會(huì)同步給從庫,這樣組從庫數(shù)據(jù)就是一致的。
2.主從同步
1.第一次同步
redis實(shí)例啟動(dòng)時(shí)可以使用 replacof ip (Redis 5.0 之前使用 slaveof)指令當(dāng)前實(shí)例是哪個(gè)主庫的從庫
現(xiàn)在有實(shí)例 1(ip:172.16.19.3)和實(shí)例 2(ip:172.16.19.5)
在實(shí)例2上執(zhí)行 replicaof 172.16.19.3,實(shí)例2就變成了實(shí)例1的從庫,并開始從實(shí)例 1 上復(fù)制數(shù)據(jù)
主從第一次同步時(shí)
第一階段:從庫會(huì)發(fā)送 給主庫 fsync ? -1 命令給主庫告訴它,我這是第一次同步,我要求全量同步,fsync 命令 有兩個(gè)參數(shù)
主庫runID(唯一隨機(jī)ID),由于第一次同步,所以從庫不知道主庫runID,傳“?”,
從庫偏移量offset,由于第一次同步傳 -1
第二階段: 主庫返回 “FULLRESYNC 主庫runID master_repl_offset(repl_backlog_buffer中主庫偏移量)” ,由于是全量同步,從庫會(huì)記錄master_repl_offset,作為自己下一次增量同步的slave_repl_offset ;
而且主庫會(huì)生成 RDB快照(執(zhí)行bgsave命令), 通過網(wǎng)絡(luò)發(fā)送給 從庫,從庫拿到RDB 快照刪除從庫本地?cái)?shù)據(jù),讀快照,同步數(shù)據(jù)
第三階段: 在主庫將數(shù)據(jù)同步給從庫的過程中,主庫不會(huì)被阻塞,仍然可以正常接收請(qǐng)求,這些寫請(qǐng)求會(huì)在replication buffer,和repl_backlog_buffer寫一份,當(dāng)主庫完成 RDB 文件發(fā)送后,就會(huì)把此時(shí) replication buffer 中的修改操作發(fā)給從庫,從庫再重新執(zhí)行這些操作
2.主從級(jí)聯(lián)模式分擔(dān)全量復(fù)制時(shí)的主庫壓力
對(duì)于主庫來說,全量同步還有兩個(gè)地方壓力:
生成RDB文件 和 傳輸RDB文件
當(dāng)從庫數(shù)量比較多,進(jìn)行全量復(fù)制,就會(huì)多次fork子進(jìn)程,生成RDB文件,會(huì)阻塞主進(jìn)程
所以,我們可以選舉處理出來一個(gè)從庫A (一般選內(nèi)存大,網(wǎng)絡(luò)好的從庫),讓其他家從庫同步從庫A的數(shù)據(jù),
就可以給主庫減輕壓力
一旦主從庫完成了全量復(fù)制,它們之間就會(huì)一直維護(hù)一個(gè)網(wǎng)絡(luò)連接,主庫會(huì)通過這個(gè)連接將后續(xù)陸續(xù)收到的命令操作再同步給從庫,這個(gè)過程也稱為基于長連接的命令傳播,可以避免頻繁建立連接的開銷。
基于長連接的命令傳播 直接走 replication_buffer
3.主從網(wǎng)絡(luò)連接斷了怎么辦
斷了會(huì)使用增量復(fù)制
repl_backlog_buffer相當(dāng)于主線程寫操作的一個(gè)備份,就是為了防止從庫斷連接,在恢復(fù)時(shí),無法恢復(fù)連接斷開到恢復(fù)的數(shù)據(jù)
replication buffer:主要用來與客戶端進(jìn)行數(shù)據(jù)傳輸,它是傳輸通道,有幾個(gè)客戶端就有幾個(gè)replication buffer;
無論全量復(fù)制、基于長連接的命令傳播,以及增量復(fù)制(傳輸偏移量offset數(shù)據(jù)),數(shù)據(jù)傳輸都會(huì)用到replication buffer。
repl_backlog_buffer,只要有從庫存在,這個(gè)repl_backlog_buffer就會(huì)存在。主庫的所有寫命令除了傳播給從庫之外,都會(huì)在這個(gè)repl_backlog_buffer中記錄一份,緩存起來,只有預(yù)先緩存了這些命令,當(dāng)從庫斷連后,從庫重新發(fā)送psync $master_runid o f f s e t , 主 庫 才 能 通 過 offset,主庫才能通過offset,主庫才能通過offset在repl_backlog_buffer中找到從庫斷開的位置,只發(fā)送$offset之后的增量數(shù)據(jù)給從庫即可。
1、repl_backlog_buffer:它是為了從庫斷開之后,如何找到主從差異數(shù)據(jù)而設(shè)計(jì)的環(huán)形緩沖區(qū),從而避免全量同步帶來的性能開銷。如果從庫斷開時(shí)間太久,repl_backlog_buffer環(huán)形緩沖區(qū)被主庫的寫命令覆蓋了,那么從庫連上主庫后只能乖乖地進(jìn)行一次全量同步,所以repl_backlog_buffer配置盡量大一些,可以降低主從斷開后全量同步的概率。而在repl_backlog_buffer中找主從差異的數(shù)據(jù)后,如何發(fā)給從庫呢?這就用到了replication buffer。
2、replication buffer:Redis和客戶端通信也好,和從庫通信也好,Redis都需要給分配一個(gè) 內(nèi)存buffer進(jìn)行數(shù)據(jù)交互,客戶端是一個(gè)client,從庫也是一個(gè)client,我們每個(gè)client連上Redis后,Redis都會(huì)分配一個(gè)client buffer,所有數(shù)據(jù)交互都是通過這個(gè)buffer進(jìn)行的:Redis先把數(shù)據(jù)寫到這個(gè)buffer中,然后再把buffer中的數(shù)據(jù)發(fā)到client socket中再通過網(wǎng)絡(luò)發(fā)送出去,這樣就完成了數(shù)據(jù)交互。所以主從在增量同步時(shí),從庫作為一個(gè)client,也會(huì)分配一個(gè)buffer,只不過這個(gè)buffer專門用來傳播用戶的寫命令到從庫,保證主從數(shù)據(jù)一致,我們通常把它叫做replication buffer。如果主從連接斷開,那么replication_buffer就不存在了。
3、既然有這個(gè)內(nèi)存buffer存在,那么這個(gè)buffer有沒有限制呢?如果主從在傳播命令時(shí),因?yàn)槟承┰驈膸焯幚淼梅浅B?#xff0c;那么主庫上的這個(gè)buffer就會(huì)持續(xù)增長,消耗大量的內(nèi)存資源,甚至OOM。所以Redis提供了client-output-buffer-limit參數(shù)限制這個(gè)buffer的大小,如果超過限制,主庫會(huì)強(qiáng)制斷開這個(gè)client的連接,也就是說從庫處理慢導(dǎo)致主庫內(nèi)存buffer的積壓達(dá)到限制后,主庫會(huì)強(qiáng)制斷開從庫的連接,此時(shí)主從復(fù)制會(huì)中斷,中斷后如果從庫再次發(fā)起復(fù)制請(qǐng)求,那么此時(shí)可能會(huì)導(dǎo)致惡性循環(huán),引發(fā)復(fù)制風(fēng)暴,這種情況需要格外注意。
repl_backlog_buffer環(huán)形緩沖區(qū)在主庫中只有一個(gè),每個(gè)replication_buffer對(duì)應(yīng)一個(gè)客戶端(redis client或者從庫)
4.總結(jié)
Redis 的主從庫同步的基本原理,總結(jié)來說,有三種模式:全量復(fù)制、基于長連接的命令傳播,以及增量復(fù)制。
1. 第一次復(fù)制時(shí) 全量復(fù)制
2. 全量復(fù)制完成之后會(huì)建立長連接,進(jìn)行數(shù)據(jù)同步
3. 如果連接斷了,等連接恢復(fù)后就進(jìn)行增量復(fù)制
5.《哨兵集群》
1.哨兵機(jī)制流程
主從機(jī)制下主庫掛了存在的問題?
主庫是否真的下線了?
選主機(jī)制,是怎么進(jìn)行的?
怎么把主庫信息通知給從庫和客戶端呢
什么是哨兵呢?
哨兵就是一個(gè)運(yùn)行在特殊模式下的redis服務(wù),主從實(shí)例在運(yùn)行的同時(shí),他們也在運(yùn)行;
哨兵機(jī)制主要有三個(gè)作用:監(jiān)視,選主,通知
監(jiān)視:哨兵集群會(huì)每個(gè)一段時(shí)間ping 主從庫, 如果主從庫沒有響應(yīng)ping命令,那么就判定為“主觀下線”
選主:如果主庫被 “N/2+1” 個(gè)認(rèn)為哨兵判定為“主觀下線”,那么主庫就被認(rèn)定為 “客觀下線”,這時(shí)候就要進(jìn)行選主機(jī)制;
通知:選主成功后,需要把新主庫信息,通知給客戶端和從庫,并讓從庫執(zhí)行 replicaof 命令,與新主庫進(jìn)行數(shù)據(jù)同步
2.選主機(jī)制
1.選主之前我們來先看一下什么情況下會(huì)被判定為“客觀下線”?
哨兵集群內(nèi)當(dāng)有 N/2+1 個(gè)哨兵節(jié)點(diǎn) 認(rèn)為 “主觀下線”就認(rèn)為主庫 “客觀下線”;
對(duì)于從庫,有一個(gè)哨兵判定為“主觀下線”,那么就下線了,因?yàn)?#xff0c;從庫掛了對(duì)整個(gè)redis集群來說沒有太大影響;
2.怎樣進(jìn)行選主的?
先篩選,后打分
先按照 一定的條件篩選, 后 按一定條件 打分;
a.篩選:過濾掉下線的 從庫,然后,過濾網(wǎng)絡(luò)連接不好的從庫?
那么怎么判定網(wǎng)絡(luò)連接不好呢?
根據(jù) down_after_milliseconds*10這個(gè)配置項(xiàng),down_after_milliseconds為 網(wǎng)絡(luò)連接最大超時(shí)時(shí)間,如果,從庫連接超時(shí)超過這個(gè)時(shí)間就被認(rèn)定為 斷連,如果斷連超過10次,就判定,該從庫網(wǎng)絡(luò)狀態(tài)不穩(wěn)定,不參與主庫選舉;
b.打分,根據(jù) 優(yōu)先級(jí)->從庫同步進(jìn)度->從庫ID號(hào) 進(jìn)行三輪打分
第一輪:優(yōu)先級(jí)最高的為主庫(我們可以設(shè)置內(nèi)存大,網(wǎng)絡(luò)帶寬高的從庫 為高優(yōu)先級(jí)),相同進(jìn)行第二輪
第二輪:數(shù)據(jù)同步進(jìn)度最快的為主庫(每個(gè)從庫都有一個(gè)slave_repl_offset值,越大代表同步的越快)相同進(jìn)行第三輪
第三輪:從庫ID號(hào)小的為主庫
3.主從切換的過程中是否能夠正常處理請(qǐng)求呢?
讀寫分離的情況下,讀請(qǐng)求不影響個(gè),寫操作會(huì)出現(xiàn)異常;
寫失敗的持續(xù)時(shí)間=主從切換的時(shí)間+客戶端感知到主庫的時(shí)間
主從切換的時(shí)間我們可以通過 down_after_milliseconds(連接最大超時(shí)時(shí)間)設(shè)置,down_after_milliseconds值越小,哨兵集群就越敏感,down_after_milliseconds設(shè)置過小,可能會(huì)由于主庫壓力過大,或網(wǎng)絡(luò)不好導(dǎo)致發(fā)生不必要的主從切換,但是當(dāng)主庫真正的故障時(shí),down_after_milliseconds越小,對(duì)業(yè)務(wù)的影響也就越小;
當(dāng)主從切換有寫操作到達(dá)時(shí),我們可以把 寫操作放入客戶端緩存或使用消息中間件緩存,主從切換完成,客戶端感知到主庫時(shí),再讀入緩存中寫操作;注意,如果down_after_milliseconds過大,主從切換時(shí)間過長,會(huì)導(dǎo)致消息對(duì)列有大量寫請(qǐng)求;
4.哨兵集群
配置哨兵信息 只需要設(shè)置主庫的 IP 和端口
sentinel monitor
哨兵并沒有 將自己的連接信息發(fā)送給主庫,也就意味 哨兵之間是互相不知道地址的,那么哨兵是如何通信的呢?
問題:
哨兵之間是如何通信的?
哨兵和從庫之間是如何通信的?
哨兵是如何把 主從切換后的新主庫信息發(fā)送給客戶端的?哨兵和客戶端之間是如何通信的?
5.PUB/SUB模式解決哨兵,主從庫,客戶端之間的通信
1.哨兵之間是如何通信的?
通過發(fā)布/訂閱模式,每個(gè)哨兵發(fā)布自己的連接配置信息給 主庫上的 “sentinel:hello”頻道,并訂閱這個(gè)頻道,這樣就可以獲取其他哨兵節(jié)點(diǎn)的信息啦;
2.哨兵和從庫之間是如何通信的?
哨兵會(huì)發(fā)送INFO命令給主庫,主庫就會(huì)返回 “從庫 信息列表” 給哨兵節(jié)點(diǎn),這樣哨兵節(jié)點(diǎn)就可以和從庫之間建立長連接,保持心跳了
3.哨兵和客戶端之間是如何通信的?客戶端通過監(jiān)控了解哨兵進(jìn)行主從切換的過程呢?
不但主庫提供發(fā)布訂閱機(jī)制,哨兵節(jié)點(diǎn)也提供PUB/SUB機(jī)制,哨兵提供的消息訂閱頻道有很多,不同頻道包含了主從庫切換過程中的不同關(guān)鍵事件。
客戶端同過 “SUBSCRIBE 頻道 ” 監(jiān)控主從庫的裝態(tài)了
比如:想監(jiān)聽主從切換完成的狀態(tài): SUBSCRIBE +switch-master
那么知道了這些頻道,客戶端就可以根據(jù)自己的需要訂閱這些頻道了,就能實(shí)時(shí)監(jiān)控主從切換,甚至主從庫的狀態(tài)了
6.由哪個(gè)哨兵節(jié)點(diǎn)來執(zhí)行主從切換呢?
使用了Raft的公式算法
任何一個(gè)哨兵節(jié)點(diǎn)判斷主庫“主觀下線”后,都會(huì)發(fā)送 is-matser-down-by-addr命令給其他哨兵節(jié)點(diǎn);
其他哨兵節(jié)點(diǎn)會(huì)根據(jù)自己與主庫的連接狀態(tài)返回對(duì)應(yīng)的結(jié)果,連接完好返回不贊成N,斷連返回Y
當(dāng)某個(gè)哨兵節(jié)點(diǎn)拿到的贊成票>= quorum 的值,就判定主庫“客觀下線”
當(dāng)一個(gè)哨兵節(jié)點(diǎn)判斷一個(gè) 主庫“客觀下線”,就選舉自己為Leader ,完成主從切換操作;
任何一個(gè)想成為 Leader 的哨兵,要滿足兩個(gè)條件:
第一,拿到半數(shù)以上的贊成票;
第二,拿到的票數(shù)同時(shí)還需要大于等于哨兵配置文件中的 quorum 值。
以 3 個(gè)哨兵為例,假設(shè)此時(shí)的 quorum 設(shè)置為 2,那么,任何一個(gè)想成為 Leader 的哨兵只要拿到 2 張贊成票,就可以了。
在 T1 時(shí)刻,S1 判斷主庫為“客觀下線”,它想成為 Leader,就先給自己投一張贊成票,然后分別向 S2 和 S3 發(fā)送命令,表示要成為 Leader。
在 T2 時(shí)刻,S3 判斷主庫為“客觀下線”,它也想成為 Leader,所以也先給自己投一張贊成票,再分別向 S1 和 S2 發(fā)送命令,表示要成為 Leader。
在 T3 時(shí)刻,S1 收到了 S3 的 Leader 投票請(qǐng)求。因?yàn)?S1 已經(jīng)給自己投了一票 Y,所以它不能再給其他哨兵投贊成票了,所以 S1 回復(fù) N 表示不同意。同時(shí),S2 收到了 T2 時(shí) S3 發(fā)送的 Leader 投票請(qǐng)求。因?yàn)?S2 之前沒有投過票,它會(huì)給第一個(gè)向它發(fā)送投票請(qǐng)求的哨兵回復(fù) Y,給后續(xù)再發(fā)送投票請(qǐng)求的哨兵回復(fù) N,所以,在 T3 時(shí),S2 回復(fù) S3,同意 S3 成為 Leader。
在 T4 時(shí)刻,S2 才收到 T1 時(shí) S1 發(fā)送的投票命令。因?yàn)?S2 已經(jīng)在 T3 時(shí)同意了 S3 的投票請(qǐng)求,此時(shí),S2 給 S1 回復(fù) N,表示不同意 S1 成為 Leader。發(fā)生這種情況,是因?yàn)?S3 和 S2 之間的網(wǎng)絡(luò)傳輸正常,而 S1 和 S2 之間的網(wǎng)絡(luò)傳輸可能正好擁塞了,導(dǎo)致投票請(qǐng)求傳輸慢了。
最后,在 T5 時(shí)刻,S1 得到的票數(shù)是來自它自己的一票 Y 和來自 S2 的一票 N。而 S3 除了自己的贊成票 Y 以外,還收到了來自 S2 的一票 Y。此時(shí),S3 不僅獲得了半數(shù)以上的 Leader 贊成票,也達(dá)到預(yù)設(shè)的 quorum 值(quorum 為 2),所以它最終成為了 Leader。
如果這一輪沒有產(chǎn)生Leader 就會(huì)進(jìn)行下一輪投票
?
要保證所有哨兵實(shí)例的配置是一致的,尤其是主觀下線的判斷值 down-after-milliseconds。
1,哨兵投票機(jī)制:
a:哨兵實(shí)例只有在自己判定主庫下線時(shí),才會(huì)給自己投票,而其他的哨兵實(shí)例會(huì)把票投給第一個(gè)來要票的請(qǐng)求,其后的都拒絕
b:如果出現(xiàn)多個(gè)哨兵同時(shí)發(fā)現(xiàn)主庫下線并給自己投票,導(dǎo)致投票選舉失敗,就會(huì)觸發(fā)新一輪投票,直至成功
2,哨兵Leader切換主從庫的機(jī)制:
哨兵成為Leader的必要條件:a:獲得半數(shù)以上的票數(shù),b:得到的票數(shù)要達(dá)到配置的quorum閥值
主從切換只能由Leader執(zhí)行,而成為Leader有兩個(gè)必要的條件,所以當(dāng)哨兵集群中實(shí)例異常過多時(shí),會(huì)導(dǎo)致主從庫無法切換
6.《redis主從同步與故障切換的坑》
1.主從數(shù)據(jù)不一致
redis主庫同步時(shí),是異步傳輸命令,不會(huì)等待命令傳輸成功,有以下兩種情況導(dǎo)致redis主從同步數(shù)據(jù)不一致
redis主庫有網(wǎng)絡(luò)延遲,導(dǎo)致從庫沒有接收到同步命令,此時(shí)剛好訪問到改數(shù)據(jù),就會(huì)返回舊數(shù)據(jù)
從庫及時(shí)收到了主庫的命令,但是,也可能會(huì)因?yàn)檎谔幚砥渌鼜?fù)雜度高的命令(例如集合操作命令)而阻塞。
解決方案:
針對(duì)第一種情況,主從在同一個(gè)局域網(wǎng)內(nèi),并保證網(wǎng)絡(luò)通暢
第二種情況,我們還可以開發(fā)一個(gè)外部程序來監(jiān)控主從庫間的復(fù)制進(jìn)度
Redis 的 INFO replication 命令可以查看主庫接收寫命令的進(jìn)度信息(master_repl_offset)和從庫復(fù)制寫命令的進(jìn)度信息(slave_repl_offset)。
我們就可以開發(fā)一個(gè)監(jiān)控程序,先用 INFO replication 命令查到主、從庫的進(jìn)度,然后,我們用 master_repl_offset 減去 slave_repl_offset,這樣就能得到從庫和主庫間的復(fù)制進(jìn)度差值了。
如果某個(gè)從庫的進(jìn)度差值大于我們預(yù)設(shè)的閾值,我們可以讓客戶端不再和這個(gè)從庫連接進(jìn)行數(shù)據(jù)讀取,這樣就可以減少讀到不一致數(shù)據(jù)的情況。不過,為了避免出現(xiàn)客戶端和所有從庫都不能連接的情況,我們需要把復(fù)制進(jìn)度差值的閾值設(shè)置得大一些。
2.過期數(shù)據(jù)
1.reids過期策略
定期淘汰:Redis 每隔一段時(shí)間(默認(rèn) 100ms),就會(huì)隨機(jī)選出一定數(shù)量的數(shù)據(jù),檢查它們是否過期,并把其中過期的數(shù)據(jù)刪除
惰性過期:讀到過期數(shù)據(jù)刪除
但是,這兩種策略都不能保證不會(huì)讀到過期數(shù)據(jù);
對(duì)于惰性過期,Redis 3.2 之前的版本,從庫在服務(wù)讀請(qǐng)求時(shí),并不會(huì)判斷數(shù)據(jù)是否過期,直接返回過期數(shù)據(jù);
Redis 3.2之后判斷是否過期,過期返回null
2.EXPIRE,PEXPIRE,EXPIREAT和PEXPIREAT
當(dāng)主從庫全量同步時(shí),如果主庫接收到了一條 EXPIRE 命令,那么,主庫會(huì)直接執(zhí)行這條命令。這條命令會(huì)在全量同步完成后,發(fā)給從庫執(zhí)行。而從庫在執(zhí)行時(shí),就會(huì)在當(dāng)前時(shí)間的基礎(chǔ)上加上數(shù)據(jù)的存活時(shí)間,這樣一來,從庫上數(shù)據(jù)的過期時(shí)間就會(huì)比主庫上延后了。
所以在業(yè)務(wù)應(yīng)用中使用 EXPIREAT/PEXPIREAT 命令,把數(shù)據(jù)的過期時(shí)間設(shè)置為具體的時(shí)間點(diǎn),避免讀到過期數(shù)據(jù)。
3.不合理配置項(xiàng)導(dǎo)致的服務(wù)掛掉(protected-mode 和 cluster-node-timeout。)
1.protected-mode
這個(gè)配置項(xiàng)的作用是限定哨兵實(shí)例能否被其他服務(wù)器訪問,yes時(shí),其余哨兵實(shí)例部署在其它服務(wù)器,那么,這些哨兵實(shí)例間就無法通信。當(dāng)主庫故障時(shí),哨兵無法判斷主庫下線,也無法進(jìn)行主從切換,最終 Redis 服務(wù)不可用。
protected-mode no
bind 192.168.10.3 192.168.10.4 192.168.10.5
1
2
2.cluster-node-timeout
當(dāng)我們?cè)?Redis Cluster 集群中為每個(gè)實(shí)例配置了“一主一從”模式時(shí),如果主實(shí)例發(fā)生故障,從實(shí)例會(huì)切換為主實(shí)例,受網(wǎng)絡(luò)延遲和切換操作執(zhí)行的影響,切換時(shí)間可能較長,就會(huì)導(dǎo)致實(shí)例的心跳超時(shí)(超出 cluster-node-timeout)。實(shí)例超時(shí)后,就會(huì)被 Redis Cluster 判斷為異常。而 Redis Cluster 正常運(yùn)行的條件就是,有半數(shù)以上的實(shí)例都能正常運(yùn)行。
所以,如果執(zhí)行主從切換的實(shí)例超過半數(shù),而主從切換時(shí)間又過長的話,就可能有半數(shù)以上的實(shí)例心跳超時(shí),從而可能導(dǎo)致整個(gè)集群掛掉。所以,我建議你將 cluster-node-timeout 調(diào)大些(例如 10 到 20 秒)。
4.總結(jié)
Redis 中的 **slave-serve-stale-data 配置項(xiàng)設(shè)置了從庫能否處理數(shù)據(jù)讀寫命令,**你可以把它設(shè)置為 no,這樣一來,從庫只能服務(wù) INFO、SLAVEOF 命令,這就可以避免在從庫中讀到不一致的數(shù)據(jù)了。
slave-read-only 是設(shè)置從庫能否處理寫命令,slave-read-only 設(shè)置為 yes 時(shí),從庫只能處理讀請(qǐng)求,無法處理寫請(qǐng)求
7.《腦裂現(xiàn)象》
1.什么是腦裂?
所謂的腦裂,就是指在主從集群中,同時(shí)有兩個(gè)主節(jié)點(diǎn),它們都能接收寫請(qǐng)求,腦裂一般發(fā)生在 主從切換的某一個(gè)環(huán)節(jié)。
一次數(shù)據(jù)丟失的排查?
第一步: 我們根據(jù)可以監(jiān)控(INFO replication 可以獲可取master_repl_offset和slave_repl_offset) 主庫的寫進(jìn)度 master_repl_offset和 從庫復(fù)制進(jìn)度 slave_repl_offset,比較兩者差值,如果差值過大,再看從庫未復(fù)制的數(shù)據(jù)是否為丟失的數(shù)據(jù); 如果master_repl_offset,slave_repl_offset一致,表明不是主從同步導(dǎo)致數(shù)據(jù)丟失,執(zhí)行第二步;
第二步:查看redis客戶端,在主從切換后的一段時(shí)間內(nèi),有一個(gè)客戶端仍然在和原主庫通信,并沒有和升級(jí)的新主庫進(jìn)行交互。這就相當(dāng)于主從集群中同時(shí)有了兩個(gè)主庫。
得出結(jié)論:出現(xiàn)腦裂現(xiàn)象;
2.腦裂發(fā)生的原因及腦裂為什么會(huì)導(dǎo)致數(shù)據(jù)丟失呢?
a.腦裂發(fā)生的原因
主庫阻塞或 cpu資源被其他進(jìn)程占用,獲取不到cpu資源
主庫執(zhí)行耗資源易阻塞操作或發(fā)生內(nèi)存 swap,無法及時(shí)響應(yīng)客戶端,響應(yīng)哨兵心跳,例如:bigkey,keys等操作
主庫所在機(jī)器cpu被其他 資源(比較消耗cpu的程序)占用,導(dǎo)致主庫進(jìn)入 “假死” 狀態(tài)
b.腦裂為什么會(huì)導(dǎo)致數(shù)據(jù)丟失?
假如說,響應(yīng)哨兵心跳的最大延時(shí)時(shí)間down-after-milliseconds 為12s, 主庫由于cpu資源受限或 執(zhí)行bigkey操作阻塞13無法響應(yīng)請(qǐng)求,主從切換要3s,那么在第12s的時(shí)刻主庫 就會(huì)被判定為 “客觀下線”,第13s主庫恢復(fù)正常,那么客戶端就能夠?qū)χ鲙爝M(jìn)行 2s的寫操作,這時(shí) 主從切換還沒完成;
主從切換完成后,原主庫降級(jí)為 從庫,會(huì)清空自己的數(shù)據(jù),讀取 新主庫的全量復(fù)制RDB文件,那么 “ 從切換還沒完成的2s內(nèi)的寫數(shù)據(jù)” 就會(huì)丟失
3.如何避免腦裂問題?
主要用到兩個(gè)配置項(xiàng):min-slaves-to-write 和 min-slaves-max-lag
min-slaves-to-write : 主從同步時(shí),主庫同步的最少從庫數(shù)量
min-slaves-max-lag: 主從同步數(shù)據(jù)復(fù)制時(shí),主庫給從庫發(fā)送ACK最大延時(shí)時(shí)間(以秒為單位)
例子:
假設(shè)我們將 min-slaves-to-write 設(shè)置為 1,把 min-slaves-max-lag 設(shè)置為 12s,把哨兵的 down-after-milliseconds 設(shè)置為 10s,主庫因?yàn)槟承┰蚩ㄗ×?15s,導(dǎo)致哨兵判斷主庫客觀下線,開始進(jìn)行主從切換。同時(shí),因?yàn)樵鲙炜ㄗ×?15s,沒有一個(gè)從庫能和原主庫在 12s 內(nèi)進(jìn)行數(shù)據(jù)復(fù)制,原主庫也無法接收客戶端請(qǐng)求了。這樣一來,主從切換完成后,也只有新主庫能接收請(qǐng)求,不會(huì)發(fā)生腦裂,也就不會(huì)發(fā)生數(shù)據(jù)丟失的問題了。
建議:
假設(shè)從庫有 K 個(gè),可以將 min-slaves-to-write 設(shè)置為 K/2+1(如果 K 等于 1,就設(shè)為 1),將 min-slaves-max-lag 設(shè)置為十幾秒(例如 10~20s),在這個(gè)配置下,如果有一半以上的從庫和主庫進(jìn)行的 ACK 消息延遲超過十幾秒,我們就禁止主庫接收客戶端寫請(qǐng)求。
8.《切片集群》
數(shù)據(jù)分片存儲(chǔ),數(shù)據(jù)如何分布到不同的實(shí)例?
客戶端如何知道數(shù)據(jù)在哪個(gè)實(shí)例?
案例:Redis 保存 5000 萬個(gè)鍵值對(duì),每個(gè)鍵值對(duì)大約是 512B,大概有25GB數(shù)據(jù)
橫向擴(kuò)展:橫向增加當(dāng)前 Redis 實(shí)例的個(gè)數(shù)(不受硬件限制,大小幾乎無限,不影響性能,但是復(fù)雜度增加)
縱向擴(kuò)展:增大計(jì)算機(jī)內(nèi)存和磁盤空間(受磁盤大小限制,性能低,但是配置簡(jiǎn)單)
1.數(shù)據(jù)分片存儲(chǔ),數(shù)據(jù)如何分布到不同的實(shí)例?
使用redis cluster 實(shí)現(xiàn)分片集群
引入槽(slot)的概念,使用CRC16算法計(jì)算key hash值,與16384取模,獲得的數(shù)在0~16383的范圍,數(shù)據(jù)就分部在
16384個(gè)槽中;
哈希槽又是如何被映射到具體的 Redis 實(shí)例上的呢?
兩種方式為實(shí)例分配 槽:
cluster create 命令創(chuàng)建集群,Redis 會(huì)自動(dòng)把這些槽平均分布在集群實(shí)例上。例如,如果集群中有 N 個(gè)實(shí)例,那么,每個(gè)實(shí)例上的槽個(gè)數(shù)為 16384/N 個(gè)。
如果每臺(tái)機(jī)器內(nèi)存,性能不一樣,可以手動(dòng)設(shè)置每臺(tái)實(shí)例的槽數(shù); cluster addslots 命令手動(dòng)分配哈希槽。
例如:
redis-cli -h 172.16.19.3 –p 6379 cluster addslots 0,1
redis-cli -h 172.16.19.4 –p 6379 cluster addslots 2,3
redis-cli -h 172.16.19.5 –p 6379 cluster addslots 4
注意: 手動(dòng)分配槽需要把16384個(gè)槽分配完,否則redis集群無法工作
2.客戶端如何定位數(shù)據(jù)?
一般來說,客戶端和集群實(shí)例建立連接后,實(shí)例就會(huì)把哈希槽的分配信息發(fā)給客戶端。但是,在集群剛剛創(chuàng)建的時(shí)候,每個(gè)實(shí)例只知道自己被分配了哪些哈希槽,是不知道其他實(shí)例擁有的哈希槽信息的。
那么,客戶端為什么可以在訪問任何一個(gè)實(shí)例時(shí),都能獲得所有的哈希槽信息呢?
這是因?yàn)?#xff0c;Redis 實(shí)例會(huì)把自己的哈希槽信息發(fā)給和它相連接的其它實(shí)例,來完成哈希槽分配信息的擴(kuò)散。當(dāng)實(shí)例之間相互連接后,每個(gè)實(shí)例就有所有哈希槽的映射關(guān)系了。
客戶端收到哈希槽信息后,會(huì)把哈希槽信息緩存在本地。當(dāng)客戶端請(qǐng)求鍵值對(duì)時(shí),會(huì)先計(jì)算鍵所對(duì)應(yīng)的哈希槽,然后就可以給相應(yīng)的實(shí)例發(fā)送請(qǐng)求了。
3.MOVED,ASK,ASKING
實(shí)例和槽的映射關(guān)系不是一成不變的:
刪除,添加實(shí)例,redis會(huì)從新分配hash槽
為了負(fù)載均衡,redis需要把實(shí)例上的hash槽重新分配一遍
所以,hash槽和實(shí)例映射關(guān)系改變了怎么辦?怎么定位數(shù)據(jù)呢?
redis cluster 提供了 重定向機(jī)制
1.MOVED命令
假如一個(gè)鍵值對(duì) “hello:redis”,分布在實(shí)例A的10001這個(gè)槽,但是增加節(jié)點(diǎn),數(shù)據(jù)重新,10001這個(gè)槽被分配給了實(shí)例B,
且10001槽的數(shù)已完全搬移,但是客戶端無法感知,redis槽分配的內(nèi)部變化,客戶端內(nèi)存里還是原來的 實(shí)例-槽 的映射關(guān)系;
客戶端會(huì)先訪問實(shí)例A(實(shí)例之間槽數(shù)據(jù)共享,實(shí)例A知道10001槽被分配給了實(shí)例B),找不到10001槽,那實(shí)例A就會(huì)返回 “MOVED 10001 實(shí)例B:port” 告訴 客戶端數(shù)據(jù)在實(shí)例B上,那么客戶端就會(huì)訪問實(shí)例B,并更新本地 實(shí)例&槽 的映射關(guān)系,下次再找10001槽就直接去實(shí)例B上找。
2.ASK 命令
如果10001槽數(shù)據(jù)正在搬移到 實(shí)例B上呢?
那么實(shí)例A就會(huì)返回 “ASK 10001 實(shí)例B:port” ,告訴 10001槽在實(shí)例B上,但是10001槽數(shù)據(jù)還未搬移完;
3.ASKING
客戶端收到 ASK命令,就會(huì) 發(fā)送ASKING命令去告知實(shí)例B我要去訪問你的數(shù)據(jù),你給我放行哦!
但是 ,ASK,ASKING命令,客戶端不會(huì)更新本地 實(shí)例&槽 的映射關(guān)系表;
也就是說當(dāng)10001槽還未完成數(shù)據(jù)搬移, 客戶端又去 訪問10001槽,還是會(huì)去實(shí)例A,而不是 實(shí)例B;
?
總結(jié)
以上是生活随笔為你收集整理的redis核心技术与实战(四)高可用高扩展篇的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: shell知识点
- 下一篇: weblogic启动慢