如何讓網站不下線而從 Redis 2 遷移到 Redis 3

發表於2017-10-20

我們在 Sky Betting&Gaming 中使用 Redis 作為共享記憶體快取,用於那些需要跨 API 伺服器或者 Web 伺服器鑑別令牌之類的操作。在 Core Tribe 內,它用來幫助處理日益龐大的登入數量,特別是在繁忙的時候,我們在一分鐘內登入數量會超過 20,000 人。這在很大程度上適用於資料存放在大量伺服器的情況下(在 SSO 令牌用於 70 臺 Apache HTTPD 伺服器的情況下)。我們最近著手升級 Redis 伺服器,此升級旨在使用 Redis 3.2 提供的原生叢集功能。這篇部落格希望解釋為什麼我們要使用叢集、我們遇到的問題以及我們的解決方案。

在開始階段(或至少在升級之前)

我們的傳統快取中每個快取都包括一對 Redis 伺服器,使用 keepalive 確保始終有一個主節點監聽浮動 IP floating IP地址。當出現問題時,這些伺服器對需要很大的精力來進行管理,而故障模式有時是非常各種各樣的。有時,只允許讀取它所持有的資料,而不允許寫入的從屬節點卻會得到浮動 IP 地址,這種問題是相對容易診斷的,但會讓無論哪個程式試圖使用該快取時都很麻煩。

新的應用程式

因此,這種情況下,我們需要構建一個新的應用程式,一個使用共享記憶體快取shared in-memory cache的應用程式,但是我們不希望對該快取進行迂迴的故障切換過程。因此,我們的要求是共享的記憶體快取,沒有單點故障,可以使用盡可能少的人為干預來應對多種不同的故障模式,並且在事件恢復之後也能夠在很少的人為干預下恢復,一個額外的要求是提高快取的安全性,以減少資料洩露的範圍(稍後再說)。當時 Redis Sentinel 看起來很有希望,並且有許多程式支援代理 Redis 連線,比如 twemproxy。這會導致還要安裝其它很多元件,它應該有效,並且人際互動最少,但它複雜而需要執行大量的伺服器和服務,並且相互通訊。

如何讓網站不下線而從 Redis 2 遷移到 Redis 3

將會有大量的應用伺服器與 twemproxy 進行通訊,這會將它們的呼叫路由到合適的 Redis 主節點,twemproxy 將從 sentinal 叢集獲取主節點的資訊,它將控制哪臺 Redis 例項是主,哪臺是從。這個設定是複雜的,而且仍有單點故障,它依賴於 twemproxy 來處理分片,來連線到正確的 Redis 例項。它具有對應用程式透明的優點,所以我們可以在理論上做到將現有的應用程式轉移到這個 Redis 配置,而不用改變應用程式。但是我們要從頭開始構建一個應用程式,所以遷移應用程式不是一個必需條件。

幸運的是,這個時候,Redis 3.2 出來了,而且內建了原生叢集,消除了對單一 sentinel 叢集需要。

如何讓網站不下線而從 Redis 2 遷移到 Redis 3

它有一個更簡單的設定,但 twemproxy 不支援 Redis 叢集分片,它能為你分片資料,但是如果嘗試在與分片不一致的叢集中這樣做會導致問題。有參考的指南可以使其匹配,但是叢集可以自動改變形式,並改變分片的設定方式。它仍然有單點故障。正是在這一點上,我將永遠感謝我的一位同事發現了一個 Node.js 的 Redis 的叢集發現驅動程式,讓我們完全放棄了 twemproxy。

如何讓網站不下線而從 Redis 2 遷移到 Redis 3

因此,我們能夠自動分片資料,故障轉移和故障恢復基本上是自動的。應用程式知道哪些節點存在,並且在寫入資料時,如果寫入錯誤的節點,叢集將自動重定向該寫入。這是被選的配置,這讓我們共享的記憶體快取相當健壯,可以沒有干預地應付基本的故障模式。在測試期間,我們的確發現了一些缺陷。複製是在一個接一個節點的基礎上進行的,因此如果我們丟失了一個主節點,那麼它的從節點會成為一個單點故障,直到死去的節點恢復服務,也只有主節點對叢集健康投票,所以如果我們一下失去太多主節點,那麼叢集無法自我恢復。但這比我們過去的好。

向前進

隨著使用叢集 Redis 配置的新程式,我們對於老式 Redis 例項的狀態變得越來越不適應,但是新程式與現有程式的規模並不相同(超過 30GB 的記憶體專用於我們最大的老式 Redis 例項資料庫)。因此,隨著 Redis 叢集在底層得到了證實,我們決定遷移老式的 Redis 例項到新的 Redis 叢集中。

由於我們有一個原生支援 Redis 叢集的 Node.js Redis 驅動程式,因此我們開始將 Node.js 程式遷移到 Redis 叢集。但是,如何將數十億位元組的資料從一個地方移動到另一個地方,而不會造成重大問題?特別是考慮到這些資料是認證令牌,所以如果它們錯了,我們的終端使用者將會被登出。一個選擇是要求網站完全下線,將所有內容都指向新的 Redis 群集,並將資料遷移到其中,以希望獲得最佳效果。另一個選擇是切換到新叢集,並強制所有使用者再次登入。由於顯而易見的原因,這些都不是非常合適的。我們決定採取的替代方法是將資料同時寫入老式 Redis 例項和正在替換它的叢集,同時隨著時間的推移,我們將逐漸更多地向該叢集讀取。由於資料的有效期有限(令牌在幾個小時後到期),這種方法可以導致零停機,並且不會有資料丟失的風險。所以我們這麼做了。遷移是成功的。

剩下的就是服務於我們的 PHP 程式碼(其中還有一個專案是有用的,其它的最終是沒必要的)的 Redis 的例項了,我們在這過程中遇到了一個困難,實際上是兩個。首先,也是最緊迫的是找到在 PHP 中使用的 Redis 叢集發現驅動程式,還要是我們正在使用的 PHP 版本。這被證明是可行的,因為我們升級到了最新版本的 PHP。我們選擇的驅動程式不喜歡使用 Redis 的授權方式,因此我們決定使用 Redis 叢集作為一個額外的安全步驟 (我告訴你,這將有更多的安全性)。當我們用 Redis 叢集替換每個老式 Redis 例項時,修復似乎很直接,將 Redis 授權關閉,這樣它將會響應所有的請求。然而,這並不是真的,由於某些原因,Redis 叢集不會接受來自 Web 伺服器的連線。 Redis 在版本 3 中引入的稱為“保護模式”的新安全功能將在 Redis 繫結到任何介面時將停止監聽來自外部 IP 地址的連線,並無需配置 Redis 授權密碼。這被證明相當容易修復,但讓我們保持警惕。

現在?

這就是我們現在的情況。我們已經遷移了我們的一些老式 Redis 例項,並且正在遷移其餘的。我們通過這樣做解決了我們的一些技術債務,並提高了我們的平臺的穩定性。使用 Redis 叢集,我們還可以擴充套件記憶體資料庫並擴充套件它們。 Redis 是單執行緒的,所以只要在單個例項中留出更多的記憶體就會可以得到這麼多的增長,而且我們已經緊跟在這個限制後面。我們期待著從新的叢集中獲得改進的效能,同時也為我們提供了擴充套件和負載均衡的更多選擇。

未來怎麼樣?

我們解決了一些技術性債務,這使我們的服務更容易支援,更加穩定。但這並不意味著這項工作完成了,Redis 4 似乎有一些我們可能想要研究的功能。而且 Redis 並不是我們使用的唯一軟體。我們將繼續努力改進平臺,縮短處理技術債務的時間,但隨著客戶群體的擴大,我們力求提供更豐富的服務,我們總是會遇到需要改進的事情。下一個挑戰可能與每分鐘超過 20,000次 登入到超過 40,000 次甚至更高的擴充套件有關。

相關文章