面對集中式緩存實現上的挑戰,Redis交出的是何種答卷?聊聊Redis在分散式方面的能力設計

来源:https://www.cnblogs.com/softwarearch/archive/2023/01/12/16927957.html
-Advertisement-
Play Games

對於一個集中式緩存的分散式能力構建,必須要額外提供一些機制,來保障數據在各個節點上的安全與一致性。本文以Redis為代表,看下集Redis面對上述問題交出的是怎樣一份答卷。 ...


大家好,又見面了。


本文是筆者作為掘金技術社區簽約作者的身份輸出的緩存專欄系列內容,將會通過系列專題,講清楚緩存的方方面面。如果感興趣,歡迎關註以獲取後續更新。


在本專欄前面的文章中,我們介紹了各種本地緩存框架,也知曉了本地緩存的常見特性與設計理念。在前兩篇文章中,我們介紹了集中式緩存 Redis的一些主流特性與典型使用場景。現在我們來對比一下,分散式緩存相比於本地緩存,在實現層面需要關註的點有哪些不同。梳理如下:

維度 本地緩存 集中式緩存
緩存量 受限於單機記憶體大小,存儲數據有限 需要提供給分散式系統裡面所有節點共同使用,對於大型系統而言,對集中式緩存的容量訴求非常的大,遠超單機記憶體的容量大小。
可靠性 影響有限,只有本進程使用,不會影響其他進程的可靠性。 作為整個系統扛壓屏障,系統內所有節點共同依賴的通用服務,一旦集中式緩存出問題,會影響與其對接的所有業務節點,對系統的影響是致命性的。
承壓性 承載單機節點的壓力,請求量有限 承載整個分散式集群所有節點的流量,系統內業務分散式節點部署數量越多、業務體量越大,會導致集中緩存要承載的壓力就越大,甚至是上不封頂的。

從上述幾個維度的對比可以發現,同樣是緩存,但集中式緩存所承擔的使命是完全不一樣的,業務對集中式緩存的存儲容量可靠性承壓性等方面的訴求也是天壤之別,不可等同視之。以Redis為例:

  • 如何打破redis緩存容量受限於機器單機記憶體大小的問題?
  • 如何使得redis能夠扛住多方過來的請求壓力?
  • 如何保證redis不會成為單點故障源?

其實答案很簡單,加機器!通過多台機器的疊加使用,達到比單機更優的效果 —— 現在業務系統的集群化部署,也都是採用的這個思路。Redis的分散式之路亦是如此,但相比於常規的業務系統分散式集群化構建更加複雜:

  1. 很多業務實現集群化部署會很簡單,因為每個業務進程節點都是無狀態的,只需要部署下然後通過負載均衡的方式對外提供請求應答即可。
  2. Redis作為一個集中式緩存資料庫,它是有狀態的,不僅需要將進程分別部署在多個節點上,還需要將數據也分散存儲在各個節點上,同時還得保證整個Redis集群對外是一個統一整體。

所以對於一個集中式緩存的分散式能力構建,必須要額外提供一些機制,來保障數據在各個節點上的安全與一致性,還需要將分散在各個節點上的數據都組成一個邏輯上的整體。

下麵,我們以Redis作為集中式緩存的代表,來看下集Redis面對上述各種難題,交出的是怎樣的答卷。

Reids部署方式的演進史

單機部署 —— 原始形態,最簡單

單機部署只能算是一個開發或測試場景去小範圍使用的場景,它與普通本地緩存無二,在可靠性與承壓性上無法得到保證。

雖說Redis的性能很高,但俗話也說雙拳難敵四手,單機性能再高,也無法抗住大規模集群中所有節點過來的併發請求。此外,單機部署還有個致命點在於其不具備高可用性,系統容易出現單點故障

所以說,稍微正規點的項目,幾乎不會有人天真到會用單機模式去部署線上使用。

主從(master-replica)

前面說過單機節點存在諸多問題,很少在生產環境上使用。在實際項目中,有些項目的存儲容量要求其實並不是特別的高(比如常規的16G或者32G就已經足夠使用),但是需要保證數據的可靠、並且支持大併發量請求,這種情況下,就可以選擇主從部署的方式。

