前言
今天跟小伙伴們一起學習Redis的主從、哨兵、Redis Cluster集群。
Redis主從
Redis哨兵
Redis Cluster集群
基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://github.com/YunaiV/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
1. Redis 主從
面試官經常會問到Redis的高可用。Redis高可用回答包括兩個層面,一個就是數據不能丟失,或者說盡量減少丟失 ;另外一個就是保證Redis服務不中斷 。
對于盡量減少數據丟失,可以通過AOF和RDB保證。
對于保證服務不中斷的話,Redis就不能單點部署,這時候我們先看下Redis主從。
1.1 Redsi主從概念
Redis主從模式,就是部署多臺Redis服務器,有主庫和從庫,它們之間通過主從復制,以保證數據副本的一致。
主從庫之間采用的是讀寫分離 的方式,其中主庫負責讀操作和寫操作,從庫則負責讀操作。
如果Redis主庫掛了,切換其中的從庫成為主庫。
1.2 Redis 主從同步過程
Redis主從同步包括三個階段。
第一階段:主從庫間建立連接、協商同步。
從庫向主庫發送psync 命令,告訴它要進行數據同步。
主庫收到 psync 命令后,響應FULLRESYNC命令(它表示第一次復制采用的是全量復制 ),并帶上主庫runID和主庫目前的復制進度offset。
第二階段:主庫把數據同步到從庫,從庫收到數據后,完成本地加載。
主庫執行bgsave命令,生成RDB文件,接著將文件發給從庫。從庫接收到RDB 文件后,會先清空當前數據庫,然后加載 RDB 文件。
主庫把數據同步到從庫的過程中,新來的寫操作,會記錄到replication buffer。
第三階段,主庫把新寫的命令,發送到從庫。
主庫完成RDB發送后,會把replication buffer中的修改操作發給從庫,從庫再重新執行這些操作。這樣主從庫就實現同步啦。
1.3 Redis主從的一些注意點
1.3.1 主從數據不一致
因為主從復制是異步進行的,如果從庫滯后執行,則會導致主從數據不一致 。
主從數據不一致一般有兩個原因:
主從庫網路延遲。
從庫收到了主從命令,但是它正在執行阻塞性的命令(如hgetall等)。
如何解決主從數據不一致問題呢?
可以換更好的硬件配置,保證網絡暢通。
監控主從庫間的復制進度
1.3.2 讀取過期數據
Redis刪除數據有這幾種策略:
惰性刪除:只有當訪問一個key時,才會判斷該key是否已過期,過期則清除。
定期刪除:每隔一定的時間,會掃描一定數量的數據庫的expires字典中一定數量的key,并清除其中已過期的key。
主動刪除:當前已用內存超過最大限定時,觸發主動清理策略。
如果使用Redis版本低于3.2,讀從庫時,并不會判斷數據是否過期,而是會返回過期數據 。而3.2 版本后,Redis做了改進,如果讀到的數據已經過期了,從庫不會刪除,卻會返回空值,避免了客戶端讀到過期數據 。
因此,在主從Redis模式下,盡量使用 Redis 3.2 以上的版本。
1.3.3 一主多從,全量復制時主庫壓力問題
如果是一主多從模式,從庫很多的時候,如果每個從庫都要和主庫進行全量復制的話,主庫的壓力是很大的。因為主庫fork進程生成RDB,這個fork的過程是會阻塞主線程處理正常請求的。同時,傳輸大的RDB文件也會占用主庫的網絡寬帶。
可以使用主-從-從 模式解決。什么是主從從模式呢?其實就是部署主從集群時,選擇硬件網絡配置比較好的一個從庫,讓它跟部分從庫再建立主從 關系。如圖:
1.3.4 主從網絡斷了怎么辦呢?
主從庫完成了全量復制后,它們之間會維護一個網絡長連接,用于主庫后續收到寫命令傳輸到從庫,它可以避免頻繁建立連接的開銷。但是,如果網絡斷開重連后,是否還需要進行一次全量復制呢?
如果是Redis 2.8之前,從庫和主庫重連后,確實會再進行一次全量復制,但是這樣開銷就很大。而Redis 2.8之后做了優化,重連后采用增量復制方式,即把主從庫網絡斷連期間主庫收到的寫命令,同步給從庫。
主從庫重連后,就是利用repl_backlog_buffer 實現增量復制。
當主從庫斷開連接后,主庫會把斷連期間收到的寫操作命令,寫入replication buffer ,同時也會把這些操作命令寫入repl_backlog_buffer 這個緩沖區。repl_backlog_buffer是一個環形緩沖區,主庫會記錄自己寫到的位置,從庫則會記錄自己已經讀到的位置。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://github.com/YunaiV/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
2. Redis哨兵
主從模式中,一旦主節點由于故障不能提供服務,需要人工將從節點晉升為主節點,同時還要通知應用方更新主節點地址。顯然,多數業務場景都不能接受這種故障處理方式。Redis從2.8開始正式提供了Redis哨兵機制 來解決這個問題。
哨兵作用
哨兵模式簡介
哨兵如何判定主庫下線
哨兵模式如何工作
哨兵是如何選主的
由哪個哨兵執行主從切換呢?
哨兵下的故障轉移
2.1 哨兵作用
哨兵其實是一個運行在特殊模式下的Redis進程。它有三個作用,分別是:監控、自動選主切換(簡稱選主)、通知 。
哨兵進程在運行期間,監視所有的Redis主節點和從節點。它通過周期性給主從庫 發送PING命令,檢測主從庫是否掛了。如果從庫 沒有在規定時間內響應哨兵的PING命令,哨兵就會把它標記為下線狀態 ;如果主庫沒有在規定時間內響應哨兵的PING命令,哨兵則會判定主庫下線,然后開始切換到選主 任務。
所謂選主 ,其實就是從多個從庫中,按照一定規則,選出一個當做主庫。至于通知 呢,就是選出主庫后,哨兵把新主庫的連接信息發給其他從庫,讓它們和新主庫建立主從關系。同時,哨兵也會把新主庫的連接信息通知給客戶端,讓它們把請求操作發到新主庫上。
2.2 哨兵模式
因為Redis哨兵也是一個Redis進程,如果它自己掛了呢,那是不是就起不了監控的作用啦。我們一起來看下Redis哨兵模式
哨兵模式,就是由一個或多個哨兵實例組成的哨兵系統,它可以監視所有的Redis主節點和從節點,并在被監視的主節點進入下線狀態時,自動將下線主服務器屬下的某個從節點升級為新的主節點。,一個哨兵進程對Redis節點進行監控,就可能會出現問題(單點問題)。因此,一般使用多個哨兵來進行監控Redis節點,并且各個哨兵之間還會進行監控。
其實哨兵之間是通過發布訂閱機制 組成集群的,同時,哨兵又通過INFO命令,獲得了從庫連接信息,也能和從庫建立連接,從而進行監控。
2.3 哨兵如何判定主庫下線
哨兵是如何判斷主庫是否下線的呢?我們先來了解兩個基礎概念哈:主觀下線和客觀下線 。
哨兵進程向主庫、從庫 發送PING命令,如果主庫或者從庫沒有在規定的時間內響應PING命令,哨兵就把它標記為主觀下線 。
如果是主庫被標記為主觀下線 ,則正在監視這個主庫的所有哨兵 要以每秒一次的頻率,以確認主庫是否真的進入了主觀下線 。當有多數 的哨兵(一般少數服從多數,由 Redis 管理員自行設定的一個值 )在指定的時間范圍內確認主庫的確進入了主觀下線狀態,則主庫會被標記為客觀下線 。這樣做的目的就是避免對主庫的誤判 ,以減少沒有必要的主從切換,減少不必要的開銷。
假設我們有N個哨兵實例,如果有N/2+1個實例判斷主庫主觀下線 ,此時就可以把節點標記為客觀下線 ,就可以做主從切換了。
2.4 哨兵的工作模式
每個哨兵以每秒鐘一次的頻率向它所知的主庫、從庫以及其他哨兵實例發送一個PING命令。
如果一個實例節點距離最后一次有效回復PING命令的時間超過down-after-milliseconds選項所指定的值, 則這個實例會被哨兵標記為主觀下線。
如果主庫 被標記為主觀下線,則正在監視這個主庫的所有哨兵要以每秒一次的頻率確認主庫的確進入了主觀下線狀態。
當有足夠數量的哨兵(大于等于配置文件指定的值 )在指定的時間范圍內確認主庫的確進入了主觀下線狀態, 則主庫會被標記為客觀下線 。
當主庫被哨兵標記為客觀下線 時,就會進入選主模式 。
若沒有足夠數量的哨兵同意主庫已經進入主觀下線, 主庫的主觀下線狀態就會被移除 ;若主庫重新向哨兵的PING命令返回有效回復,主庫的主觀下線狀態就會被移除。
2.5 哨兵是如何選主的?
如果明確主庫已經客觀下線了,哨兵就開始了選主模式。
哨兵選主包括兩大過程,分別是:過濾和打分 。其實就是在多個從庫中,先按照一定的篩選條件,把不符合條件的從庫過濾 掉。然后再按照一定的規則,給剩下的從庫逐個打分,將得分最高的從庫選為新主庫
選主時,會判斷從庫的狀態,如果已經下線,就直接過濾 。
如果從庫網絡不好,老是超時,也會被過濾掉。看這個參數down-after-milliseconds,它表示我們認定主從庫斷連的最大連接超時時間。
過濾掉了不適合做主庫的從庫后,就可以給剩下的從庫打分,按這三個規則打分:從庫優先級、從庫復制進度以及從庫ID號 。
從庫優先級最高的話,打分就越高,優先級可以通過slave-priority配置。如果優先級一樣,就選與舊的主庫復制進度最快的從庫。如果優先級和從庫進度都一樣,從庫ID 號小的打分高。
2.6 由哪個哨兵執行主從切換呢?
一個哨兵標記主庫為主觀下線 后,它會征求其他哨兵的意見,確認主庫是否的確進入了主觀下線狀態。它向其他實例哨兵發送is-master-down-by-addr命令。其他哨兵會根據自己和主庫的連接情況,回應Y或N(Y 表示贊成,N表示反對票)。如果這個哨兵獲取得足夠多的贊成票數(quorum配置),主庫會被標記為客觀下線 。
標記主庫客觀下線的這個哨兵,緊接著向其他哨兵發送命令,再發起投票 ,希望它可以來執行主從切換。這個投票過程稱為Leader 選舉 。因為最終執行主從切換的哨兵稱為Leader,投票過程就是確定Leader。一個哨兵想成為Leader需要滿足兩個條件:
需要拿到num(sentinels)/2+1的贊成票。
并且拿到的票數需要大于等于哨兵配置文件中的quorum值。
舉個例子,假設有3個哨兵。配置的quorum值為2。即一個一個哨兵想成為Leader至少需要拿到2張票。為了更好理解,大家可以看下
在t1時刻,哨兵A1判斷主庫為客觀下線 ,它想成為主從切換的Leader,于是先給自己投一張贊成票,然后分別向哨兵A2 和A3發起投票命令,表示想成為 Leader。
在 t2 時刻,A3 判斷主庫為客觀下線 ,它也想成為 Leader,所以也先給自己投一張贊成票,再分別向 A1 和 A2 發起投票命令,表示也要成為 Leader。
在 t3 時刻,哨兵A1 收到了A3 的Leader投票請求。因為A1已經把票Y投給自己了,所以它不能再給其他哨兵投贊成票了,所以A1投票N給A3。
在 t4時刻,哨兵A2收到A3 的Leader投票請求,因為哨兵A2之前沒有投過票,它會給第一個向它發送投票請求的哨兵回復贊成票Y。
在 t5時刻,哨兵A2收到A1 的Leader投票請求,因為哨兵A2之前已經投過贊成票給A3了,所以它只能給A1投反對票N。
最后t6時刻,哨兵A1只收到自己的一票Y贊成票,而哨兵A3得到兩張贊成票(A2和A3投的),因此哨兵A3成為了Leader 。
假設網絡故障等原因,哨兵A3也沒有收到兩張票 ,那么這輪投票就不會產生Leader。哨兵集群會等待一段時間(一般是哨兵故障轉移超時時間的2倍),再進行重新選舉。
2.7 故障轉移
假設哨兵模式架構如下,有三個哨兵,一個主庫M,兩個從庫S1和S2。
當哨兵檢測到Redis主庫M1出現故障,那么哨兵需要對集群進行故障轉移。假設選出了哨兵3 作為Leader。故障轉移流程如下:
從庫S1解除從節點身份,升級為新主庫
從庫S2成為新主庫的從庫
原主節點恢復也變成新主庫的從節點
通知客戶端應用程序新主節點的地址。
故障轉移后:
3.Redis Cluster集群
哨兵模式基于主從模式,實現讀寫分離,它還可以自動切換,系統可用性更高。但是它每個節點存儲的數據是一樣的,浪費內存,并且不好在線擴容 。因此,Reids Cluster集群(切片集群的實現方案) 應運而生,它在Redis3.0加入的,實現了Redis的分布式存儲 。對數據進行分片,也就是說每臺Redis節點上存儲不同的內容,來解決在線擴容的問題。并且,它可以保存大量數據 ,即分散數據到各個Redis實例,還提供復制和故障轉移的功能。
比如你一個Redis實例保存15G甚至更大的數據,響應就會很慢,這是因為Redis RDB 持久化機制導致的,Redis會fork子進程完成 RDB 持久化操作,fork執行的耗時與 Redis 數據量成正相關。
這時候你很容易想到,把15G數據分散來存儲就好了嘛。這就是Redis切片集群 的初衷。切片集群是啥呢?來看個例子,如果你要用Redis保存15G的數據,可以用單實例Redis,或者3臺Redis實例組成切片集群 ,對比如下:
切片集群和Redis Cluster 的區別:Redis Cluster是從Redis3.0版本開始,官方提供的一種實現切片集群 的方案。
既然數據是分片分布到不同Redis實例的,那客戶端到底是怎么確定想要訪問的數據在哪個實例上呢?我們一起來看下Reids Cluster 是怎么做的哈。
3.1 哈希槽(Hash Slot)
Redis Cluster方案采用哈希槽(Hash Slot),來處理數據和實例之間的映射關系。
一個切片集群被分為16384個slot(槽),每個進入Redis的鍵值對,根據key進行散列,分配到這16384插槽中的一個。使用的哈希映射也比較簡單,用CRC16算法計算出一個16bit的值,再對16384取模。數據庫中的每個鍵都屬于這16384個槽的其中一個,集群中的每個節點都可以處理這16384個槽。
集群中的每個節點負責一部分的哈希槽,假設當前集群有A、B、C3個節點,每個節點上負責的哈希槽數 =16384/3,那么可能存在的一種分配:
節點A負責0~5460號哈希槽
節點B負責5461~10922號哈希槽
節點C負責10923~16383號哈希槽
客戶端給一個Redis實例發送數據讀寫操作時,如果這個實例上并沒有相應的數據,會怎么樣呢?MOVED重定向和ASK重定向了解一下哈
3.2 MOVED重定向和ASK重定向
在Redis cluster模式下,節點對請求的處理過程如下:
通過哈希槽映射,檢查當前Redis key是否存在當前節點
若哈希槽不是由自身節點負責,就返回MOVED重定向
若哈希槽確實由自身負責,且key在slot中,則返回該key對應結果
若Redis key不存在此哈希槽中,檢查該哈希槽是否正在遷出(MIGRATING)?
若Redis key正在遷出,返回ASK錯誤重定向客戶端到遷移的目的服務器上
若哈希槽未遷出,檢查哈希槽是否導入中?
若哈希槽導入中且有ASKING標記,則直接操作,否則返回MOVED重定向
3.2.1 Moved 重定向
客戶端給一個Redis實例發送數據讀寫操作時,如果計算出來的槽不是在該節點上,這時候它會返回MOVED重定向錯誤,MOVED重定向錯誤中,會將哈希槽所在的新實例的IP和port端口帶回去。這就是Redis Cluster的MOVED重定向機制。流程圖如下:
3.2.2 ASK 重定向
Ask重定向一般發生于集群伸縮的時候。集群伸縮會導致槽遷移,當我們去源節點訪問時,此時數據已經可能已經遷移到了目標節點,使用Ask重定向 可以解決此種情況。
3.3 Cluster集群節點的通訊協議:Gossip
一個Redis集群由多個節點組成,各個節點之間是怎么通信的呢?通過Gossip協議 !Gossip是一種謠言傳播協議,每個節點周期性地從節點列表中選擇 k 個節點,將本節點存儲的信息傳播出去,直到所有節點信息一致,即算法收斂了。
Gossip協議基本思想:一個節點想要分享一些信息給網絡中的其他的一些節點。于是,它周期性的隨機選擇一些節點,并把信息傳遞給這些節點。這些收到信息的節點接下來會做同樣的事情,即把這些信息傳遞給其他一些隨機選擇的節點。一般而言,信息會周期性的傳遞給N個目標節點,而不只是一個。這個N被稱為fanout
Redis Cluster集群通過Gossip協議進行通信,節點之前不斷交換信息,交換的信息內容包括節點出現故障、新節點加入、主從節點變更信息、slot信息 等等。gossip協議包含多種消息類型,包括ping,pong,meet,fail,等等
meet消息:通知新節點加入。消息發送者通知接收者加入到當前集群,meet消息通信正常完成后,接收節點會加入到集群中并進行周期性的ping、pong消息交換。
ping消息:節點每秒會向集群中其他節點發送 ping 消息,消息中帶有自己已知的兩個節點的地址、槽、狀態信息、最后一次通信時間等
pong消息:當接收到ping、meet消息時,作為響應消息回復給發送方確認消息正常通信。消息中同樣帶有自己已知的兩個節點信息。
fail消息:當節點判定集群內另一個節點下線時,會向集群內廣播一個fail消息,其他節點接收到fail消息之后把對應節點更新為下線狀態。
特別的,每個節點是通過集群總線(cluster bus) 與其他的節點進行通信的。通訊時,使用特殊的端口號,即對外服務端口號加10000。例如如果某個node的端口號是6379,那么它與其它nodes通信的端口號是 16379。nodes 之間的通信采用特殊的二進制協議。
3.4 故障轉移
Redis集群實現了高可用,當集群內節點出現故障時,通過故障轉移 ,以保證集群正常對外提供服務。
redis集群通過ping/pong消息,實現故障發現。這個環境包括主觀下線和客觀下線 。
主觀下線: 某個節點認為另一個節點不可用,即下線狀態,這個狀態并不是最終的故障判定,只能代表一個節點的意見,可能存在誤判情況。
主觀下線
客觀下線: 指標記一個節點真正的下線,集群內多個節點都認為該節點不可用,從而達成共識的結果。如果是持有槽的主節點故障,需要為該節點進行故障轉移。
假如節點A標記節點B為主觀下線,一段時間后,節點A通過消息把節點B的狀態發到其它節點,當節點C接受到消息并解析出消息體時,如果發現節點B的pfail狀態時,會觸發客觀下線流程;
當下線為主節點時,此時Redis Cluster集群為統計持有槽的主節點投票,看投票數是否達到一半,當下線報告統計數大于一半時,被標記為客觀下線 狀態。
流程如下:
客觀下線
故障恢復 :故障發現后,如果下線節點的是主節點,則需要在它的從節點中選一個替換它,以保證集群的高可用。流程如下:
資格檢查:檢查從節點是否具備替換故障主節點的條件。
準備選舉時間:資格檢查通過后,更新觸發故障選舉時間。
發起選舉:到了故障選舉時間,進行選舉。
選舉投票:只有持有槽的主節點 才有票,從節點收集到足夠的選票(大于一半),觸發替換主節點操作
3.5 加餐:為什么Redis Cluster的Hash Slot 是16384?
對于客戶端請求過來的鍵值key,哈希槽=CRC16(key) % 16384,CRC16算法產生的哈希值是16bit的,按道理該算法是可以產生2^16=65536個值,為什么不用65536,用的是16384(2^14)呢?
大家可以看下作者的原始回答:
Redis 每個實例節點上都保存對應有哪些slots,它是一個 unsigned char slots[REDIS_CLUSTER_SLOTS/8] 類型
在redis節點發送心跳包時需要把所有的槽放到這個心跳包里,如果slots數量是 65536 ,占空間= 65536 / 8(一個字節8bit) / 1024(1024個字節1kB) =8kB ,如果使用slots數量是 16384 ,所占空間 = 16384 / 8(每個字節8bit) / 1024(1024個字節1kB) = 2kB ,可見16384個slots比 65536省 6kB內存左右,假如一個集群有100個節點,那每個實例里就省了600kB啦
一般情況下Redis cluster集群主節點數量基本不可能超過1000個,超過1000會導致網絡擁堵。對于節點數在1000以內的Redis cluster集群,16384個槽位其實夠用了。
既然為了節省內存網絡開銷,為什么 slots不選擇用8192(即16384/2) 呢?
8192 / 8(每個字節8bit) / 1024(1024個字節1kB) = 1kB ,只需要1KB!可以先看下Redis 把 Key 換算成所屬 slots 的方法
unsignedintkeyHashSlot(char*key,intkeylen){ ints,e;/*start-endindexesof{and}*/ for(s=0;s
Redis 將key換算成slots 的方法:其實就是是將crc16(key) 之后再和slots的數量進行與計算
這里為什么用0x3FFF(16383) 來計算,而不是16384呢?因為在不產生溢出的情況下 x % (2^n)等價于x & (2^n - 1)即 x % 16384 == x & 16383
那到底為什么不用8192呢?
crc16 出來結果,理論上出現重復的概率為 1?65536,但實際結果重復概率可能比這個大不少,就像crc32 結果 理論上 1/40億 分之一,但實際有人測下來10萬碰撞的概率就比較大了。假如 slots 設置成 8192, 200個實例的節點情況下,理論值是 每40個不同key請求,命中就會失效一次,假如節點數增加到400,那就是20個請求。并且1kb 并不會比 2k 省太多,性價比不是特別高,所以可能 選16384會更為通用一點
責任編輯:彭菁
-
管理系統
+關注
關注
1文章
2704瀏覽量
36569 -
小程序
+關注
關注
1文章
241瀏覽量
12649 -
Redis
+關注
關注
0文章
381瀏覽量
11199
原文標題:Redis主從、哨兵、 Cluster集群一鍋端!
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
Redis的四種模式復制、哨兵、Cluster以及集群模式

什么是Redis主從復制

Cloud MemoryStore for Redis Cluster 正式發布

評論