淺談Redis(二)——叢集版

語言: CN / TW / HK

前言

之前簡單分享了一下單機版Redis。包括: - 為什麼用 Redis 以及為什麼不用 Redis - Redis 為什麼這麼快 - Redis 的資料結構 - Redis 單執行緒吞吐量高的原因

單機版 Redis 響應快、支援的併發量高,如果有持久化的記憶體,甚至還能做到資料不丟失。那我們可以直接使用單機版 Redis 作為資料庫嗎

假設我們使用單機版 Redis 作為儲存介質。Redis 一般分為兩種用途,快取和持久化資料庫。

我們先看最常見的一種情況,斷電/重啟: - 業務資料庫: 使用持久化記憶體可以保證重啟後資料不丟失。但是斷電的過程中整個業務沒有資料來源就會癱瘓掉。 - 快取: 如果快取斷電癱瘓,倒不至於會對業務造成直接癱瘓,畢竟業務資料庫中有完整的資料。如果 Redis 宕機,就會導致快取雪崩——所有快取都失效,所有的請求都是直接打到資料庫,導致資料庫壓力激增,導致服務響應時間增加。

在斷電情況下,單機 Redis 作為業務資料庫肯定是不滿足條件的;作為快取,如果資料量小、請求量少的話,並不是很影響業務使用(這種場景也沒有用快取的必要)。

那如果不考慮重啟的情況,單機 Redis 就沒問題了嗎

資料量、訪問量小的情況下,貌似確實不會有什麼問題,我們還是考慮資料量、訪問量大的情況。

比如現在有一個單機 redis, 需要儲存幾億個key。當 Redis 持久化時,如果資料量增加,需要的記憶體也會增加,主執行緒 fork 子程序時就可能會阻塞。即使在記憶體、CPU資源充足的情況下,一次資料備份就要消耗很長時間,導致 Redis 的服務不可用

不過,如果你不要求持久化儲存 Redis 資料,那麼,增加cpu的硬體效能和記憶體容量會是一個不錯的選擇。但是這樣又有第二個問題,就是硬體成本問題:一塊128G的記憶體條的價格遠大於四塊32G 的記憶體條的價格之和,cpu也是同理。硬體的成本會隨著效能的不斷提高呈指數上升

綜上所述,單機版 Redis 主要存在以下兩個問題: 1. 可用性(斷電後,業務不可用) 2. 資料臃腫(單機CPU對大量資料的操作) - 備份阻塞導致服務不可用 - 成本指數增長

那怎麼辦呢?單機不夠用,就多加幾臺機器嘛!下面我們來看一下 Redis 提供的在多機環境下幾種常見的資源使用策略,看看能否解決上述的兩個問題

一、主從同步(Master-Slave)

主從同步就是,多個 Redis 節點,一個主節點(master),多個副節點(slave)。讀請求會發送到各個節點,寫請求只會發給 master,然後 slave 定時會從 master 拉取新增的寫操作以保證資料的一致性。

master-slave.jpg

那如何從單機版加一個 slave 節點過渡到主從模式呢? 例如,現在有例項 1(ip:192.168.19.3)和例項 2(ip:192.168.19.5),我們在例項 2 上執行以下這個命令後,例項 2 就變成了例項 1 的從庫,並從例項 1 上覆制資料: shell replicaof 192.168.19.3 6379 第一次請求到 master 的 RDB 檔案全量同步,在 RDB 檔案內容之後的操作都是增量同步進行。

master-slave-sync.jpg

之後就是重複1和3的過程。

二、哨兵機制(Sentinel)

上面講的主從同步只是保證了 Redis 的資料有及時備份。可以分擔一部分主庫的訪問壓力,以及保證了從庫的可用性。但是如果主庫發生故障了,那就直接會影響到從庫的同步,因為從庫沒有相應的主庫可以進行資料複製操作了。

哨兵(Sentinel)就是為了解決這個問題而產生的。它實現了主從庫自動切換的關鍵機制,它有效地解決了主從複製模式下故障轉移的三個問題。 - 主庫真的掛了嗎? - 該選擇哪個從庫作為主庫? - 怎麼把新主庫的相關資訊通知給從庫和客戶端呢?

下面我們就從這三個問題來看 Sentinel 對應的三種職能,監控、選主和通知。

2.1 監控

哨兵需要判斷主庫是否處於下線狀態。這裡要先說兩個概念,即主觀下線客觀下線

哨兵程序會使用 PING 命令檢測它自己和主、從庫的網路連線情況,用來判斷例項的狀態。如果哨兵發現主庫響應超時了,那麼,哨兵就會先把它標記為“主觀下線”。如果檢測的是從庫,那麼,哨兵簡單地把它標記為主觀下線就行了,因為從庫的下線影響一般不太大,叢集的對外服務不會間斷。

如果檢測的是主庫,那麼,哨兵還不能簡單地把它標記為“主觀下線”。因為很有可能存在這麼一個情況:那就是哨兵誤判了,其實主庫並沒有故障。可是,一旦啟動了主從切換,後續的選主和通知操作都會帶來額外的計算和通訊開銷。

那怎麼減少誤判呢?在日常生活中,當我們要對一些重要的事情做判斷的時候,經常會和家人或朋友一起商量一下,然後再做決定。哨兵機制也是類似的,它通常會採用多例項組成的叢集模式進行部署,這也被稱為哨兵叢集。引入多個哨兵例項一起來判斷,就可以避免單個哨兵因為自身網路狀況不好,而誤判主庫下線的情況。同時,多個哨兵的網路同時不穩定的概率較小,由它們一起做決策,誤判率也能降低。在判斷主庫是否下線時,不能由一個哨兵說了算,只有大多數的哨兵例項,都判斷主庫已經“主觀下線”了,主庫才會被標記為“客觀下線”,這個叫法也是表明主庫下線成為一個客觀事實了。這個判斷原則就是:少數服從多數。

“客觀下線”的標準就是,當有 N 個哨兵例項時,最好要有 N/2 + 1 個例項判斷主庫為“主觀下線”,才能最終判定主庫為“客觀下線”。

客觀下線.jpg

2.2 選主

當哨兵們判斷主庫客觀下線的時候,就要開始下一個決策過程了,即從許多從庫中,選出一個從庫來做新主庫。

一般來說,我把哨兵選擇新主庫的過程稱為“篩選 + 打分”。

選主.png

篩選條件: 網路連線狀態(現在 + 之前)。在選主時,除了要檢查從庫的當前線上狀態,還要判斷它之前的網路連線狀態。如果從庫總是和主庫斷連,而且斷連次數超出了一定的閾值,我們就有理由相信,這個從庫的網路狀況並不是太好,就可以把這個從庫篩掉了。

打分: - 首先,優先順序最高的從庫得分高。使用者可以手動設定從庫的優先順序。 - 其次,和舊主庫同步程度最接近的從庫得分高。 - 最後,有一個預設的規定:在優先順序和複製進度都相同的情況下,ID 號最小的從庫得分最高,會被選為新主庫。

哨兵也是叢集模式,也要保證服務的可用性,所以也會有相應的保證機制。這裡不在本篇的 Redis 叢集討論範圍之內,所以就不展開描述了。

三、 Redis Cluster

看到這裡,還記得我們一開始發現的 Redis 單機版的兩個問題嗎?可用性資料臃腫

上面主從模式 + 哨兵模式足以保證 Redis 服務的可用性,但是看起來並沒有解決資料臃腫的問題, Redis Cluster 就為我們提供瞭解決這個問題的方案。

3.1 資料切片

切片叢集,也叫分片叢集,就是指啟動多個 Redis 例項組成一個叢集,然後按照一定的規則,把收到的資料劃分成多份,每一份用一個例項來儲存。

這樣單份資料可以縮小到備份不影響該例項的主程序,而且還可以解決硬體成本問題。

那資料都分散了,使用者需要查某一條資料,Redis 怎麼知道這條資料在哪個例項呢

我們要先弄明白切片叢集和 Redis Cluster 的聯絡與區別。 切片叢集是一種儲存大量資料的通用機制,這個機制可以有不同的實現方案。 Redis Cluster 方案採用 hash 槽(Hash Slot,接下來我會直接稱之為 Slot),來處理資料和例項之間的對映關係。在 Redis Cluster 方案中,一個切片叢集共有 16384 個 hash 槽,這些 hash 槽類似於資料分割槽,每個鍵值對都會根據它的 key,被對映到一個 hash 槽中。具體的對映過程分為兩大步: - 首先根據鍵值對的 key,按照CRC16 演算法計算一個 16 bit 的值; - 然後,再用這個 16bit 值對 16384 取模,得到 0~16383 範圍內的模數,每個模數代表一個相應編號的 hash 槽。

關於 CRC16 演算法,不是我們討論的重點。我們只需要每一個 key 都能算出來一個 0~16383 的數就好。

每一個 0~16383 的數被稱為一個slot(槽)。一個 Redis 例項有多個槽,叢集中的所有 Redis 例項的槽加起來一定等於 16384。換句話說,在手動分配 hash 槽時,需要把 16384 個槽都分配完,否則 Redis 叢集無法正常工作。

slots.jpg

3.2 客戶端如何定位資料?

在定位鍵值對資料時,它所處的 hash 槽是可以通過計算得到的,這個計算可以在客戶端傳送請求時來執行。但是,要進一步定位到例項,還需要知道 hash 槽分佈在哪個例項上。

Redis 例項會把自己的 hash 槽資訊發給和它相連線的其它例項,來完成 hash 槽分配資訊的擴散,Redis叢集採用P2P的Gossip(流言)協議, Gossip協議工作原理就是節點彼此不斷通訊交換資訊,一段時間後所有的節點都會知道叢集完整的資訊,這種方式類似流言傳播 當例項之間相互連線後,每個例項就有所有 hash 槽的對映關係了。相當於每一個例項都有一份 slot 與例項地址的對映關係表

當客戶端把一個鍵值對的操作請求發給一個例項時,如果這個例項上並沒有這個鍵值對對映的 hash 槽,那麼,這個例項就會給客戶端返回下面的 MOVED 命令響應結果,這個結果中就包含了新例項的訪問地址。

shell GET hello:key (error) MOVED 13320 192.168.19.5:6379

redis_cluster_instances.jpg

如上圖,使用者連線上任意一個 Redis 例項,都可以查詢叢集中所有的key。比如連上例項2,查詢key = “hello”。例項2會根據key計算 hash 槽的值。 - 如果槽就在例項2上,則直接查詢結果返回; - 如果不在例項2上,則返回存在key的例項地址。

客戶端也會快取請求過的key對應例項的地址。

如果例項槽有變動,比如手動增刪了幾個例項,剩下的例項還是可以自動同步新的 hash 槽對應的例項位置。

3.3 實際應用

Redis Cluster 模式解決了資料臃腫的問題,但是單獨的 Redis Cluster 模式並沒有可用性,如果任意一個例項斷電或者斷網,這個例項所有 hash 槽的資料就會處於不可用的狀態。

所以在實際使用場景下,上述的三種叢集模式都會結合起來。

cluster_node.jpg

cluster_nodes.jpg

到這裡我們就解決了開篇提出的兩個問題。 - 保證可用性: cluster node 中的主從同步和哨兵選舉機制 - 避免資料臃腫:cluster node 叢集的資料切片

四、小結

開篇我們提出的單機版 Redis 作為資料庫的兩個弊端。然後介紹了 Redis 官方的叢集方案分別是如何解決這兩個弊端的。

其實除了 Redis 官方提供的叢集方案,也有很多優秀的開源 Redis 叢集方法。 比如:

推特的 twemproxy

國內豌豆莢出品的 Codis

這兩種開源方案和 Redis 官方的叢集方案最大的區別就是資料節點的管理方式。 - Redis 是去中心化,hash 槽和節點的對映每個節點都會有儲存,通過 Gossip 協議傳播; - 這兩種方案是中心化。有單獨的代理節點管理hash 槽和節點的對映。

那去中心化和中心化我們又該如何選擇呢?