1. 程式人生 > >分散式Redis

分散式Redis

  • 叢集

先來簡單瞭解下redis中提供的叢集策略, 雖然redis有持久化功能能夠保障redis伺服器宕機也能恢復並且只有少量的資料損失,但是由於所有資料在一臺伺服器上,如果這臺伺服器出現硬碟故障,那就算是有備份也仍然不可避免資料丟失的問題。

在實際生產環境中,我們不可能只使用一臺redis伺服器作為快取伺服器,必須要多臺實現叢集,避免出現單點故障。

  • 主從複製

複製的作用是把redis的資料庫複製多個副本部署在不同的伺服器上,如果其中一臺伺服器出現故障,也能快速遷移到其他伺服器上提供服務。 複製功能可以實現當一臺redis伺服器的資料更新後,自動將新的資料同步到其他伺服器上。

主從複製就是我們常見的master/slave模式, 主資料庫可以進行讀寫操作,當寫操作導致資料發生變化時會自動將資料同步給從資料庫。而一般情況下,從資料庫是隻讀的,並接收主資料庫同步過來的資料。 一個主資料庫可以有多個從資料庫。

  • 全量複製

Redis全量複製一般發生在Slave初始化階段,這時Slave需要將Master上的所有資料都複製一份。

完成上面幾個步驟後就完成了slave伺服器資料初始化的所有操作,slave伺服器此時可以接收來自使用者的讀請求。 

master/slave 複製策略是採用樂觀複製,也就是說可以容忍在一定時間內master/slave資料的內容是不同的,但是兩者的資料會最終同步。具體來說,redis的主從同步過程本身是非同步的,意味著master執行完客戶端請求的命令後會立即返回結果給客戶端,然後非同步的方式把命令同步給slave。這一特徵保證啟用master/slave後 master的效能不會受到影響。

但是另一方面,如果在這個資料不一致的視窗期間,master/slave因為網路問題斷開連線,而這個時候,master是無法得知某個命令最終同步給了多少個slave資料庫。不過redis提供了一個配置項來限制只有資料至少同步給多少個slave的時候,master才是可寫的。

min-slaves-to-write 3 表示只有當3個或以上的slave連線到master,master才是可寫的。

min-slaves-max-lag 10表示允許slave最長失去連線的時間,若10秒還沒收到slave的響應,則master認為該slave已斷開。

  • 增量複製

從redis 2.8開始,就支援主從複製的斷點續傳,如果主從複製過程中,網路連線斷掉了,那麼可以接著上次複製的地方,繼續複製下去,而不是從頭開始複製一份。

master node會在記憶體中建立一個backlog,master和slave都會儲存一個replica offset還有一個master id,offset就是儲存在backlog中的。如果master和slave網路連線斷掉了,slave會讓master從上次的replica offset開始繼續複製但是如果沒有找到對應的offset,那麼就會執行一次全量同步。

  • 無硬碟複製

前面我們說過,Redis複製的工作原理基於RDB方式的持久化實現的,也就是master在後臺儲存RDB快照,slave接收到rdb檔案並載入,但是這種方式會存在一些問題。

1. 當master禁用RDB時,如果執行了複製初始化操作,Redis依然會生成RDB快照,當master下次啟動時執行該RDB檔案的恢復,但是因為複製發生的時間點不確定,所以恢復的資料可能是任何時間點的。就會造成資料出現問題。

2. 當硬碟效能比較慢的情況下(網路硬碟),那初始化複製過程會對效能產生影響因此2.8.18以後的版本,Redis引入了無硬碟複製選項,可以不需要通過RDB檔案去同步,直接傳送資料。

通過以下配置來開啟該功能:repl-diskless-sync yes

master在記憶體中直接建立rdb,然後傳送給slave,不會在自己本地落地磁碟了。

  • sentinel(哨兵)

在master/slave模式裡,一個典型的一主多從的系統中,slave在整個體系中起到了資料冗餘備份和讀寫分離的作用。當master遇到異常終端後,需要從slave中選舉一個新的master繼續對外提供服務,比如在zk中通過leader選舉、kafka中可以基於zk的節點實現master選舉。所以在redis中也需要一種機制去實現master的決策,redis並沒有提供自動master選舉功能,而是需要藉助一個哨兵來進行監控。

  • 什麼是哨兵

哨兵是一個獨立的程序,哨兵的作用就是監控Redis系統的執行狀況。

它的功能包括兩個:

  1. 監控master和slave是否正常執行。
  2. master出現故障時自動將slave資料庫升級為master。

在解決master選舉問題同時,又引出了一個單點問題,也就是哨兵的可用性如何解決?在一個一主多從的Redis系統中,可以使用多個哨兵進行監控任務以保證系統足夠穩定。此時哨兵不僅會監控master和slave,同時還會互相監控,這種方式稱為哨兵叢集,哨兵叢集需要解決故障發現、和master決策的協商機制問題。

  • sentinel之間的相互感知

sentinel節點之間會因為共同監視同一個master從而產生了關聯,若一個新加入的sentinel節點想要和其他監視相同master節點的sentinel相互感知,則要滿足以下幾點:

  1. 需要相互感知的sentinel都向他們共同監視的master節點訂閱channel:sentinel:hello
  2. 新加入的sentinel節點向這個channel釋出一條訊息,包含自己本身的資訊,這樣訂閱了這個channel的sentinel就可以發現這個新的sentinel。
  3. 新加入得sentinel和其他sentinel節點建立長連線master的。
  • master的故障發現

