日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

redis核心技术与实战(四)高可用高扩展篇

發(fā)布時(shí)間:2025/3/8 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 redis核心技术与实战(四)高可用高扩展篇 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。