雲時代的到來,無論傳統行業還是網際網路行業,業務越來越多樣,疊代速度越來越快,使得整體資料量大幅提升。
近兩年,随着 Docker + Kubernetes 等技術的興起,大家都将業務往容器化遷移,團隊的技術也在往雲原生方向演進。早期的 Kubernetes 着重解決 Stateless 和 Share Nothing 的應用部署場景,然而在如今愈發複雜的應用場景中經常會遇到有狀态儲存的需求。
從 Kubernetes 1.9 開始,針對有狀态服務的資源類型 Statefulset 進入 GA,而且 Kubernetes 1.14 版本 Local Volume、CSI 等存儲功能也進入 GA 階段,Kubernetes 對有狀态服務的支援得到全面加強,這使得很多資料存儲型基礎中間件往 Kubernetes 遷移成為可能。
MySQL 作為目前比較受歡迎的開源關系型資料庫(RDS),集可靠、易用、功能豐富、适用範圍廣等特點于一身,使其成為關系型資料庫的主要選擇。雖然備受關注,但 MySQL 在雲原生浪潮中卻也面臨着諸多挑戰。如何用 Cloud Native 的設計原則,通過沙箱隔離、計算和資料的完全分離,實作低成本、可擴充、高可用的 Cloud RDS 方案?
本文介紹一種雲原生分布式 MySQL 高可用資料庫方案(下稱“SlightShift MySQL 高可用方案”),并對雲原生場景下傳統資料庫的發展趨勢做簡要分析。

