Redis全面解析二:redis高可用高併發集羣方案

前言

Redis 緩存作爲使用最多的緩存工具被各大廠商爭相使用。通常我們會使用單體的 Redis 應用作爲緩存服務,然而我們日常在對於redis的使用中,經常會遇到一些問題:

  • 高可用問題,如何保證redis的持續高可用性。
  • 容量問題,單實例redis內存無法無限擴充,達到32G後就進入了64位世界,性能下降。
  • 併發性能問題,redis號稱單實例10萬併發,但也是有盡頭的。

如果只使用一個redis實例時,其中保存了服務器中全部的緩存數據,這樣會有很大風險,如果單臺redis服務宕機了將會影響到整個服務。解決的方法就是我們可以採用分片/分區的技術,將原來一臺服務器維護的整個緩存,現在換爲由多臺服務器共同維護內存空間。下面對高可用和高併發處理方案進行介紹。

redis分片原理

redis分片概念:按照某種規則去劃分數據,分散存儲在多個節點上。通過將數據分到多個 Redis 服務器上,來減輕單個 Redis 服務器的壓力。

分片思路:採用在一臺主機上實現分片的方式,所以只需要在該主機上配置啓動三臺redis的實例即可。因爲redis默認使用的端口號爲6379,所以這裏我們分別使用6379、6380以及6381三個端口來實現。

分片算法:分片方式雖然解決了高可用的問題,但是分片後數據如何均勻的分步到每個機器上呢?

【重點】實現數據均勻分佈在多個節點方式:

①普通的HASH算法

通常的做法就是獲取節點的 Hash 值,然後根據節點數求模。 hash(key) % length

但是當緩存服務器變化時(宕機或新增節點),length字段變化,導致所有緩存的數據需要重新進行HASH運算,這樣就導致原來的哪些數據訪問不到了。而這段時間如果訪問量上升了,容易引起服務器雪崩。因此,引入了一致性哈希算法。

②一致性 Hash 算法

該算法對 2^32 取模,將 Hash 值空間組成虛擬的圓環,整個圓環按順時針方向組織,每個節點依次爲 0、1、2…2^32-1。

  • 將每個服務器進行 Hash 運算,對 2^32 取模,確定服務器在這個 Hash 環上的地址,確定了服務器地址後,對數據使用同樣的 Hash 算法,將數據定位到特定的 Redis 服務器上。
  • 如果定位到的地方沒有 Redis 服務器實例,則繼續順時針尋找,找到的第一臺服務器即該數據最終的服務器位置。

Redisä»å¥é¨å°ç²¾éï¼è³å°è¦ççè¿ç¯

Hash 環的數據傾斜問題:

Hash 環在服務器節點很少的時候,容易遇到服務器節點不均勻的問題,這會造成數據傾斜,數據傾斜指的是被緩存的對象大部分集中在 Redis 集羣的其中一臺或幾臺服務器上。

Redisä»å¥é¨å°ç²¾éï¼è³å°è¦ççè¿ç¯

如上圖,一致性 Hash 算法運算後的數據大部分被存放在 A 節點上,而 B 節點只存放了少量的數據,久而久之 A 節點將被撐爆。我們可以通過虛擬節點的方式解決Hash環的偏移。

  如果想要均衡的將緩存分佈到這三臺服務器上,最好能讓這三臺服務器儘量多的,均勻的出現在Hash環上,但是,真實的服務器資源只有3臺,那麼如何憑空的讓他們多起來呢?

做法就是既然沒有多餘的真正的物理服務器節點,我們就可能將現有的物理節點通過虛擬的方法複製出來,而被複製出來的節點被稱爲“虛擬節點”

簡單地說,就是爲每一個服務器節點計算多個 Hash,每個計算結果位置都放置一個此服務器節點,稱爲虛擬節點,可以在服務器 IP 或者主機名後放置一個編號實現。例如上圖:將 NodeA 和 NodeB 兩個節點分爲 Node A#1-A#3,NodeB#1-B#3。

一致性Hash能解決容災問題嗎?

