《Redis深度歷險:核心原理和應用實踐》讀書筆記

HeddaChu發表於2020-12-21

1:主從同步

很多企業沒有使用redis叢集,但是至少都做了主從。

CAP原理

現代分散式系統的理論基石——CAP原理:

  • C:Consistent,一致性
  • A:Availability,可用性
  • P:Partition tolerance,分割槽容錯性

分散式系統的節點往往都是分佈在不同機器上進行網路隔離開的,這意味著必然會有網路斷開的風險,這個網路斷開的場景專業詞彙叫做網路分割槽
網路分割槽

在發生網路分割槽時,兩個分散式節點之間無法進行通訊,我們對一個節點進行的修改操作將無法同步到另一個節點,所以資料的一致性將無法滿足,因為兩個分散式節點的資料不再保持一致。
除非犧牲可用性,也就是暫停分散式節點服務,不再提供修改資料的功能直到網路狀況完全恢復正常再繼續對外提供服務。

當網路分割槽發生時,一致性和可用性兩難全。

最終一致性

redis保證最終一致性,從節點努力追趕主節點,最終從節點的狀態會和主節點的狀態保持一致。如果網路斷開,主從出現不一致,一旦網路恢復,從節點採用多種策略努力追趕,繼續盡力保持和主節點一致。

主從同步與從從同步

redis支援主從同步與從從同步,從從同步減輕主節點的同步負擔。
主從同步與從從同步

增量同步

redis同步的是指令流,主節點會將那些對自己狀態產生修改影響的指令記錄在本地記憶體buffer中,然後非同步將buffer中的指令同步到從節點,從節點一邊執行同步的指令流來達到和主節點一致的狀態,一邊向主節點反饋自己同步到哪裡了(偏移量)。
環形陣列
記憶體buffer有限,主節點不能將所有指令都記錄在記憶體buffer。redis複製記憶體buffer是一個定長的環形陣列,如果陣列滿了,就會從頭開始覆蓋前面的內容。

如果因為網路,從節點在短時間內無法同步,那麼網路恢復後,主節點中那些沒有同步的指令在buffer中有可能已經被後續的指令覆蓋掉了,從節點將無法直接通過指令流來進行同步,需要用到更加複雜的同步機制——快照同步。

快照同步

快照同步非常耗資源,首先在主節點進行一次bgsave,將當前記憶體資料全部快照到磁碟檔案中,然後將快照檔案的內容全部傳送到從節點。從節點接受快照後,立即執行一次全量載入,載入之前先將當前的記憶體資料清空,載入完畢後通知主節點繼續進行增量同步。快照同步
在快照同步中,主節點的複製buffer還在不停地向前移動,如果快照同步時間過長或者複製buffer太小,都會導致同步期間的增量指令在複製buffer中被覆蓋,導致快照同步完成後無法進行增量複製,然後再次發起快照同步,如此極有可能陷入快照同步的死迴圈。

務必配置一個合適的複製buffer大小引數。

增加從節點

當節點剛加入叢集,先快照同步後增量同步

無盤複製

主節點在快照同步時,會進行很耗時的檔案IO。特別當系統進行AOF的fsync,如果發生快照同步,fsync將會推遲執行,這就很影響主節點的服務效率。

redis支援無盤複製,主伺服器直接通過socket將快照內容傳送到從節點,生成快照是一個遍歷過程,主節點一邊遍歷記憶體,一邊將序列化內容發給從節點,從節點現將接受內容存到磁碟,再進行一次性載入。

wait指令

redis複製是非同步進行的,wait指令可以讓非同步變身同步,確保系統的強一致(不嚴格)

小結

主從複製是redis分散式基礎。
不過複製也不是必須的,如果只是用redis做快取,跟memcache一樣對待,也就不需要從節點備份,掛掉了重啟一下就好,若使用redis的持久化功能,就必須認真對待主從複製。

2:Sentinel

抵抗節點故障,當故障發生時可以自動進行主從切換。
Sentinel
將redis sentinel叢集看成是一個zookeeper叢集,它是叢集高可用的心臟,一般由3~5個節點組成,這樣即使個別節點掛了,叢集還可以正常運轉。

Sentinel負責持續監控主從節點的健康,當主節點掛掉時,自動選擇一個最優的從節點切換成主節點。客戶端來連線叢集時,會首先連線sentinel,通過sentinel查詢主節點的地址,然後再連線主節點進行資料互動。當主節點發生故障時,客戶端會重新向sentinel要地址,sentinel將最新的主節點地址告訴客戶端。如此應用程式無需重啟即可完成節點切換。

主節點掛掉
sentinel會持續監控已經掛掉的主節點,待它恢復後,叢集調整為:

在這裡插入圖片描述
原先的主節點現在變成從節點,從新的主節點那裡建立複製關係。

訊息丟失

當主節點掛掉,從節點可能沒收到全部的同步訊息,這部分未同步的訊息就丟失了。sentinel無法保證訊息完全不丟失,但是儘量保證訊息少丟失。有兩個選項可以限制主從延遲過大。

