火山引擎 Redis 雲原生實踐

作者|解寧,火山引擎研發工程師

Redis 簡介

Redis 是大家日常工作中使用較多的典型 KV 存儲,常年位居 DB-Engines Key-Value 存儲第一。Redis 是基於內存的存儲,提供了豐富的數據結構,支持字符串類型、哈希 / 列表 / 集合類型以及 stream 結構。Redis 內置了很多特性,其中比較重要的有:

Redis 的典型應用場景有以下 3 種:

Redis 協議是二進制安全的文本協議。它很簡單,可以通過 telnet 連接到一個 Redis server 實例上執行 get 和 set 操作。

K8s 簡介

K8s 是一個容器編排系統,可以自動化容器應用的部署、擴展和管理。K8s 提供了一些基礎特性:

現實工作中遇到的服務根據是否需要數據持久化可分爲有狀態服務和無狀態服務。不需要數據持久化的服務被認爲是無狀態的,包含以下幾種類型:

有狀態服務需要穩定的持久化存儲。除此之外,可能還會有一些其它的特性要求:

在 K8s 上,我們一般會用 StatefulSet resource 來託管有狀態服務。

Redis 雲原生實踐

下面將介紹火山引擎 Redis 雲原生實踐。首先我們會明確 Redis 雲原生的目標,主要有以下幾個:

Redis 集羣架構

下面介紹一下我們的 Redis 集羣架構。集羣裏有三個組件:Server、Proxy 和 Configserver,分別完成不同的功能。

結合以上介紹的 Redis 架構以及 K8s 的特性,我們抽象了一個 Redis 集羣在 K8s 集羣上部署的基本形態:

Redis Server 啓動的時候需要一些配置文件,裏面涉及到一些用戶名和密碼,我們是用 Secret 來存儲的。在 Server Pod 運行的時候通過 volume 機制掛載到 Server Pod 內部。

對於 Proxy,通過 HPA,基於 Proxy 的 CPU 利用率,支持 Proxy 服務的動態擴縮容。

放置策略

對於一個 Redis 集羣涉及到的 Server 和 Proxy 組件,我們有一些放置策略的要求,比如:

存儲

存儲使用的是 PVCPV 再加上具有動態供給能力的 StorageClass。使用 StorageClass 是爲了抽象不同的存儲後端,可支持本地磁盤和分佈式存儲。可以通過 StorageClass 的配置直接申請對應的存儲,不用瞭解具體後端的實現。

另外,我們使用的是支持動態供給的 StorageClass,可自動按需創建不同大小的 PV。如果使用靜態供給,就無法提前預知所有 Redis 實例的規格,也無法把它們對應的指定數量的 PV 都創建出來。

Redis 雲原生功能介紹

Redis 雲原生化以後,Operator 組件是基於 Operator Pattern 實現的一個 custom controller,主要用於編排 Redis Cluster resource,管理 Redis 集羣的一些變更。Configserver 也部署在 K8s 上,所有跟 Redis 相關的組件都是雲原生化的

新建集羣

  1. 對於常見的新建集羣的請求,會先發給 ApiServer。ApiServer 接收到請求之後,會通過 client go 的 watch 機制讓 Operator 感知到。

  2. 隨後 Operator 會請求 ApiServer 創建對應 Server 的 StatefulSet。

  3. K8s 把所有 Server 的 StatefulSet 創建成功之後,等所有的 Pod 都處於 ready 狀態,這時所有分片內的 Server Pod 之間是沒有主從關係的。

  4. Operator 感知到所有的 StatefulSet 都已經處於 ready 的狀態之後,會獲取所有 Server Pod 信息,並註冊到 Configserver。

  5. Configserver 接下來會連接到所有分片內的 Slave 節點,執行實際的 SLAVEOF 命令,保證建立真正的主從關係。

  6. Operator 會定期查詢 Configserver 裏建立主從關係的進度。等所有分片的主從關係建立成功之後, Operator 會請求 ApiServer 把對應的 Proxy 創建出來。

  7. Proxy 創建出來之後,會去 Configserver 拉取最新的拓撲,保證對外提供服務的時候可以把請求打給後端正常的分片。

分片擴容

在實際使用的過程中如果遇到容量不足的情況,需要進行水平擴容。分片擴容的請求也是類似的:

  1. 請求發給 ApiServer。

  2. ApiServer 把請求推送給 Operator。

  3. Operator 感知到之後,會先給 ApiServer 發請求,把新的分片對應的 StatefulSet 創建出來。

  4. K8s 會把新分片的 StatefulSet 創建好,在處於 ready 狀態之後,一個 StatefulSet 下的每個 Pod 也都是獨立的狀態,沒有建立真正的主從關係。

  5. Operator 獲知新創建的 Server StatefulSet 分片已經處於 ready 狀態之後,會把新的 Server 分片的實例地址註冊到 Configserver。Configserver 現在會有兩個階段:

  1. Operator 會一直檢查數據遷移或者 rebalance 的進度。等進度結束之後,Operator 會更新 Redis Cluster 裏 status 的字段,反映出來當前的分片擴容的操作已經結束了。

分片縮容

分片縮容的流程和分片擴容類似:請求先發送給 ApiServer,Operator 會感知到請求,然後把縮容分片的請求發送給 Configserver。

Configserver 此時做的事情是:

  1. 先指導數據遷移。考慮到後邊的一些分片下線,需要把分片上的數據先遷移到其他可用分片上,保證數據不丟。

  2. Operator 會一直查詢 Configserver 指導的數據 rebalance 的進度。等縮容操作在 Configserver 完成之後,Operator 會請求 ApiServer 執行真正的 Server StatefulSet 刪除,這時纔是安全的刪除操作。

