3萬字聊聊什么是Redis(二)
大家好,我是Leo
繼上篇Redis技術(shù)總結(jié)一,我們繼續(xù)聊聊Redis的相關技術(shù)!
上一篇我們介紹了
Redis五大類型的底層實現(xiàn) 數(shù)據(jù)量的增加,性能變慢的問題分析,原理剖析 Redis單線程與多線程這個高頻話題 IO模型,多路復用機制 AOF寫入策略,重寫機制 RDB寫時復制技術(shù)
推薦閱讀
思路

Redis主從庫的由來
隨著數(shù)據(jù)量的增大,單臺Redis無法很好的提供讀寫緩存服務了。于是就采用多臺Redis完成更大的訪問請求,那么多臺Redis在提供服務時,數(shù)據(jù)肯定是要一致的。
Redis的處理方案是采用主從庫模式,主從庫之間采用的是讀寫分離方式,以保證數(shù)據(jù)副本的一致性。
讀操作?:主從庫都可以接收,因為讀操作不影響主從庫的數(shù)據(jù)一致問題
寫操作?:如果在從庫寫,那么主庫數(shù)據(jù)就不一致了。
如果不采用讀寫分離模式,那么我們就需要用其他方案解決數(shù)據(jù)一致性問題,比如加鎖,多個Redis實例協(xié)商等等。這樣的開銷是非常大的,對于高性能的Redis來說顯然是不能接受的。
如果采用讀寫分離模式,所有的寫請求都打到主庫上,主庫再利用RDB文件同步給從庫。達到數(shù)據(jù)的一致性!就可以省去一些不必要的開銷了。
主從庫數(shù)據(jù)如何實現(xiàn)一致
聊到Redis的主從庫數(shù)據(jù)的一致性,我們可以先聊聊MySQL是如何實現(xiàn)主從庫數(shù)據(jù)一致性的。
MySQL是借助binlog實現(xiàn)數(shù)據(jù)同步的,binlog主要有三種格式,statement,row,mixed。statement過于簡單,row過于復雜數(shù)據(jù)量大,mixed中和了一下適合同步傳輸。
介紹了binlog三種格式,具體的流程是首先會在從庫上執(zhí)行?change master?設計好主從庫之后,再執(zhí)行?start slave?主庫會發(fā)送從庫binlog文件進行同步。
Redis這里也是一樣的邏輯,通過?replicaof?實現(xiàn)從庫認主。認主完成之后主庫借助RDB文件進行數(shù)據(jù)同步操作。首先fork子線程進行生成RDB文件,生成RDB文件主要有兩種形式,save?和?bgsave
save:在主線程中執(zhí)行,會導致阻塞; bgsave:創(chuàng)建一個子進程,專門用于寫入 RDB 文件,避免了主線程的阻塞,這也是 Redis RDB 文件生成的默認配置。
一般我們都采用bgsave生成RDB文件,提升性能減少主線程阻塞!
生成完RDB就該進行數(shù)據(jù)同步了,接下來我們分析一下數(shù)據(jù)同步這個過程中都發(fā)生了哪些事情。
第一部分?肯定是主從庫間建立連接,協(xié)商同步的過程,主要為了全量復制做準備。主從庫建立連接之后,從庫給主庫發(fā)送了一個?psync ID -1?命令
這里的ID是簡寫,它是每個Redis實例啟動時都會自動生成的一個隨機ID也叫 runID,也是每個實例的唯一標識 -1,他是同步的偏移量,也可以說是進度下標,第一次復制時,傳-1就代表是全量同步,第二次之后就不是-1了,就是當前的復制下標了,比如從庫已經(jīng)同步到5000了,從庫發(fā)送主庫的時候就會從5000開始進行生成RDB文件進行增量同步。
第二部分?從庫收到主庫的RDB文件后,如果是第一次同步的話,會先清空當前從庫的數(shù)據(jù)庫然后再加載RDB文件。這是因為從庫在通過 replicaof 命令開始和主庫同步前,可能保存了其他數(shù)據(jù)。為了避免之前數(shù)據(jù)的影響,從庫需要先把當前數(shù)據(jù)庫清空。
第三部分?當主庫在生成RDB期間,仍然有寫請求,就會導致數(shù)據(jù)不一致,Redis這里的處理是開辟一個replication buffer 緩沖區(qū),記錄RDB文件生成后的主庫寫操作。第三部分就是把這部分少量的數(shù)據(jù)同步到從庫上。
擴展1:除了主從同步,還有一種為了提升性能而誕生的一種同步是主從從模式。因為在主線程fork子線程后生成RDB文件是要消耗主線程資源的,如果存在多個從庫的話,那么主庫恐怕要一直fork和發(fā)送RDB。我們可以在部署主從集群時,手動選擇一個內(nèi)存資源配置較高的從庫作為,從庫與從庫的數(shù)據(jù)同步源。然后讓他們建立主從關系。
擴展2:一旦在傳輸過程沖斷連了,可以通過復制下標進行重新數(shù)據(jù)同步。就不需要每次都走全量備份了。
高可用性體現(xiàn)在哪
Redis的高可用體現(xiàn)在哨兵機制,哨兵機制是等主從庫掛了之后,通過哨兵機制可以自動切換選舉出新的主庫,然后進行數(shù)據(jù)同步,達到一致性。最終繼續(xù)為用戶提供服務。
哨兵主要負責的就是三個任務:監(jiān)控、選主(選擇主庫)和通知。
監(jiān)控:哨兵會周期性的給所有的主從庫發(fā)送ping命令,監(jiān)測它們是否處于在線運行狀態(tài),
如果從庫沒有響應ping命令,哨兵就會把從庫標記為下線狀態(tài)。 如果主庫沒有響應ping命令,哨兵就會判斷主庫下線并且開始自動切換主庫。
選主?:主庫掛了之后,哨兵就需要在很多從庫中,按照一定的規(guī)則選擇出一個從庫實例,把它作為新的主庫。
通知?:主庫重新誕生之后,哨兵就會通知從庫,告訴它們新主庫的信息。讓他們執(zhí)行replicaof?命令。重新建立之后開始數(shù)據(jù)同步流程。
一系列流程之后,主庫出現(xiàn)了,從庫也正常了。一切又回到了出故障之前的那個狀態(tài)!
如何選定新主庫
在上面高可用中,臨時加了一個技術(shù)點,這個知識點就是在哨兵選主時,選擇規(guī)則的介紹。
哨兵選主庫時,一般都稱為?"篩選+打分"
篩選:(網(wǎng)絡波動)除了檢查從庫當前的在線狀態(tài)還要判斷它網(wǎng)絡的連接狀態(tài)。如果從庫和主庫響應過慢,并且超過了一定的閾值,那么肯定是不能選擇該從庫充當我們的主庫的。因為一旦該從庫選擇主庫,一旦在后續(xù)的寫入操作,數(shù)據(jù)同步操作中網(wǎng)絡波動大,或者直接斷開連接了,我們還需要重新做一下選擇,通知,同步等。這樣性能是非常低效的!
打分:(擇偶標準)主要有三點如下
優(yōu)先級最高的從庫得分高:用戶可以通過? slave-priority?配置項,給不同的從庫設置不同的優(yōu)先級,比如不同的從庫中的內(nèi)存配置,CPU配置等同步進度:一般選擇一個從庫為主庫,如果我們從庫的數(shù)據(jù)同步進度更接近與前主庫,那么從庫切換成主庫之后,數(shù)據(jù)同步的時間消耗更低。性能會更好一些。 ID 號小的從庫得分高。(Redis的默認規(guī)定沒啥好說的)。在優(yōu)先級和復制進度都相同的情況下,ID 號最小的從庫得分最高,會被選為新主庫。
哨兵掛了,主從還能切換嗎
答案是可以的。哨兵集群中的一個哨兵實例掛了,主從依然還是可以切換的,因為我們在配置哨兵信息的時,我們只需要設置主庫的IP和端口,并沒有配置其他的哨兵連接信息。
那么其他哨兵是如何知道彼此的地址的呢?
這應該就需要我們先了解一下?pub/sub機制的哨兵集群組成?。翻譯一下分別是發(fā)布/訂閱機制。
我們先講一個對應的白話文故事,pub/sub機制就是幾年前QQ非?;鸨腝Q群功能,一個一個加好友交友也好,處理事情也好,都是比較麻煩的,如果說群主建立一個QQ群,然后拉自己的好友。拉進來之后,只要是這個群的人員都能收到任意好友發(fā)送的信息。這與Redis的pub/sub機制類似。
回到Redis中!哨兵只要和主庫建立了連接,就可以在主庫上發(fā)布消息了,同時也可以從主庫訂閱消息,獲取其他哨兵發(fā)布的連接信息。當多個哨兵都在主庫上發(fā)布了和訂閱了信息后,就能知道彼此的IP地址和端口了 。
發(fā)布和訂閱一直所說的頻道信息,就類似于不同的群號,接收不同的好友信息一樣。下面我們實踐一下,必須是兩個窗口或者多個Redis才能進行測試。
第一個窗口負責訂閱一個頻道叫huanshao,訂閱之后就處于等待接收的狀態(tài)了。 第二個窗口是用于發(fā)送消息的,往huanshao這個頻道發(fā)送一個HelloWord然后第一個窗口自動就接收了