我們假設有5臺服務器,有a,f,c對象映射在A服務器上,r,t,v對象映射在E服務器上。如下圖:

假如此時E服務器宕機了,其他服務器的對象仍然能被命中,因爲對象的映射到服務器的位置已經固定了,不會出現因爲宕機而讓對象找不到。而宕機E上的對象會在下次容災分配的時候,會把r,t,v這些對象重新分配到就近的服務器上,如下圖:

分區的不足:

  1. 分區是多臺redis共同作用的,如果其中一臺出現了宕機現象,則整個分片都將不能使用,雖然是在一定程度上緩減了內存的壓力,但是沒有實現高可用。
  2. 涉及多個key的操作通常是不被支持的。舉例來說,當兩個set映射到不同的redis實例上時,你就不能對這兩個set執行交集操作。
  3. 涉及多個key的redis事務不能使用。
  4. 當使用分區時,數據處理較爲複雜,比如你需要處理多個rdb/aof文件,並且從多個實例和主機備份持久化文件。

高可用的解決方案:可以採用哨兵機制實現主從複製從而實現高可用。

redis-cluster 常用方案

所謂集羣,就是通過添加服務器的數量,提供相同的服務,從而使服務器達到一個穩定、高效的狀態。爲什麼要使用Redis集羣?

  • 因爲單臺的Redis服務器一旦宕機,就無法正常的提供服務了;
  • 單臺Redis服務器的讀寫性能有限,利用集羣可以提高讀寫能力

總結起來使用集羣的原因可以歸爲提高服務器的穩定性和提高讀寫能力。

(1)主從模式

【主從同步,讀寫分離】

像MySQL一樣,redis是支持主從同步的,而且也支持一主多從以及多級從結構。特點:

  1. 主從結構,一是爲了純粹的冗餘備份,二是爲了提升讀性能,主服務器負責寫,從服務器負責讀。
  2. redis的主從同步是異步進行的,這意味着主從同步不會影響主邏輯,也不會降低redis的處理性能。
  3. 主從架構中,可以考慮關閉主服務器的數據持久化功能,只讓從服務器進行持久化,這樣可以提高主服務器的處理性能。

因此,主從模型可以很大的提高數據庫讀的能力,也能間接的提高寫的能力,由於在Slave中分擔Master讀的壓力,使Master中有更多的資源可以分配到寫資源中。

【主從同步原理】

①全量複製

從服務器會向主服務器發出SYNC指令,當主服務器接到此命令後,就會調用BGSAVE指令來創建一個子進程專門進行數據持久化工作,也就是將主服務器的數據寫入RDB文件中。在數據持久化期間,主服務器將執行的寫指令都緩存在內存中。

在BGSAVE指令執行完成後,主服務器會將持久化好的RDB文件發送給從服務器,從服務器接到此文件後會將其存儲到磁盤上,然後再將其讀取到內存中。這個動作完成後,主服務器會將這段時間緩存的寫指令再以redis協議的格式發送給從服務器。

另外,要說的一點是,即使有多個從服務器同時發來SYNC指令,主服務器也只會執行一次BGSAVE,然後把持久化好的RDB文件發給多個下游。

②部分複製

部分複製是Redis 2.8以後出現的,用於處理在主從複製中因網絡閃斷等原因造成的數據丟失場景,當從節點再次連上主節點後,如果條件允許,主節點會補發丟失數據給從節點。因爲補發的數據遠遠小於全量數據,可以有效避免全量複製的過高開銷

在redis2.8版本之前,如果從服務器與主服務器因某些原因斷開連接的話,都會進行一次主從之間的全量的數據同步;而在2.8版本之後,redis支持了效率更高的增量同步策略,這大大降低了連接斷開的恢復成本。

  1. 主服務器會在內存中維護一個緩衝區,緩衝區中存儲着將要發給從服務器的內容。
  2. 從服務器在與主服務器出現網絡瞬斷之後,從服務器會嘗試再次與主服務器連接。
  3. 一旦連接成功,從服務器就會把“希望同步的主服務器ID”和“希望請求的數據的偏移位置(replication offset)”發送出去。
  4. 主服務器接收到這樣的同步請求後,首先會驗證主服務器ID是否和自己的ID匹配,其次會檢查“請求的偏移位置”是否存在於自己的緩衝區中,如果兩者都滿足的話,主服務器就會向從服務器發送增量內容。