組件升級

一個 Redis 集羣會涉及到兩個組件:Proxy 和 Server。

無狀態的 Proxy 用 Deployment 託管,如果要進行組件升級,直接升級對應的 image 即可。

Server 是一個有狀態的組件,它的升級流程相對來說複雜一點。爲了簡化流程,我們以 Server 只有 1 分片的 Redis 集羣爲例,介紹升級過程。

  1. Server 組件的升級請求發送給 ApiServer,ApiServer 接收到這個請求之後會把它推送給 Operator。

  2. Operator 首先會按照分片內 Pod 編號從大到小的順序選擇要升級的 Pod。

  3. 選定 Pod 之後,會把它從 Configserver 的讀拓撲裏摘掉。(如果要摘除的這個 Pod 在集羣拓撲裏是 Master,我們會先調用 Configserver 的 API,執行 Failover,把它變成 Slave,然後再把它從讀的拓撲裏邊給摘除掉。)

  4. 之後,Operator 等待 30 秒。這個機制的出發點是:

  1. 30 秒之後請求 ApiServer 執行實際的 Pod 刪除操作。刪除之後 K8s 會重新調度一個新的 Pod 起來,這時新創建的 Server Pod 也是一個獨立的 Server 的狀態,沒有跟任何節點建立主從關係。Operator 感知到新的 Server Pod 已經處於 ready 的狀態,會把它註冊到 Configserver。

  2. Configserver 連到新的 Server Pod 上,根據它所處的分片跟所在分片的 Master 節點建立主從關係,同時進行數據同步。

  3. Operator 會定期檢查新的 Server 分片在 Configserver 是否已經完成數據同步。如果是,Operator 纔會認爲一個 Server Pod 完成了升級。該分片內其他所有 Server pod 的升級流程都是類似的,直到該分片內所有 Server Pod 都升級完,才認爲這個分片升級完成。最後 Operator 會更新自己 Redis Cluster 的 CRD 裏對應的 Status 狀態信息,反映當前組件升級的流程和變更操作已經結束了。

總結展望

本次分享的以 Redis 爲例,介紹了有狀態服務部署到 K8s 的抽象流程,並介紹了火山引擎在 Redis 雲原生方向的一些探索和實踐。目前火山引擎對於 Redis 雲原生已經完成了基本功能的構建,未來會在動態擴縮容、異常恢復、細粒度的資源分配和管理方面,結合 K8s 的特性,進行更多深層次的思考和實踐,期望通過雲原生化的方式,進一步提升運維自動化能力和資源的利用率。

Q&A

Q1:沒用 Cluster 的模式嗎?

A:沒有,最早也使用過 Cluster 模式,後來業務體量變大,發現 Cluster 有集羣規模上限,不能滿足業務的需求。

Q2:Redis 的 Proxy 會計算 Key 在哪個分片嗎?

A:會的,Proxy 會參考類似 Redis Cluster 的 Key Hash 算法對 Key 進行 hash,之後分佈到不同的 Server 分片上。

Q3:如何界定 Slave 可以提升爲 Master?切換步驟是怎樣的?

A:Configserver 會定期給 Master 發送 health check 請求進行探活。只有連續多次對一個 Master 的探活都失敗時,纔會認爲 Master 不可用。這時 Configserver 會從分片內所有 Slave 中選擇可用的提升成新的 Master(不是所有的 Slave 都可用,可能某一個 Slave 也掛了,或者主從數據同步的延遲比較高)。

Q4:Proxy 是每個 Redis 集羣獨有還是所有集羣共享的?

A:Proxy 不是每個 Redis 集羣獨佔的。首先,所有集羣共享一個 Proxy 集羣,有隔離性的問題。另外,Proxy 支持動態擴縮容,可以做到彈性資源擴縮容,所以不會導致資源浪費。

Q5:系統的穩定性如何,主從切換耗時怎樣?

A:穩定性挺好。主從切換的耗時是一個策略問題,需要做一些 tradeoff。如果判斷策略太激進,可能會因爲臨時的網絡抖動等因素頻繁觸發主從切換。實際使用中我們的主從切換耗時在 10s 左右。

Q6:Redis 在什麼規模等級下的 K8s 部署會需要修改較多默認配置或者直接更改源碼? 在動態擴容的基礎上建立 Redis 集羣是否會加大困難?有什麼方式可以讓 Redis 集羣無限擴容嗎?最多到多少?

A:Redis 目前部署的 K8s 集羣規模可選,根據需要的 Redis 集羣容量來選擇 K8s 規模就可以。適配雲原生會需要調整一些組件之間的服務發現方式,但是不需要太多源碼的修改。我們目前只支持 Proxy 的動態擴縮容,Server 是有狀態的服務,還不太好接入 HPA(因爲可能會涉及到一些數據的遷移),雖然 HPA 也支持對 Statefulset 服務的自動化擴縮容。我們的 Redis 架構理論上集羣的規模可以很大,現在 CRD 的限制是一個 Redis 集羣最多 1024 個分片。

關於火山引擎

火山引擎是字節跳動旗下的數字服務與智能科技品牌,基於公司服務數億用戶的大數據、人工智能和基礎服務等技術能力,爲企業提供系統化的全鏈路解決方案,助力企業務實地創新,給企業帶來持續、快速增長。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/VCpuZ0lvgSgfvG7voBl9fw