哨兵集群中每個哨兵都拿到了所需要的IP地址和端口號,哨兵除了要監(jiān)測主庫外還需要監(jiān)測從庫,因為主庫掛了,要從從庫中選舉一個成為主庫。那么從庫的信息哨兵如何拿到呢?
可以通過哨兵告訴主庫發(fā)送info命令來完成!主庫執(zhí)行了info命令就會把當前的從庫信息返給哨兵。哨兵拿到了從庫的IP地址和端口號一切就都好辦了。

上述哨兵集群拿到了主從庫的信息,在整個主從庫切換這些不止是這些,還有重要的一步就是通知客戶端修改主庫信息。
和上述獲取從庫信息一樣,通過不同的頻道獲取不同的消息,下面列舉幾個常用的頻道事件
主庫下線事件
+sdown(實例進入主觀下線狀態(tài)) -sdown(實例退出主觀下線狀態(tài)) +odown(實例進入客觀下線狀態(tài)) -odown(實例退出下線狀態(tài))
從庫重新配置事件
+slave-reconf-sent(哨兵發(fā)送SLAVEOF命令重新配置從庫) +slave-reconf-inprog(從庫配置了新主庫,但尚未進行同步) +slave-reconf-done(從庫配置了新主庫,且和新主庫完成同步)
新主庫切換
+switch-master(主庫地址發(fā)生變化)
知道了頻道信息,就可以讓客戶端訂閱相關信息,一旦哨兵監(jiān)測出主庫掛了,通過選舉出新主庫之后,以事件的方式通知客戶端。就可以實現(xiàn)短暫宕機后的服務恢復了!
Redis訂閱命令
SUBSCRIBE
訂閱所有事件
PSUBSCRIBE *
馬上結(jié)束了,大家再堅持一下!?簡單總結(jié)一下,哨兵掛了之后,其他哨兵拿到了主從庫的信息,同時通過頻道事件的方式通知客戶端重新綁定主庫。那么選主時由誰來選?
一般應用哨兵我們都會采用哨兵集群,因為只通過1個哨兵實例的話,往往適得其反,如果當哨兵發(fā)送ping命令給主庫時,那個時刻主庫剛好網(wǎng)絡不好,或者正常處理比較大的數(shù)據(jù)延誤了給哨兵響應信息,那么哨兵就會認為當前主庫掛了,就會給他設為主觀/客觀下線。然后就game over了。
于是引用多哨兵實例共同監(jiān)控,這里我們設為哨兵A,哨兵B,哨兵C。
當哨兵A發(fā)現(xiàn)某個主庫掛了,那么它會把這個主庫設為主觀下線,然后過段時間哨兵B也會給主庫發(fā)送ping命令,當哨兵B也發(fā)現(xiàn)了這個主庫掛了時,也會給他設為主觀下線。在多個哨兵實例中如果有一半以上的哨兵都認為這個主庫掛了。那么這個主庫就真的掛了,會被設為客觀下線。
判斷出下線之后,多個實例哨兵就會先推選出一個執(zhí)行l(wèi)eader。由其中一個哨兵來處理后續(xù)的通知相關操作。
這就類似于我們學生時代的小組組長一樣,由一個小組6個人共同投票選舉一個人,為這個小組的組長。如果選擇同一個人的票數(shù)大于小組總?cè)藬?shù)。那么這個人就是組長,在Redis中這個人就是哨兵集群中的leader,由它來執(zhí)行操作。
切片集群解決了什么
切片集群也叫作分片集群,就是啟動多個Redis實例組成一個集群,然后按照一定的規(guī)則把收到的數(shù)據(jù)劃分成多份,每一份用一個實例來保存數(shù)據(jù)。
如果我們生產(chǎn)環(huán)境有50G的數(shù)據(jù),全部放入一臺Redis實例的話,肯定是成本比較高,而且很多地方不好把控的。于是我們就把50個G的數(shù)據(jù)分成10份,每個Redis實例存5個G。
從性能上來分析,每個Redis只需要處理5個G的數(shù)據(jù),也是非??斓?/p>
從硬件上來分析,每個Redis只需要一般配置就可以達到我們的需求。
從擴展上來分析,隨著數(shù)據(jù)的增多,我們只需要不斷加Redis實例就夠了。也是方便擴展的
單機跟集群最難處理的點就是
分布式的一致性, 數(shù)據(jù)在多個實例上如何分布, 客戶端如何得到自己想到的數(shù)據(jù)存在哪個實例上。
下面我們先從?數(shù)據(jù)如何分布上 進行介紹。我們可以采用Redis Cluster方案。
Redis Cluster 方案采用哈希槽,來處理數(shù)據(jù)和實例之間的映射關系。每個鍵值對都會根據(jù)它的 key,被映射到一個哈希槽中。主要分兩步實現(xiàn)
首先根據(jù)鍵值對的 key,按照CRC16 算法計算一個 16 bit 的值; 然后,再用這個 16bit 值對 切片集群的哈希槽總數(shù)取模,得到模數(shù),每個模數(shù)代表一個相應編號的哈希槽。
接下來介紹一下?客戶端如何定位數(shù)據(jù)
Redis 實例會把自己的哈希槽信息發(fā)給和它相連接的其它實例,來完成哈希槽分配信息的擴散。當實例之間相互連接后,每個實例就有所有哈希槽的映射關系了。
客戶端收到哈希槽信息后,會把哈希槽信息緩存在本地。當客戶端請求鍵值對時,會先計算鍵所對應的哈希槽,然后就可以給相應的實例發(fā)送請求了。
哈希值隨著數(shù)據(jù)的增多與減少并不是一成不變的,任何的增多與減少Redis都需要重新分配哈希槽。同時為了數(shù)據(jù)能均勻的分散在多個實例上,Redis也會把哈希槽在實例上重新分布一遍。
Redis的實例與實例之后可以通過相互傳遞消息獲取最新的哈希槽分配信息,但是客戶端無法感知,這就導致客戶端的緩存數(shù)據(jù)與Redis的哈希槽會有不一致的情況。如何解決 ?
Redis Cluster 方案提供了一種?重定向機制,所謂的“重定向”,就是指,客戶端給一個實例發(fā)送數(shù)據(jù)讀寫操作時,這個實例上并沒有相應的數(shù)據(jù)時,Redis會給客戶端發(fā)送一個MOVED命令,這個MOVED命令就包含了新實例的IP和端口。然后客戶端要再給一個新實例發(fā)送操作命令就拿到了自己想要的數(shù)據(jù)了。
GET hello:key
(error) MOVED 13320 172.16.19.5:6379
細節(jié)擴展
Redis給客戶端返回一個新實例信息,客戶端再次請求時,同時也會修改本地的緩存,把當前的key更新到緩存中。 如果客戶端請求的這個key剛好遇到了重新分配哈希槽途中,且數(shù)據(jù)還沒有完全遷移完。就會返回ACK報錯信息,這個命令的意思是,讓這個實例允許執(zhí)行客戶端接下來發(fā)送的命令。然后,客戶端再向這個實例發(fā)送 GET 命令,以讀取數(shù)據(jù)。
GET?hello:key
(error)?ASK?13320?172.16.19.5:6379
ASK 命令表示兩層含義:第一,表明數(shù)據(jù)還在遷移中;第二,ASK 命令把客戶端所請求數(shù)據(jù)的最新實例地址返回給客戶端,此時,客戶端需要給Redis實例 發(fā)送 ASKING 命令,然后再發(fā)送操作命令。
和 MOVED 命令不同,ASK 命令并不會更新客戶端緩存的哈希槽分配信息。所以如果客戶端再次請求正在遷移的key,它還是會給實例 2 發(fā)送請求。這也就是說,ASK 命令的作用只是讓客戶端能給新實例發(fā)送一次請求,而不像 MOVED 命令那樣,會更改本地緩存,讓后續(xù)所有命令都發(fā)往新實例。
CAP原理
C - Consistent ,一致性?,訪問所有的節(jié)點得到的數(shù)據(jù)應該是一樣的。注意,這里的一致性指的是強一致性,也就是數(shù)據(jù)更新完,訪問任何節(jié)點看到的數(shù)據(jù)完全一致,要和弱一致性,最終一致性區(qū)分開來。 A - Availability ,可用性,所有的節(jié)點都保持高可用性。注意,這里的高可用還包括不能出現(xiàn)延遲,比如如果節(jié)點B由于等待數(shù)據(jù)同步而阻塞請求,那么節(jié)點B就不滿足高可用性。也就是說,任何沒有發(fā)生故障的服務必須在有限的時間內(nèi)返回合理的結(jié)果集。 P - Partition tolerance ,分區(qū)容忍性?這里的分區(qū)是指網(wǎng)絡意義上的分區(qū)。由于網(wǎng)絡是不可靠的,所有節(jié)點之間很可能出現(xiàn)無法通訊的情況,在節(jié)點不能通信時,要保證系統(tǒng)可以繼續(xù)正常服務。
一個系統(tǒng)中不可能同時滿足C,A,P三個條件,所以系統(tǒng)架構(gòu)師在設計系統(tǒng)時,不要將精力浪費在如何設計能滿足三者的完美分布式系統(tǒng),而是應該進行取舍。由于網(wǎng)絡的不可靠性質(zhì),大多數(shù)開源的分布式系統(tǒng)都會實現(xiàn)P,也就是分區(qū)容忍性,之后在C和A中做抉擇。
接下來我們分三個場景分析:
在保證C和P的情況下:為了保證數(shù)據(jù)一致性,data1需要將數(shù)據(jù)復制給data2,即data1和data2需要進行通信。但是由于網(wǎng)絡是不可靠的,我們系統(tǒng)有保證了分區(qū)容忍性,也就是說這個系統(tǒng)是可以容忍網(wǎng)絡的不可靠的。這時候data2就不一定能及時的收到data1的數(shù)據(jù)復制消息,當有請求向data2訪問number數(shù)據(jù)時,為了保證數(shù)據(jù)的一致性,data2只能阻塞等待數(shù)據(jù)真正同步完成后再返回,這時候就沒辦法保證高可用性了。
所以,在保證C和P的情況下,是無法同時保證A的。
在保證A和P的情況下:為了保證高可用性,data1和data2都有在有限時間內(nèi)返回。同樣由于網(wǎng)絡的不可靠,在有限時間內(nèi),data2有可能還沒收到data1發(fā)來的數(shù)據(jù)更新消息,這時候返回給客戶端的可能是舊的數(shù)據(jù),和訪問data1的數(shù)據(jù)是不一致的,也就是違法了C。
也就是說,在保證A和P的情況下,是無法同時保證C的。
在保證A和C的情況下:如果要保證高可用和一致性,只有在網(wǎng)絡情況良好且可靠的情況下才能實現(xiàn)。這樣data1才能立即將更新消息發(fā)送給data2。但是我們都知道網(wǎng)絡是不可靠的,是會存在丟包的情況的。所以要滿足即時可靠更新,只有將data1和data2放到一個區(qū)內(nèi)才可以,也就喪失了P這個保證。其實這時候整個系統(tǒng)也不能算是一個分布式系統(tǒng)了。
下述圖片是CAP原理在各個系統(tǒng)中的應用

結(jié)尾
每個知識點都是自己整理濃縮表達出來的,部分有些不容易懂的地方請及時指出,我們一起共同進步!
看到這里,應該都是真粉了,點贊+分享+在看+關注?就是對我最大支持。歡少的成長之路 感謝你