增量同步功能,需要服務器端支持全新的PSYNC指令。這個指令,只有在redis-2.8之後才具有。

注:服務器運行ID(run_id):每個Redis節點(無論主從),在啓動時都會自動生成一個隨機ID(每次啓動都不一樣),由40個隨機的十六進制字符組成;run_id用來唯一識別一個Redis節點。 通過info server命令,可以查看節點的run_id。

【主從同步數據不一致問題】

主庫進行寫操作,同時同步從庫,這時還沒有完成主從同步,新來個查請求會在從庫查詢到舊值,完後數據完成主從同步。解決方案:

  • 雙刪延時策略,睡眠時間修改爲在主從同步的延時時間基礎上,加幾百ms。
  • 常見的方法是引入中間件,業務層不直接訪問數據庫,而是通過中間件訪問數據庫,這個中間件會記錄哪一些key上發生了寫請求,在數據主從同步時間窗口之內,如果key上又出了讀請求,就將這個請求也路由到主庫上去,使用這個方法來保證數據的一致性。
  • 讀寫都路由到主庫,從庫只做備份。

附上鍊接:https://blog.csdn.net/zdy0_2004/article/details/50565117

【擴展1】數據庫和緩存的不一致問題的解決方案:

兩個實踐:第一個是緩存雙淘汰機制,第二個是建議爲所有item設定過期時間(前提是允許cache miss)。

(1)緩存雙淘汰,傳統的玩法在進行寫操作的時候,先淘汰cache再寫主庫。上文提到,在主從同步時間窗口之內可能有髒數據入cache,此時如果再發起一個異步的淘汰,即使不一致時間窗內臟數據入了cache,也會再次淘汰掉。 

public void write(String key,Object data){
        redis.delKey(key);
        db.updateData(data);
        Thread.sleep(1000);
        redis.delKey(key);
}

這個1秒怎麼確定的,具體該休眠多久呢?

針對上面的情形,讀者應該自行評估自己的項目的讀數據業務邏輯的耗時。然後寫數據的休眠時間則在讀數據業務邏輯的耗時基礎上,加幾百ms即可。這麼做的目的,就是確保讀請求結束,寫請求可以刪除讀請求造成的緩存髒數據。

(2)爲所有item設定超時時間,例如10分鐘。極限時序下,即使有髒數據入cache,這個髒數據也最多存在十分鐘。帶來的副作用是,可能每十分鐘,這個key上有一個讀請求會穿透到數據庫上,但我們認爲這對數據庫的從庫壓力增加是非常小的。

【擴展2】redis持久化機制

聊了主從同步問題,避免不了redis持久化問題。Redis 將數據存儲在內存而不是磁盤中,所以內存一旦斷電,Redis 中存儲的數據也隨即消失,這往往是用戶不期望的,所以 Redis 有持久化機制來保證數據的安全性。redis提供了兩種持久化的方式,分別是RDB(Redis DataBase)和AOF(Append Only File)。

①聊聊redis持久化 – RDB

RDB方式,是將redis某一時刻的數據持久化到磁盤中,是一種快照式的持久化方法。

redis在進行數據持久化的過程中,會先將數據寫入到一個臨時文件中,待持久化過程都結束了,纔會用這個臨時文件替換上次持久化好的文件。正是這種特性,讓我們可以隨時來進行備份,因爲快照文件總是完整可用的。

對於RDB方式,redis會單獨創建(fork)一個子進程來進行持久化,而主進程是不會進行任何IO操作的,這樣就確保了redis極高的性能。

RDB 持久化方式的缺點如下:

  • 內存數據全量同步,數據量大的狀況下,會由於 I/O 而嚴重影響性能。
  • 可能會因爲 Redis 宕機而丟失從當前至最近一次快照期間的數據。

RDB指令:

SAVE:阻塞 Redis 的服務器進程,直到 RDB 文件被創建完畢。SAVE 命令很少被使用,因爲其會阻塞主線程來保證快照的寫入,由於 Redis 是使用一個主線程來接收所有客戶端請求,這樣會阻塞所有客戶端請求。

BGSAVE:該指令會 Fork 出一個子進程來創建 RDB 文件,不阻塞服務器進程,子進程接收請求並創建 RDB 快照,父進程繼續接收客戶端的請求。

子進程在完成文件的創建時會向父進程發送信號,父進程在接收客戶端請求的過程中,在一定的時間間隔通過輪詢來接收子進程的信號。

我們也可以通過使用 lastsave 指令來查看 BGSAVE 是否執行成功,lastsave 可以返回最後一次執行成功 BGSAVE 的時間。 

②聊聊redis持久化 – AOF

相對 RDB 來說,RDB 持久化是通過備份內存中某一時刻數據的全量快照實現持久化,而 AOF 持久化是備份數據庫接收到的寫指令,在數據恢復時按照從前到後的順序再將指令都執行一遍,就這麼簡單:

  • AOF 記錄除了查詢以外的所有變更數據庫狀態的指令。
  • 以增量的形式追加保存到 AOF 文件中。

因爲採用了追加方式,如果不做任何處理的話,AOF文件會變得越來越大,爲此,redis提供了AOF文件重寫(rewrite)機制,即當AOF文件的大小超過所設定的閾值時,redis就會啓動AOF文件的內容壓縮,只保留可以恢復數據的最小指令集。舉個例子或許更形象,假如我們調用了100次INCR指令,在AOF文件中就要存儲100條指令,但這明顯是很低效的,完全可以把這100條指令合併成一條SET指令,這就是重寫機制的原理。

重寫過程如下:

  • 調用 fork(),創建一個子進程。
  • 子進程把新的 AOF 寫到一個臨時文件裏,不依賴原來的 AOF 文件。
  • 主進程持續將新的變動同時寫到內存和原來的 AOF 裏。
  • 主進程獲取子進程重寫 AOF 的完成信號,往新 AOF 同步增量變動。
  • 使用新的 AOF 文件替換掉舊的 AOF 文件。

注:fork() 在 Linux 中創建子進程採用 Copy-On-Write(寫時拷貝技術),即如果有多個調用者同時要求相同資源(如內存或磁盤上的數據存儲)。他們會共同獲取相同的指針指向相同的資源,直到某個調用者試圖修改資源的內容時,系統纔會真正複製一份專用副本給調用者,而其他調用者所見到的最初的資源仍然保持不變。

開啓 AOF 持久化指令:

①打開 redis.conf 配置文件,將 appendonly 屬性改爲 yes。

②修改 appendfsync 屬性,該屬性可以接收三種參數,分別是 always,everysec,no。

always 表示總是即時將緩衝區內容寫入 AOF 文件當中,everysec 表示每隔一秒將緩衝區內容寫入 AOF 文件,no 表示將寫入文件操作交由操作系統決定。

一般來說,操作系統考慮效率問題,會等待緩衝區被填滿再將緩衝區數據寫入 AOF 文件中。

AOF 和 RDB 的優缺點如下:

  • RDB 優點:全量數據快照,文件小,恢復快。
  • RDB 缺點:無法保存最近一次快照之後的數據。
  • AOF 優點:可讀性高,適合保存增量數據,數據不易丟失。
  • AOF 缺點:文件體積大,恢復時間長。

RDB-AOF 混合持久化方式

Redis 4.0 之後推出了此種持久化方式,RDB 作爲全量備份,AOF 作爲增量備份,並且將此種方式作爲默認方式使用。

問題:在上述兩種方式中,RDB 方式是將全量數據寫入 RDB 文件,這樣寫入的特點是文件小,恢復快,但無法保存最近一次快照之後的數據,AOF 則將 Redis 指令存入文件中,這樣又會造成文件體積大,恢復時間長等弱點。

方案:在 RDB-AOF 方式下,持久化策略首先將緩存中數據以 RDB 方式全量寫入文件,再將寫入後新增的數據以 AOF 的方式追加在 RDB 數據的後面,在下一次做 RDB 持久化的時候將 AOF 的數據重新以 RDB 的形式寫入文件。

可以說,在此種方式下的持久化文件,前半段是 RDB 格式的全量數據,後半段是 AOF 格式的增量數據。此種方式是目前較爲推薦的一種持久化方式。

這種方式既可以提高讀寫和恢復效率,也可以減少文件大小,同時可以保證數據的完整性。

 

(2)主從同步+哨兵模式

Redis的主從模式配置簡單,在提高單臺服務器數據庫讀性能的同時,也能間接性的提高寫的能力;與此同時,它的弊端也顯而易見,那就是當主節點Master宕機後,整個集羣就沒有可寫的節點了,爲此就衍生出了一種新的模式,Sentinel(哨兵模式)

什麼是哨兵機制?

Redis的哨兵(sentinel) 機制用於管理多個 Redis 服務器,該系統執行以下三個任務:

  • 監控(Monitoring): 哨兵(sentinel) 會不斷地檢查你的Master和Slave是否運作正常。
  • 提醒(Notification):當被監控的某個 Redis出現問題時, 哨兵(sentinel) 可以通過 API 向管理員或者其他應用程序發送通知。
  • 自動故障遷移(Automatic failover):當一個Master不能正常工作時,哨兵(sentinel) 會開始一次自動故障遷移操作,它會將失效Master的其中一個Slave升級爲新的Master, 並讓失效Master的其他Slave改爲複製新的Master; 當客戶端試圖連接失效的Master時,集羣也會向客戶端返回新Master的地址,使得集羣可以使用Master代替失效Master。

哨兵機制原理

哨兵(sentinel) 是一個分佈式系統,你可以在一個架構中運行多個哨兵(sentinel) 進程, 這些進程使用流言協議(gossipprotocols)來接收關於Master是否下線的信息,並使用投票協議(agreement protocols)來決定是否執行自動故障遷移,以及選擇哪個Slave作爲新的Master。

      每個哨兵(sentinel) 會向其它哨兵(sentinel)、master、slave定時發送消息,以確認對方是否”活”着,如果發現對方在指定時間(可配置)內未迴應,則暫時認爲對方已掛(所謂的”主觀認爲宕機” Subjective Down,簡稱sdown).

若“哨兵羣”中的多數sentinel,都報告某一master沒響應,系統才認爲該master"徹底死亡"(即:客觀上的真正down機,Objective Down,簡稱odown),通過一定的vote算法,從剩下的slave節點中,選一臺提升爲master,然後自動修改相關配置。

注:選點的依據依次是:網絡連接正常->5秒內回覆過INFO命令->10*down-after-milliseconds內與主連接過的->從服務器優先級->複製偏移量->運行id較小的。選出之後通過slaveif no ont將該從服務器升爲新主服務器。

附上鍊接:https://blog.csdn.net/u012240455/article/details/81843714

(3)Redis官方集羣方案 Redis Cluster

 Redis在3.0版正式引入了集羣這個特性。Redis集羣是一個分佈式(distributed)、容錯(fault-tolerant)的 Redis內存K/V服務, 集羣可以使用的功能是普通單機 Redis 所能使用的功能的一個子集(subset),比如Redis集羣並不支持處理多個keys的命令,因爲這需要在不同的節點間移動數據,從而達不到像Redis那樣的性能,在高負載的情況下可能會導致不可預料的錯誤。

集羣介紹:

Redis集羣的幾個重要特徵:
  (1). Redis 集羣的分片特徵在於將鍵空間分拆了16384個槽位,每一個節點負責其中一些槽位。
  (2). Redis提供一定程度的可用性,可以在某個節點宕機或者不可達的情況下繼續處理命令.
  (3). Redis 集羣中不存在中心(central)節點或者代理(proxy)節點, 集羣的其中一個主要設計目標是達到線性可擴展性(linear scalability)。