#主節點必須至少有一個從節點在進行正常複製,否則就停止對外寫服務,喪失可用性
min-slaves-to-write 1
#如果在10s內沒有收到從節點的反饋,就意味著從節點同步不正常,要麼網路斷開,要麼一直沒有反饋
min-slaves-max-lag 10

sentinel基本用法

sentinel基本用法
當sentinel進行主從切換時,客戶端如何知道地址變更了?
redis-py在建立連線時進行主節點地址變更判斷。
連線池新建連線時,會查詢主地址,然後跟記憶體中的主地址比對,如果變更了,就斷開所有連線,重新使用新連線。如果舊節點掛掉了,那麼所有正在使用的連線都被關閉。
如果sentinel主動進行主從切換,但主節點並沒有掛掉,而之前的主節點連線已經建立且在使用中,沒有新連線建立,那麼如何切換?
redis-py在處理命令時捕獲了一個特殊異常ReadOnlyError,在這個異常裡將所有舊連線關閉,後續指令就會重連。主從切換後,之前主節點的修改指令都會丟擲ReadOnlyError。如果沒有修改指令,雖然連線不會切換,但是資料不會被破壞,即使不切換也沒關係。

3:Codis

單個redis記憶體不宜過大,記憶體太大導致rdb過大,進一步導致主從全量同步時間長,例項重啟載入時間也長。

redis叢集,將眾多小記憶體的redis例項整合起來,將分佈在多臺機器上的眾多CPU核心計算能力聚集到一起,完成海量資料儲存和高併發讀寫。

Codis是redis叢集方案之一,使用go開發,是一個代理中介軟體,和redis一樣也使用redis協議對外提供服務。當客戶端想Codis傳送指令,codis負責將指令轉發到後面的redis例項執行,並將返回結果再轉回客戶端。
codis

codis是無狀態的,只是一個轉發代理中介軟體,可以啟動多個codis例項。
多個codis

Codis分片原理

預設將所有key劃分為1024個slot,先將key進行crc32運算計算hash值,再將hash整數值對1024取模得到餘數,這個餘數就是key的slot。

hash = crc32(command.key)
slot_index = hash % 1024
redis = slots[slot_index].redis
redis.do(command)

slot
每個slot都會唯一對映到後面的多個redis例項之一,codis會在記憶體中維護slot和redis例項對映的關係。

slot數量可以設定,預設是1024

不同Codis例項之間槽位關係如何同步

Codis需要一個分散式配置儲存資料庫持久化slot關係。
分散式儲存
Codis將slot關係儲存到zookeeper中,並提供DashBoard觀察和修改槽位關係。當槽位關係發生變化,Codis Proxy監聽變化並同步槽位關係,從而實現多個Codis Proxy共享槽位關係配置。

擴容

Codis只有一個redis例項,1024個slot都指向同一個redis。
增加一個redis例項,需要調整槽位關係,將一半的slot劃分給新節點。也就是對這一半的slot對應的所有key進行遷移。

Codis如何找到slot對應的所有key?
Codis對redis進行了改造,增加了SLOTSSCAN指令,可以遍歷指定slot下所有key。
Codis通過SLOTSSCAN掃描出待遷移槽位的所有key,然後挨個遷移到新的redis例項。

codis接收到位於遷移slot的key後,會立即強制對當前的單個key進行遷移,遷移完成後,再將請求轉發到新的redis例項。

自動均衡

自動均衡會在系統比較空閒時觀察每個redis例項對應的slot數量,如果不平衡,就會自動遷移

codis的代價

所有key分散在不同的redis例項中,不能支援事務
rename操作,引數是兩個key,若在不同redis例項中,無法正確完成。
為了支援擴容,單個key對應的value不宜過大。官方建議單個集合結構的總位元組數不要超過1MB。
因為增加了Proxy作為中轉層,網路開銷上要比單個redis大。
叢集配置中心使用zookeeper,意味著在部署上增加了zookeeper運維代價

codis的優點

在設計上比Redis Cluster官方叢集方案簡單很多,因為將分散式問題轉交給第三方。Redis Cluster為了實現去中心化,混合使用複雜的Raft協議和Gossip協議,還有大量需要調優的配置引數。

mget指令的操作過程

批量獲取多個key的值
mget指令

架構變遷

Codis的尷尬

不是Redis官方專案

Codis的後臺管理

介面非常友好

4:Cluster

Redis Cluster是去中心化的。
每個節點負責整個叢集的一部分資料,每個節點負責的多少可能不一樣。節點相互連線組成一個對等的叢集,他們之間通過一種特殊的二進位制協議互動叢集資訊。
Redis Cluster
Redis Cluster將所有資料劃分為16384個slot,slot資訊儲存於每個節點中,不需要另外的分散式儲存空間。每個節點將叢集配置資訊持久化到配置檔案,必須保證配置檔案是可寫的。
當客戶端連線叢集時,也會得到一份叢集的slot資訊配置資訊。這樣當客戶端要查詢某個key時,可以直接定位到目標節點。
客戶端為了直接定位到某個key的目標節點,需要快取slot相關資訊。因為可能存在客戶端與伺服器資訊不一致,需要糾正機制來實現slot資訊的校驗調整。

