1.寫在前面
在之前文章中,探討過到底爲什麼要雲原生,以及雲原生的核心應該是【彈性伸縮】和【按需計費】。並且簡單描述了下應用層做到【彈性伸縮】所需要解決的問題。
所以本文主要是繼續探討一下,彈性伸縮如何應用在數據庫上。以及目前業界一些案例。
2.彈性伸縮前提
要實現【彈性伸縮】的前提是什麼?
答:通過一些監控指標,自動化快速拉起一個服務,無需人工介入
而要做到這點,業界提出一個概念:服務無狀態
既然有無狀態,那麼就會存在有狀態,兩者的區別是什麼呢?
無狀態:如果發佈一臺新機器無需任何操作,直接打包代碼,編譯,部署即可完成,那麼就是無狀態
有狀態:需要配置上游IP,白名單,本地磁盤存儲了數據等
可以看出如果服務是有狀態的,是無法做到自動化快速拉起一個服務的,最少得人爲配置IP,白名單,或者遷移數據。可以很明顯的看出數據庫MySQL天然是有狀態服務。因爲本地磁盤存儲數據。
無狀態服務天然適應【彈性伸縮】,完全可以自動化快速拉起一個新的無狀態服務進程。
而以數據庫爲例的有狀態服務,如果要【彈性伸縮】,那麼動態拉起一個Slave實例,必須等待漫長的主從同步,纔可以生效。此時不符合我們的應用場景。
3.數據庫的彈性問題的本質
我們先回到問題的本質,【彈性伸縮】解決的問題是什麼,其主要解決的是資源利用率的問題。
對於應用層,資源利用率可以認爲是CPU核數,內存數,機器數等,應用層的彈性伸縮通過動態的擴容機器(可以擴容高配置機器,也可以擴容低配置機器)完成對這些資源的調整。
對於數據庫層,資源利用率可以任務是CPU核數,內存數,機器數,硬盤容量等,概括來說可以分爲兩類,計算資源(CPU+內存)+存儲資源(硬盤容量)。
思考一下,我們遇見什麼場景需要擴容MySQL實例?
我們簡單分析一下,遇見下述兩種情況會擴容MySQL
-
流量過大,增加MySQL實例,承擔更多的流量壓力【計算資源】
-
磁盤空間不足,單表過大,增加MySQL實例,分庫分表【存儲資源】
綜上,我們可以得出問題本質:數據庫需要伸縮的是計算資源和存儲資源
想明白了問題的本質,所以我們可以針對不同的場景,得出不同的解決方案。
4.實現邏輯
計算機科學領域的任何問題都可以通過增加一個間接的中間層來解決。
當下,大多數公司的數據庫架構都如下圖所示:
通過增加從庫增強讀能力,提升可用性(主從災備),通過分庫分表擴展寫能力和數據規模。
根據上述分析,我們只在兩個場景對MySQL進行擴容,流量過大,磁盤容量不足;這兩者又可以概括爲計算和存儲兩個不同的緯度。
此時,我們可以直接想到,將其分層,劃分爲兩個不同的緯度,從而分別解決其存在的問題。
所以,我們可以將數據庫劃分爲【計算層】+【存儲層】
下面,我將從上至下,簡單的解釋下兩個分層的作用
計算層
首先,我們思考一個問題,我們搭建MySQL集羣一主多從是爲什麼?
答:災備+提升讀能力
我們把關注點鎖定在讀能力上,我們是通過增加機器數量,讓更多的機器可以同時的接受應用層請求過來的讀接口。因爲一臺機器的計算資源(CPU,內存,線程)是有限的,除非更換更好的配置,否則只能水平增加機器數來增強。
所以我們可以在計算層,抽象出Writer節點+Reader節點兩個不同的概念
-
Writer節點:處理寫SQL請求
-
Reader節點:處理讀SQL請求
此時,我們可以通過動態的擴展節點數量,增強整個集羣的讀寫能力。
要做到Writer節點+Reader節點,和現在的MySQL讀寫分離其實差不多。
區別是Writer節點+Reader節點只關注SQL執行,MySQL不僅僅關注SQL執行,還關注數據存儲
存儲層
首先,我們先拋棄一切MySQL的知識,別讓其干擾你的思維。
思考一個問題,平時軟件項目開發過程中,我們最關心數據的什麼?
答:數據存儲,數據查詢,數據可靠
再思考一個問題,如果你電腦硬盤不夠,你將會做什麼操作緩解一下
答:加一塊硬盤
那麼,對應在存儲層,應該要做到以下三點:
-
如果數據量快佔滿整個集羣的容量了,那麼存儲層得必須做到動態的增加“硬盤”的能力,
-
提供讀寫接口,以供上層(計算層)調用。
-
當一塊“硬盤”損壞後,必須做到還能保證數據不丟失的能力,那麼必須做到同一份數據,多保存幾份在不同的“硬盤”上。
如何才能做到上述三點呢?
第一:抽象出Region節點的概念(可以類比Redis,Kafka,ElasticSearch當中的分片的概念),將數據水平切分在一個一個的Region(分片)上。當需要擴展容量的時候,動態增加Region即可。
第二:無論是Redis還是MySQL,它們存儲數據時候都給數據提供唯一標示,Redis當中的Key,MySQL當中的主鍵,查詢過程中,無論是直接還是間接都是通過主鍵去檢索到指定數據的。所以Region在保存數據的時候,也得爲數據添加上Key或者主鍵,這種唯一標示的概念,才能做到對外提供讀寫接口
第三:分佈式的基礎操作,冗災備份
綜上:如下圖所示
其實換個角度來看,MySQL可以分爲邏輯層和引擎層
在這裏可以把計算層看作邏輯層,存儲層看作引擎層
到這裏,我們通過劃分計算層,存儲層,即完成了可以動態完成數據庫的【彈性伸縮】
-
當流量過大,計算層的Reader節點和Writer節點已經是無狀態服務,天然適合【彈性伸縮】
-
當容量不足,存儲層完全可以通過新增Region(分片)的方式,增大容量
5.存在的問題
看到這裏,你或許已經發現了一個特別明顯的的問題,那就是查詢操作是通過RPC操作來完成,一次範圍查詢豈不是得許多次RPC操作纔可以完成,網絡傳輸的耗時都難以接受!
舉個例子,有下面一條SQL
Select * from user where id > 5 and id < 10
如果計算層,每次都調用存儲層next接口獲取下一行元組,提取出來,然後判斷是否符合,符合加入結果集,不符合丟棄,然後繼續調用next接口。
假設User表中有100條元組,那麼將發生100次RPC調用。
有什麼辦法可以優化嗎?思考下ElasticSearch是怎麼做檢索的呢?
ElasticSearch是將檢索請求下發到所有的分片上,讓每個分片執行檢索操作,返回符合邏輯的值,然後統一起來再進行處理後,返回給Client。
同理,我們可以照葫蘆畫瓢。
我們將過濾邏輯下沉到存儲層,這樣返回你的數量是減小了很多,這樣就能減小無意義的網絡傳輸,而且減少了許多無謂的RPC調用。
但是:無論怎麼優化,SQL性能比MySQL是降低了的
降低不意味着無法使用,針對不同的業務場景,其實部分的性能損耗是可以接受的
6.寫在最後
將MySQL分解成上述模式,其實還存在很多的問題,例如,MySQL中的事務,MVCC,Join查詢等等,這些都是需要解決的問題,但是這些問題總是有許多的方案解決的。
業界對於雲數據庫的【彈性伸縮】實現方案,大體上是一致的,落在細節上的實現卻大相徑庭。例如阿里的POLARDB,騰訊的CynosDB,PingCAP的TiDB等,有興趣大家可以瞭解更多細節。
本文主要是帶領大家快速瞭解數據庫這種有狀態服務如何實現【彈性伸縮】