Redis 集羣沒有使用一致性hash, 而是引入了 哈希槽的概念.Redis 集羣有16384個哈希槽(slot,每個key通過CRC16校驗後對16384取模來決定放置哪個槽.集羣的每個節點負責一部分hash槽,也就是說,*** 每個slot都對應一個node負責處理。當動態添加或減少node節點時,需要將16384個槽做個再分配,槽中的鍵值也要遷移 ***。舉個例子,比如當前集羣有3個節點,那麼:

  • 節點 A 包含 0 到 5500號哈希槽.
  • 節點 B 包含5501 到 11000 號哈希槽.
  • 節點 C 包含11001 到 16384號哈希槽.

這種結構很容易添加或者刪除節點. 比如如果我想新添加個節點D, 我需要從節點 A, B, C中得部分槽到D上. 如果我想移除節點A,需要將A中的槽移到B和C節點上,然後將沒有任何槽的A節點從集羣中移除即可. 由於從一個節點將哈希槽移動到另一個節點並不會停止服務,所以無論添加刪除或者改變某個節點的哈希槽的數量都不會造成集羣不可用的狀態.

Redis Cluster特點如下:

  • 所有的節點相互連接;
  • 集羣消息通信通過集羣總線通信,集羣總線端口大小爲客戶端服務端口+10000,這個10000是固定值;
  • 節點與節點之間通過二進制協議進行通信;
  • 客戶端和集羣節點之間通信和通常一樣,通過文本協議進行;
  • 集羣節點不會代理查詢;

Redis Cluster的新節點識別能力、故障判斷及故障轉移能力是通過集羣中的每個node都在和其它nodes進行通信,這被稱爲集羣總線(cluster bus)。它們使用特殊的端口號,即對外服務端口號加10000。例如如果某個node的端口號是6379,那麼它與其它nodes通信的端口號是16379。

集羣數據分佈過程:

緩存信息通常是用 Key-Value 的方式來存放的,在存儲信息的時候,集羣會對 Key 進行 CRC16 校驗並對 16384 取模(slot = CRC16(key)%16383)。得到的結果就是 Key-Value 所放入的槽,從而實現自動分割數據到不同的節點上。然後再將這些槽分配到不同的緩存節點中保存。

Redis Cluster åç详解ï¼åºä»é¢è¯å®å°±çè¿ä¸ç¯ï¼

如圖 所示,假設有三個緩存節點分別是 1、2、3。Redis Cluster 將存放緩存數據的槽(Slot)分別放入這三個節點中:
緩存節點 1 存放的是(0-5000)Slot 的數據。
緩存節點 2 存放的是(5001-10000)Slot 的數據。
緩存節點 3 存放的是(10000-16383)Slot 的數據。

此時 Redis Client 需要根據一個 Key 獲取對應的 Value 的數據,首先通過 CRC16(key)%16383 計算出 Slot 的值,假設計算的結果是 5002。將這個數據傳送給 Redis Cluster,集羣接受到以後會到一個對照表中查找這個 Slot=5002 屬於那個緩存節點。

發現屬於“緩存節點 2”,於是順着紅線的方向調用緩存節點 2 中存放的 Key-Value 的內容並且返回給 Redis Client。

哈希槽與節點映射關係:

Redis Cluster中有一個16384長度的槽的概念,他們的編號爲0、1、2、3……16382、16383。這個槽是一個虛擬的槽,並不是真正存在的。正常工作的時候,Redis Cluster中的每個Master節點都會負責一部分的槽當有某個key被映射到某個Master負責的槽,那麼這個Master負責爲這個key提供服務,至於哪個Master節點負責哪個槽,這是可以由用戶指定的,也可以在初始化的時候自動生成(redis-trib.rb腳本)。這裏值得一提的是,在Redis Cluster中,只有Master才擁有槽的所有權,如果是某個Master的slave,這個slave只負責槽的使用,但是沒有所有權。Redis Cluster怎麼知道哪些槽是由哪些節點負責的呢?某個Master又怎麼知道某個槽自己是不是擁有呢?

Redis Cluster åç详解ï¼åºä»é¢è¯å®å°±çè¿ä¸ç¯ï¼

Redis Cluster åç详解ï¼åºä»é¢è¯å®å°±çè¿ä¸ç¯ï¼

Master節點維護着一個16384/8字節的位序列,由於每個字節包含 8 個 bit 位(二進制位),所以共包含 16384 個 bit,也就是 16384 個二進制位。Master節點用bit來標識對於某個槽自己是否擁有。假設這個圖表示節點 A 所管理槽的情況。

通過二進制數組存放槽信息0、1、2 三個數組下標就表示 0、1、2 三個槽,如果對應的二進制值是 1,表示該節點負責存放 0、1、2 三個槽的數據。同理,後面的數組下標位 0 就表示該節點不負責存放對應槽的數據。用二進制存放的優點是,判斷的效率高,例如對於編號爲 1 的槽,節點只要判斷序列的第二位,時間複雜度爲 O(1)。

【重點】高併發高可用 Redis Cluster

Redis集羣,要保證16384個槽對應的node都正常工作,如果某個node發生故障,那它負責的slots也就失效,整個集羣將不能工作。
爲了增加集羣的可訪問性,官方推薦的方案是將node配置成主從結構,即一個master主節點,掛n個slave從節點。這時,如果主節點失效,Redis Cluster會根據選舉算法從slave節點中選擇一個上升爲主節點,整個集羣繼續對外提供服務,Redis Cluster本身提供了故障轉移容錯的能力。

redis-cluster

圖中描述的是六個redis實例構成的集羣

6379端口爲客戶端通訊端口,16379端口爲集羣總線端口。集羣內部劃分爲16384個數據分槽,分佈在三個主redis中。

從redis中沒有分槽,不會參與集羣投票,也不會幫忙加快讀取數據,僅僅作爲主機的備份。三個主節點中平均分佈着16384數據分槽的三分之一,每個節點中不會存有有重複數據,僅僅有自己的從機幫忙冗餘。

其中三個節點對應16384個哈希槽,下面以6個槽爲列進行說明:

從這種redis cluster的架構圖中可以很容易的看出首先將數據根據hash規則分配到6個slot中(這裏只是舉例子分成了6個槽),然後根據CRC算法和取模算法將6個slot分別存儲到3個不同的Master節點中,每個master節點又配套部署了一個slave節點,當一個master出現問題後,slave節點可以頂上。這種cluster的方案對比第一種簡單的主從方案的優點在於提高了讀寫的併發,分散了IO,在保障高可用的前提下提高了性能。

(4)codis集羣方案

Codis是一個豌豆莢團隊開源的使用Go語言編寫的Redis Proxy使用方法和普通的redis沒有任何區別,設置好下屬的多個redis實例後就可以了,使用時在本需要連接redis的地方改爲連接codis,它會以一個代理的身份接收請求 並使用一致性hash算法,將請求轉接到具體redis,將結果再返回codis,和之前比較流行的twitter開源的Twemproxy功能類似,但是相比官方的redis cluster和twitter的Twemproxy還是有一些獨到的優勢

優點:Codis一個比較大的優點是可以不停機動態新增或刪除數據節點,舊節點的數據也可以自動恢復到新節點。並且提供圖形化的dashboard,方便集羣管理。

codis方案推出的時間比較長,而且國內很多互聯網公司都已經使用了該集羣方案,所以該方案還是比較適合大型互聯網系統使用的,畢竟成功案例比較多,但是codis因爲要實現slot切片,所以修改了redis-server的源碼,對於後續的更新升級也會存在一定的隱患。,但是codis的穩定性和高可用確實是目前做的最好的,只要有足夠多的機器能夠做到非常好的高可用緩存系統。

對於小規模應用最好還是使用官方的cluster方案,在redis3.0後官方社區也在逐步完善cluster方案,小團隊可以隨着官方版本的升級享受功能和穩定性的提升,也便於日後的維護。

擴展

1、故障機制

 (1)心跳和gossip消息

  Redis Cluster持續的交換PING和PONG數據包。這兩種數據包的數據結構相同,都包含重要的配置信息,唯一的不同是消息類型字段。PING和PONG數據包統稱爲心跳數據包。

  每個節點在每一秒鐘都向一定數量的其它節點發送PING消息,這些節點應該向發送PING的節點回復一個PONG消息。節點會儘可能確保擁有每個其它節點在NOTE_TIMEOUT/2秒時間內的最新信息,否則會發送一個PING消息,以確定與該節點的連接是否正常。

  假定一個Cluster有301個節點,NOTE_TIMEOUT爲60秒,那麼每30秒每個節點至少發送300個PING,即每秒10個PING, 整個Cluster每秒發送10x301=3010個PING。這個數量級的流量不應該會造成網絡負擔。

(2)故障檢測。

Redis Cluster的故障檢測用於檢測一個master節點何時變得不再有效,即不能提供服務,從而應該讓slave節點提升爲master節點。如果提升失敗,則整個Cluster失效,不再接受客戶端的服務請求。

Redis Cluster故障檢測機制最終應該讓所有節點都一致同意某個節點處於某個確定的狀態。如果發生這樣的情況少數節點確信某個節點爲FAIL,同時有少數節點確認某個節點爲非FAIL,則Redis Cluster最終會處於一個確定的狀態:

  • 情況1:最終大多數節點認爲該節點FAIL,該節點最終實際爲FAIL。
  • 情況2:最終在N x NODE_TIMEOUT時間內,仍然只有少數節點將給節點標記爲FAIL,此時最終會清除這個節點的FAIL標誌。

2、重定向客戶端

  文章開始講到,Redis Cluster並不會代理查詢,那麼如果客戶端訪問了一個key並不存在的節點,這個節點是怎麼處理的呢?比如我想獲取key爲msg的值,msg計算出來的槽編號爲254,當前節點正好不負責編號爲254的槽,那麼就會返回客戶端下面信息:

GET msg
-MOVED 254 127.0.0.1:6381

表示客戶端想要的254槽由運行在IP爲127.0.0.1,端口爲6381的Master實例服務。如果根據key計算得出的槽恰好由當前節點負責,則當期節點會立即返回結果。這裏明確一下,沒有代理的Redis Cluster可能會導致客戶端兩次連接急羣中的節點才能找到正確的服務,推薦客戶端緩存連接,這樣最壞的情況是兩次往返通信。

3、slots配置傳播

  Redis Cluster採用兩種方式進行各個master節點的slots配置信息的傳播。所謂slots配置信息,即master負責存儲哪幾個slots。

  • (1)心跳消息,在PING/PONG消息中包含了所負責的slots配置信息。
  • (2)UPDATE消息,當一個節點收到PING/PONG消息後,如果發現發送者的世代小於自己的世代,則向其發送UPDATE消息,來更新其有可能已經過時的slots配置信息。如果發現發送者的世代大於自己的世代,則用消息中的slots配置信息更新自己的slots配置信息。 

總結

(1)所有的redis節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬.

(2)節點的fail是通過集羣中超過半數的節點檢測失效時才生效.

(3)客戶端與redis節點直連,不需要中間proxy層.客戶端不需要連接集羣所有節點,連接集羣中任何一個可用節點即可

(4)redis-cluster把所有的物理節點映射到[0-16383]slot上,cluster 負責維護node<->slot<->value

Redis 集羣中內置了 16384 個哈希槽,當需要在 Redis 集羣中放置一個 key-value 時,redis 先對 key 使用 crc16 算法算出一個結果,然後把結果對 16384 求餘數,這樣每個 key 都會對應一個編號在 0-16383 之間的哈希槽,redis 會根據節點數量大致均等的將哈希槽映射到不同的節點。

至此redis高併發高可用集羣方案介紹完畢 ,如有疑問歡迎交流。最後提出一個問題點:Redis 集羣中爲什麼內置 16384 個哈希槽呢?下期見
 

文章參考:

https://www.jianshu.com/p/1ecbd1a88924

https://www.cnblogs.com/kerwinC/p/6611634.html

https://www.cnblogs.com/huangfuyuan/p/9880379.html

https://blog.51cto.com/14279308/2484807?source=drh



 

       

      

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章