sentinel節點會定期向master節點發送心跳包來判斷存活狀態,一旦master節點沒有正確響應,sentinel會把master設定為“主觀不可用狀態”,然後它會把“主觀不可用”傳送給其他所有的sentinel節點去確認,當確認的sentinel節點數大於>quorum(規定數量)時,則會認為master是“客觀不可用”,接著就開始進入選舉新的master流程。

在選舉新的master之前,需要從sentinel叢集中選擇一個leader來做決策,這裡用到了一致性演算法Raft演算法,它和Paxos演算法類似,都是分散式一致性演算法,基於投票機制,只要保證過半數節點通過提議即可。

  • Redis-Cluster(叢集)

即使是使用哨兵,此時的Redis叢集的每個資料庫依然存有叢集中的所有資料,從而導致叢集的總資料儲存量受限於可用儲存記憶體最小的節點,形成了木桶效應。而因為Redis是基於記憶體儲存的,所以這一個問題在redis中就顯得尤為突出了。

在redis3.0之前,我們是通過在客戶端去做的分片,通過hash環的方式對key進行分片儲存。分片雖然能夠解決各個節點的儲存壓力,但是導致維護成本高、增加、移除節點比較繁瑣。因此在redis3.0以後的版本最大的一個好處就是支援叢集功能,叢集的特點在於擁有和單機例項一樣的效能,同時在網路分割槽以後能夠提供一定的可訪問性以及對主資料庫故障恢復的支援。

哨兵和叢集是兩個獨立的功能,當不需要對資料分片時使用哨兵就夠了,如果要進行水平擴容,叢集是一個較好的方式。

  • 拓撲結構

一個Redis Cluster由多個Redis節點構成。不同節點組服務的資料沒有交集,也就是每個一節點組對應資料sharding的一個分片。節點組內部分為主備兩類節點,對應master和slave節點。兩者資料準實時一致,通過非同步化的主備複製機制來保證。一個節點組有且只有一個master節點,同時可以有0到多個slave節點,在這個節點組中只有master節點對使用者提供寫服務,讀服務可以由master或者slave提供,當master掛掉,會從slave中選出一個成為新的master。

  • Redis的資料分割槽

分散式資料庫首要解決把整個資料集按照分割槽規則對映到多個節點的問題,即把資料集劃分到多個節點上,每個節點負責整個資料的一個子集, Redis Cluster採用雜湊分割槽規則,採用虛擬槽分割槽。

虛擬槽分割槽巧妙地使用了雜湊空間,使用分散度良好的雜湊函式把所有的資料對映到一個固定範圍內的整數集合,整數定義為槽(slot)。比如Redis Cluster槽的範圍是0 ~ 16383。槽是叢集內資料管理和遷移的基本單位。採用大範圍的槽的主要目的是為了方便資料的拆分和叢集的擴充套件,每個節點負責一定數量的槽。計算公式:slot = CRC16(key)%16383。每一個節點負責維護一部分槽以及槽所對映的鍵值資料。

  • HashTags

有些時候我們希望某一部分key可以落到同一個資料分片上。

例如對於使用者資訊:user:user1:id、user:user1:name;

通過hashtag的方式可以表示為:user:{user1}:id、user:{user1}.name;

當一個key包含 {} 的時候,就不對整個key做hash,而僅對 {} 包括的字串做hash。

  • 重定向客戶端

Redis Cluster並不會代理查詢,那麼如果客戶端訪問了一個key並不存在的節點,這個節點是怎麼處理的呢?比如我想獲取key為msg的值,msg計算出來的槽編號為254,當前節點正好不負責編號為254的槽,那麼就會返回客戶端下面資訊:-MOVED 254 127.0.0.1:6381。表示客戶端想要的254槽由執行在IP為127.0.0.1、埠為6381的Master例項服務。如果根據key計算得出的槽恰好由當前節點負責,則當期節點會立即返回結果。

  • 分片遷移

在一個穩定的Redis cluster下,每一個slot對應的節點是確定的,但是在某些情況下,節點和分片對應的關係會發生變更。也就是說當動態新增或減少node節點時,需要將16384個槽做個再分配,槽中的鍵值也要遷移。

一. 新增一個主節點

新增一個節點D,redis cluster的這種做法是從各個節點的前面各拿取一部分slot到D上。大致就會變成這樣:

節點A覆蓋1365-5460

節點B覆蓋6827-10922

節點C覆蓋12288-16383

節點D覆蓋0-1364,5461-6826,10923-12287

二. 刪除一個主節點

先將節點的資料移動到其他節點上,然後才能執行刪除。

  • 槽遷移的過程

槽遷移的過程中有一個不穩定狀態,這個不穩定狀態會有一些規則,這些規則定義客戶端的行為,從而使得RedisCluster不必宕機的情況下可以執行槽的遷移。

下面這張圖描述了我們遷移編號為1、2、3的槽的過程中,他們在MasterA節點和MasterB節點中的狀態。