網易有道 REDIS 雲原生實戰

語言: CN / TW / HK

摘要

本次以Redis為範例,闡述了有道基礎架構團隊在基礎設施容器化道路上的實踐,主要將從 聲明式管理,Operator工作原理,容器編排,主從模式,集羣模式,高可用策略,集羣擴縮容 等方面展開。

youdao

背景

ydtech

Redis 是業務系統中較為常用的緩存服務,常用於流量高峯、數據分析、積分排序等場景,並且通過中間件可以實現系統之間的解耦,提升系統的可擴展性。

傳統物理機部署中間件,需要運維人員手動搭建,啟動時間較長,也不利於後期維護,無法滿足業務快速發展的需求。

雲原生相較於傳統IT, 可以助力業務平滑遷移、快速開發、穩定運維,大幅降低技術成本,節約硬件資源。

雲原生中間件是指依託容器化、服務網格、微服務、Serverless等技術,構建可擴展的基礎設施,持續交付用於生產系統的基礎軟件,在功能不變的前提下,提高了應用的可用性與穩定性。

在這種大趨勢下,有道基礎架構團隊開始了雲原生中間件的實踐,除了本文介紹的 Redis,還包括 Elasticsearch、ZooKeeper 等。

youdao

面臨的挑戰

ydtech

利用雲原生技術可以解決當前Redis部署緩慢,資源利用率低等問題,同時容器化 Redis 集羣也面臨着一些挑戰:

  • Kubernetes 如何部署 Redis 有狀態服務;

  • 容器 Crash 後如何不影響服務可用性;

  • 容器重啟後如何保證 Redis 內存中的數據不丟;

  • 節點水平擴容時如何做到 Slots 遷移時不影響業務;

  • Pod ip變化後集羣的狀態如何處理。

youdao

聲明式管理

ydtech

對於一個 Redis 集羣,我們的期望是能夠 7x24 小時無間斷提供服務,遇故障可自行修復。這與Kubernetes API的聲明式特點如出一轍。

所謂 “聲明式” , 指的就是我們只需要提交一個定義好的 API 對象來“聲明”我所期望的狀態是什麼樣子,Kubernetes中的資源對象可在無外界干擾的情況下,完成當前狀態到期望狀態的轉換,這個過程就是Reconcile過程。例如,我們通過yaml創建了一個Deployment ,Kubernetes將“自動的”根據yaml中的配置,為其創建好Pod,並拉取指定存儲捲進行掛載,以及其他一系列複雜要求。

因此,我們的Redis集羣是否可以使用一個類似的服務去完成這個過程呢?即我們需要定義這樣的對象,定義服務Reconcile的過程。Kubernetes的Operator剛好可以滿足這個需求,可以簡單的理解Operator由資源定義和資源控制器構成,在充分解讀集羣和Operator的關係後,我們將整體架構圖設計如下:

Operator集羣本身採用Deployment部署,由ETCD 完成選主,上層與Kubernetes的Api Server 、Controller Manager等組件進行通信,下層持續調和Redis集羣狀態。

哨兵模式中Redis服務用一套哨兵集羣,使用StatefulSet部署,持久化配置文件。Redis server也採用 StatefulSet部署, 哨兵模式的實例為一主多從。

集羣模式中的每個分片使用StatefulSet部署,代理採用Deployment部署。原生Pod、StatefulSet、Service、調度策略等由Kubernetes本身負責。

Redis的資源定義在ETCD中存儲一份即可,我們只需要預先提交自定義資源的 yaml配置。如下所示為 創建三個副本的Redis主從集羣:

apiVersion: Redis.io/v1beta1
kind: RedisCluster
metadata:
name: my-release
spec:
size: 3
imagePullPolicy: IfNotPresent
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 1000m
memory: 1Gi
config:
maxclients: "10000"

其中,kind定義使用的CR名稱,size為副本數,resources定義資源配額,config對應Redis Server的config,該定義存儲在Kubernetes的ETCD數據庫中,後續的具體資源申請與使用由Operator的Controller完成。

