爲何要分佈式
Redis是一款開源的基於內存的K-V型數據庫,由於內存訪問速度快,通常被用來作系統的緩存。html
Redis做爲單機部署可以支持業務簡單,數據量不大的系統需求,但在實際應用中,一旦系統規模上來,單機的Redis就會遇到下面的挑戰:node
- 伸縮性。系統隨着長期運行與業務增加,對Redis存儲的數據量需求也愈來愈大,單機必然受限於服務器的內存與磁盤大小。
- 高性能。系統規模變大後,對Redis的吞吐量需求也會提升,而單機的吞吐量必然有限,這種狀況會影響總體系統的性能。
- 高可用。Redis持久化機制必定程度上能緩解單點問題,可是須要花費時間去恢復,在恢復的過程當中服務可能不可用,或者數據會有丟失。
分佈式解決方案
分佈式的解決方案對於業內是通用的:git
- 水平拆分。單點的一個重要挑戰就是數據量大的時候單點存儲不夠。直接的想法就是部署多個實例,將須要存儲的數據分散存儲到各個實例中。當實例存儲空間不夠時,繼續擴大實例個數就能夠解決數據伸縮性問題。同時這種數據水平拆分的方法也能夠解決單機性能問題,由於不一樣的數據讀寫能夠分配到不一樣的實例。
- 主從複製。若是隻有水平拆分,若是其中一個實例出現了問題,那該實例上存儲的數據都不可訪問,仍是存在可用問題。爲此能夠進行全部數據的主備複製,同一份數據能夠有多個副本,當某個實例出現問題時,能夠啓用該實例的副本,達到高可用的目的。同時副本也能夠幫助提升系統的吞吐能力,由於對數據的訪問也能夠分發到副本上。
存儲的分佈式解決方案大抵如上,不一樣的是各類系統的實現方法不一樣。下面咱們來看下Redis的分佈式解決方案是怎樣的。github
歷史發展
分佈式解決方案不是一蹴而就的,也有個發展的過程,不一樣歷史時期提供的方案可能不一樣,最新的解決方案也可能在不遠的未來被替換。web
主從複製(replication)是Redis分佈式的基礎。Redis中包含兩種節點:master節點與slave節點。同一份數據存放在master與多個slave節點上,
master對外提供讀寫,slave不對外提供寫操做。當master宕機時,slave節點還能繼續提供服務。主從複製中最重要的就是主從數據如何同步。算法
Redis的主從同步分爲全量同步與增量同步,在下面的章節會詳細介紹。數據庫
若是隻有主從複製,當master宕機時,須要運維人工將slave節點切換成master。這在生產環境是不可接受的,在這過程當中系統可能沒法使用。因此須要有一個機制,當master宕機時能自動進行主從切換,應用程序無感知,繼續提供服務。因而Redis官方提供了一種方案: Redis Sentinel(哨兵模式)。緩存
簡單的說,哨兵模式就是在主從基礎上增長了哨兵節點,哨兵節點不存儲業務數據,它負責監控主從節點的健康,當主節點宕機時,它能及時發現,並自動選擇一個從節點,將其切換成主節點。爲了不哨兵自己成爲單點,哨兵通常也由多個節點組成。bash
主從複製與哨兵模式解決了高可用問題,但數據的伸縮性還不行,還須要進行水平拆分。而這個時期大數據高併發的需求在快速增加,哪裏有需求,哪裏就有市場。在Redis官方本身的集羣方案出來以前,Codis應運而生並獲得快速發展。服務器
Codis是中國人開發並開源的,來自前豌豆莢中間件團隊。Codis和後面的Redis Cluster類似,將特定的key分發到特定的Redis實例上,默認將key化爲1024個槽位;另外Codis採用zookeeper來維護節點間數據的一致性;更方便的是Codis提供了很是友好的後臺管理界面。更深刻的你們能夠自行去了解。可是當Redis官方的集羣方案 Redis Cluster 發佈後,Codis的境遇就有些尷尬了。畢竟不是親生的,不少新的特性總比Redis官方慢一拍。
正是由於分佈式是剛需,因此Redis官方在3.0版本推出了本身的分佈式方案,這就是Redis Cluster。這個名詞可能有些歧義,它不是簡單地表明Redis集羣,而是Redis的一種分佈式方案,該方案的名稱就叫Redis Cluster。下面咱們就重點介紹下該方案。
Redis Cluster
Redis Cluster 提供了一種去中心化的分佈式方案,該方案能夠實現水平拆分,故障轉移等需求。
拓撲結構
一個Redis Cluster由多個Redis節點組成,這裏的節點指的是Redis實例,一臺服務器上可能有多個實例。這些節點可按節點組劃分,每一個節點組裏的節點存放相同的數據,裏面有且只有一個是master節點,同時有0到多個slave節點。不一樣節點組存放的數據沒有交集,而全部節點組的數據組成這個Redis Cluster的所有數據集合。
如圖所示,這裏有3臺服務器,每臺服務器上有2個 Redis 實例。標號相同的節點組成一個節點組,他們存放相同的數據,如標號爲1的2個節點組成一個節點組,其中深色的爲master節點,另一個爲slave節點。也就是全量數據被劃分爲3份,分別標號1/2/3。每份數據又有1個 slave 節點,他們經過主從複製保存和 master 節點相同的數據。這裏主要有兩方面:一是主從複製,二是數據橫向劃分,在 Redis 中稱爲分片 Sharding。
主從複製 Replication
主從節點最重要的是如何保證主從節點數據的一致性。只有 master 節點提供數據的寫操做,數據被寫到 master 節點後,再同步到 slave 節點。
主從之間的數據同步能夠分爲全量同步與增量同步。
全量同步
全量同步也稱爲快照同步,主節點上進行一次 bgsave 操做,將當前內存的數據所有快照寫到磁盤文件中,而後將文件同步給從節點。從節點清空當前內存所有數據後全量加載該文件,這樣達到與主節點數據同步的目的。
可是在從節點加載快照文件的過程當中,主節點還在對外提供寫服務。因此當從節點加載完快照後,依舊可能與主節點數據不一致,這時就須要增量同步上場了。
增量同步
增量同步的不是數據,而是指令流。主節點會將對當前數據狀態產生修改的指令記錄在內存的一個buffer中,而後異步地將buffer同步到從節點,從節點經過執行buffer中的指令,達到與主節點數據一致的目的。
Redis的buffer是一個定長的環形結構,當指令流滿的時候,會覆蓋最前面的內容。因此當從節點上次增量同步因爲各類緣由,致使花費時間較長時,再次同步指令流時,就有可能前面沒有同步的指令被覆蓋掉了。這種狀況就須要進行全量同步了。
因此全量同步與增量同步是相輔相成的關係。全量同步時一個很耗資源與時間的操做,若是單靠全量同步,同步操做會很重,在同步的長時間過程當中不能提供服務。而增量同步有buffer容量限制,僅僅靠增量同步可能形成數據丟失,致使主從數據不一致。
分片 Sharding
所謂分片,就是將數據集按照必定規則,分散存儲在各個節點上。這裏涉及兩個問題:
1.分片規則是什麼?
2.如何存儲在各個節點上?
Redis將全部數據分爲16384個hash slot(槽),每條數據(key-value)根據key值經過算法映射到其中一個slot上,這條數據就存儲在該slot中。映射算法是:
slotId=crc16(key)%16384
Redis的每一個key都會基於該分片規則,落到特定的slot上。而在集羣部署完成時,slot的分佈就已經肯定了。
172.16.190.78:7001> cluster nodes 08a5e808d2e6f6b231d73519bd4f05f74614c2a2 172.16.190.77:7000@17000 master - 0 1592218859545 3 connected 10923-16383 2c75029ab638a48537a4c02ed0ca77a19fc4106b 172.16.190.78:7000@17000 master - 0 1592218860448 1 connected 0-5460 50884e234c5f1ccf03f5a6d1cc4e6e6dc4779752 172.16.242.36:7000@17000 master - 0 1592218860000 2 connected 5461-10922 d0381ef4aad364c42e08bf1c2d78168f4901bd90 172.16.190.78:7001@17001 myself,slave 08a5e808d2e6f6b231d73519bd4f05f74614c2a2 0 1592218859000 4 connected e5c154dcf02526b807c67bebf0a63b4c98118ffe 172.16.190.77:7001@17001 slave 50884e234c5f1ccf03f5a6d1cc4e6e6dc4779752 0 1592218861048 6 connected 4a1b49cd77dbd18eba677663777f211be6f68dae 172.16.242.36:7001@17001 slave 2c75029ab638a48537a4c02ed0ca77a19fc4106b 0 1592218860000 5 connected
如上圖,3個master節點,172.16.190.77:7000節點存放10923-16383 slot,172.16.190.78:7000節點存放0-5460 slot,172.16.242.36:7000存放5461-10922 slot。
對於一個穩定的集羣,slot的分佈也是固定的。但在一些狀況下,slot的分佈須要發生改變:
- 新的master加入
- 節點分組退出集羣
- slot分佈不均勻
這些狀況下就須要進行slot的遷移。slot遷移的觸發與過程控制都是由外部系統完成,Redis只提供能力,但不自動進行slot遷移。
MOVE & ASKING
MOVED/ASKING 相似http的重定向碼3xx,是操做的一種錯誤返回。
當客戶端向某個節點發出指令,該節點發現指令的key對應的slot不在當前節點上,這時Redis會向客戶端發送一個MOVED指令,告訴它正確的節點,而後客戶端去連這個正確的節點並進行再次操做。以下,15495爲key a所在的slot id。
172.16.190.78:7001> get a (error) MOVED 15495 172.16.190.77:7000
ASKING 是在slot遷移過程當中的一種錯誤返回。當某個slot在遷移過程當中,客戶端發了一個位於該slot的某個key的操做請求,請求被路由到舊的節點。此時該key若是在舊節點上存在,則正常操做;若是在舊的節點上找不到,那麼可能該key已被遷移到新的節點上,也可能就沒有該key,此時會返回ASKING,讓客戶端跳轉到新的節點上去執行。
MOVED 與 ASKING 的共同點是二者都是重定向,區別在於 MOVED 是永久重定向,下次對一樣的key
進行操做,客戶端就將請求發送到正確的節點,而 ASKING 是臨時重定向,它只對此次操做起做用,不會更新客戶端的槽位關係表。
故障恢復
前面提到Redis Cluster是一個去中心化的集羣方案。好比Codis採用zookeeper來維護節點間狀態一致性,Redis哨兵模式是哨兵來管理節點的狀態,這些都是中心化的例子。Redis Cluster沒有專門用於維護節點狀態的節點,而是全部節點經過Gossip協議相互通訊,廣播本身的狀態以及本身對整個集羣認知的改變。
若是一個節點宕掉了,其餘節點和它進行通訊時,會發現改節點失聯。當某個節點發現其餘節點失聯時,會將這個失聯節點狀態變成PFail(possible fail),並廣播給其餘節點。當一個節點收到某個節點PFail的數量達到了主節點的大多數,就標記該節點爲Fail,並進行廣播,經過這種方式確認節點故障。
當slave發現其master狀態爲Fail後,它會發起選舉,若是其餘master節點都贊成,則該slave進行從主切換,變成master節點。同時會將本身的狀態廣播給其餘節點,達到你們信息一致性。
根據上面的原理,下面狀況下是沒法自動從主切換,達到集羣繼續可用目的的,在實際部署時應避免:
- 若是一個節點組中的master與slave部署在同一服務器上,當服務器發生故障時,master與salve同時Fail。
- 好比總共3個master,其中2個master部署在同一服務器上,當服務器發生故障時,這兩個master同時Fail,slave沒法完成從主切換,由於PFail的數量沒法達到主節點的大多數。
當集羣節點發生變化時,須要將變化同步到客戶端,客戶端才能根據新的集羣拓撲來向正確的節點發送請求。
好比Redis客戶端Lettuce,在鏈接配置中配置了多個節點,Lettuce會選擇其中一個可用節點進行鏈接。在鏈接斷開以前,若是該節點掛掉,Lettuce不會自動進行節點切換,此時會不斷地拋鏈接異常,沒法繼續讀寫。Lettuce提供了在鏈接過程當中自適應刷新集羣拓撲,即在鏈接失敗時自動刷新,也能夠設計定時刷新。
ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder() .enablePeriodicRefresh(Duration.ofMinutes(10)) .enableAllAdaptiveRefreshTriggers() .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30)).build();
參考:
1.《深刻分佈式緩存 從原理到實戰》於君澤 曹洪偉 邱碩等著
2.《Redis 深度歷險:核心原理與應用實踐》老錢
3.Redis官網:https://lettuce.io/core/release/reference/index.html
更多分享,👇