Cloud + TiDB 技術解讀
作者:鄧栓
來源:細說雲計算
作為一款定位在 Cloud-native 的資料庫,現如今 TiDB 在雲整合上已取得了階段性的進展。日前 Cloud TiDB 產品 在 UCloud 平台正式開啟公測,TiDB 彈性伸縮的特性在 Cloud 提供的基礎設施支持下發揮的淋漓盡致。在感受雲資料庫魅力的同時,讓我們來一探究竟,看一下 TiDB 與 Cloud 背後的技術秘密。
TiDB 的架構
首先還是要先從 TiDB 的架構說起,TiDB 和傳統的單機關係型資料庫有什麼不同?相信長期以來一直關注 TiDB 的同學都比較了解了,但這裡還是科普一下。TiDB 作為一個開源的分散式資料庫產品,具有多副本強一致性的同時能夠根據業務需求非常方便的進行彈性伸縮,並且擴縮容期間對上層業務無感知。TiDB 的主體架構包含三個模塊,對應 GitHub 上面 PingCAP 組織下的三個開源項目,TiDB / TiKV / PD:
- TiDB 主要是負責 SQL 的解析器和優化器,它相當於計算執行層,同時也負責客戶端接入和交互;
- TiKV 是一套分散式的 Key-Value 存儲引擎,它承擔整個資料庫的存儲層,數據的水平擴展和多副本高可用特性都是在這一層實現;
- PD 相當於分散式資料庫的大腦,一方面負責收集和維護數據在各個 TiKV 節點的分布情況,另一方面 PD 承擔調度器的角色,根據數據分布狀況以及各個存儲節點的負載來採取合適的調度策略,維持整個系統的平衡與穩定。
上面的這三個模塊,每個角色都是一個多節點組成的集群,所以最終 TiDB 的架構看起來是這樣的。
由此可見,分散式系統本身的複雜性導致手工部署和運維的成本是比較高的,並且容易出錯。傳統的自動化部署運維工具如 Puppet / Chef / SaltStack / Ansible 等,由於缺乏狀態管理,在節點出現問題時不能及時自動完成故障轉移,需要運維人員人工干預。有些則需要寫大量的 DSL 甚至與 Shell 腳本一起混合使用,可移植性較差,維護成本比較高。
TiDB 與 Kubernetes 的整合曆程
在雲時代,容器成為應用分發部署的基本單位,而谷歌基於內部使用數十年的容器編排系統 Borg 經驗推出的開源容器編排系統 Kubernetes 成為當前容器編排技術的主流。作為 Cloud Native Database,TiDB 選擇擁抱容器技術,並與 Kubernetes 進行深度整合,使其可以非常方便地基於 Kubernetes 完成資料庫的自動化管理。
Kubernetes 項目可以說是為 Cloud 而生,利用雲平台的 IaaS 層提供的 API 可以很方便的和雲進行整合。這樣我們要做的事情就很明確了,只要讓 TiDB 與 Kubernetes 結合的更好,進而就實現了和各個雲平台的整合, 使得 TiDB 在雲上的快速部署和高效運維成為現實。
Kubernetes 最早是作為一個純粹的容器編排系統而誕生的,用戶部署好 Kubernetes 集群之後,直接使用其內置的各種功能部署應用服務。由於這個 PaaS 平台使用起來非常便利,吸引了很多用戶,不同用戶也提出了各種不同的需求,有些特性需求 Kubernetes 直接在其核心代碼裡面實現了,但是有些特性並不適合合併到主幹分支,為滿足這類需求,Kubernetes 開放出一些 API 供用戶自己擴展,實現自己的需求。當前 Kubernetes 已經發展到 v1.8,其內部的 API 變得越來越開放,使其更像是一個跑在雲上的操作系統。用戶可以把它當作一套雲的 SDK 或 Framework 來使用,而且可以很方便地開發組件來擴展滿足自己的業務需求。對有狀態服務的支持就是一個很有代表性的例子。
Kubernetes 項目最早期只支持無狀態服務 (Stateless Service) 來管理的,無狀態服務通過 ReplicationController 定義多個副本,由 Kubernetes 調度器來決定在不同節點上啟動多個 Pod,實現負載均衡和故障轉移。對於無狀態服務,多個副本對應的 Pod 是等價的,所以在節點出現故障時,在新節點上啟動一個 Pod 與失效的 Pod 是等價的,不會涉及狀態遷移問題,因而管理非常簡單。但是對於有狀態服務 (Stateful Service),由於需要將數據持久化到磁碟,使得不同 Pod 之間不能再認為成等價,也就不能再像無狀態服務那樣隨意進行調度遷移。Kubernetes v1.3 版本提出 PetSet 的概念用來管理有狀態服務並於 v1.5 將其更名為 StatefulSet。StatefulSet 明確定義一組 Pod 中每個的身份,啟動和升級都按特定順序來操作。另外使用持久化卷存儲 (PersistentVolume) 來作為存儲數據的載體,當節點失效 Pod 需要遷移時,對應的 PV 也會重新掛載,而 PV 的底層依託於分散式文件系統,所以 Pod 仍然能訪問到之前的數據。同時 Pod 在發生遷移時,其網路身份例如 IP 地址是會發生變化的,很多分散式系統不能接受這種情況。所以 StatefulSet 在遷移 Pod 時可以通過綁定域名的方式來保證 Pod 在集群中網路身份不發生變化。
然而現實中一些分散式系統更為複雜,StatefulSet 也顯得捉襟見肘。舉例來說,某些分散式系統的節點在加入集群或下線時還需要做些額外的註冊和清理操作,或者滾動升級要考量版本兼容性等。基於這個原因 CoreOS 公司提出了 Operator 概念,並實現了 etcd-operator 和 prometheus-operator 來管理 Etcd 和 Prometheus 這樣的複雜分散式系統。用戶可以開發自己的 Operator,在 Kubernetes 之上實現自定義的 Controller,將有狀態服務的領域特定的運維知識編碼進去,從而實現對特定分散式系統的管理。同時 Operator 本身也是跑在 Kubernetes 中的一組 Pod(deployment),對 Kubernetes 系統並無侵入性。
TiDB 系列組件及其作用
針對 TiDB 這種複雜的分散式服務,我們開發了 tidb-operator 等一系列組件,來管理 TiDB 集群實例在 Kubernetes 平台上的創建、銷毀、擴縮容、滾動升級和故障轉移等運維操作。同時在上層封裝一個 tidb-cloud-manager 組件,提供 RESTful 介面,實現與雲平台的控制台打通。這樣也就實現了一個 DBaaS (資料庫即服務)架構的基本形態。
由於 TiDB 對磁碟 I/O 有比較高的要求,通過 PV 掛載網路盤性能上會有明顯的性能損耗。另外 TiKV 本身維護了數據多副本,這點和分散式文件系統的多副本是有重複的。所以我們要給 Pod 上掛載本地磁碟,並且在 Kubernetes 上面把 Local PV 管理起來,作為一種特定的資源來維護。Kubernetes 長期以來官方一直沒有提供 Local PV 支持,本地存儲只支持 hostPath 和 emptyDir 兩種方式。其中 hostPath 的生命周期是脫離 Kubernetes 管理的,使用 hostPath 的 Pod 銷毀後,裡面的數據是不會被自動清理,下次再掛載 Pod 就會造成臟數據。而 emptyDir 更像一個臨時磁碟,在 Pod 重建時會被清理重置,不能成為持久化 PV 來使用。為此我們開發了一個 tidb-volume-manager 組件,用於管理 Kubernetes 集群中每台物理主機上的本地磁碟,並且將其暴露成一種特殊的 PV 資源。結合 Operator 在部署 TiDB 節點時會參考 Local PV 資源的情況來選擇特定的節點來部署,分配一個空的 Local PV 和 Pod 綁定。而當 Pod 銷毀時候會根據具體情況來決定是否結束 Local PV 的生命周期,釋放掉的 Local PV 再經歷一個 gc 周期後,被 tidb-volume-manager 回收,清理其盤上數據等待再次被分配使用。
將這些組件整合起來,就形成了上圖描述了 Cloud TiDB 的總體架構,在 Kubenetes 管理的集群之上通過 tidb-operator 等組件來針對性的調配和使用集群資源,從而實現 TiDB 集群實例的生命周期管理。通過這種方式,來實現 TiDB 分散式資料庫和雲平台的整合。接下來,我們再針對 Cloud TiDB 的關鍵特性和實現細節分別進行解讀。
自動化運維
資料庫產品上雲的一個先決條件是能實現自動化的運維管理,否則在雲上靠手工運維幾乎是不現實的。我們首先用 Kubernetes 將雲平台的主機資源管理起來,組成一個大的資源池。然後再通過 tidb-opeartor 及 tidb-cloud-manager 等組件來自動化完成 TiDB 實例的一鍵部署、擴容縮容、在線滾動升級、自動故障轉移等運維操作。
- 首先拿集群創建來說。前面提到過,TiDB 包含三大核心組件:TiDB / TiKV / PD,每個服務又都是一個多節點的分散式結構。服務和服務之間的啟動順序也存在依賴關係。此外,PD 節點的創建和加入集群方式和 etcd 類似,是需要先創建一個單節點的 initial 集群,後面加入的節點需要用特殊的 join 方式,啟動命令上都有差別。有一些操作完成後還需要調用 API 進行通知。Kubernetes 自身提供的 StatefulSet 是很難應付這種複雜的部署,所以需要 tidb-operator 中實現特定的 Controller 來完成這樣一系列的操作。並且結合 Kubernetese 強大的調度功能,合理的規劃和分配整個集群資源,盡量讓新部署的 TiDB 實例節點在集群中均勻分布,最終通過 LB 暴露給對應的租戶使用。
- 在線升級也是類似。由於 TiKV / PD 的 Pod 掛載的是本地存儲,並不能像雲平台提供的塊存儲或網路文件系統那樣可以隨意掛載。如果 TiKV / PD 遷移到其它節點,相當於數據目錄也被清空,所以必須保證 TiKV / PD 的 Pod 在升級完成後仍然能夠調度在原地,這也是要由 tidb-operator 的 Controller 來保證。TiDB 的數據副本之間由 Raft 演算法來保證一致性,因此當集群中某一個節點暫時斷開可以不影響整個服務的。所以在集群升級的過程中,必須嚴格按照服務的依賴關係,再依次對 Pod 進行升級。
- 當節點出現故障時,同樣是由於掛載本地數據盤的原因,也不能像 StatefulSet 那樣直接把 Pod 遷移走。當 TiDB Operator 檢測到節點失效,首先要等一定的時間確認節點不會再恢復了,開始遷移恢復的操作。首先調度選擇一個新節點啟動一個 Pod, 然後通知 TiDB 將失效的節點放棄掉,並將新啟的 Pod 加入集群。後面會由 TiDB 的 PD 模塊來完成數據副本數的恢復,以及數據往新節點上進行搬移,從而重新維持集群內數據平衡。
以上只是列舉了 TiDB 幾種典型的運維操作流程,實際生產上運維還有很多 case 需要考慮,這些都以程序的方式實現在 tidb-operator 裡面。藉助 Kubernetes 和 tidb-operator 來代替人工,高效的完成 TiDB 資料庫在雲平台上的複雜運維管理。
動態擴縮容
彈性水平伸縮是 TiDB 資料庫最主要的特性之一。在大數據時代,人們對數據存儲的需求在快速膨脹。有時候用戶很難預估自己的業務規模的增長速度,如果採用傳統的存儲方案,可能很快發現存儲容量達到了瓶頸,然後不得不停機來做遷移和完成擴容。如果使用 Cloud TiDB 的方案,這個過程就非常簡單,只需要在 Cloud 控制台上修改一下 TiDB 的節點數量,很快就能完成擴容操作,期間還不會影響業務的正常服務。
那麼在 Cloud 後台,同樣藉助 Kubernetes 和 tidb-operator 的能力來完成 TiDB 增減節點操作。Kubernetes 本身的運作是基於一種 Reconcile 的機制。簡單來說當用戶提交一個新的請求,比如期望集群裡面跑 5 個 TiKV 節點,而目前正在跑的只有 3 個,那麼 Reconcile 機制就會發現這個差異,首先由 Kubernetes 的調度器根據集群整體資源情況,並結合 TiDB 節點分配的親和性原則和資源隔離原則來分配節點。另外很重要一點就是選擇有空閑 Local PV 的機器來創建 Pod 並進行掛載。最終通過 tidb-operator 將 2 個節點加入 TiDB 集群。
對於縮容的過程也是類似。假如資料庫存儲的總數據量變少,需要減少節點以節省成本。首先用戶通過雲控制台向後端提交請求,在一個 Reconciling 周期內發現差異,tidb-operator 的 Controller 開始通知 TiDB 集群執行節點下線的操作。安全下線可能是個比較長的過程,因為期間需要由 PD 模塊將下線節點的數據搬移到其他節點,期間集群都可以正常服務。當下線完成,這些 TiKV 變成 tombstone 狀態。而 tidb-operator 也會通知 Kubernetes 銷毀這些 Pod,並且由 tidb-volume-manager 來回收 Local PV。
資源隔離
資源隔離也是雲上用戶關心的一個問題。尤其是資料庫這類應用,不同租戶的資料庫實例,甚至一個租戶的多套資料庫實例,都跑在一套大的 Kubernetes 管理的集群上,相互間會不會有資源的爭搶問題,某個實例執行高負載的計算任務時,CPU、內存、I/O 等會不會對同台機器上部署的其他實例產生影響。其實容器本身就是資源隔離的一個解決方案,容器的底層是 Linux 內核提供的 cgroups 技術,用於限制容器內的 CPU、內存以及 IO 等資源的使用,並通過 namespace 技術實現隔離。而 Kubernetes 作為容器編排系統,能夠根據集群中各個節點的資源狀況,選擇最優的策略來調度容器。同時 tidb-operator 會根據 TiDB 自身的特性和約束,來綜合決策 TiDB 節點的調度分配。舉例來說,當一個 Kubernetes 集群橫跨多個可用區,用戶申請創建一個 TiDB 集群,那麼首先根據高可用性原則,將存儲節點盡量分配到不同的可用區,並給 TiKV 打上 label。那麼同一個可用區內也盡量不把多個 TiKV 部署到相同的物理節點上,以保證集群資源最大化利用。此外,每個 Local PV 也是一塊獨立的磁碟,每個 TiKV 的 Pod 分別掛載不同的盤,所以 I/O 上也是完全隔離的。Kubernetes 還可以配置 Pod 之間的親和性(affinity)和反親和性(anti-affinity),例如 TiKV 和 TiDB 之間我們可以通過親和性使其調度到網路延時較小的節點之上,提高網路傳輸效率,TiKV 之間藉助反親和性,使其分散部署到不同的主機、機架和可用區上,降低因硬體或機房故障造成的丟數據的風險。
上面解釋了容器層面的隔離,可以看作是物理層面的隔離。那麼數據層面的隔離,TiDB 的調度體系也是有所考慮的。比如一個大的 TiDB 集群,節點分布在很多台主機,跨越多個機架、可用區。那麼用戶可以定義 Namespace,這是一個邏輯概念,不同業務的資料庫和表放置在不同的 Namespace。再通過配置 Namespace 和 TiKV 節點以及區域的對應關係,由 PD 模塊來進行調度,從而實現不同業務的數據在物理上的隔離。
高可用性
TiDB 作為一個分散式資料庫本身就具有高可用性,每個核心組件都可以獨立的擴縮容,任意一個模塊在部署多份副本時如果有一個掛掉,整體仍然可以正常對外提供服務,這是由 Raft 協議保證的。但是如果對資料庫節點的調度不加任何限制,包含一份數據的多個副本的節點可能會被調度到同一台主機。這時如果主機發生故障,就會同時失去多個副本,一個 Raft 分組內在失去多數派節點就會使整個集群處於不可用的狀態。因此 tidb-operator 在調度 TiKV 節點時需要避免出現這種情況。
另外 TiDB 支持基於 label 的數據調度的,給不同的 TiKV 實例加上描述物理信息的 label,例如地域(Region)、可用區(AZ)、機架(Rack)、主機(Host),這樣 PD 在對數據進行調度時就會參考這些信息更加智能的制定調度策略,盡最大可能保證數據的可用性。例如 PD 會基於 label 信息盡量把相同數據的副本分散調度到不同的主機、機架、可用區、地域上,這樣在物理節點掛掉或機架掉電或機房出故障時,其它地方仍然有該數據足夠的副本數。藉助 tidb-operator 中 controller-manager 組件我們可以自動給 TiKV 實例加上物理拓撲位置標籤,充分發揮 PD 對數據的智能調度能力,實現數據層面的高可用性。
同時我們還可以實現實例級別的高可用性,通過 Kubernetes 強大的調度規則和我們擴展的調度器,我們按優先順序會盡量選擇讓 TiKV 部署到不同的主機、機架和可用區上,把因主機、機架、機房出問題造成的影響降到最低,使數據具有最大的高可用性。
另外運行在 Kubernetes 之上我們能實時監測到 TiDB 各組件的運行情況,當出現問題時,我們也能第一時間讓 tidb-operator 對集群進行自動修復 (self-healing)。具體表現為 TiDB / TiKV / PD 實例出現故障時,執行安全的下線操作。同時增加新的實例,來保證集群的規模和之前一致。
總結
TiDB 作為一款 Cloud Native Database,通過 tidb-operator 的方式充分發揮 Kubernetes 平台的強大能力,實現雲上自動化管理,極大降低人力運維成本。用戶可以根據業務需要進行動態擴容縮容,多租戶隔離特性讓不同租戶的實例可以共享計算和存儲資源,互不干擾,同時最大程度充分使用雲上資源。Raft 演算法和 tidb-operator 自動修復能力以及兩層調度機制保證了 Cloud TiDB 的高可用性。UCloud 和 PingCAP 公司深度合作,推出 Cloud TiDB 產品現已開啟公測,歡迎大家來體驗雲時代的新一代資料庫。
推薦閱讀: