redis核心技術與實戰(四)高可用高擴展篇

1.《redis架構組成》
1.redis學習維度


2.一個基本的鍵值型數據庫包括什么?


1.訪問框架

redis通過網絡框架進行訪問,使得 Redis 可以作為一個基礎性的網絡服務進行訪問,擴大了redis應用范圍;

過程:如果客戶端發送“put hello world ”這條指令會被包裝成一個網絡包,服務端收到后進行解析,執行指令;

問題:請求連接,解析,數據存取這些操作使用單線程還是多線程呢?

如果單線程,其中一個環節出錯或阻塞就會導致整個流程阻塞,降低了系統響應速度;

如果是多線程,那么當有共享資源的時候,會產生線程競爭,也會影響系統效率,這又該怎么辦呢?

注意:Redis 又是如何做到“單線程,高性能”的呢?

2.索引模塊

redis采用哈希表作為索引,因為鍵值數據基本都是保存在內存中的,而內存的高性能隨機訪問特性可以很好地與哈希表 O(1) 的操作復雜度相匹配。

3.存儲模塊

redis提供AOF和RDB持久化數據;

4.高可用集群模塊

redis提供主從架構,哨兵模式

5.高擴展模塊

redis支持分片機制

2.《redis IO多路復用》
redis之所以那么快,除了使用hash表這樣的高效的數據結構,還因為他的IO多路復用模型;

1.redis真的是的單線程嗎?
redis的鍵值操作以及IO處理是單線程的

其他操作時多線程的,例如:持久化,異步刪除,集群數據同步等;

2.redis為什么使用單線程,多線程不行嗎?
多線程情況下,勢必會有共享資源的爭奪,這就需要 引入 同步語句或者鎖來協調多線程之間的運作,一旦引入鎖,同步語句,就會增加redis的復雜性,發生阻塞,影響性能;

3.redis 基于多路復用的IO模型
1.我們先來看看客戶端,服務端IO通信需要哪些操作?
首先服務端監聽客戶端的請求(bind/listen),和客戶端建立連接(accept),接收客戶

端的數據(recv),解析(parse),鍵值操作,然后返回(send)

潛在阻塞點有兩個:

當redis監聽到一個客戶端連接請求(accept),但是一直未能連接成功時,阻塞

當redis接收客戶端發送的請求數據包時,一直未收到數據,阻塞

這就導致 Redis 整個線程阻塞,無法處理其他客戶端請求,效率很低。不過,幸運的是,socket 網絡模型本身支持非阻塞模式。

2.socket 非阻塞模式
socket 網絡模型的非阻塞模式主要體現在三個函數上;

調用socket()函數返回 主動套接字,調用listen() 將主動套接字轉換為 監聽套接字,此時,可以監聽來自客戶端的連接請求。調用accept() 返回已連接套接字。

針對監聽套接字,我們可以設置非阻塞模式:當 Redis 調用 accept() 但一直未有連接請求到達時,Redis 線程可以返回處理其他操作,而不用一直等待。但是,你要注意的是,調用 accept() 時,已經存在監聽套接字了。

雖然 Redis 線程可以不用繼續等待,但是總得有機制繼續在監聽套接字上等待后續連接請求,并在有請求時通知 Redis。

我們也可以針對已連接套接字設置非阻塞模式:Redis 調用 recv() 后,如果已連接套接字上一直沒有數據到達,Redis 線程同樣可以返回處理其他操作。我們也需要有機制繼續監聽該已連接套接字,并在有數據達到時通知 Redis。

到此,Linux 中的 IO 多路復用機制就要登場了。

3.redis 基于多路復用的IO 模型
redis 的多路復用IO模型 基于 select/epoll機制實現的;

select/epoll支持以下兩點:

red單線程運行下,支持linux內核中同時存在多個 “監聽套接字”和“連接套接字”,內核會一直監聽這些套接字,每當有請求到達,就會通知redis,處理
select/epoll機制提供 事件回調函數,不同的事件對應不同的回調函數,時間發生調用對應的回調函數


圖中的多個 FD 就是剛才所說的多個套接字。Redis 網絡框架調用 epoll 機制,讓內核監聽這些套接字。此時,Redis 線程不會阻塞在某一個特定的監聽或已連接套接字上,也就是說,不會阻塞在某一個特定的客戶端請求處理上。

為了在請求到達時能通知到 Redis 線程,select/epoll 提供了基于事件的回調機制,即針對不同事件的發生,調用相應的處理函數。

select/epoll 一旦監測到 FD 上有請求到達時,就會觸發相應的事件。這些事件會被放進一個事件隊列,Redis 單線程對該事件隊列不斷進行處理。這樣一來,Redis 無需一直輪詢是否有請求實際發生,這就可以避免造成 CPU 資源浪費。同時,Redis 在對事件隊列中的事件進行處理時,會調用相應的處理函數,這就實現了基于事件的回調。因為 Redis 一直在對事件隊列進行處理,所以能及時響應客戶端請求,提升 Redis 的響應性能。

例子:

假如兩個請求分別對應 Accept 事件和 Read 事件,Redis 分別對這兩個事件注冊 accept 和 get 回調函數。當 Linux 內核監聽到有連接請求或讀數據請求時,就會觸發 Accept 事件和 Read 事件,此時,內核就會回調 Redis 相應的 accept 和 get 函數進行處理。

4.redis 多路復用IO 模型有沒有什么性能瓶頸?
1.任何一個請求在server中耗時,就會影響整個server的性能,后面所有請求都會阻塞等待

操作bigkey,分配內存空間比較耗時,同樣刪除bigkey時釋放內存空間也比較耗時
使用復雜度過高的命令:例如SORT/SUNION/ZUNIONSTORE,或者時間復雜度O(N),但是N特別大
大量key集中過期:Redis的過期機制也是在主線程中執行的,大量key集中過期會導致處理一個請求時,耗時都在刪除過期key,耗時變長;
**淘汰策略:**淘汰策略也是在主線程執行的,當內存超過Redis內存上限后,每次寫入都需要淘汰一些key,也會造成耗時變長;
**AOF刷盤開啟always機制:**每次寫入都需要把這個操作刷到磁盤,寫磁盤的速度遠比寫內存慢,會拖慢Redis的性能;
主從全量同步生成RDB:雖然采用fork子進程生成數據快照,但fork這一瞬間也是會阻塞整個線程的,實例越大,阻塞時間越久;
解決:一方面需要開發人員手動處理,另一方面 Redis在4.0推出了lazy-free機制,把bigkey釋放內存的耗時操作放在了異步線程中執行,降低對主線程的影響。

2.并發量大時,雖然使用 多路復用IO,單個線程,多個IO,當時redis主線程處理仍然是單線程,順序執行,無法利用計算機多個CPU(內核)

解決:Redis在6.0推出了多線程,可以在高并發場景下利用CPU多核多線程讀寫客戶端數據,進一步提升server性能,當然,只是針對客戶端的讀寫是并行的,每個命令的真正操作依舊是單線程的。

3.《持久化:AOF和RDB》
1.AOF
1.AOF寫日志
AOF 寫日志使用的是 寫后記錄日志

好處:

避免命令錯誤,AOF日志恢復時出現錯誤。redis不會檢查 命令是否正確,所以如果先記日志,后執行命令,會導致記日志成功,執行命令失敗
不會阻塞主線程。因為寫日志是磁盤操作,影響性能,最終導致后面請求耗時
AOF的缺點:

寫日志和命令執行都是在主線程執行,所以記錄日志會阻塞下一個命令操作

寫后 記錄日志 ,宕機會導致數據丟失

*案例:set testkey testvalue,“3”表示當前命令有三個部分,每部分都是由“$+數字”開頭,后面緊跟著具體的命令、鍵或值。這里,“數字”表示這部分中的命令、鍵或值一共有多少字節。例如,“$3 set”表示這部分有 3 個字節,也就是“set”命令。

2. 三種寫回策略
AOF 配置項 appendfsync 的三個可選值。

Always:同步寫回,同步刷盤,寫一次,記錄一次(丟失數據少,影響redis性能)
Everysec:每秒寫回,每秒執行一次刷盤(最多丟失一秒數據)
No:操作系統控制的寫回, 每個命令執行完把日志寫回 AOF文件的內存緩沖區,由操作系統決定什么時候刷盤(速度快,但是會出現數據丟失嚴重的情況)


3.AOF日志文件過大問題
.文件過大帶來的問題

文件系統不支持太大的文件

文件過大導致 命令追加的耗時,影響性能

文件過大, AOF日志回寫的時,由于是逐條命令執行,會導致系統啟動過慢

4.AOF重寫
1.什么是AOF重寫?

AOF重寫時,redis根據當前數據庫的現狀,創建一個新的AOF日志文件,他會獲取redis當前狀態所有數據,原來的AOF文件每個鍵值對可能有多個命令,而重寫機制把多個命令合并成一個命令,記錄該鍵值對的當前狀態;

當我們對一個列表先后做了 6 次修改操作后,列表的最后狀態是[“D”, “C”, “N”],此時,只用 LPUSH u:list “N”, “C”, "D"這一條命令就能實現該數據的恢復,這就節省了五條命令的空間。

2.AOF重寫是異步的嗎?

重寫時,會有由主線程fork(fork會阻塞主線程)出來一個后臺子線程 bgrewriteaof,會把主線程當狀態所有內存復制給后臺子線程bgrewriteaof,子線程去執行AOF重寫。

fork原理

a、fork子進程,fork這個瞬間一定是會阻塞主線程的(注意,fork時并不會一次性拷貝所有內存數據給子進程,老師文章寫的是拷貝所有內存數據給子進程,我個人認為是有歧義的),fork采用操作系統提供的寫實復制(Copy On Write)機制,就是為了避免一次性拷貝大量內存數據給子進程造成的長時間阻塞問題,但**fork子進程需要拷貝進程必要的數據結構,其中有一項就是拷貝內存頁表(虛擬內存和物理內存的映射索引表),這個拷貝過程會消耗大量CPU資源,拷貝完成之前整個進程是會阻塞的,阻塞時間取決于整個實例的內存大小,實例越大,內存頁表越大,fork阻塞時間越久。拷貝內存頁表完成后,子進程與父進程指向相同的內存地址空間,也就是說此時雖然產生了子進程,但是并沒有申請與父進程相同的內存大小。**那什么時候父子進程才會真正內存分離呢?“寫實復制”顧名思義,就是在寫發生時,才真正拷貝內存真正的數據,這個過程中,父進程也可能會產生阻塞的風險,就是下面介紹的場景。

b、fork出的子進程指向與父進程相同的內存地址空間,此時子進程就可以執行AOF重寫,把內存中的所有數據寫入到AOF文件中。但是此時父進程依舊是會有流量寫入的,如果父進程操作的是一個已經存在的key,那么這個時候父進程就會真正拷貝這個key對應的內存數據,申請新的內存空間,這樣逐漸地,父子進程內存數據開始分離,父子進程逐漸擁有各自獨立的內存空間。因為內存分配是以頁為單位進行分配的,默認4k,如果父進程此時操作的是一個bigkey,重新申請大塊內存耗時會變長,可能會產阻塞風險。另外,如果操作系統開啟了內存大頁機制(Huge Page,頁面大小2M),那么父進程申請內存時阻塞的概率將會大大提高,所以在Redis機器上需要關閉Huge Page機制。Redis每次fork生成RDB或AOF重寫完成后,都可以在Redis log中看到父進程重新申請了多大的內存空間。

重寫的過程總結為**“一個拷貝,兩處日志”**。

一個拷貝:主線程的內存數據拷貝給子線程bgrewriteaof,子線程對數據進行分析,重寫日志

兩處日志:

主線程操作的AOF日志,如果重寫期間有新的寫入操作,寫入操作放入 AOF日志緩沖區
AOF重寫日志 ,重寫期間新的寫入操作,也會放入AOF重寫日志緩沖區(等bgrewriteaof重寫日志完成,就把AOF重寫日志緩沖區的數據重寫 形成新的AOF日志文件,這時候就可以代替舊的文件了)


每次 AOF 重寫時,Redis 會先執行一個內存拷貝,用于重寫;然后,使用兩個日志保證在重寫過程中,新寫入的數據不會丟失。而且,因為 Redis 采用額外的線程進行數據重寫,所以,這個過程并不會阻塞主線程。

5.問題
1.AOF 日志重寫的時候,是由 bgrewriteaof 子進程來完成的,不用主線程參與,我們今天說的非阻塞也是指子進程的執行不阻塞主線程。但是,你覺得,這個重寫過程有沒有其他潛在的阻塞風險呢?如果有的話,會在哪里阻塞?

fork進程會阻塞

copy-onwrite機制,當父進程有數據寫入已存在的key,且為bigkey,申請新的內存空間時父進程會阻塞

2.AOF 重寫也有一個重寫日志,為什么它不共享使用 AOF 本身的日志呢?

父子進程共享一個日志文件,出現資源爭奪的情況,阻塞父進程

如果重寫失敗呢?那么AOF文件會被污染,無法進行宕機后,數據回寫;新建一個重寫日志文件,重寫失敗,直接刪除就好了;

2.RDB(既可以保證可靠性,還能在宕機時實現快速恢復)
1.redis生成RDB快照的兩個命令
save:阻塞主線程

bgsave: 創建一個子線程,專門生成RDB文件,避免主線程阻塞,RDB默認使用方式

2.redis處理RDB快照,主線程怎么進行寫操作
進行 全量快照 時,主進程會fork出來一個子進程,會copy內存數據的內存頁表地址,copy完直到父子進程就有共享同一個內存數據的內存地址;

在子進程寫RDB的時候,主進程正常讀,寫的時候是使用操作系統 Copy-On-Write(寫時復制)機制,會申請新的內存空間存放訪問的key鍵值對數據,bgsave 子進程會把這個副本數據寫入 RDB 文件,主線程仍然寫原來的數據;

3.增量快照
我們可以頻繁的執行全量快照嗎?

注意:如果頻繁地執行全量快照,也會帶來兩方面的開銷。

**頻繁將全量數據寫入磁盤,會給磁盤帶來很大壓力,**多個快照競爭有限的磁盤帶寬,前一個快照還沒有做完,后一個又開始做了,容易造成惡性循環。
bgsave 子進程需要通過 fork 操作從主線程創建出來。雖然,子進程在創建后不會再阻塞主線程,但是,**fork 這個創建過程本身會阻塞主線程,而且主線程的內存越大,阻塞時間越長。**如果頻繁 fork 出 bgsave 子進程,這就會頻繁阻塞主線程了
當我們第一次做完全量快照之后,后面T1,T2時刻做快照時只需要把修改的數據寫入快照文件就行了。

所以,我們需要申請額外的內存空間來存儲 元數據信息;

雖然跟 AOF 相比,快照的恢復速度快,但是,快照的頻率不好把握,如果頻率太低,兩次快照間一旦宕機,就可能有比較多的數據丟失。如果頻率太高,又會產生額外開銷

4.混合使用 AOF 日志和內存快照
redis4.0中提出混合使用 AOF 日志和內存快照的方案。

內存快照以一定的頻率執行,在兩次快照之間,使用 AOF 日志記錄這期間的所有命令操作。

好處:

避免頻繁fork對主進程的影響
主進程寫入時不會有額外的內存空間開銷
AOF日志文件也不會過大
如下圖所示,T1 和 T2 時刻的修改,用 AOF 日志記錄,等到第二次做全量快照時,就可以清空 AOF 日志,因為此時的修改都已經記錄到快照中了,恢復時就不再用日志了。

3.持久化方案總結
數據不能丟失時,內存快照RDB和 AOF 的混合使用是一個很好的選擇;
如果允許分鐘級別的數據丟失,可以只使用 RDB;
如果只用 AOF,優先使用 everysec 的配置選項,因為它在可靠性和性能之間取了一個平衡;
4.《主從數據同步》
1.讀寫分離
主庫寫數據,并同步給從庫

主從庫均可讀數據

為什么讀寫分離,寫數據可以寫在從庫上嗎?

不可以,寫在所有庫上,如果某一個寫操作失敗了呢?是不是意味著主從庫數據不一致了呢?

這時候我們為了數據一致,就要協調主從庫寫操作要么都完成,有一個不完成就回滾,這勢必會用到鎖,以及實例之間協商是否均完成寫操作,這樣會影響redis性能;

主從庫模式,主庫有數據,會同步給從庫,這樣組從庫數據就是一致的。

2.主從同步
1.第一次同步
redis實例啟動時可以使用 replacof ip (Redis 5.0 之前使用 slaveof)指令當前實例是哪個主庫的從庫

現在有實例 1(ip:172.16.19.3)和實例 2(ip:172.16.19.5)

在實例2上執行 replicaof 172.16.19.3,實例2就變成了實例1的從庫,并開始從實例 1 上復制數據

主從第一次同步時

第一階段:從庫會發送 給主庫 fsync ? -1 命令給主庫告訴它,我這是第一次同步,我要求全量同步,fsync 命令 有兩個參數

主庫runID(唯一隨機ID),由于第一次同步,所以從庫不知道主庫runID,傳“?”,

從庫偏移量offset,由于第一次同步傳 -1

第二階段: 主庫返回 “FULLRESYNC 主庫runID master_repl_offset(repl_backlog_buffer中主庫偏移量)” ,由于是全量同步,從庫會記錄master_repl_offset,作為自己下一次增量同步的slave_repl_offset ;

而且主庫會生成 RDB快照(執行bgsave命令), 通過網絡發送給 從庫,從庫拿到RDB 快照刪除從庫本地數據,讀快照,同步數據

第三階段: 在主庫將數據同步給從庫的過程中,主庫不會被阻塞,仍然可以正常接收請求,這些寫請求會在replication buffer,和repl_backlog_buffer寫一份,當主庫完成 RDB 文件發送后,就會把此時 replication buffer 中的修改操作發給從庫,從庫再重新執行這些操作

2.主從級聯模式分擔全量復制時的主庫壓力
對于主庫來說,全量同步還有兩個地方壓力:

生成RDB文件 和 傳輸RDB文件

當從庫數量比較多,進行全量復制,就會多次fork子進程,生成RDB文件,會阻塞主進程

所以,我們可以選舉處理出來一個從庫A (一般選內存大,網絡好的從庫),讓其他家從庫同步從庫A的數據,

就可以給主庫減輕壓力

一旦主從庫完成了全量復制,它們之間就會一直維護一個網絡連接,主庫會通過這個連接將后續陸續收到的命令操作再同步給從庫,這個過程也稱為基于長連接的命令傳播,可以避免頻繁建立連接的開銷。

基于長連接的命令傳播 直接走 replication_buffer

3.主從網絡連接斷了怎么辦
斷了會使用增量復制

repl_backlog_buffer相當于主線程寫操作的一個備份,就是為了防止從庫斷連接,在恢復時,無法恢復連接斷開到恢復的數據

replication buffer:主要用來與客戶端進行數據傳輸,它是傳輸通道,有幾個客戶端就有幾個replication buffer;

無論全量復制、基于長連接的命令傳播,以及增量復制(傳輸偏移量offset數據),數據傳輸都會用到replication buffer。

repl_backlog_buffer,只要有從庫存在,這個repl_backlog_buffer就會存在。主庫的所有寫命令除了傳播給從庫之外,都會在這個repl_backlog_buffer中記錄一份,緩存起來,只有預先緩存了這些命令,當從庫斷連后,從庫重新發送psync $master_runid o f f s e t , 主 庫 才 能 通 過 offset,主庫才能通過offset,主庫才能通過offset在repl_backlog_buffer中找到從庫斷開的位置,只發送$offset之后的增量數據給從庫即可。

1、repl_backlog_buffer:它是為了從庫斷開之后,如何找到主從差異數據而設計的環形緩沖區,從而避免全量同步帶來的性能開銷。如果從庫斷開時間太久,repl_backlog_buffer環形緩沖區被主庫的寫命令覆蓋了,那么從庫連上主庫后只能乖乖地進行一次全量同步,所以repl_backlog_buffer配置盡量大一些,可以降低主從斷開后全量同步的概率。而在repl_backlog_buffer中找主從差異的數據后,如何發給從庫呢?這就用到了replication buffer。

2、replication buffer:Redis和客戶端通信也好,和從庫通信也好,Redis都需要給分配一個 內存buffer進行數據交互,客戶端是一個client,從庫也是一個client,我們每個client連上Redis后,Redis都會分配一個client buffer,所有數據交互都是通過這個buffer進行的:Redis先把數據寫到這個buffer中,然后再把buffer中的數據發到client socket中再通過網絡發送出去,這樣就完成了數據交互。所以主從在增量同步時,從庫作為一個client,也會分配一個buffer,只不過這個buffer專門用來傳播用戶的寫命令到從庫,保證主從數據一致,我們通常把它叫做replication buffer。如果主從連接斷開,那么replication_buffer就不存在了。

3、既然有這個內存buffer存在,那么這個buffer有沒有限制呢?如果主從在傳播命令時,因為某些原因從庫處理得非常慢,那么主庫上的這個buffer就會持續增長,消耗大量的內存資源,甚至OOM。所以Redis提供了client-output-buffer-limit參數限制這個buffer的大小,如果超過限制,主庫會強制斷開這個client的連接,也就是說從庫處理慢導致主庫內存buffer的積壓達到限制后,主庫會強制斷開從庫的連接,此時主從復制會中斷,中斷后如果從庫再次發起復制請求,那么此時可能會導致惡性循環,引發復制風暴,這種情況需要格外注意。

repl_backlog_buffer環形緩沖區在主庫中只有一個,每個replication_buffer對應一個客戶端(redis client或者從庫)

4.總結
Redis 的主從庫同步的基本原理,總結來說,有三種模式:全量復制、基于長連接的命令傳播,以及增量復制。

1. 第一次復制時 全量復制

2. 全量復制完成之后會建立長連接,進行數據同步

3. 如果連接斷了,等連接恢復后就進行增量復制

5.《哨兵集群》
1.哨兵機制流程
主從機制下主庫掛了存在的問題?

主庫是否真的下線了?
選主機制,是怎么進行的?
怎么把主庫信息通知給從庫和客戶端呢
什么是哨兵呢?

哨兵就是一個運行在特殊模式下的redis服務,主從實例在運行的同時,他們也在運行;

哨兵機制主要有三個作用:監視,選主,通知

監視:哨兵集群會每個一段時間ping 主從庫, 如果主從庫沒有響應ping命令,那么就判定為“主觀下線”

選主:如果主庫被 “N/2+1” 個認為哨兵判定為“主觀下線”,那么主庫就被認定為 “客觀下線”,這時候就要進行選主機制;

通知:選主成功后,需要把新主庫信息,通知給客戶端和從庫,并讓從庫執行 replicaof 命令,與新主庫進行數據同步

2.選主機制
1.選主之前我們來先看一下什么情況下會被判定為“客觀下線”?

哨兵集群內當有 N/2+1 個哨兵節點 認為 “主觀下線”就認為主庫 “客觀下線”;

對于從庫,有一個哨兵判定為“主觀下線”,那么就下線了,因為,從庫掛了對整個redis集群來說沒有太大影響;

2.怎樣進行選主的?

先篩選,后打分

先按照 一定的條件篩選, 后 按一定條件 打分;

a.篩選:過濾掉下線的 從庫,然后,過濾網絡連接不好的從庫?

那么怎么判定網絡連接不好呢?

根據 down_after_milliseconds*10這個配置項,down_after_milliseconds為 網絡連接最大超時時間,如果,從庫連接超時超過這個時間就被認定為 斷連,如果斷連超過10次,就判定,該從庫網絡狀態不穩定,不參與主庫選舉;

b.打分,根據 優先級->從庫同步進度->從庫ID號 進行三輪打分

第一輪:優先級最高的為主庫(我們可以設置內存大,網絡帶寬高的從庫 為高優先級),相同進行第二輪

第二輪:數據同步進度最快的為主庫(每個從庫都有一個slave_repl_offset值,越大代表同步的越快)相同進行第三輪

第三輪:從庫ID號小的為主庫

3.主從切換的過程中是否能夠正常處理請求呢?
讀寫分離的情況下,讀請求不影響個,寫操作會出現異常;

寫失敗的持續時間=主從切換的時間+客戶端感知到主庫的時間

主從切換的時間我們可以通過 down_after_milliseconds(連接最大超時時間)設置,down_after_milliseconds值越小,哨兵集群就越敏感,down_after_milliseconds設置過小,可能會由于主庫壓力過大,或網絡不好導致發生不必要的主從切換,但是當主庫真正的故障時,down_after_milliseconds越小,對業務的影響也就越小;

當主從切換有寫操作到達時,我們可以把 寫操作放入客戶端緩存或使用消息中間件緩存,主從切換完成,客戶端感知到主庫時,再讀入緩存中寫操作;注意,如果down_after_milliseconds過大,主從切換時間過長,會導致消息對列有大量寫請求;

4.哨兵集群
配置哨兵信息 只需要設置主庫的 IP 和端口

sentinel monitor

哨兵并沒有 將自己的連接信息發送給主庫,也就意味 哨兵之間是互相不知道地址的,那么哨兵是如何通信的呢?

問題:

哨兵之間是如何通信的?
哨兵和從庫之間是如何通信的?
哨兵是如何把 主從切換后的新主庫信息發送給客戶端的?哨兵和客戶端之間是如何通信的?
5.PUB/SUB模式解決哨兵,主從庫,客戶端之間的通信


1.哨兵之間是如何通信的?

通過發布/訂閱模式,每個哨兵發布自己的連接配置信息給 主庫上的 “sentinel:hello”頻道,并訂閱這個頻道,這樣就可以獲取其他哨兵節點的信息啦;

2.哨兵和從庫之間是如何通信的?

哨兵會發送INFO命令給主庫,主庫就會返回 “從庫 信息列表” 給哨兵節點,這樣哨兵節點就可以和從庫之間建立長連接,保持心跳了

3.哨兵和客戶端之間是如何通信的?客戶端通過監控了解哨兵進行主從切換的過程呢?

不但主庫提供發布訂閱機制,哨兵節點也提供PUB/SUB機制,哨兵提供的消息訂閱頻道有很多,不同頻道包含了主從庫切換過程中的不同關鍵事件。

客戶端同過 “SUBSCRIBE 頻道 ” 監控主從庫的裝態了

比如:想監聽主從切換完成的狀態: SUBSCRIBE +switch-master

那么知道了這些頻道,客戶端就可以根據自己的需要訂閱這些頻道了,就能實時監控主從切換,甚至主從庫的狀態了

6.由哪個哨兵節點來執行主從切換呢?
使用了Raft的公式算法

任何一個哨兵節點判斷主庫“主觀下線”后,都會發送 is-matser-down-by-addr命令給其他哨兵節點;

其他哨兵節點會根據自己與主庫的連接狀態返回對應的結果,連接完好返回不贊成N,斷連返回Y

當某個哨兵節點拿到的贊成票>= quorum 的值,就判定主庫“客觀下線”

當一個哨兵節點判斷一個 主庫“客觀下線”,就選舉自己為Leader ,完成主從切換操作;

任何一個想成為 Leader 的哨兵,要滿足兩個條件:

第一,拿到半數以上的贊成票;

第二,拿到的票數同時還需要大于等于哨兵配置文件中的 quorum 值。

以 3 個哨兵為例,假設此時的 quorum 設置為 2,那么,任何一個想成為 Leader 的哨兵只要拿到 2 張贊成票,就可以了。

在 T1 時刻,S1 判斷主庫為“客觀下線”,它想成為 Leader,就先給自己投一張贊成票,然后分別向 S2 和 S3 發送命令,表示要成為 Leader。

在 T2 時刻,S3 判斷主庫為“客觀下線”,它也想成為 Leader,所以也先給自己投一張贊成票,再分別向 S1 和 S2 發送命令,表示要成為 Leader。

在 T3 時刻,S1 收到了 S3 的 Leader 投票請求。因為 S1 已經給自己投了一票 Y,所以它不能再給其他哨兵投贊成票了,所以 S1 回復 N 表示不同意。同時,S2 收到了 T2 時 S3 發送的 Leader 投票請求。因為 S2 之前沒有投過票,它會給第一個向它發送投票請求的哨兵回復 Y,給后續再發送投票請求的哨兵回復 N,所以,在 T3 時,S2 回復 S3,同意 S3 成為 Leader。

在 T4 時刻,S2 才收到 T1 時 S1 發送的投票命令。因為 S2 已經在 T3 時同意了 S3 的投票請求,此時,S2 給 S1 回復 N,表示不同意 S1 成為 Leader。發生這種情況,是因為 S3 和 S2 之間的網絡傳輸正常,而 S1 和 S2 之間的網絡傳輸可能正好擁塞了,導致投票請求傳輸慢了。

最后,在 T5 時刻,S1 得到的票數是來自它自己的一票 Y 和來自 S2 的一票 N。而 S3 除了自己的贊成票 Y 以外,還收到了來自 S2 的一票 Y。此時,S3 不僅獲得了半數以上的 Leader 贊成票,也達到預設的 quorum 值(quorum 為 2),所以它最終成為了 Leader。

如果這一輪沒有產生Leader 就會進行下一輪投票

?

要保證所有哨兵實例的配置是一致的,尤其是主觀下線的判斷值 down-after-milliseconds。

1,哨兵投票機制:
a:哨兵實例只有在自己判定主庫下線時,才會給自己投票,而其他的哨兵實例會把票投給第一個來要票的請求,其后的都拒絕
b:如果出現多個哨兵同時發現主庫下線并給自己投票,導致投票選舉失敗,就會觸發新一輪投票,直至成功

2,哨兵Leader切換主從庫的機制:
哨兵成為Leader的必要條件:a:獲得半數以上的票數,b:得到的票數要達到配置的quorum閥值

主從切換只能由Leader執行,而成為Leader有兩個必要的條件,所以當哨兵集群中實例異常過多時,會導致主從庫無法切換

6.《redis主從同步與故障切換的坑》
1.主從數據不一致
redis主庫同步時,是異步傳輸命令,不會等待命令傳輸成功,有以下兩種情況導致redis主從同步數據不一致

redis主庫有網絡延遲,導致從庫沒有接收到同步命令,此時剛好訪問到改數據,就會返回舊數據
從庫及時收到了主庫的命令,但是,也可能會因為正在處理其它復雜度高的命令(例如集合操作命令)而阻塞。
解決方案:

針對第一種情況,主從在同一個局域網內,并保證網絡通暢
第二種情況,我們還可以開發一個外部程序來監控主從庫間的復制進度
Redis 的 INFO replication 命令可以查看主庫接收寫命令的進度信息(master_repl_offset)和從庫復制寫命令的進度信息(slave_repl_offset)。

我們就可以開發一個監控程序,先用 INFO replication 命令查到主、從庫的進度,然后,我們用 master_repl_offset 減去 slave_repl_offset,這樣就能得到從庫和主庫間的復制進度差值了。

如果某個從庫的進度差值大于我們預設的閾值,我們可以讓客戶端不再和這個從庫連接進行數據讀取,這樣就可以減少讀到不一致數據的情況。不過,為了避免出現客戶端和所有從庫都不能連接的情況,我們需要把復制進度差值的閾值設置得大一些。

2.過期數據
1.reids過期策略
定期淘汰:Redis 每隔一段時間(默認 100ms),就會隨機選出一定數量的數據,檢查它們是否過期,并把其中過期的數據刪除

惰性過期:讀到過期數據刪除

但是,這兩種策略都不能保證不會讀到過期數據;

對于惰性過期,Redis 3.2 之前的版本,從庫在服務讀請求時,并不會判斷數據是否過期,直接返回過期數據;

Redis 3.2之后判斷是否過期,過期返回null

2.EXPIRE,PEXPIRE,EXPIREAT和PEXPIREAT


當主從庫全量同步時,如果主庫接收到了一條 EXPIRE 命令,那么,主庫會直接執行這條命令。這條命令會在全量同步完成后,發給從庫執行。而從庫在執行時,就會在當前時間的基礎上加上數據的存活時間,這樣一來,從庫上數據的過期時間就會比主庫上延后了。

所以在業務應用中使用 EXPIREAT/PEXPIREAT 命令,把數據的過期時間設置為具體的時間點,避免讀到過期數據。

3.不合理配置項導致的服務掛掉(protected-mode 和 cluster-node-timeout。)
1.protected-mode
這個配置項的作用是限定哨兵實例能否被其他服務器訪問,yes時,其余哨兵實例部署在其它服務器,那么,這些哨兵實例間就無法通信。當主庫故障時,哨兵無法判斷主庫下線,也無法進行主從切換,最終 Redis 服務不可用。

protected-mode no
bind 192.168.10.3 192.168.10.4 192.168.10.5
1
2
2.cluster-node-timeout
當我們在 Redis Cluster 集群中為每個實例配置了“一主一從”模式時,如果主實例發生故障,從實例會切換為主實例,受網絡延遲和切換操作執行的影響,切換時間可能較長,就會導致實例的心跳超時(超出 cluster-node-timeout)。實例超時后,就會被 Redis Cluster 判斷為異常。而 Redis Cluster 正常運行的條件就是,有半數以上的實例都能正常運行。

所以,如果執行主從切換的實例超過半數,而主從切換時間又過長的話,就可能有半數以上的實例心跳超時,從而可能導致整個集群掛掉。所以,我建議你將 cluster-node-timeout 調大些(例如 10 到 20 秒)。

4.總結


Redis 中的 **slave-serve-stale-data 配置項設置了從庫能否處理數據讀寫命令,**你可以把它設置為 no,這樣一來,從庫只能服務 INFO、SLAVEOF 命令,這就可以避免在從庫中讀到不一致的數據了。

slave-read-only 是設置從庫能否處理寫命令,slave-read-only 設置為 yes 時,從庫只能處理讀請求,無法處理寫請求

7.《腦裂現象》
1.什么是腦裂?
所謂的腦裂,就是指在主從集群中,同時有兩個主節點,它們都能接收寫請求,腦裂一般發生在 主從切換的某一個環節。

一次數據丟失的排查?

第一步: 我們根據可以監控(INFO replication 可以獲可取master_repl_offset和slave_repl_offset) 主庫的寫進度 master_repl_offset和 從庫復制進度 slave_repl_offset,比較兩者差值,如果差值過大,再看從庫未復制的數據是否為丟失的數據; 如果master_repl_offset,slave_repl_offset一致,表明不是主從同步導致數據丟失,執行第二步;

第二步:查看redis客戶端,在主從切換后的一段時間內,有一個客戶端仍然在和原主庫通信,并沒有和升級的新主庫進行交互。這就相當于主從集群中同時有了兩個主庫。

得出結論:出現腦裂現象;

2.腦裂發生的原因及腦裂為什么會導致數據丟失呢?
a.腦裂發生的原因
主庫阻塞或 cpu資源被其他進程占用,獲取不到cpu資源

主庫執行耗資源易阻塞操作或發生內存 swap,無法及時響應客戶端,響應哨兵心跳,例如:bigkey,keys等操作
主庫所在機器cpu被其他 資源(比較消耗cpu的程序)占用,導致主庫進入 “假死” 狀態


b.腦裂為什么會導致數據丟失?


