Redis cluster, no longer vaporware.

Salvatore Sanfilippo

Redis Cluster,不再是空談

原文由 Salvatore Sanfilippo 發布,訂閱此部落格

在我的 git 紀錄裡,能找到關於 Redis Cluster 最早的一次提交是在 2011 年 3 月 29 日,但那其實是一次「複製後提交」的合併:cluster 分支原本的歷史紀錄被清掉了,因為當時全是為了勾勒 API 的初步構想、以及它與系統其他部分互動方式而留下的、亂七八糟的半成品提交。

算起來,這是一個將近四年的專案。這差不多佔了 Redis 整個專案歷史的三分之二。然而,直到今天,我才釋出 Redis 3.0.0 的第一個候選版,也就是第一個支援 Cluster 的版本。

一路跌跌撞撞

為什麼花了這麼久,原因其實很簡單:我當初是匆匆忙忙啟動 Cluster 專案的,那時看起來,如果沒有自動擴展的能力,Redis 似乎就會變得一無是處。但那根本不是啟動 Cluster 專案的正確時機,單純是因為當時的 Redis 本身還太不成熟,我們甚至還沒能把「單一實例」的故事說好。

雖然我在錯誤的時機啟動專案確實犯了錯,但至少我沒有掉進無視社群需求的陷阱,因此這個專案被一次又一次、無數次地暫停,好把更多精力投入到其他更基礎的功能上。持久化、複製、延遲、內省得到的關注都遠比 Cluster 多,單純是因為對使用者來說,它們更重要。

這個專案的另一個限制是,我剛開始時對分散式程式設計根本一竅不通。我做的第一版設計糟透了,唯一有掌握好的只有「產品面」的需求:低延遲、線性擴展能力,以及在小規模叢集時的低開銷。然而所有細節都是錯的,設計也遠比必要的複雜,使用的演算法也不安全,諸如此類。

在取得一些微小進展的同時,我開始研讀分散式程式設計的基礎,重新設計了 Redis Cluster,並把同樣的想法應用到新版的 Sentinel 上。這兩個系統所使用的分散式演算法至今仍相當原始,因為它們是非同步複製、最終一致性的系統,所以我不需要處理共識這類不簡單的問題。不過,即使處理的是相對簡單的問題——至少比起寫一個 CP 儲存系統來說——你還是得搞清楚自己在做什麼,否則做出來的系統可能會完全錯誤。

儘管有這些問題,我仍持續投入這個專案,試圖修正它、修正實作,並讓它趨於成熟,因為有一個簡單的事實,就像一頭塞進小房間的大象,瀰漫在整個 Redis 社群之中,那就是:人們一次又一次地,靠著自己的力量,而且很多時候是用完全錯誤的方式,在做兩件事:

  1. 將資料集分片到 N 個節點上。
  2. 建立一套靈敏的容錯移轉機制,以在特定故障發生時存活下來。

問題「2」的狀況是如此糟糕,以至於在某個時間點,我決定在 Cluster 完成之前先啟動 Redis Sentinel 專案,以便盡快提供一套高可用系統,而且對於大多數只需要「2」而不需要「1」的使用場景來說,它比 Redis Cluster 更合適。

終於,我開始看到這些努力的第一批實際成果,而現在我們有了一個候選版,這是邁向被採用、修復剩餘錯誤、並以更漸進的方式改進系統所必需的關鍵里程碑。

它實際上做了什麼?

Redis Cluster 基本上是一種資料分片策略,能夠在叢集運行期間將鍵從一個節點重新分片到另一個節點,同時搭配一套容錯移轉機制,確保系統能夠在特定類型的故障中存活下來。

從分散式資料庫的角度來看,Redis Cluster 在發生網路分割時只提供有限程度的可用性,以及一種較弱的一致性。基本上,它既不是 CP 系統,也不是 AP 系統。換句話說,Redis Cluster 並沒有追求分散式系統理論上所能達到的極限,而是為了換取某些現實世界中的特性。

其一致性模型就是著名的「最終一致性」模型。基本上,如果節點因為分割而失去同步,可以保證當分割癒合後,所有負責同一個鍵的節點最終會對其值達成一致。

然而,其合併策略是「最後一次容錯移轉者勝出」,因此在網路分割期間接收到的寫入可能會遺失。一個常見的例子是:當一個主節點被分割到少數派的那一側,而客戶端仍嘗試對它寫入時會發生什麼事。如果當分割癒合時,在多數派那一側已經有一個從節點被提升來取代這個主節點,那麼舊主節點所收到的寫入就會遺失。

這反過來意味著,Redis Cluster 不必在資料結構中攜帶詮釋資料來嘗試合併值,而且 Redis 所支援的那些豐富指令與資料結構,在 Redis Cluster 中也同樣受到支援。因此,沒有額外的記憶體開銷、沒有 API 限制、也沒有限制一個值能包含的元素數量,但在分割期間的安全性就比較低。

不難理解,在像 Redis Cluster 這樣設計的系統中,節點之間產生分歧並不是好事,因此系統試圖透過降低兩個節點分歧的機率以及分歧的程度來緩解其缺點。這透過以下幾種方式來達成:

  1. 分割中處於少數派的一側會變為不可用。
  2. 複製機制的設計是讓回覆給客戶端的訊息與發送給從節點的複製串流,通常是同時發出的。
  3. 當有多個從節點可對主節點進行容錯移轉時,系統會嘗試挑選看起來與故障主節點分歧最小的那一個。

這些策略並不會改變系統的理論特性,但能為常見的 Redis Cluster 故障模式提供更多現實層面的保護。

就 Redis 的 API 與使用場景而言,我認為這樣的設計是合理的,但過去有許多人並不同意。不過我的看法是,每位設計者都有自由按照自己的想法去設計系統,只有一條規則:要說實話,因此 Redis Cluster 在官方文件中清楚地記載了它的限制與故障模式。

決定一個系統是否有用的,是使用者以及眼前的使用場景。我的感覺是,六年來,使用者即使在完全沒有叢集支援的情況下仍持續使用 Redis,是因為其使用場景讓這成為可能,而且 Redis 提供了某些特定功能與效能,使它非常適合解決特定問題。我希望 Redis Cluster 能改善許多這類使用者的體驗。

未來的路

終於,我們有了一個足以釋出的最小可行產品,它已經穩定到足以讓使用者認真開始測試,甚至在某些情況下直接採用。採用的人越多,我們就能把它改得越好。我從 Redis 和 Sentinel 的經驗中深知這一點:現在進入的是一個讓軟體從堪用到成熟的漸進過程。傾聽使用者、修復錯誤、讓更多程式碼被測試覆蓋……

同時,我也開始思考 Redis Cluster 的下一個版本,希望在 v1 的基礎上加入許多目前還來不及加入的好用功能,例如多資料中心支援、透過指令重播在少數派分割中提供更高的寫入安全性、節點自動平衡,以及更多其他功能。

此外,我認為 Redis Cluster 還可以受惠於一種專為快取設計的特殊執行模式,在這種模式下,節點會接受對其不負責的雜湊槽的寫入,以便在少數派分割中仍保持可用。

我們永遠有時間去改進與修正我們的實作與設計,但如果過度執著於軟體「應該」是什麼樣子,就有可能讓它在空談的分類裡停留得比必要的更久。是時候讓它問世了。好好享受 Redis Cluster 吧!

Redis Cluster RC1 已可在 GitHub 上以 '3.0.0-rc1' 標籤取得,或是在 Redis.io 下載頁面以 tarball 形式下載,網址為 http://redis.io/download

本文章由 muse-spark-1.2-contributor 進行翻譯

留言