對於一個集中式緩存的分散式能力構建,必須要額外提供一些機制,來保障數據在各個節點上的安全與一致性。本文以Redis為代表,看下集Redis面對上述問題交出的是怎樣一份答卷。 ...
大家好,又見面了。
本文是筆者作為掘金技術社區簽約作者的身份輸出的緩存專欄系列內容,將會通過系列專題,講清楚緩存的方方面面。如果感興趣,歡迎關註以獲取後續更新。
在本專欄前面的文章中,我們介紹了各種本地緩存框架,也知曉了本地緩存的常見特性與設計理念。在前兩篇文章中,我們介紹了集中式緩存 Redis的一些主流特性與典型使用場景。現在我們來對比一下,分散式緩存相比於本地緩存,在實現層面需要關註的點有哪些不同。梳理如下:
維度 | 本地緩存 | 集中式緩存 |
---|---|---|
緩存量 | 受限於單機記憶體大小,存儲數據有限 | 需要提供給分散式系統裡面所有節點共同使用,對於大型系統而言,對集中式緩存的容量訴求非常的大,遠超單機記憶體的容量大小。 |
可靠性 | 影響有限,只有本進程使用,不會影響其他進程的可靠性。 | 作為整個系統扛壓屏障,系統內所有節點共同依賴的通用服務,一旦集中式緩存出問題,會影響與其對接的所有業務節點,對系統的影響是致命性的。 |
承壓性 | 承載單機節點的壓力,請求量有限 | 承載整個分散式集群所有節點的流量,系統內業務分散式節點部署數量越多、業務體量越大,會導致集中緩存要承載的壓力就越大,甚至是上不封頂的。 |
從上述幾個維度的對比可以發現,同樣是緩存,但集中式緩存所承擔的使命是完全不一樣的,業務對集中式緩存的存儲容量
、可靠性
、承壓性
等方面的訴求也是天壤之別,不可等同視之。以Redis為例:
- 如何打破redis緩存容量受限於機器單機記憶體大小的問題?
- 如何使得redis能夠扛住多方過來的請求壓力?
- 如何保證redis不會成為單點故障源?
其實答案很簡單,加機器!通過多台機器的疊加使用,達到比單機更優的效果 —— 現在業務系統的集群化部署,也都是採用的這個思路。Redis的分散式之路亦是如此,但相比於常規的業務系統分散式集群化構建更加複雜:
- 很多業務實現集群化部署會很簡單,因為每個業務進程節點都是無狀態的,只需要部署下然後通過負載均衡的方式對外提供請求應答即可。
- Redis作為一個集中式緩存資料庫,它是有狀態的,不僅需要將進程分別部署在多個節點上,還需要將數據也分散存儲在各個節點上,同時還得保證整個Redis集群對外是一個統一整體。
所以對於一個集中式緩存的分散式能力構建,必須要額外提供一些機制,來保障數據在各個節點上的安全與一致性,還需要將分散在各個節點上的數據都組成一個邏輯上的整體。
下麵,我們以Redis作為集中式緩存的代表,來看下集Redis面對上述各種難題,交出的是怎樣的答卷。
Reids部署方式的演進史
單機部署 —— 原始形態,最簡單
單機部署只能算是一個開發或測試場景去小範圍使用的場景,它與普通本地緩存無二,在可靠性與承壓性上無法得到保證。
雖說Redis的性能很高,但俗話也說雙拳難敵四手,單機性能再高,也無法抗住大規模集群中所有節點過來的併發請求。此外,單機部署還有個致命點在於其不具備高可用性,系統容易出現單點故障。
所以說,稍微正規點的項目,幾乎不會有人天真到會用單機模式去部署線上使用。
主從(master-replica)
前面說過單機節點存在諸多問題,很少在生產環境上使用。在實際項目中,有些項目的存儲容量要求其實並不是特別的高(比如常規的16G或者32G就已經足夠使用),但是需要保證數據的可靠、並且支持大併發量請求,這種情況下,就可以選擇主從部署的方式。
對於redis來說,一主兩從
是比較常見的搭配,如下所示:
主從模式按照讀寫分離的策略來提升整體的請求處理能力:
-
主節點(Master)同時對外提供讀和寫操作
-
從節點(Slave)通過
replicate
同步的方式,從主節點複製數據,保持自身數據與主節點一致 -
從節點只能對外提供讀操作
當然,對於讀多寫少類的操作,為了提升整體讀請求的處理能力,可以採用一主多從
的方式:
所有的從節點都從主節點進行數據同步,這樣會導致主節點的同步處理壓力過大而成為瓶頸。為瞭解決這個問題,redis還支持了從slave節點分發的能力:
Redis的主從模式重點在於解決整體的承壓能力,利用從節點分擔讀取操作的壓力。但是其在容錯恢復等可靠性層面欠缺明顯,不具備自動的故障轉移與恢復能力:
- 如果slave從節點宕機,整個redis依舊可以正常提供服務,待slave節點重新啟動後,可以恢復從master節點的數據同步、然後繼續提供服務。
- 如果master主節點宕機,則redis功能受損,無法繼續提供寫服務,直到手動修複master節點方可恢復。
當然,master節點故障後,也可以手動將其中一個從節點切換為新的master節點來恢復故障。而原先的master節點恢復後,需要手動將其降級為slave節點,對外提供只讀服務。
實際使用的時候,手動故障恢復的時效無法得到保證,為了支持自動的故障轉移與恢復能力,Redis在主從模式的基礎上進行優化增強,提供了哨兵(Sentinel)架構模式。
哨兵(sentinel)
哨兵模式是在現代自動化系統裡面常見的一種模式。比如特斯拉汽車就配置了哨兵模式,當車輛停車鎖定並啟動哨兵模式時,會通過車輛四周的攝像頭持續的監控車輛四周的環境,如果發現異常則啟動報警系統。
同樣地,在軟體架構領域,也可以通過設定一些主進程之外的輔助進程,充當“哨兵”的角色時刻監控著主服務,一旦主服務出現異常則進行報警或者自動介入輔助故障轉移,以最大限度的保證系統功能的持續性。
Redis的哨兵模式,就是在主從模式的基礎上,額外部署若幹獨立的哨兵進程,通過哨兵進程去監視者Redis主從節點的狀態,一旦發現主節點宕機,則哨兵可以重新從剩餘slave節點中推選一個新的節點並將其升級為master節點,以此保證整個系統功能可以正常使用。
比較典型的一個Redis sentinel
部署場景是“一主二從三哨兵”的組合,如下:
哨兵可以準實時的監控著組網內所有的節點的狀態信息,如果判定master節點宕機之後,所有的sentinel節點會一起推選出一個新的master節點。由於sentinel哨兵節點需要承擔著master節點推選的責任,所以實施的時候要去sentinel節點個數必須為奇數(比如3個、5個等),這是為了保證投票的時候不會出現平局的情況。
在哨兵模式下:
- 如果Redis的master節點宕機之後,Sentinel監控到之後,需要先判定確認master節點已經宕機,然後會從剩餘存活的slave節點中投票選出一個新的節點作為master節點。
- Sentinel監控到此前宕機的master節點重新恢復之後,會將其作為slave節點,掛到現有的新的master節點下麵。
哨兵模式有效的解決了高可用的問題,保證了主節點的自動切換操作,進一步保障了Redis緩存節點的可靠性。但是,不管是哨兵模式還是主從模式,其增加的多台部署機器,都僅僅是擴展其承壓能力與可靠性,並沒有解決分散式場景下對於集中緩存容量的焦慮 —— 只能適用於數據量有限的場景。
成年人的世界總是貪婪的。如果我們既想要保證Redis的可靠性與承壓性,還想要突破容量上的限制,就需要Redis的集群模式登場了。
集群(cluster)
Redis提供了去中心化的集群部署模式,集群內所有Redis節點之間兩兩連接,而很多的客戶端工具會根據key將請求分發到對應的分片下的某一個節點上進行處理。
一個典型的Redis集群部署場景如下圖所示:
在Redis集群裡面,又會劃分出分區
的概念,一個集群中可有多個分區。分區有幾個特點:
- 同一個分區內的Redis節點之間的數據完全一樣,多個節點保證了數據有多份副本冗餘保存,且可以提供高可用保障。
- 不同分片之間的數據不相同。
- 通過水平增加多個分片的方式,可以實現整體集群的容量的擴展。
按照Cluster模式進行部署的時候,要求最少需要部署6個
Redis節點(3個分片,每個分片中1主1從),其中集群中每個分片的master節點負責對外提供讀寫操作,slave節點則作為故障轉移使用(master出現故障的時候充當新的master)、對外提供只讀請求處理。
集群數據分佈策略
Redis Sharding(數據分片)
在Redis Cluster
前,為瞭解決數據分發到各個分區的問題,普遍採用的是Redis Sharding
(數據分片)方案。所謂的Sharding,其實就是一種數據分發的策略。根據key的hash值進行取模,確定最終歸屬的節點。
使用Redis Sharding方式進行數據分片的時候,當集群內數據分區個數出現變化的時候,比如集群擴容的時候,會導致請求被分發到錯誤節點上,導致緩存命中率降低。
如果需要解決這個問題,就需要對原先擴容前已經存儲的數據重新進行一次hash計算和取模操作,將全部的數據重新分發到新的正確節點上進行存儲。這個操作被稱為重新Sharding
,重新sharding期間服務不可用,可能會對業務造成影響。
一致性Hash
為了降低節點的增加或者移除對於整體已有緩存數據訪問的影響,最大限度的保證緩存命中率,改良後的一致性Hash
演算法浮出水面。
通過一致性Hash演算法,將所有的存儲節點排列在首尾相接的Hash環上,每個key在計算Hash後會順時針找到最近的存儲節點存放。而當有新的分區節點加入或退出時,僅影響該節點在Hash環上順時針相鄰的後續一個節點。
當然咯,如果Hash圓環上的分區節點數太少,可能會出現數據在各個分片中分佈不均衡的情況,也即出現數據傾斜。
為瞭解決這個問題,引入了虛擬節點
的機制,通過增加虛擬節點,來實現數據儘可能的均勻分佈在各個節點上。
上圖中,A1、A2實際上都對應真實的A分區節點,而B1、B2則對應真實的B分區節點。通過虛擬節點的方式,儘可能讓節點在Hash環上保持均分,實現數據在分區內的均分。
Hash槽
不管是原本的Hash取模,還是經過改良後的一致性Hash,在節點的新增或者刪減的時候,始終都會出現部分緩存數據丟失的問題 —— 只是丟失的數據量的多少區別。如何才能實現擴展或者收縮節點的時候,保持已有數據不丟失呢?
既然動態變更調整的方式行不通,那就手動指定咯!Hash槽的實現策略因此產生。何為Hash槽?Hash槽的原理與HashMap有點相似,共有16384個槽位,每個槽位對應一個數據桶,然後每個Redis的分區都可以負責這些hash槽中的部分區間。存儲數據的時候,數據key經過Hash計算後會匹配到一個對應的槽位,然後數據存儲在該槽位對應的分片中。然後各個分區節點會與Hash槽之間有個映射綁定關係,由指定的Redis分區節點負責存儲對應的Has槽對應的具體分片文件。
數據查詢的時候,先根據key的Hash值進行計算,確定應該落入哪個Hash槽,進而根據映射關係,確定負責此Hash槽數據存儲的redis分區節點是哪個,然後就可以去做對應的查詢操作。
執行數據節點增加的時候,需要手動執行下處理:
- 為新的節點分配新其負責的Hash槽位區間段;
- 調整已有的節點的Hash槽位負責區間段;
- 將調整到新節點上的hash槽位區間段對應的數據分片文件拷貝到新的節點上。
這樣,就不會出現已有數據無法使用的情況了。鑒於Hash槽的自主可控性以及節點伸縮場景下的優勢,其也成為了Redis Cluster中使用的方案。
小結回顧
好啦,關於Redis部署模式的演進探討,就聊到這裡了。通過本篇文章,我們也可以感受出集中式緩存相對本地而言,在實現與設計機制上要更加的複雜,因為需要考慮與解決多方面的問題,比如可靠性、承壓性、容量以及後期的水平擴容能力等等,而這些也都是一個合格的集中式緩存所必須要具備的基本品格。
那麼,瞭解Redis對於集中式緩存在節點安全性與擴展性上的實現後,如果讓你來設計一個集中緩存的話,你會採用何種方式來保證其可靠性與後續的擴展性呢?歡迎評論區一起交流下,期待和各位小伙伴們一起切磋、共同成長。