假如說,響應哨兵心跳的最大延時時間down-after-milliseconds 為12s, 主庫由于cpu資源受限或 執行bigkey操作阻塞13無法響應請求,主從切換要3s,那么在第12s的時刻主庫 就會被判定為 “客觀下線”,第13s主庫恢復正常,那么客戶端就能夠對主庫進行 2s的寫操作,這時 主從切換還沒完成;

主從切換完成后,原主庫降級為 從庫,會清空自己的數據,讀取 新主庫的全量復制RDB文件,那么 “ 從切換還沒完成的2s內的寫數據” 就會丟失

3.如何避免腦裂問題?
主要用到兩個配置項:min-slaves-to-write 和 min-slaves-max-lag

min-slaves-to-write : 主從同步時,主庫同步的最少從庫數量

min-slaves-max-lag: 主從同步數據復制時,主庫給從庫發送ACK最大延時時間(以秒為單位)

例子:

假設我們將 min-slaves-to-write 設置為 1,把 min-slaves-max-lag 設置為 12s,把哨兵的 down-after-milliseconds 設置為 10s,主庫因為某些原因卡住了 15s,導致哨兵判斷主庫客觀下線,開始進行主從切換。同時,因為原主庫卡住了 15s,沒有一個從庫能和原主庫在 12s 內進行數據復制,原主庫也無法接收客戶端請求了。這樣一來,主從切換完成后,也只有新主庫能接收請求,不會發生腦裂,也就不會發生數據丟失的問題了。

建議:

假設從庫有 K 個,可以將 min-slaves-to-write 設置為 K/2+1(如果 K 等于 1,就設為 1),將 min-slaves-max-lag 設置為十幾秒(例如 10~20s),在這個配置下,如果有一半以上的從庫和主庫進行的 ACK 消息延遲超過十幾秒,我們就禁止主庫接收客戶端寫請求。

8.《切片集群》
數據分片存儲,數據如何分布到不同的實例?
客戶端如何知道數據在哪個實例?
案例:Redis 保存 5000 萬個鍵值對,每個鍵值對大約是 512B,大概有25GB數據

橫向擴展:橫向增加當前 Redis 實例的個數(不受硬件限制,大小幾乎無限,不影響性能,但是復雜度增加)

縱向擴展:增大計算機內存和磁盤空間(受磁盤大小限制,性能低,但是配置簡單)

1.數據分片存儲,數據如何分布到不同的實例?
使用redis cluster 實現分片集群

引入槽(slot)的概念,使用CRC16算法計算key hash值,與16384取模,獲得的數在0~16383的范圍,數據就分部在

16384個槽中;

哈希槽又是如何被映射到具體的 Redis 實例上的呢?

兩種方式為實例分配 槽:

cluster create 命令創建集群,Redis 會自動把這些槽平均分布在集群實例上。例如,如果集群中有 N 個實例,那么,每個實例上的槽個數為 16384/N 個。

如果每臺機器內存,性能不一樣,可以手動設置每臺實例的槽數; cluster addslots 命令手動分配哈希槽。

例如:

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

注意: 手動分配槽需要把16384個槽分配完,否則redis集群無法工作

2.客戶端如何定位數據?
一般來說,客戶端和集群實例建立連接后,實例就會把哈希槽的分配信息發給客戶端。但是,在集群剛剛創建的時候,每個實例只知道自己被分配了哪些哈希槽,是不知道其他實例擁有的哈希槽信息的。

那么,客戶端為什么可以在訪問任何一個實例時,都能獲得所有的哈希槽信息呢?

這是因為,Redis 實例會把自己的哈希槽信息發給和它相連接的其它實例,來完成哈希槽分配信息的擴散。當實例之間相互連接后,每個實例就有所有哈希槽的映射關系了。

客戶端收到哈希槽信息后,會把哈希槽信息緩存在本地。當客戶端請求鍵值對時,會先計算鍵所對應的哈希槽,然后就可以給相應的實例發送請求了。

3.MOVED,ASK,ASKING
實例和槽的映射關系不是一成不變的:

刪除,添加實例,redis會從新分配hash槽
為了負載均衡,redis需要把實例上的hash槽重新分配一遍
所以,hash槽和實例映射關系改變了怎么辦?怎么定位數據呢?

redis cluster 提供了 重定向機制

1.MOVED命令

假如一個鍵值對 “hello:redis”,分布在實例A的10001這個槽,但是增加節點,數據重新,10001這個槽被分配給了實例B,

且10001槽的數已完全搬移,但是客戶端無法感知,redis槽分配的內部變化,客戶端內存里還是原來的 實例-槽 的映射關系;

客戶端會先訪問實例A(實例之間槽數據共享,實例A知道10001槽被分配給了實例B),找不到10001槽,那實例A就會返回 “MOVED 10001 實例B:port” 告訴 客戶端數據在實例B上,那么客戶端就會訪問實例B,并更新本地 實例&槽 的映射關系,下次再找10001槽就直接去實例B上找。

2.ASK 命令

如果10001槽數據正在搬移到 實例B上呢?

那么實例A就會返回 “ASK 10001 實例B:port” ,告訴 10001槽在實例B上,但是10001槽數據還未搬移完;

3.ASKING

客戶端收到 ASK命令,就會 發送ASKING命令去告知實例B我要去訪問你的數據,你給我放行哦!

但是 ,ASK,ASKING命令,客戶端不會更新本地 實例&槽 的映射關系表;

也就是說當10001槽還未完成數據搬移, 客戶端又去 訪問10001槽,還是會去實例A,而不是 實例B;
?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/535358.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/535358.shtml
英文地址,請注明出處:http://en.pswp.cn/news/535358.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

tomcat監控腳本

#!/bin/sh# func:自動監控tomcat腳本并且執行重啟操作# 獲取tomcat進程ID(其中[grep -w .....]中的.....需要替換為實際部署的tomcat文件夾名,如下) TomcatID$(ps -ef |grep tomcat |grep -w /usr/local/tomcat/apache-tomcat-8.5.31|grep -v…

weblogic命令行操作

啟動和停止子節點: [rootoud bin]# cd /sotware/oracle_ldap/Middleware/user_projects/domains/base_domain/bin/ [rootoud bin]# ./startManagedWebLogic.sh Server-0 http://192.168.63.129:7001 -Dweblogic.management.usernameweblogic -Dweblogic.management…

Ansible系列--Copy模塊

copy模塊 copy模塊在ansible里的角色就是把ansible執行機器上的文件拷貝到遠程節點上。 與fetch模塊相反的操作 常用參數 參數名是否必須默認值選項說明srcno 用于定位ansible執行的機器上的文件,需要絕對路徑。如果拷貝的是文件夾,那么文件夾會整體…

ANSIBLE--handlers的概念

handlers可以理解成另一種tasks,handlers是另一種’任務列表’,handlers中的任務會被tasks中的任務進行”調用”,但是,被”調用”并不意味著一定會執行,只有當tasks中的任務”真正執行”以后(真正的進行實際…

ansible--- tags

tags可以幫助我們對任務進行’打標簽’的操作,當任務存在標簽以后,我們就可以在執行playbook時,借助標簽,指定執行哪些任務,或者指定不執行哪些任務。在實際的使用中,我們應該讓tags的值能夠見名知義。 當…

ANSIBLE---變量

注冊變量 ansible的模塊在運行之后,其實都會返回一些”返回值”,只是默認情況下,這些”返回值”并不會顯示而已,我們可以把這些返回值寫入到某個變量中,這樣我們就能夠通過引用對應的變量從而獲取到這些返回值了&…

inux中限制用戶進程CPU和內存占用率

#!/bin/sh PIDStop -bn 1 | grep "^ *[1-9]" | awk { if($9 > 50 || $10 > 25 && id -u $2 > 500) print $1} echo $PIDS for PID in $PIDS dorenice 10 $PIDecho "renice 10 $PID" done

按月拆分數據庫表--oracle

生產有一張日志表,數據量很大,需要按月進行存儲,存儲過程如下: CREATE OR REPLACE PROCEDURE NEWLOG4_SUB_TABLE IStable_name1 VARCHAR2(50);create_table_sql VARCHAR2(4000);insert_data_sql VARC…

plsql定時器

Oralce中的任務有2種:Job和Dbms_job,兩者的區別有: 1. jobs是oracle數據庫的對象, dbms_jobs只是jobs對象的一個實例, 就像對于tables, emp和dept都是表的實例。 2. 創建方式也有…

PL/SQL批處理語句:BULK COLLECT 和 FORALL

PL/SQL程序中運行SQL語句是存在開銷的,因為SQL語句是要提交給SQL引擎處理,這種在PL/SQL引擎和SQL引擎之間的控制轉移叫做上下文卻換,每次卻換時,都有額外的開銷 請看下圖: 但是,FORALL和BULK COLLEC…

oracle 中DATETIME與TIMESTAMP區別

1.DATETIME的日期范圍是1001——9999年,TIMESTAMP的時間范圍是1970——2038年。 2.DATETIME存儲時間與時區無關,TIMESTAMP存儲時間與時區有關,顯示的值也依賴于時區。在mysql服務器,操作系統以及客戶端連接都有時區的設置。 3.DAT…

PARALLEL(并行)

在Oracle中,PARALLEL(并行)方式最大化調用計算機資源來成倍提高數據分析效率。 1. 用途 強行啟用并行度來執行當前SQL。這個在Oracle 9i之后的版本可以使用,之前的版本現在沒有環境進行測試。也就是說,加…

Oracle數據庫查詢優化

1.對查詢進行優化,應盡量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。 2.應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如: select id from t w…

redis-full-check

https://github.com/alibaba/RedisFullCheck/releases redis-full-check是阿里云Redis&MongoDB團隊開源的用于校驗2個redis數據是否一致的工具。 ??redis-full-check通過全量對比源端和目的端的redis中的數據的方式來進行數據校驗,其比較方式通過多輪次比較&a…

2021-06-22

服務器信息 [rootiZs7z01dz0z12dyttz9zn5Z cluster]# /app/redis/redis-3.2.1/src/redis-cli -c -h 10.252.120.9 -p 8003 10.252.120.9:8003> cluster nodes b1f543d646c5c97a70b0635439a44a72f8a143b1 10.252.120.10:8004 master - 0 1624349601417 7 connected 0-5460 1…

Docker目錄掛載

Docker容器啟動的時候,如果要掛載宿主機的一個目錄,可以用-v參數指定。 譬如我要啟動一個centos容器,宿主機的/test目錄掛載到容器的/soft目錄,可通過以下方式指定: # docker run -it -v /test:/soft centos /bin/ba…

Redis主從復制原理學習

Redis主從復制原理學習總結 - 運維筆記 和Mysql主從復制的原因一樣,Redis雖然讀取寫入的速度都特別快,但是也會產生讀壓力特別大的情況。為了分擔讀壓力,Redis支持主從復制,Redis的主從結構可以采用一主多從或者級聯結構&#xff…

redis數據恢復

公司線上一個項目數據存儲采用MySQL,共分為10個庫,分布在4臺機器上,每個庫數據量約為10G,各機器均采用RAID5加速磁盤訪問; 當同時在線人數達高峰期(10w),DB磁盤IO壓力巨大&#xff0…

Redis哨兵模式(sentinel)學習總結及部署記錄(主從復制、讀寫分離、主從切換)

Redis的集群方案大致有三種:1)redis cluster集群方案;2)master/slave主從方案;3)哨兵模式來進行主從替換以及故障恢復。 一、sentinel哨兵模式介紹 Sentinel(哨兵)是用于監控redis集群中Master狀態的工具&…

Redis之Redis內存模型

Redis是目前最火爆的內存數據庫之一,通過在內存中讀寫數據,大大提高了讀寫速度,可以說Redis是實現網站高并發不可或缺的一部分。 我們使用Redis時,會接觸Redis的5種對象類型(字符串、哈希、列表、集合、有序集合&…