一 需求&挑戰
在考慮雲原生場景下的 MySQL 高可用架構時,主要有如下幾個方面的挑戰:
- 故障轉移:主庫發生當機時,叢集能夠自動選主并快速轉移故障,且轉移前後資料一緻。
- 靈活彈性伸縮:基于副本的彈性橫向擴充,擴縮容過程不中斷業務通路。
- 資料安全性:資料定時冷備/實時熱備,以便故障恢複和資料遷移。
- 資料強一緻性:用作備份/隻讀副本的Slave節點資料應該和主節點資料保持實時或半實時一緻。
二 目标&關鍵考慮點
SlightShift MySQL 高可用方案要達到的目标:
- SLA保障:一年内可接受最高 52.56 分鐘服務不可用(99.99%)。
- 故障轉移:主庫出現異常時主從切換耗時 < 2min。
- 彈性擴充:從庫理論可無限擴充,擴充從庫耗時 < 2min。
- 冷備恢複:MySQL叢集出現不可恢複性問題時,從冷備恢複耗時 < 10min。
除以上目标外,在技術架構設計時還需重點考慮以下關鍵點:
- 高可用
- 應用接入成本
- 資源占用量
- 可擴充性
- 可維護性
三 架構設計
該 MySQL 高可用方案使用一主多從的複制結構,主從資料複制采用半同步複制,保證了資料一緻性和讀寫效率,理論上從庫可無限擴充。
在資料引擎上層添加了仲裁器,使用 Raft 分布式一緻性算法實作自動化選主和故障切換。
路由層使用 ProxySQL 作為 SQL 請求代理,能夠實作讀寫分離、負載均衡和動态配置探測。
監控告警方面,采用 Prometheus-Operator 方案實作整個 MySQL 高可用系統的資源監控告警。
運維管控方面,引入 Kubernetes Operator 的管控模型來實作 DB-Operator,能夠做到聲明式配置、叢集狀态管理以及On Demand(按需建立)。另外,可以在 MySQL 控制台上進行 MySQL 的基礎運維,例如:資料庫管理、表管理、SQL查詢,索引變更、配置變更、資料備份&恢複等。
四 關鍵技術
狀态持久化
容器技術誕生後,大家很快發現用它來封裝“無狀态應用”(Stateless Application)非常好用。但如果想要用容器運作“有狀态應用”,其困難程度就會直線上升。
對于 MySQL 等存儲型分布式應用,它的多個執行個體之間往往有依賴關系,比如:主從關系、主備關系。各個執行個體往往都會在本地磁盤上儲存一份資料,當執行個體被殺掉,即便重建出來,執行個體與資料之間的對應關系也已經丢失,進而導緻應用失敗。
這種執行個體之間有不對等關系,以及執行個體對外部資料有依賴關系的應用,被稱為“有狀态應用”(Stateful Application)。
Kubernetes 叢集中使用節點本地存儲資源的方式有 emptyDir、hostPath、Local PV 等幾種方式。其中,emptyDir 無法持久化資料,hostPath 方式需要手動管理卷的生命周期,運維壓力大。
是以在MySQL場景中,出于性能和運維成本考慮需要使用本地存儲,Local PV 是目前為止唯一的選擇。
Local PV 利用機器上的磁盤來存放業務需要持久化的資料,和遠端存儲類似,Pod 的資料和生命周期是互相獨立的,即使業務 Pod 被删除,資料也不會丢失。
同時,和遠端存儲相比,本地存儲可以避免網絡 IO 開銷,擁有更高的讀寫性能,是以分布式檔案系統和分布式資料庫這類對 IO 要求很高的應用非常适合本地存儲。
不同于其他類型的存儲,Local PV 本地存儲強依賴于節點。換言之,在排程 Pod 的時候還要考慮到這些 Local PV 對容量和拓撲域的要求。
MySQL 在使用 Local PV 時,主要用到兩個特性:延遲綁定機制和 volume topology-aware scheduling。延遲綁定機制可以讓 PVC 的綁定推遲到有 MySQL Pod 使用它并且完成排程後,而 volume topology-aware scheduling 則可以讓 Kubernetes 的排程器知道卷的拓撲限制,也就是這個存儲卷隻能在特定的區域或節點上使用(通路),讓排程器在排程 Pod 的時候必須考慮這一限制條件。
另外,MySQL 使用的 Local PV 還需通過 nodeAffinity 将 Pod 排程到正确的 Node 上。
下面展示了 MySQL 使用 Local PV 的簡單配置示例:
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
creationTimestamp: null
name: local-storage
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Delete
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
pv.kubernetes.io/bound-by-controller: "yes"
creationTimestamp: null
labels:
app: slightshift-mysql
mysql-node: "true"
name: data-slightshift-mysql-0
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-volume-storage
volumeName: mysqlha-local-pv-0
---
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/resource-policy: keep
volume.alpha.kubernetes.io/node-affinity: '{ "requiredDuringSchedulingIgnoredDuringExecution":
{ "nodeSelectorTerms": [ { "matchExpressions": [ { "key": "kubernetes.io/hostname",
"operator": "In", "values": ["yz2-worker004"] } ]} ]} }'
labels:
pv-label: slightshift-mysql-data-pv
type: local
name: slightshift-mysql-data-pv-0
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 500Gi
local:
path: /var/lib/ali/mysql
persistentVolumeReclaimPolicy: Retain
storageClassName: pxc-mysql-data
以上是 Local PV 的基礎用法,而 MySQL 還需考慮節點的彈性伸縮,這就要求底層存儲也能夠随着 MySQL 執行個體伸縮來動态配置。這裡提出兩種解決方案:
- 在 Kubernetes 叢集中引入 LVM Manager,以 DaemonSet 形式運作,負責管理每個節點上的磁盤,彙報節點磁盤容量和剩餘容量、動态建立 PV 等;再引入 local storage scheduler 排程子產品,負責為使用本地存儲的 Pod 選擇合适(有足夠容量)的節點。
- 使用開源方案 OpenEBS:iSCSI 提供底層存儲功能,OpenEBS 管理 iSCSI(目前隻支援PV的 ReadWriteOnce 通路模式)。
自動化選主
SlightShift MySQL 高可用架構基于 Raft 強一緻協定實作分布式 MySQL 自動化選主。
Raft 使用心跳來觸發選主,當 MySQL Server 啟動時狀态是 follower。當 server 從 leader 或者 candidate 接收到合法的RPC時,它會保持在 follower 狀态,leader 會發送周期性的心跳來表明自己是 leader。
當一個 follower 在 election timeout 時間内沒有接收到通信,那麼它會開始選主。
選主的步驟如下:
- 增加 current term。
- 轉成 candidate 狀态。
- 選自己為主,然後把選主RPC并行地發送給其他的 server。
- candidate 狀态會繼續保持,直到下述三種情況出現。
candidate 會在下述三種情況下退出:
- server 本身成為 leader。
- 其他的 server 選為 leader。
- 一段時間後,沒有 server 成為 leader。
故障轉移
在正常運作的主從複制環境中,故障轉移(Failover)子產品會監聽叢集狀态,當 Master 發生故障時會自動觸發故障轉移。
故障轉移的第一步是自動化選主,自動化選主的邏輯在上面已介紹過;其次是資料一緻性保障,需最大化保證 Dead Master的 資料被同步到 New Master。
在将 Master 切換到 New Master 之前,部分 slave 可能還未接收到最新的 relay log events,故障轉移子產品也會從最新的 slave 自動識别差異的 relay log events,并 apply 差異的 event 到其他 slaves,以此保證所有 slave 的資料都是一緻的。
關于代理層,ProxySQL 會實時探測 MySQL 執行個體的可讀寫配置,當 MySQL 執行個體的可讀寫配置發生變化時,ProxySQL 會自動調整MySQL 執行個體的讀寫分組配置,最終保證在 Failover 之後讀寫分離能正确運作。
SlightShift MySQL 自動故障轉移的步驟如下:
- HA-Manager 偵測到 Master Server 連接配接異常,啟動 Failover。
- 嘗試關閉 Dead Master 以避免腦裂(此步驟可選)。
- 擷取最新資料 slave 的 end_log_pos,并從 Dead Master 同步 bin-log,最終保證所有 slave 的 end_log_pos 一緻。
- 使用 raft 分布式算法選舉 New Master。
- 将 Master 切換到 New Master。
- 回調 ProxySQL 代碼服務,剔除 Dead Master 配置。
- ProxySQL 網關檢測到各個 MySQL 執行個體的可讀寫配置變化,調整讀寫分離配置。
- 通知切換結果(郵件、釘釘群機器人)。
SlightShift MySQL 能做到秒級故障轉移,5-10秒監測到主機故障,5-10秒 apply 差異 relay logs,然後注冊到新的 master,通常10-30秒即可 total downtime。另外,可在配置檔案裡配置各個 slave 當選為 New Master 的優先級,這在多機房部署 MySQL 場景下很實用。
故障自動恢複
當機的 Master 節點恢複時:
- 如果恢複的節點重新建立 Mysql Pod 并加入叢集,Sentinel 會配置新 Pod 為 Slave,通過擷取目前 Master Mysql 的 log file 和 Position 來實作新加入 Slave 的資料同步。
- 如果恢複的節點中 Mysql Pod 依然存在且可用,Sentinel 此時會發現2個 Label 為 master 的 Pod。Sentinel 會依然使用當機期間選擇的新 Master,且把恢複的 Master 強制設定為Slave,并開啟隻讀模式。
當機的 Slave 節點恢複時:
- 如果恢複節點重新建立 Mysql Pod 并加入叢集,Sentinel會配置新 Pod 為Slave,通過擷取目前 Master Mysql 的 log file 和 Position 來實作新加入 Slave 的資料同步。
- 如果恢複的節點中 Mysql Pod 依然存在且可用,排程器不會執行操作,proxysql-service 會監測到新加入的 Slave,并将其加入到 endpoints 清單。
狀态管理
狀态管理并非新鮮話題,它為中心化系統分發一緻的狀态,確定分布式系統總是朝預期的狀态收斂,是中心化系統的基石之一 。
MySQL 服務由一個 Master 節點和多個從 Master 上異步複制資料的 Slave 節點組成,即一主多從複制模型。其中,Master 節點可用來處理使用者的讀寫請求,Slave 節點隻能用來處理使用者的讀請求。
為了部署這樣的有狀态服務,除了 StatefulSet 之外,還需要使用許多其它類型的 k8s 資源對象,包括 ConfigMap、Headless Service、ClusterIP Service 等。正是它們間的互相配合,才能讓 MySQL 這樣的有狀态服務有條件運作在 k8s 之上。
在 MySQL 叢集中,狀态轉移最常發生在 Master 發生故障,叢集進行故障轉移期間。當 Master 發生故障當機時會自動觸發選主邏輯,選主結束後會進行故障切換,直至 New Master 正常提供讀寫服務,故障轉移過程是需要時間的,在故障轉移過程中 MySQL 服務會處于不可寫狀态。
在故障轉移期間,Dead Master 執行個體會被 k8s 叢集自動拉起,Dead Master 被拉起後會認為自己是合法的 Master,這樣會造成叢集中同時存在兩個 Master,寫請求很可能會被随機配置設定到兩個 Master 節點,進而造成腦裂問題。
為了解決這種場景下的腦裂問題,我們引入了 InitContainer 機制,再配合 Sentinel 就能很好的解決該問題。
InitContainer,顧名思義,在容器啟動的時候會先啟動一個或多個容器去完成一些前置性工作,如果有多個,Init Container将按照指定的順序依次執行,隻有所有的 InitContainer 執行完後主容器才會啟動。
我們在每一個 MySQL 的執行個體中都加入了 InitContainer,在 Dead Master 被 k8s 自動拉起之後,InitContainer 會自動檢測叢集中是否處于 Failover 階段,如果處于Failover 階段會進入 Sleep 輪詢狀态,直至 Failover 結束。
當 Failover 結束後,Sentinel 檢測到 Dead Master 被拉起,會自動将 Dead Master 設定為 New Master 的 Slave 節點,以此來完成一次完整的 Failover 過程,并避免叢集出現腦裂問題。
聲明式運維
我們在使用 k8s 時,一般會通過 k8s 的 Resource 滿足應用管理的需求:
- 通過 Deployment、StatefulSet 等 workload 部署服務。
- 通過 Service、Ingress 管理對服務的通路。
- 通過 ConfigMap、Secrets 管理服務的配置。
- etc.
上述 Resource 表征 User 的期望,kube-controller-mananger 中的 Controller 會監聽 Resource Events 并執行相應的動作,來實作 User 的期望。
這種操作方式給應用管理帶來了很大的便利,User 可以通過聲明式的方式管理應用,不用再關心如何使用傳統的 HTTP API、RPC 調用等。
Controller 會通過各種機制來確定實作 User 的期望,如通過不斷檢測 Object 的狀态來驅使 Object 目前狀态符合使用者期望,這種運維模式我們稱之為聲明式運維。
但如果僅僅使用 k8s 提供的基礎類型,對于 MySQL 這類複雜應用來說運維成本依然很高。如果能将聲明式運維的模式進行擴充延伸到 MySQL 應用,會極大程度降低 MySQL 應用的部署和運維成本。
為了解決這個問題,slightshift-mysql-operator 應運而生。
- slightshift-mysql-operator 本質上是 Resource + Controller:
-
- Resource
自定義資源(CRD, Custom Resource Definitions),為 User 提供一種聲明式的方式描述對服務的期望。
- Controller
-
- 實作 Resource 中 User 的期望。
slightshift-mysql-operator 通過組合 k8s 中已有的概念,極大降低了部署和運維 MySQL 的成本:
- User 通過類似使用 Deployment 的方式描述對 MySQL 的需求
-
- 在 k8s 上部署 MySQL 應用的姿勢與 k8s 官方資源的操作方式相同。
- 由 slightshift-mysql-operator 的 Controller 監聽、處理事件請求
-
- 監聽 Resource Events。
-
- 針對不同類型 (ADD/UPDATE/DELETE) 的 Events 執行相應的動作。
-
- 不斷檢測 Object 的狀态來執行動作,驅使服務的狀态符合 User 期望。
slightshift-mysql-operator 的設計理念:
- 聲明式配置:提升可讀性和運維效率,降低運維成本。
- 最終一緻性:動态調整叢集狀态實作最終一緻性。
slightshift-mysql-operator 極大程度上降低了在 Kubernetes 叢集中使用和管理 MySQL 應用的成本,User 可以通過聲明式的 CR 建立應用,Vendor 可将管理應用的專業知識封裝,對 User 透明。
slightshift-mysql-operator 在架構層面上使用分層設計,簡化了架構複雜度,同時盡可能降低了 MySQL 多個叢集執行個體間互相幹擾,實作各個執行個體的自治。
部署結構如下圖所示:
備份與恢複
為保證資料安全性,還需對資料進行定期備份,保證使用者資料在極端情況下(叢集崩潰)時資料可恢複。
通過 Cronjob 對 Mysql 資料進行備份:
- 在 Kubernetes 叢集中建立 Cronjob,Cronjob 會定期進行資料備份。
- 備份資料會被打上時間标簽,并上傳到對象存儲伺服器(Ceph、Minio)。
通過建立 Job 對 Mysql 進行資料恢複:
- 在叢集中建立恢複 Job,Job 根據 SNAP_SHOT 到存儲伺服器(OSS、Minio)上擷取備份資料。
- Job 把資料恢複到 Mysql Master 執行個體,同時 Slave 會完成資料同步。
五 技術演進
唯有進化,才能站到食物鍊頂端。
根據 Gartner 報告預測,資料庫雲平台市場佔有率将會在下一個五年中翻倍,而70%的使用者将開始使用 dbPaaS 資料庫雲平台。是以,為了滿足各類應用程式對資料庫雲平台的需求,同時為了減少私有雲部署中對大量不同類型資料存儲産品的運維複雜性,資料庫的架構演進将是未來十年資料庫轉型的主要方向之一。
雲原生資料庫是未來資料庫發展的一個重要方向,雲原生資料庫架構随着雲化要求也需要進行相應的疊代,未來在雲原生資料庫架構的演進還會随着需求的變化而持續發展。
六 未來展望
中間件作為構築上層業務系統的基石和核心技術,具有高可靠性、高擴充性、強專業性等特點。
關于未來,由于中間件的這些特點,未來雲原生中間件會向着規範化、建構化、松耦合和平台化的方向發展。平台化能夠更快速響應業務變化,為業務的橫向發展提供集中的技術解決方案。
從終态來看,我們想做一個企業級雲原生中間件 PaaS 平台,涵蓋基礎中間件的部署、配置、更新、伸縮、故障處理等自動化能力。
首先談談我對中間件平台的了解,中間件平台應該是一系列中間件解決方案集合的能力開放式技術中台,它形成了完整、久經考驗、開放群組件化的解決方案,旨在為複雜多變的上層業務領域提供穩定可靠的計算、存儲服務。
任何一個平台型産品的發展曆程,都不是一開始就做平台的,而是先有業務需求,為了解決實際業務需求而積累了某些領域的知識,進而成為了這些領域的專家,最終再向平台轉變。換句話說,平台是一個持續積累的過程,也是一個水到渠成的過程。
在企業資訊平台建構過程中,有效、合理、規範化的利用中間件平台來快速建構上層業務系統,可以為企業及時響應需求變化提供有力保障,形成企業的集約化管理,進而提升企業核心力量獲得可持續競争的優勢。