對於redis來說,一主兩從是比較常見的搭配,如下所示:

主從模式按照讀寫分離的策略來提升整體的請求處理能力:

  1. 主節點(Master)同時對外提供讀和寫操作

  2. 從節點(Slave)通過replicate同步的方式,從主節點複製數據,保持自身數據與主節點一致

  3. 從節點只能對外提供讀操作

當然,對於讀多寫少類的操作,為了提升整體讀請求的處理能力,可以採用一主多從的方式:

所有的從節點都從主節點進行數據同步,這樣會導致主節點的同步處理壓力過大而成為瓶頸。為瞭解決這個問題,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個等),這是為了保證投票的時候不會出現平局的情況。

在哨兵模式下:

  1. 如果Redis的master節點宕機之後,Sentinel監控到之後,需要先判定確認master節點已經宕機,然後會從剩餘存活的slave節點中投票選出一個新的節點作為master節點。
  2. Sentinel監控到此前宕機的master節點重新恢復之後,會將其作為slave節點,掛到現有的新的master節點下麵。

哨兵模式有效的解決了高可用的問題,保證了主節點的自動切換操作,進一步保障了Redis緩存節點的可靠性。但是,不管是哨兵模式還是主從模式,其增加的多台部署機器,都僅僅是擴展其承壓能力與可靠性,並沒有解決分散式場景下對於集中緩存容量的焦慮 —— 只能適用於數據量有限的場景。

成年人的世界總是貪婪的。如果我們既想要保證Redis的可靠性與承壓性,還想要突破容量上的限制,就需要Redis的集群模式登場了。

集群(cluster)

Redis提供了去中心化的集群部署模式,集群內所有Redis節點之間兩兩連接,而很多的客戶端工具會根據key將請求分發到對應的分片下的某一個節點上進行處理。

一個典型的Redis集群部署場景如下圖所示:

在Redis集群裡面,又會劃分出分區的概念,一個集群中可有多個分區。分區有幾個特點:

  1. 同一個分區內的Redis節點之間的數據完全一樣,多個節點保證了數據有多份副本冗餘保存,且可以提供高可用保障。
  2. 不同分片之間的數據不相同。
  3. 通過水平增加多個分片的方式,可以實現整體集群的容量的擴展。

按照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對於集中式緩存在節點安全性與擴展性上的實現後,如果讓你來設計一個集中緩存的話,你會採用何種方式來保證其可靠性與後續的擴展性呢?歡迎評論區一起交流下,期待和各位小伙伴們一起切磋、共同成長。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 摘要:華為雲FusionInsight MRS HetuEngine持續提升自助用數分析平臺的可服務、易運維能力,基於AI技術持續提升對數據分析平臺的智能化賦能水平,引領現代數據分析平臺向專業化、智能化、易運維、高性能方向演進。 本文分享自華為雲社區《現代數據平臺要實現自助用數還要解決的三大問題》, ...
  • 過濾數據 使用WHERE子句 搜索條件也稱為過濾條件(filter condition)。在SELECT語句中,數據根據WHERE子句中指定的搜索條件進行過濾: SELECT prod_name, prod_price FROM products WHERE prod_price = 2.50; 註 ...
  • 排序檢索數據 排序數據 不明確規定排序順序,則不應該假定檢索出的數據的順序有意義。 子句(clause) SQL語句由子句構成,有些子句是必需的,而有的是可選的。一個子句通常由一個關鍵字和所提供的數據組成。子句的例子有SELECT語句的FROM子句。 為了明確地排序用SELECT語句檢索出的數據,可 ...
  • 1、合作背景 萬里開源軟體有限公司 ​ 北京萬里開源軟體有限公司,是專註於國產自主可控資料庫產品研發超 20年的國家高新技術企業,參與多個國家級的資料庫行業標準制定工作。本次用於測試的 GreatSQL 開源資料庫是適用於金融級應用的國內自主 MySQL 版本,專註於提升 MGR 可靠性及性能,支持 ...
  • Calcite在大數據系統中有著廣泛的運用, 比如Apache Flink, Apache Drill等都大量使用了Calcite,理解Calcite的原理可以說已經成為理解大數據系統中SQL訪問層實現原理的必備條件之一。 但是不少人在學習Calcite的過程中都發現關於Calcite的實踐案例其實 ...
  • 摘要:通過雲服務形式提供資料庫功能的雲資料庫應運而生,但這還僅僅是資料庫變革的開端。 本文分享自華為雲社區《透視華為云云原生資料庫的前世今生及未來演進,能給行業帶來哪些啟發?》,作者:萬佳。 自雲計算出現後,風雲變幻十餘載,硬體、軟體行業都經歷了重構變革所帶來的機遇與激蕩。企業 IT 基礎設施逐漸雲 ...
  • Day1 選擇 595. 大的國家 World表: + + + | Column Name | Type | + + + | name | varchar | | continent | varchar | | area | int | | population | int | | gdp | in ...
  • 如果入職一些中小型公司,往往需要接手一些很“坑”的項目,到底多坑就不牢騷了,只講一下,如果破解這些歷史遺留的項目問題。項目代碼可能短時間無法進行通讀研究,我們就需要從底層資料庫進行挖掘問題,有經驗的老開發工程師,他會開啟Sql Server Profiler 這個功能,進行語句的跟蹤。這個是一個很好 ...
一周排行
    -Advertisement-
    Play Games
  • 下麵是一個標準的IDistributedCache用例: public class SomeService(IDistributedCache cache) { public async Task<SomeInformation> GetSomeInformationAsync (string na ...
  • 這個庫提供了在啟動期間實例化已註冊的單例,而不是在首次使用它時實例化。 單例通常在首次使用時創建,這可能會導致響應傳入請求的延遲高於平時。在註冊時創建實例有助於防止第一次Request請求的SLA 以往我們要在註冊的時候實例單例可能會這樣寫: //註冊: services.AddSingleton< ...
  • 最近公司的很多項目都要改單點登錄了,不過大部分都還沒敲定,目前立刻要做的就只有一個比較老的項目 先改一個試試手,主要目標就是最短最快實現功能 首先因為要保留原登錄方式,所以頁面上的改動就是在原來登錄頁面下加一個SSO登錄入口 用超鏈接寫的入口,頁面改造後如下圖: 其中超鏈接的 href="Staff ...
  • Like運算符很好用,特別是它所提供的其中*、?這兩種通配符,在Windows文件系統和各類項目中運用非常廣泛。 但Like運算符僅在VB中支持,在C#中,如何實現呢? 以下是關於LikeString的四種實現方式,其中第四種為Regex正則表達式實現,且在.NET Standard 2.0及以上平... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式記憶體會偶發性暴漲,自己分析了下是非托管記憶體問題,讓我幫忙看下怎麼回事?哈哈,看到這個dump我還是非常有興趣的,居然還有這種游戲幣自助機類型的程式,下次去大玩家看看他們出幣的機器後端是不是C#寫的?由於dump是linux上的程式,剛好win ...
  • 前言 大家好,我是老馬。很高興遇到你。 我們為 java 開發者實現了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何處理的,可以參考我的另一個項目: 手寫從零實現簡易版 tomcat minicat 手寫 ngin ...
  • 上一次的介紹,主要圍繞如何統一去捕獲異常,以及為每一種異常添加自己的Mapper實現,並且我們知道,當在ExceptionMapper中返回非200的Response,不支持application/json的響應類型,而是寫死的text/plain類型。 Filter為二方包異常手動捕獲 參考:ht ...
  • 大家好,我是R哥。 今天分享一個爽飛了的面試輔導 case: 這個杭州兄弟空窗期 1 個月+,面試了 6 家公司 0 Offer,不知道問題出在哪,難道是杭州的 IT 崩盤了麽? 報名面試輔導後,經過一個多月的輔導打磨,現在成功入職某上市公司,漲薪 30%+,955 工作制,不咋加班,還不捲。 其他 ...
  • 引入依賴 <!--Freemarker wls--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> ...
  • 你應如何運行程式 互動式命令模式 開始一個互動式會話 一般是在操作系統命令行下輸入python,且不帶任何參數 系統路徑 如果沒有設置系統的PATH環境變數來包括Python的安裝路徑,可能需要機器上Python可執行文件的完整路徑來代替python 運行的位置:代碼位置 不要輸入的內容:提示符和註 ...