Redis專案實戰---應用及理論(二)---Redis叢集原理

fullStack發表於2019-07-29

一、 Redis官方推薦叢集方案:Redis Cluster

      適用於redis3.0以後版本,

       redis cluster 是redis官方提供的分散式解決方案,在3.0版本後推出的,有效地解決了redis分散式的需求,當一個redis節點掛了可以快速的切換到另一個節點。
  架構細節:
  (1)所有的redis節點彼此互聯(PING-PONG機制),內部使用二進位制協議優化傳輸速度和頻寬.
  (2)節點的fail是通過叢集中超過半數的節點檢測失效時才生效.
  (3)客戶端與redis節點直連,不需要中間proxy層.客戶端不需要連線叢集所有節點,連線叢集中任何一個可用節點即可
  (4)redis-cluster把所有的物理節點對映到[0-16383]slot上,cluster 負責維護node<->slot<->value
 
      redis-cluster選舉:容錯
  (1)領著選舉過程是叢集中所有master參與,如果半數以上master節點與master節點通訊超過(cluster-node-timeout),認為當前master節點掛掉.
  (2):什麼時候整個叢集不可用(cluster_state:fail),當叢集不可用時,所有對叢集的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)錯誤
      a:如果叢集任意master掛掉,且當前master沒有slave.叢集進入fail狀態,也可以理解成進群的slot對映[0-16383]不完成時進入fail狀態.
      b:如果進群超過半數以上master掛掉,無論是否有slave叢集進入fail狀態.
 
 
      相關概念:

  Redis群集資料分片

·  Redis Cluster不使用一致的雜湊,而是使用不同形式的分片,其中每個鍵稱之為 hash slot.

   Redis叢集中有16384個雜湊槽,為了計算給定金鑰的雜湊槽,我們只需採用金鑰模數16384的CRC16。

   Redis群集中的每個節點都負責雜湊槽的子集,例如,擁有一個包含3個節點的叢集,其中:

  •  節點A包含從0到5500的雜湊槽。
  •  節點B包含從5501到11000的雜湊槽。
  •  節點C包含從11001到16383的雜湊槽。

  這允許輕鬆新增和刪除叢集中的節點。例如,如果我想新增一個新節點D,我需要將一些雜湊槽從節點A,B,C移動到D.同樣,如果我想從群集中刪除節點A,我只需移動A服務的雜湊槽。到B和C.當節點A為空時,我可以完全從叢集中刪除它。

因為將雜湊槽從一個節點移動到另一個節點不需要停止操作,新增和刪除節點,或者更改節點所持有的雜湊槽的百分比,所以不需要任何停機時間。

只要涉及單個命令執行(或整個事務或Lua指令碼執行)的所有鍵都屬於同一個雜湊槽,Redis Cluster就支援多個鍵操作。使用者可以通過使用稱為雜湊標記的概念強制多個金鑰成為同一雜湊槽的一部分

Hash標籤記錄在Redis叢集規範中,但要點是如果金鑰中{}括號之間有子字串,則只對字串內部的內容進行雜湊處理,例如this{foo}keyanother{foo}key 保證位於相同的雜湊槽中,並且可以在具有多個鍵作為引數的命令中一起使用。

  Redis Cluster主從模型

   為了在主節點子集發生故障或無法與大多數節點通訊時保持可用,Redis Cluster使用主從模型,其中每個雜湊槽從1(主機本身)到N個副本(N) -1個額外的從節點)。建立叢集時,每個主節點新增一個從節點,以便最終叢集由作為主節點的A,B,C和作為從節點的A1,B1,C1組成。如果節點B出現故障,系統就能繼續執行。節點B1複製B,B失敗,叢集將節點B1升級為新的主節點,並將繼續正常執行。

         注意,如果節點B和B1同時發生故障,Redis Cluster將無法繼續執行。

  Redis群集一致性保證

  Redis Cluster無法保證強一致性在某些條件下,Redis Cluster可能會丟失系統向客戶端確認的寫入。

  Redis Cluster可能丟失寫入的第一個原因是它使用非同步複製。這意味著在寫入期間會發生以下情況:

  • 客戶端寫入master B.
  • master B向客戶端回覆確定。
  • master B將寫入傳播到其從裝置B1,B2和B3。

  (1)B在回覆客戶端之前並沒有等待來自B1,B2,B3的確認,因為這對Redis來說是一個過高的延遲,所以如果客戶端寫了一些東西,B會確認寫入,但是在崩潰之前能夠將寫入傳送到其slave,其中一個slave(沒有接收到寫入)被提升為master ,永遠丟失寫入。

  這與大多數資料庫配置為每秒將資料重新整理到磁碟的所發生的情況非常相似同樣,可以通過在回覆客戶端之前強制資料庫重新整理磁碟上的資料來提高一致性,但會導致效能過低。在Redis Cluster情況下,相當於同步複製。

       解決辦法,即需要在效能和一致性之間進行權衡。

      Redis Cluster在絕對需要時支援同步寫入,通過WAIT命令實現,這使得丟失寫入的可能性大大降低,但即使使用同步複製,Redis Cluster也不會實現強一致性:在更復雜的情況下總是可以實現失敗場景,無法接收寫入的slave被選為master。

      (2)還有另一個值得注意的情況是,Redis叢集將丟失寫入,這種情況發生在網路分割槽中,其中客戶端與少數例項(至少包括主伺服器)隔離。如,

以6個節點簇為例,包括A,B,C,A1,B1,C1,3個主站和3個從站。還有一個客戶,我們稱之為Z1。

在發生分割槽之後,可能在分割槽的一側有A,C,A1,B1,C1,在另一側有B和Z1。

Z1仍然可以寫入B,它將接受其寫入。如果分割槽在很短的時間內恢復,叢集將繼續正常執行。但是,如果分割槽持續足夠的時間使B1在分割槽的多數側被提升為主,則Z1傳送給B的寫入將丟失。

注意,Z1將能夠傳送到B的寫入量存在maximum window:如果分割槽的多數方面已經有足夠的時間將slave選為master,則少數端的每個主節點都會停止接受寫入。

這段時間是Redis Cluster的一個非常重要的配置指令,稱為節點超時

節點超時過後,master被視為失敗,可以由其中一個副本替換。類似地,在節點超時已經過去而主節點無法感知大多數其他主節點之後,它進入錯誤狀態並停止接受寫入。

 
 
 
二、擴充套件:
    Redis3.0之前的叢集相關概念:
   (1)哨兵(Sentinel )機制
    Sentinel(哨兵)是Redis 的高可用性解決方案:由一個或多個Sentinel 例項 組成的Sentinel 系統可以監視任意多個主伺服器,以及這些主伺服器屬下的所有從伺服器,並在被監視的主伺服器進入下線狀態時,自動將下線主伺服器屬下的某個從伺服器升級為新的主伺服器。
    Redis哨兵機制執行流程:
   1):每個Sentinel以每秒鐘一次的頻率向它所知的Master,Slave以及其他 Sentinel 例項傳送一個 PING 命令 
   2):如果一個例項(instance)距離最後一次有效回覆 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 則這個例項會被 Sentinel 標記為主觀下線。 
   3):如果一個Master被標記為主觀下線,則正在監視這個Master的所有 Sentinel 要以每秒一次的頻率確認Master的確進入了主觀下線狀態。 
   4):當有足夠數量的 Sentinel(大於等於配置檔案指定的值)在指定的時間範圍內確認Master的確進入了主觀下線狀態, 則Master會被標記為客觀下線 
   5):在一般情況下, 每個 Sentinel 會以每 10 秒一次的頻率向它已知的所有Master,Slave傳送 INFO 命令 
   6):當Master被 Sentinel 標記為客觀下線時,Sentinel 向下線的 Master 的所有 Slave 傳送 INFO 命令的頻率會從 10 秒一次改為每秒一次 
   7):若沒有足夠數量的 Sentinel 同意 Master 已經下線, Master 的客觀下線狀態就會被移除。 
   若 Master 重新向 Sentinel 的 PING 命令返回有效回覆, Master 的主觀下線狀態就會被移除。
 
     (2)Redis主從複製

  主從複製:主節點負責寫資料,從節點負責讀資料,主節點定期把資料同步到從節點保證資料的一致性

  備註:主從複製和哨兵機制需要進行手動配置。

 

三、Redis作為快取應用問題及解決方案:

     1)快取穿透

      快取穿透是指查詢一個一定不存在的資料,由於快取是不命中時需要從資料庫查詢,查不到資料則不寫入快取,這將導致這個不存在的資料每次請求都要到資料庫去查詢,造成快取穿透。
         解決辦法:
  1. 對所有可能查詢的引數以hash形式儲存,在控制層先進行校驗,不符合則丟棄。還有最常見的則是採用布隆過濾器,將所有可能存在的資料雜湊到一個足夠大的bitmap中,一個一定不存在的資料會被這個bitmap攔截掉,從而避免了對底層儲存系統的查詢壓力。
  2. 也可以採用一個更為簡單粗暴的方法,如果一個查詢返回的資料為空(不管是資料不存在,還是系統故障),我們仍然把這個空結果進行快取,但它的過期時間會很短,最長不超過五分鐘。

     2)快取雪崩

    如果快取集中在一段時間內失效,發生大量的快取穿透,所有的查詢都落在資料庫上,造成了快取雪崩。
         解決辦法:
  1. 在快取失效後,通過加鎖或者佇列來控制讀資料庫寫快取的執行緒數量。比如對某個key只允許一個執行緒查詢資料和寫快取,其他執行緒等待。
  2. 可以通過快取reload機制,預先去更新快取,再即將發生大併發訪問前手動觸發載入快取
  3. 不同的key,設定不同的過期時間,讓快取失效的時間點儘量均勻. 比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鐘隨機,這樣每一個快取的過期時間的重複率就會降低,就很難引發集體失效的事件
  4. 做二級快取,或者雙快取策略。A1為原始快取,A2為拷貝快取,A1失效時,可以訪問A2,A1快取失效時間設定為短期,A2設定為長期。

     3)快取擊穿

   快取被“擊穿”的問題,這個和快取雪崩的區別在於這裡針對某一key快取,前者則是很多key。

     4)快取預熱

  快取預熱就是系統上線後,提前將相關的快取資料直接載入到快取系統。避免在使用者請求的時候,先查詢資料庫,然後再將資料快取的問題!使用者直接查詢事先被預熱的快取資料!
        快取預熱解決方案:
  1. 直接寫個快取重新整理頁面,上線時手工操作下;
  2. 資料量不大,可以在專案啟動的時候自動進行載入;
  3. 定時重新整理快取;

     5)快取更新

  我們知道通過expire來設定key 的過期時間,那麼對過期的資料怎麼處理呢?除了快取伺服器自帶的快取失效策略之外(Redis預設的有6中策略可供選擇),我們還可以根據具體的業務需求進行自定義的快取淘汰,常見的策略有兩種:
  1. 定時去清理過期的快取;
  2. 當有使用者請求過來時,再判斷這個請求所用到的快取是否過期,過期的話就去底層系統得到新資料並更新快取。
  兩者各有優劣,第一種的缺點是維護大量快取的key是比較麻煩的,第二種的缺點就是每次使用者請求過來都要判斷快取失效,邏輯相對比較複雜!具體用哪種方案,大家可以根據自己的應用場景來權衡。

     6)快取降級

  當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的效能時,仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵資料進行自動降級,也可以配置開關實現人工降級。 降級的最終目的是保證核心服務可用,即使是有損的。而且有些服務是無法降級的(如加入購物車、結算)。   在進行降級之前要對系統進行梳理,看看系統是不是可以丟卒保帥;從而梳理出哪些必須誓死保護,哪些可降級;比如可以參考日誌級別設定預案:
  1. 一般:比如有些服務偶爾因為網路抖動或者服務正在上線而超時,可以自動降級;
  2. 警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,併傳送告警;
  3. 錯誤:比如可用率低於90%,或者資料庫連線池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級;
  4. 嚴重錯誤:比如因為特殊原因資料錯誤了,此時需要緊急人工降級。

四、redis作為分散式鎖方案(效能最優)

        分散式鎖是控制分散式系統之間同步訪問共享資源的一種方式。

         實現思路:

  1. 使用SETNX命令獲取鎖,若不存在則設定值,設定成功則表示取得鎖成功;
  2. 設定expire,保證超時後能自動釋放鎖(使用lua指令碼將setnx和expire變成一個原子操作);
  3. 釋放鎖,使用DEL命令將鎖資料刪除。
        或使用Redis官方推薦的redission

 

相關文章