槽位定位演算法

預設對key值使用crc16進行hash,得到一個整數值,然後對16384進行取模來得到具體slot。
Redis Cluster允許使用者強制把某個key掛在特定slot上。在key字串里加上tag標記

跳轉

當client向一個錯誤節點發出指令後,該節點發現key所在slot不歸自己管理,會向客戶端傳送跳轉指令攜帶目標操作地址,告訴客戶端去這個節點獲取資料

GET X
-MOVED 3999 127.0.0.1:6381

MOVED前的減號,表示該指令是一個錯誤訊息
3999:key對應的slot編號
127.0.0.1:6381:目標地址
客戶端收到MOVED後,立即糾正本地槽位對映表

遷移

Redis Cluster提供redis-trib讓運維人員手動調整slot分配

redis遷移的單位是slot,redis一個slot一個slot地進行遷移,當一個slot進行遷移時,這個slot就處於中間過渡狀態。
在這裡插入圖片描述

redis-trib會先在原節點和目標節點設定好過渡狀態,然後一次性獲取源節點slot的所有key列表(keysinslot指令,可以部分獲取),再挨個key進行遷移。每個key遷移過程是原節點作為目標節點的“客戶端”,原節點對當前key執行dump指令得到序列化內容,然後通過“客戶端”向目標節點傳送restore指令攜帶序列化的內容作為引數,目標節點再進行反序列化就可以將內容恢復到目標節點記憶體中,然後返回“客戶端”OK,原節點再把當前節點的key刪除。
大致流程:從原節點獲取內容——存到目標節點——從原節點刪除
這裡的遷移是同步的,同步時原節點的主執行緒處於阻塞狀態。

遷移過程中,客戶端訪問流程:
首先新舊兩個節點都存在部分資料,客戶端先嚐試訪問舊節點,如果資料還在舊節點,那麼舊節點正常處理。
如果資料不在舊節點,那麼兩種可能,要麼在新節點,要麼根本就不存在。舊節點會向客戶端返回一個-ASK targetNodeAddr的重定向指令。客戶端收到這個指令,先去目標節點執行一個不帶任何引數的ASKING指令,然後在目標節點重新執行原先的操作指令。

為什麼執行ASKING指令?
在遷移沒有完成前,這個slot還不歸新節點管理,如果直接訪問新節點,節點不認,它會返回-MOVED重定向。如此就造成重定向迴圈。ASKING指令就是開啟目標節點的選項,告訴它下一條指令不能不理,要當成自己的slot來處理。

遷移會影響服務效率,同樣的指令正常情況下一個ttl就能完成,而在遷移下需要3個ttl。

容錯

Redis Cluster可以為每個主節點設定若干從節點,當主節點發生故障,叢集會自動將其中某個從節點提升為主節點。如果某個主節點沒有從節點,那麼當它發生故障時,叢集將處於完全不可用狀態。
redis提供引數cluster-require-full-coverage允許部分節點發生故障,其他節點還可以繼續提供對外訪問。

網路抖動

Redis Cluster提供選項cluster-node-timeout,當某個節點持續timeout的時間失聯時,才可以認定該節點出現故障,需要主從切換。如果沒有這個選項,網路抖動會導致主從頻繁切換(資料的重新複製)。

還有另外一個選項cluster-slave-validity-factor作為倍數放大這個超時時間。

可能下線(PFail)與確定下線(Fail)

Redis Cluster去中心化,一個節點認為某個節點失聯了(PFail,Possibly Fail),不代表所有節點都認為它失聯。Redis叢集節點採用Gossip協議廣播自己的狀態以及改變對整個叢集的認知。
節點認為某節點PFail,將這個訊息向叢集廣播,如果收到某個節點失聯的節點數量PFail Count達到叢集大多數,就可以標記該失聯節點為下線狀態Fail,然後向叢集廣播,並立即對該失聯節點進行主從切換。

Cluster基本用法

槽位遷移感知

MOVED和ASKING命令
會存在多次重試的情況,因此客戶端的原始碼裡執行指令時都會有一個迴圈,然後設定最大重試次數。

叢集變更感知

  1. 目標節點掛了,客戶端丟擲ConnectionError,緊接著隨機挑一個節點重試,這首被重試節點通過MOVED告訴目標槽位被分配到新的地址
  2. 運維手動修改叢集資訊,將主節點遷移到其他節點,並將舊的主節點移出叢集。這時打在舊主節點的指令會收到ClusterDown錯誤,告知當前節點所在叢集不可用。這時客戶端關閉所有連線,清空slot對映表,向上拋錯。待下一條指令過來時,重新嘗試初始化節點資訊。

相關文章