youdao

Operator工作原理

ydtech

Operator 是 Kubernetes 的擴展模式,由CRD、Controller構成。它利用定製資源管理特定應用及其組件,Operator 遵循 Kubernetes 的理念。

Operator 無需任何修改,即可從 Kubernetes 核心中獲得許多內置的自動化功能,如使用 Kubernetes 自動化部署和運行工作負載, 甚至可以自動化 Kubernetes 自身。

Kubernetes 的 Operator 模式可在不修改 Kubernetes 自身的代碼基礎上,通過控制器關聯到一個以上的定製資源,即可以擴展集羣的行為。Operator 是 Kubernetes API 的客户端,核心功能是充當定製資源的控制器。

CRD: Custom Resource Definition, 在Kubernetes中一切皆是資源,資源就是CRD,用户自定義的Kubernetes資源是一個類型 ,比如默認自帶的由Deployment,Pod ,Service等。

CR:   Custom Resource 是實現CRD的具體實例。

用户創建一個CRD自定義資源,ApiServer把CRD轉發給webhook,webhook 進行缺省值配置 驗證配置和修改配置,webhook處理完成後的的配置會存入ETCD中 ,返回給用户是否創建成功信息。Controller 會監測到CRD,按照預先寫的業務邏輯,處理這個CRD,比如創建Pod、處理新節點與舊集羣關係等,保證運行的狀態與期望的一致。

youdao

容器編排

ydtech

Redis 集羣在 Kubernetes 中的最小部署單位為 Pod,因此在架構設計之前,需預先考慮Redis特性、資源限制、部署形態、數據存儲、狀態維護等內容,為不同類型的Redis集羣配置合適的部署方式。

資源限制:

Kubernetes 採用 request 和 limit 兩種限制類型來對資源進行分配。

request(資源需求):即運行Pod的節點必須滿足運行Pod的最基本需求才能啟動。

limit(資源限制):即運行Pod期間,可能內存使用量會增加,那最多能使用多少內存,這就是資源限額。

Redis 基本不會濫用 cpu,因此配置1-2個核即可。內存根據具體業務使用分配,考慮到部分場景下會fork較多的內存,例如 aof 頻繁刷寫,aof 重寫過程中,Redis 主程序稱依舊可以接收寫操作,這時會採用 copy on write (寫時複製)的方法操作內存數據,若業務使用特點為“寫多讀少”,那麼刷寫期間將產生大量的內存拷貝,從而導致 OOM,服務重啟。

一個有效的解決方式 為減少刷寫次數,將刷寫操作放在夜間低流量時段進行。減少刷寫次數的方法為適當增加auto-aof-rewrite-min-size的大小,可配置使用內存的5倍甚至更大的最小刷寫量;其次可以主動觸發刷寫,判斷內存使用達到的配額兩倍時進行刷寫,實際部署時一般也會預留50%的內存防止OOM。

部署的基本形態:

依據數據是否需要持久化或是否需要唯一標識區分服務為無狀態和有狀態的服務,Redis集羣需要明確主從、分片標識,大部分場景也需要數據持久化,Kubernetes使用StatefulSet來滿足這一類需求。StatefulSet的順序部署、逆序自動滾動更新更能提高Redis集羣的可用性。

具體的:

Redis Server 使用 StatefulSet 啟動,為標識為{StatefulSetName}-0的Pod設置Master角色,給其他Pod設置為該Master的從節點。

Proxy無需存儲任何數據,使用Deployment部署,便於動態擴展。

配置文件:

Redis Server 啟動時需要一些配置文件,裏面涉及到用户名和密碼,我們使用 Configmap 和 Secret 來存儲的。Configmap 是 Kubernetes的Api 對象,常用於存儲小於1MB的非機密鍵值對。而 Secret 可以用於存儲包含敏感信息的密碼、令牌、密鑰等數據的對象。

兩種資源均可以在 Pod 運行的時候通過 Volume 機制掛載到 Pod 內部。

存儲:

存儲使用的是 PVC(PersistentVolumeClaim) 加 PV (Persistent Volumes),PV為Kubernetes集羣中的資源,由存儲類StorageClass來動態供應,PV支持多種訪問模式:ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany,通過PV定義存儲資源,PVC申請使用該存儲資源。另外通過根據存儲的 StorageClass 字段 可抽象不同的存儲後端,如Cephfs、Cephrbd、Openebs、LocalStorage 等。

youdao

主從模式

ydtech

主從拓撲圖

Redis容器化後建立的每個 CR 表示一個完整的Redis服務,具體的服務模式包括哨兵模式和集羣模式兩種,在進行容器化過程中,除覆蓋裸服務器部署結構外,也對架構進行了一定程度的優化。

原哨兵模式:

原哨兵模式為每套實例配一組哨兵。

共用哨兵模式:

所有實例共用一組哨兵將進一步提高實例啟動速度,並在一定程度上可提高硬件資源利用率,實測單組哨兵可輕鬆應對百規模的主從集羣。

調和原理

Reconcile 實現持續監測並對主從集羣進行修復的功能。

1. 檢查是否按照預期啟動了全部的Pod,比如創建3個Server,那麼需要按照預期啟動三個才能繼續進行後面的操作。

2. 檢查Master的數量,確保該實例僅有一個主節點(數量為0主動選一個;數量大於1手動修復)。

3. 檢查哨兵:

(1)所有的哨兵是否監控了正確的Master;

(2)所有的哨兵均知道相同的Slave;

(3)再次檢查哨兵的數量,確保哨兵均可用。

4. 檢查 Service,使Service 的Endpoints指向正確的Master。

5. 檢查Redis config是否有做修改,有則對所有節點重寫config參數。

youdao

集羣模式

ydtech

集羣拓撲圖

Redis Cluster + Proxy模式:

通過在傳統Redis Cluster架構中引入代理功能,實現動態路由分發,並基於Kubernetes原生動態擴縮容特性,更易應對突發流量,合理分配使用資源。

代理基礎轉發規則如下:

對於操作 單個 Key的命令,Proxy會根據Key所屬的Slot(槽)將請求發送給所屬的數據分片。

對於操作 多個 Key的命令,如果這些Key是儲存在不同的數據分片,Proxy會將命令拆分成多個命令分別發送給對應的分片。

服務部署前,也對代理的部分功能進行了補充,例如移除不可用節點等。

調和原理

reconcile 實現持續監測並對Redis Cluster進行修復功能。

確保集羣健康的步驟:

1. 等待所有 Pod 狀態變為 Ready 且每個節點相互識別後,Operator 會在每個 StatefulSet 的 Pod 中挑選一個作為 Master 節點,其餘節點為該 Master 的 Slave。

2. 獲取實例集羣所有Pod的ip、所有Pod的cluster info(包含nodeIP,主從關係等)。

3. 進入恢復流程

(1)處理失敗節點, 對部分節點重啟後的無效ip、狀態為noaddr的殭屍節點進行forget操作;

(2) 處理不可信節點 (所有handshake狀態的節點),發生於某一個節點被移除(由forget node觸發),但試圖加入集羣時,即該Pod在Operator角度下存在,但實際集羣節點並不需要該節點,處理方式為刪掉這個Pod,並再次做forget操作直到Pod被刪除。

4. 任選一個節點,使用CLUSTER MEET給該節點加入所有已知節點。

5. 為StatefulSet中的Pod建立主從關係,同時給其分配Slots。若當前Master數量同預期不一致,則對應擴縮容操作,具體見’集羣擴縮容’的橫向擴縮容小節。

6. 檢查Redis config是否有做修改,有則對所有節點重寫config參數。

確保代理健康的步驟:

1. 獲取所有Running狀態代理的Pod ip。

2. 從代理獲取Redis Server信息,將集羣信息同步到所有的代理上,代理中不存在的Server ip做移除操作。

3. 若代理中無可用Redis Server, 表示被全部移除,則添加一個,代理可自動發現集羣其他Redis節點。

youdao

高可用策略

ydtech

Kubernetes保證的高可用

(1) 容器部署保證高可用:

Redis部署最小資源對象為Pod,Pod是Kubernetes創建或部署的最小/最簡單的基本單位。

當啟動出錯,例如出現“CrashLoopBackOff”時,Kubernetes將自動在該節點上重啟該Pod,當出現物理節點故障時,Kubernetes將自動在其他節點上重新拉起一個。

Pod未出問題,但程序不可用時,依託於健康檢查策略,Kubernetes也將重啟該Redis節點。

(2) 滾動升級:

節點縱向擴容時,使用StatefulSet的滾動升級機制,Kubernetes將逆序重啟更新每個Pod,提高了服務的可用性。

(3) 調度的高可用:

Kubernetes本身不處理Redis 多個Pod組建的集羣之間的部署關係,但提供了部署策略,為保證特定場景下的高可用,如因物理節點導致所有Redis節點均宕機,CRD在設計中加入了親和與反親和字段。

默認使用 podAntiAffinity 做節點打散,如下所示實例instance1的所有 Pod 將被儘可能調度到不同的節點上。

    spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
Redis.io/name: instance1
topologyKey: Kubernetes.io/hostname
weight: 1


Redis集羣的高可用

Redis 服務運行期間不可避免的出現各種特殊情況,如節點宕機、網絡抖動等,如何持續監測這類故障並進行修復,實現 Redis 集羣的高可用,也是 Operator 需解決的問題 ,下面以哨兵模式模式為例描述集羣如何進行故障恢復。

主節點宕機 因物理節點驅逐、節點重啟、進程異常結束等導致的Redis主節點宕機情況,哨兵會進行切主操作,然後K ub ernetes會在可用物理節點上重新拉起一個Pod。

從節點宕機 哨兵模式的Redis集羣未開啟讀寫分離,從節點宕機對服務無影響,後續Kubernetes會重啟拉起一個Pod,Operator會將該Pod設置為新主節點的從節點。

集羣全部節點宕機 發生概率極小,但基於持久化可將服務影響降至最低,集羣恢復後可繼續提供服務。

節點網絡故障 主從模式下配置了三個哨兵用於集羣選主操作,哨兵集羣的每一個節點會定時對 Redis 集羣的所有節點發心跳包檢測節點是否正常。如果一個節點在down-after-milliseconds時間內沒有回覆Sentinel節點的心跳包,則該Redis節點被該Sentinel節點主觀下線。

當節點被一個 Sentinel 節點記為主觀下線時,並不意味着該節點肯定故障了,還需要Sentinel集羣的其他Sentinel節點共同判斷為主觀下線才行。

該 Sentinel 節點會詢問其他Sentinel節點,如果 Sentinel 集羣中超過 quorum 數量的 Sentinel 節點認為該 Redis 節點主觀下線,則該 Redis 客觀下線。

如果客觀下線的 Redis 節點是從節點或者是Sentinel節點,則操作到此為止,沒有後續的操作了;如果客觀下線的Redis節點為主節點,則開始故障轉移,從從節點中選舉一個節點升級為主節點。

集羣模式故障轉移與上述類似,不過不需要哨兵干預,而是由節點之間通過PING/PONG實現。

youdao

監控觀測

ydtech

Redis 的監控採用經典的 Exporter+Promethus 的方案,Exporter 用於指標採集,數據存儲在 Prometheus 或其他數據庫中,最終 Grafana 前端將服務狀態可視化。

youdao

集羣擴縮容

ydtech

(1)縱向擴縮容

