Redis項目實戰(二)---Redis集群原理


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

      注:適用於redis3.0以后版本(官方集群版本);

      redis cluster 是redis官方提供的分布式解決方案,在3.0版本后推出的,有效地解決了redis分布式的需求,當一個redis節點掛了可以快速的切換到另一個節點。
 
   簡介:
          1)設計上使用了去中心化,去中間件,集群中的每個節點都是平等的關系,都是對等的,每個節點都保存各自的數據和整個集群的狀態。
          2)哈希槽設計,使用哈希槽 (hash slot)的方式來分配數據,redis cluster 默認分配了 16384 個slot,每set一個key 時,會用CRC16算法來取模得到所屬的slot,然后將這個key 分到哈希槽區間的節點上,即:CRC16(key) % 16384
  • 優點:去中心化,無中心節點

   (1)數據按照 slot 存儲分布在多個 Redis 實例上,能夠平滑的進行擴容/縮容節點;由於從一個節點將哈希槽移動到另一個節點並不會停止服務,所以無論添加刪除或者改變某個節點的哈希槽的數量都不會造成集群不可用的狀態。

         (2)自動故障轉移(節點之間通過 Gossip 協議交換狀態信息,進行投票機制完成 Slave 到 Master 角色的提升),提高了系統的可擴展性和高可用性。

  • 缺點:嚴重依賴外部 Redis-Trib,缺乏監控管理;需要依賴 Smart Client(連接維護, 緩存路由表, MultiOp 和 Pipeline 支持);Failover 節點的檢測過慢,不如“中心節點 ZooKeeper”及時;Gossip 消息的開銷;無法根據統計區分冷熱數據;Slave“冷備”,不能緩解讀壓力。      
 架構細節:
  (1)所有的redis節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬;
  (2)節點的fail是通過集群中超過半數的節點檢測失效時才生效;
  (3)客戶端與redis節點直連,不需要中間proxy層.客戶端不需要連接集群所有節點,連接集群中任何一個可用節點即可,
  (4)redis-cluster把所有的物理節點映射到[0-16383]slot上,cluster 負責維護node<->slot<->value.
 
   redis-cluster選舉(容錯)機制:
  (1) leader選舉過程是,集群中所有master參與,若半數以上master節點與master節點通信超時(cluster-node-timeout),則認為當前master節點掛掉.
  (2)整個集群不可用(cluster_state:fail)的條件(集群不可用時,所有對集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)錯誤),
      a:集群中任意master掛掉,且當前master沒有slave,集群進入fail狀態;
      b:集群中超過半數以上master掛掉,無論是否有slave,集群進入fail狀態。
     
 
      相關原理分析:

      分布式基礎---Redis集群數據分片

·  Redis Cluster沒有使用一致性散列,而是使用不同形式的分片slot,其中每個鍵稱之為 hash slot.。

   Redis集群中有16384個散列槽,為了計算給定密鑰的散列槽,采用密鑰模數16384的CRC16。

   Redis群集中的每個節點都負責哈希槽的子集,例如,擁有一個包含3個節點的集群,則:

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

  (1)此特點可輕松添加和刪除集群中的節點。例如,要添加一個新節點D,則將一些哈希槽從節點A,B,C移動到D即可。同樣,如果想從集群中刪除節點A,只需移動A服務的哈希槽到B和C。當節點A為空時,就可以完全從集群中刪除它。

       (2)因為將哈希槽添加和刪除節點、從一個節點移動到另一個節點、或者更改節點所持有的哈希槽的百分比,不需要停止操作,所以不需要任何停機時間。

       (3)只要涉及單個命令執行(整個事務或Lua腳本執行)的所有鍵都屬於同一個哈希槽,Redis Cluster就支持多個鍵操作。可利用此分片的特點在集群操作中進行pipline和keys等相關操作。

  高可用基礎---Redis Cluster主備模型

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

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

        備注:非主從模式,Redis Cluster集群方案采用的是去中心化的方式,所有節點皆為主節點Master,從節點指的是備份節點,保證高可用。若其一物理節點主節點掛掉的情況下,會啟動從節點提供服務,若主從同時關掉,則集群不再提供服務。

  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個從站。還有一個client,我們稱之為Z1。在發生分區之后,可能在分區的一側有A,C,A1,B1,C1,在另一側有B和Z1。Z1仍然可以寫入B,它將接受其寫入。如果分區在很短的時間內恢復,集群將繼續正常運行。但是,如果分區持續足夠的時間使B1在分區的多數側被提升為主,則Z1發送給B的寫入將丟失。

  注意,Z1將能夠發送到B的寫入量存在maximum window:如果分區的多數方面已經有足夠的時間將slave選為master,則少數端的每個主節點都會停止接受寫入。這段時間是Redis Cluster的一個非常重要的配置指令,稱為節點超時節點超時過后,master被視為失敗,可以由其中一個副本替換。類似地,在節點超時已經過去而主節點無法感知大多數其他主節點之后,它進入錯誤狀態並停止接受寫入。

  Redis集群重新分片

  執行重新分片的過程中, 讓 example.rb 程序處於運行狀態, 這樣就會看到, 重新分片並不會對正在運行的集群程序產生任何影響, 重新分片操作基本上就是將某些節點上的哈希槽移動到另外一些節點上面, 也可以使用 redis-trib 程序來執行,執行以下命令可以開始一次重新分片操作:

./redis-trib.rb reshard 127.0.0.1:7000

(1)只需要指定集群中其中一個節點的地址, redis-trib 就會自動找到集群中的其他節點。

(2)目前 redis-trib 只能在管理員的協助下完成重新分片, 要讓 redis-trib 自動將哈希槽從一個節點移動到另一個節點, 目前來說還做不到。

(3)在重新分片結束后通過如下命令檢查集群狀態:

./redis-trib.rb check 127.0.0.1:7000
 
         
 
二、擴展:
 
    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主從讀寫分離

          1)主從復制:主節點負責寫數據,從節點負責讀數據,主節點定期把數據同步到從節點保證數據的一致性。

   2)負載均衡:當Master宕機后,通過選舉算法從slave中選舉出新Master繼續對外提供服務,主機恢復后以slave的身份重新加入,從節點通過負載均衡提供取服務。

     備注:主從復制和哨兵機制需要進行手動配置,使用zookeeper作為監控中心;3.0以后的版本去中心化,不再需要zookeeper提供監控服務。

     

 

三、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
 
 
 借鑒了不少文章,感謝各路大佬分享,轉載請注明出處,謝謝:https://www.cnblogs.com/huyangshu-fs/p/11256007.html 
 另偶爾看到一位同胞使用圖文的形式直觀展示了原理部分,現附上鏈接,共同進步:https://www.jianshu.com/p/6e2a50b0f835


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM