火山引擎 Redis 雲原生實踐
作者|解寧,火山引擎研發工程師
Redis 簡介
Redis 是大家日常工作中使用較多的典型 KV 存儲,常年位居 DB-Engines Key-Value 存儲第一。Redis 是基於內存的存儲,提供了豐富的數據結構,支持字符串類型、哈希 / 列表 / 集合類型以及 stream 結構。Redis 內置了很多特性,其中比較重要的有:
-
複製:Redis 支持異步的全量和增量同步,可以把數據從 Master 複製到 Slave, 實現 Redis 數據的高可用。
-
持久化:支持數據的持久化,可以通過 RDB 和 AOF 機制實現數據落盤。
-
支持哨兵工具:哨兵工具的主要工作模式是監控 Master 節點的健康狀況。當發現 Master 節點不可用時,會主動執行 Failover, 把 Slave 節點提升成 Master,保證 Redis 服務的高可用。
-
提供集羣模式:單體 Redis 實例受限於物理機內存,當需要很大的 Redis 集羣容量時,可以使用 Redis 集羣模式。Redis 集羣模式的原理是把保存在其中的數據做了分片,每一部分數據由不同的 Redis 實例承擔。
Redis 的典型應用場景有以下 3 種:
-
緩存:因爲 Redis 是基於內存的存儲,它的讀寫請求會在內存執行,請求響應的延遲很低,所以很多場景下會把 Redis 當做緩存使用。
-
數據庫:Redis 支持持久化,可以把它當做 KV 數據庫使用。
-
消息隊列:Redis 支持 stream 數據,在 stream 數據結構基礎上封裝了 pub-sub 命令,實現了數據的發佈和訂閱,即提供了消息隊列的基本功能。
Redis 協議是二進制安全的文本協議。它很簡單,可以通過 telnet 連接到一個 Redis server 實例上執行 get 和 set 操作。
K8s 簡介
K8s 是一個容器編排系統,可以自動化容器應用的部署、擴展和管理。K8s 提供了一些基礎特性:
-
自動裝箱:可指定 K8s 裏 Pod 所需資源的最小值和最大值,即 limit 和 request 的值。K8s 可以根據 request 的值做 Pod 調度,在一個節點上拉起 Pod。
-
服務發現與負載均衡:K8s 提供基於 DNS 的服務發現機制,同時也提供基於 service 的負載均衡。
-
自動化上線和回滾:這裏會涉及到 K8s 的工作負載資源。K8s 提供幾種不同的工作負載資源對應不同的業務場景。
這些不同的工作負載資源可以實現服務的配置變更,例如更新 image、升級 binary、進行副本的擴縮容等。
-
支持 Deployment/DaemonSet
-
支持 StatefulSet
-
支持 CronJob/Job
-
水平擴縮容:K8s 天然支持水平擴縮容,可以基於 Pod 的 CPU 利用率、內存利用率以及第三方自定義 metrics 對 Pod 進行水平動態擴縮容。
-
存儲編排:K8s 支持基於 PV 和 PVC 的存儲供應模式,可以通過 PV 和 PVC 在 Pod 內部使用存儲。
-
自我修復:舉一個例子就是副本保持。比如用 Deployment 來託管一個服務,如果 Deployment 下的一個 Pod 所在的宿主機出現了不可用的情況, K8s 會在可用的節點上重新拉起一個新的 Pod 來提供服務。
現實工作中遇到的服務根據是否需要數據持久化可分爲有狀態服務和無狀態服務。不需要數據持久化的服務被認爲是無狀態的,包含以下幾種類型:
-
API 類服務:可在任意節點上執行。如果要在 K8s 上部署這類服務,可使用 K8s Deployment。
-
Agent 或 Deamon 類服務:需要部署在每一臺機器上,而且每臺機器上最多部署一個進程。在 K8s 上可選擇 DaemonSet 來完成對應的部署。
-
還有一類無狀態服務對固定的唯一標識有需求。要滿足這些需求,可使用 K8s 的 StatefulSet 來滿足。雖然 StatefulSet 是用來部署有狀態服務的,但它可提供固定的唯一標識,也可用來託管無狀態服務。
有狀態服務需要穩定的持久化存儲。除此之外,可能還會有一些其它的特性要求:
-
穩定的唯一標識
-
有序、優雅地部署和縮放
-
有序的自動滾動更新
在 K8s 上,我們一般會用 StatefulSet resource 來託管有狀態服務。
Redis 雲原生實踐
下面將介紹火山引擎 Redis 雲原生實踐。首先我們會明確 Redis 雲原生的目標,主要有以下幾個:
-
資源的抽象和交付由 K8s 來完成,無需再關注具體機型。在物理機時代我們需要根據不同機型上的 CPU 和內存配置來決定每個機型的機器上可以部署的 Redis 實例的數量。通過 Redis 雲原生,我們只需要跟 K8s 聲明需要的 CPU 和內存的大小,剩下的調度、資源供給、機器篩選由 K8s 來完成。
-
節點的調度由 K8s 來完成。在實際部署一個 Redis 集羣時,爲了保證高可用,需要讓 Redis 集羣的一些組件滿足一定的放置策略。要滿足放置策略,在物理機時代需要運維繫統負責完成機器的篩選以及計算的邏輯,這個邏輯相對比較複雜。K8s 本身提供了豐富的調度能力,可以輕鬆實現這些放置策略,從而降低運維繫統的負擔。
-
節點的管理和狀態保持由 K8s 完成。在物理機時代,如果某臺物理機掛了,需要運維繫統介入瞭解其上部署的服務和組件,然後在另外一些可用的機器節點上重新拉起新的節點,填補因爲機器宕機而缺少的節點。如果由 K8s 來完成節點的管理和狀態的保持,就可以降低運維繫統的複雜度。
-
標準化 Redis 的部署和運維的模式。儘量減少人工介入,提升運維自動化能力,這是最重要的一點。
Redis 集羣架構
下面介紹一下我們的 Redis 集羣架構。集羣裏有三個組件:Server、Proxy 和 Configserver,分別完成不同的功能。
-
Server:存儲數據的組件,即 Redis Server,其後端部署模型是一個多分片的模型。分片之間的 Server Pod 沒有通信,爲 share-nothing 的架構。分片內部爲一主多從的模式,可以一主一從、一主兩從,甚至更多。
-
Proxy:承接 client 發來的請求,同時根據讀寫拓撲,把請求轉發給後端的 Server 分片。
-
Configserver:配置管理組件,本身是無狀態的,所有的狀態信息都存儲在 etcd。集羣生命週期裏 Server 所有的分片信息都保存在 Configserver 裏。Configserver 會對每一個分片的 Master 節點進行定期探活,如果發現某一個分片的 Master 節點不可用,就會執行 Failover,把分片內可用的 Slave 提成新的 Master,保證分片可繼續對外提供服務。同時,Configserver 也會定期根據 Failover 或其他一些實例信息的變更來更新自己的讀寫拓撲關係,保證 Proxy 可以從 Configserver 拉取新的正確的配置。
結合以上介紹的 Redis 架構以及 K8s 的特性,我們抽象了一個 Redis 集羣在 K8s 集羣上部署的基本形態:
-
使用 Deployment 將無狀態的 Configserver 部署在 K8s 上。因爲 Configserver 可被所有 Redis 集羣共用,爲了簡化運維複雜度,我們規定所有的 Redis 集羣共用一個 Configserver。
-
Proxy 也是無狀態的組件,也用 Deployment 來部署。
-
因爲我們有多分片,而且 Server 是有狀態的,所以每一個分片用 StatefulSet 進行託管。在新建集羣時,我們默認分片內的 0 號 Pod 爲 Master Pod,其餘所有的 Pod 是 Slave。這是一個初始狀態,後續可能會跟隨 Failover 或其他異常發生變更,但是 Configserver 裏會實時記錄最新的狀態信息。
Redis Server 啓動的時候需要一些配置文件,裏面涉及到一些用戶名和密碼,我們是用 Secret 來存儲的。在 Server Pod 運行的時候通過 volume 機制掛載到 Server Pod 內部。
對於 Proxy,通過 HPA,基於 Proxy 的 CPU 利用率,支持 Proxy 服務的動態擴縮容。
放置策略
對於一個 Redis 集羣涉及到的 Server 和 Proxy 組件,我們有一些放置策略的要求,比如:
-
同一個 Server 分片下的節點不能在同一臺機器上,即,一個分片內的主從節點不能在同一臺機器上。轉換成 K8s 裏面的模型,即我們希望一個 StatefulSet 下所有的 Pod 部署在不同的機器上。我們會利用 Pod-AntiAffinity 下面的 required 語義,來保證 StatefulSet 下所有的 Pod 都部署在不同的機器上。
-
一個集羣下的 Proxy Pod 需要儘可能分佈在不同的機器上,可通過 Pod-AntiAffinity 下的 preferred 語義加上拓撲分佈約束來滿足。preferred 語義只能保證 Pod 儘可能分佈在不同的機器上,爲了避免極端情況下所有 Pod 都在同一臺機器上的情況,我們會使用拓撲分佈約束。
存儲
存儲使用的是 PVC 加 PV 再加上具有動態供給能力的 StorageClass。使用 StorageClass 是爲了抽象不同的存儲後端,可支持本地磁盤和分佈式存儲。可以通過 StorageClass 的配置直接申請對應的存儲,不用瞭解具體後端的實現。
另外,我們使用的是支持動態供給的 StorageClass,可自動按需創建不同大小的 PV。如果使用靜態供給,就無法提前預知所有 Redis 實例的規格,也無法把它們對應的指定數量的 PV 都創建出來。
Redis 雲原生功能介紹
Redis 雲原生化以後,Operator 組件是基於 Operator Pattern 實現的一個 custom controller,主要用於編排 Redis Cluster resource,管理 Redis 集羣的一些變更。Configserver 也部署在 K8s 上,所有跟 Redis 相關的組件都是雲原生化的。
新建集羣
-
對於常見的新建集羣的請求,會先發給 ApiServer。ApiServer 接收到請求之後,會通過 client go 的 watch 機制讓 Operator 感知到。
-
隨後 Operator 會請求 ApiServer 創建對應 Server 的 StatefulSet。
-
K8s 把所有 Server 的 StatefulSet 創建成功之後,等所有的 Pod 都處於 ready 狀態,這時所有分片內的 Server Pod 之間是沒有主從關係的。
-
Operator 感知到所有的 StatefulSet 都已經處於 ready 的狀態之後,會獲取所有 Server Pod 信息,並註冊到 Configserver。
-
Configserver 接下來會連接到所有分片內的 Slave 節點,執行實際的 SLAVEOF 命令,保證建立真正的主從關係。
-
Operator 會定期查詢 Configserver 裏建立主從關係的進度。等所有分片的主從關係建立成功之後, Operator 會請求 ApiServer 把對應的 Proxy 創建出來。
-
Proxy 創建出來之後,會去 Configserver 拉取最新的拓撲,保證對外提供服務的時候可以把請求打給後端正常的分片。
分片擴容
在實際使用的過程中如果遇到容量不足的情況,需要進行水平擴容。分片擴容的請求也是類似的:
-
請求發給 ApiServer。
-
ApiServer 把請求推送給 Operator。
-
Operator 感知到之後,會先給 ApiServer 發請求,把新的分片對應的 StatefulSet 創建出來。
-
K8s 會把新分片的 StatefulSet 創建好,在處於 ready 狀態之後,一個 StatefulSet 下的每個 Pod 也都是獨立的狀態,沒有建立真正的主從關係。
-
Operator 獲知新創建的 Server StatefulSet 分片已經處於 ready 狀態之後,會把新的 Server 分片的實例地址註冊到 Configserver。Configserver 現在會有兩個階段:
-
第一步:指導新分片內真實主從關係的建立。即連到所有的新分片的 Slave 上,執行 SLAVEOF 的命令;
-
第二步:指導數據從老分片遷移到新分片。這樣新的分片才能發揮作用,這一步很重要。
- Operator 會一直檢查數據遷移或者 rebalance 的進度。等進度結束之後,Operator 會更新 Redis Cluster 裏 status 的字段,反映出來當前的分片擴容的操作已經結束了。
分片縮容
分片縮容的流程和分片擴容類似:請求先發送給 ApiServer,Operator 會感知到請求,然後把縮容分片的請求發送給 Configserver。
Configserver 此時做的事情是:
-
先指導數據遷移。考慮到後邊的一些分片下線,需要把分片上的數據先遷移到其他可用分片上,保證數據不丟。
-
Operator 會一直查詢 Configserver 指導的數據 rebalance 的進度。等縮容操作在 Configserver 完成之後,Operator 會請求 ApiServer 執行真正的 Server StatefulSet 刪除,這時纔是安全的刪除操作。
組件升級
一個 Redis 集羣會涉及到兩個組件:Proxy 和 Server。
無狀態的 Proxy 用 Deployment 託管,如果要進行組件升級,直接升級對應的 image 即可。
Server 是一個有狀態的組件,它的升級流程相對來說複雜一點。爲了簡化流程,我們以 Server 只有 1 分片的 Redis 集羣爲例,介紹升級過程。
-
Server 組件的升級請求發送給 ApiServer,ApiServer 接收到這個請求之後會把它推送給 Operator。
-
Operator 首先會按照分片內 Pod 編號從大到小的順序選擇要升級的 Pod。
-
選定 Pod 之後,會把它從 Configserver 的讀拓撲裏摘掉。(如果要摘除的這個 Pod 在集羣拓撲裏是 Master,我們會先調用 Configserver 的 API,執行 Failover,把它變成 Slave,然後再把它從讀的拓撲裏邊給摘除掉。)
-
之後,Operator 等待 30 秒。這個機制的出發點是:
-
首先,Proxy 去 Configserver 拉取配置是異步過程,可能需要經過至少一輪的數據同步才能正常拿到數據。等待 30 秒主要是爲了保證所有的 Proxy 都已經拿到了最新的讀拓撲,新的讀請求不會再發送到要升級的 Server 節點上。
-
另外,我們要保證等待 30 秒的時間,讓已經被要升級的 Server Pod 接收的這些請求都成功地被處理,並且返回之後,才能把要升級的 Server Pod kill 掉。
-
30 秒之後請求 ApiServer 執行實際的 Pod 刪除操作。刪除之後 K8s 會重新調度一個新的 Pod 起來,這時新創建的 Server Pod 也是一個獨立的 Server 的狀態,沒有跟任何節點建立主從關係。Operator 感知到新的 Server Pod 已經處於 ready 的狀態,會把它註冊到 Configserver。
-
Configserver 連到新的 Server Pod 上,根據它所處的分片跟所在分片的 Master 節點建立主從關係,同時進行數據同步。
-
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