縱向擴縮容主要指Pod的CPU、內存資源的調整,基於Kubernetes的特性,只需修改實例對應的spec字段 ,Operator的調和機制將持續監測參數變化,並對實例做出調整 。當修改cpu 、內存等參數時,Operator同步更新StatefulSet的limit、request信息,Kubernetes將逆序滾動更新Pod,滾動更新時,若停掉的是主節點,主節點的preStop功能會先通知哨兵或者集羣進行數據保存,然後做主從切換操作,從而將服務的影響降至最低。更新後的主從關係建立以及哨兵monitor主節點功能也由Operator一併處理,全過程對客户端無感知。主從版、集羣版在該場景下均支持秒級斷閃。

(2)橫向擴縮容

橫向擴縮容主要指副本數或節點數的調整,得益於 Kubernetes 的聲明式 API,可以通過更改聲明的資源規模對集羣進行無損彈性擴容和縮容。

Redis Server擴容操作時,主從版本中Operator將獲取新節點ip, 新啟動節點將在下一輪調和時觸發slaveof 主節點操作,且同步過程中,哨兵不會將該節點選為主節點。集羣版本中Operator將在同步節點信息後進行分片遷移,保證所有節點上的Slots儘可能均勻分佈。

Redis Server縮容操作時,主從版本中Operator將逆序銷燬Pod,銷燬時會先詢問哨兵,自己是否為主節點,若為主節點則進行先failover操作再退出。集羣版本中Operator中會先進行分片遷移,再對該節點做刪除操作。

代理的擴縮容,更易實現,根據流量波峯波谷規律,可手動定期在波峯到來時對 Proxy 進行擴容,波峯過後對 Proxy 進行縮容;也可根據HPA實現動態擴縮容,HPA也是Kubernetes的一種資源,可以依據Kubernetes 的Metrics API的數據,實現基於CPU使用率、內存使用率、流量的動態擴縮容。

youdao

總結與展望

ydtech

本次以 Redis 為範例,闡述了有道基礎架構團隊在基礎設施容器化道路上的實踐,Redis上雲後將大幅縮短集羣部署時間,支持秒級部署、分鐘級啟動、啟動後的集羣支持秒級自愈,集羣依託於哨兵和代理的特性,故障切換對用户無感知。

有道架構團隊最終以雲平台的形式提供中間件能力,用户無需關注基礎設施的資源調度與運維,重點關注具體業務場景,助力業務增長。未來,將進一步圍繞Redis實例動態擴縮容、故障分析診斷、在線遷移、混合部署等內容展開探索。

Redis 容器化後有哪些優勢?

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

部署:部署更快,集羣建立無需人工干預。容器部署後可保證每個的Redis節點服務正常,節點啟動後將由Operator持續監測調和Redis集羣狀態,包括主從關係、集羣關係、哨兵監控、故障轉移等。

資源隔離:如果所有服務都用同一個集羣,修改了Redis集羣配置的話,很可能會影響到其他的服務。但如果你是每個系統獨立用一個Redis羣的話,彼此之間互不影響,也不會出現某一個應用不小心把集羣給打掛了,然後造成連鎖反應的情況。

故障恢復:

(1)實例的重啟:容器化後的健康檢查可以實現服務自動重啟功能;

(2)   網絡故障:因宿主機網絡故障帶來的實例延遲高,哨兵可進行主從切換,而為了保證集羣的健康,將由Operator負責同步集羣信息。

擴縮容:容器部署可根據limit和request限制實例的cpu和內存,也可以進行擴縮容操作,擴容後的故障恢復由Operator處理

節點調整:基於Operator對CRD資源的持續調和,可在Operator的Controller中為每個Redis實例進行狀態維護,因此,節點調整後帶來的主副關係建立、集羣Slots遷移等均可自動完成。

數據存儲:容器化可掛載Cephfs、LocalStorage等多種存儲卷。

監控與維護:實例隔離後搭配Exporter、Prometheus等監控工具更容易發現問題。

- END -

往期推薦