隨着業務的快速發展,作到未雨綢繆很重要,在提高關係型數據庫的擴展性和高可用性方面須要提早佈局,MySQL方案雖然不是萬金油,倒是架構演進中的一種典型方案,也是建設MySQL分佈式存儲平臺一個很好的切入點。前端
本文會着重討論遷移到MySQL架構體系的演進過程,相信大大小小的公司在不一樣的發展階段都會碰到其中一些共性的問題。 數據庫
咱們先來簡單介紹一下系統遷移的背景,在這個過程當中咱們不會刻意強調源數據庫的一些功能性差別,相對來講是一種更通用的架構改進方式。後端
1、架構改造背景和演進策略緩存
遷移前,咱們作了業務梳理,總體的系統現狀梳理以下表,能夠發現這個業務其實能夠劃分爲兩個大類,一個是數據業務,一個是帳單業務。數據業務負責事務性數據,而帳單業務是狀態數據的操做歷史。性能優化
改造前架構以下圖所示,對數據作了過濾,總體上庫裏面的表有上萬張,雖然是多個獨立的業務單元,可是狀態數據和流水數據是彼此經過存儲過程級聯調用。服務器
對這樣一個系統作總體的改造,存在大量存儲過程,在業務耦合度較高的狀況下,要拆分爲分佈式架構是很困難的,主要體如今3個地方:架構
(1)研發和運維對於分佈式架構的理解有限,認爲改造雖然可行,可是改動量極大,基本會在作和不作之間搖擺。併發
(2)對於你們的常規理解來講,但願達到的效果是一種透明平移的狀態,即原來的存儲過程咱們都無縫的平移過來,在MySQL分佈式的架構下,這種方案顯然是不可行的,並且若是硬着頭皮作完,效果也確定很差。app
(3)對於分佈式的理解,不是僅僅把業務拆開那麼簡單,咱們心中始終要有一個平衡點,並非全部業務都須要拆分作成分佈式。分佈式雖能帶來好處,可是同時分佈式也會帶來維護的複雜成本。負載均衡
因此對於架構的改進,咱們爲了可以落地,要在這個過程當中儘量和研發團隊保持架構的同步迭代,總體上走過了以下圖所示的4個階段。
(1)功能階段:梳理需求,對存儲過程進行轉移,適配MySQL方向。
(2)架構階段:對系統架構和業務架構進行改進設計,支持分佈式擴展。
(3)性能階段:對系統壓力進行增量測試和全量測試,全面優化性能問題。
(4)遷移階段:設計數據遷移方案,完成線上環境到MySQL分佈式環境的遷移。
咱們主要討論上面前3個階段,我總結爲8個架構演進策略,咱們逐個來講一下。
2、功能設計階段
策略1:功能平移
對於一個已經運行穩定的商業數據庫系統,若是要把它改造爲基於MySQL分佈式架構,很天然會存在一種距離感,這是一種重要但不緊急的事情,並且從改進的步調來講,是很難一步到位的。因此咱們在這裏實行的是迭代的方案,以下圖所示。
如同你們預期的那樣,既然裏面有大量的存儲過程邏輯,咱們是否是把存儲過程轉移到MySQL裏面就能夠了呢。
在沒有作完這件事情以前,你們誰都不敢這麼說,何況MySQL單機的性能和商業數據庫相比自己存在差距,在搖擺不定中,咱們仍是選擇既有的思惟來進行存儲過程轉移。
在初始階段,這部分的時間投入會略大一些,在功能和調用方式上,咱們須要作到儘量讓應用層少改動或者不改動邏輯代碼。
存儲過程轉移以後,咱們的架構演進纔算是走入了軌道,接下來咱們要作的是系統拆分。
3、系統架構演進階段
策略2:系統架構拆分
咱們以前作業務梳理時清楚地知道:系統分爲數據業務和帳單業務,那麼咱們下一步的改造目標也很明確了。
首先的切入點是數據庫的存儲容量,若是一個TB級別的MySQL庫,存在着上萬張表,並且業務的請求極高,很明顯單機存在着較大的風險,系統拆分是把原來的一個實例拆成兩個,經過這種拆分就可以強行把存儲過程的依賴解耦。
而拆分的核心思路是對於帳單數據的寫入從實時轉爲異步,這樣對於前端的響應就會更加高效。
拆分後的架構以下圖所示。
固然拆分後,新的問題出現了,帳單業務的寫入量按照規劃是很高的,不管單機的寫入性能和存儲容量都難以擴展,因此咱們須要想出新的解決方案。
策略3:寫入水平擴展
帳單數據在業務模型上屬於流水型數據,不存在事務,因此咱們的改進就是把帳單業務的存儲過程轉變爲insert語句,在轉換以後,咱們把帳單數據庫改造爲基於中間件的分佈式架構,這個過程對於應用同窗來講是透明的,由於它的調用方式依然是SQL。
同時由於以前的帳單數據有大量的表,數據分佈良莠不齊,表結構都相同,因此咱們也藉此機會把數據入口作了統一,根據業務模型梳理了幾個固定的數據入口。
這樣一來,對於應用來講,數據寫入方式就更簡單,更清晰了,改造後的架構以下圖所示。
這個改造對於應用同窗的收益是很大的,由於這個架構改造讓他們直接感覺到:不用修改任何邏輯和代碼,數據庫層就可以快速實現存儲容量和性能的水平擴展。
帳單的改進暫時告一段落,咱們開始聚焦於數據業務,發現這部分的讀請求很是高,讀寫比例能夠達到8:1左右,咱們繼續架構的改進。
策略4:讀寫分離擴展
這部分的改進方案相對清晰,咱們能夠根據業務特色建立多個從庫來對讀請求作負載均衡。這個時候數據庫業務的數據庫中依然有大量的存儲過程。
因此作讀寫分離,使用中間件來完成仍是存在瓶頸,業務層有本身的中間件方案,因此讀寫分離的模式是經過存儲過程調用查詢數據。這雖然不是咱們理想中的解決方案,可是它會比較有效,以下圖所示。經過這種方式分流了大概50%的查詢流量。
如今總體來看,業務的壓力都在數據業務方向,有的同窗看到這種狀況可能會有疑問:爲何不直接把存儲過程重構爲應用層的SQL呢,在目前的狀況下,具備說服力的方案是知足已有的需求,並且目前要業務配合改進還存在必定的困難和風險。咱們接下來繼續開始演進。
4、業務架構演進階段
策略5:業務拆分
由於數據業務的壓力如今是整個系統的瓶頸,因此一種思路就是先仔細梳理數據業務的狀況,咱們發現其實能夠把數據業務拆分爲平臺業務和應用業務,平臺業務更加統一,是全局的,應用業務相對來講種類會多一些。
作這個拆分對於應用層來講工做量也會少一些,並且也可以快速驗證改進效果。改進後的架構以下圖所示。
這個階段的改進能夠說是架構演進的一個里程碑,根據模擬測試的結果來看,數據庫的QPS指標整體在9萬左右,而總體的壓力通過估算會是目前的20倍以上,因此毫無疑問,目前的改造是存在瓶頸的,簡單來講,就是不具有真實業務的上線條件。
這個時候你們的壓力都很大,要打破目前的僵局,目前可見的方案就是對於存儲過程邏輯進行改造,這是不得已而爲之的事情,也是整個架構改進的關鍵,這個階段的改進,咱們稱之爲事務降維。
策略6:事務降維
事務降維的過程是在通過這些階段的演進以後,總體的業務邏輯脈絡已經清晰,改動的過程居然比想象的還要快不少,通過改進後的方案對原來的大量複雜邏輯校驗作了取捨,也通過了反覆迭代,最終是基於SQL的調用方案,你們在此的最大顧慮是原來使用存儲過程應用層只須要一次請求,而如今的邏輯改造後須要3次請求,可能從數據流量上會帶給集羣很大的壓力,後來通過數據驗證這種顧慮消除了。改進後的架構以下圖所示,目前已是徹底基於應用層的架構方式了。
在這個基礎之上,咱們的梳理就進入了快車道,既然改造爲應用邏輯的方式已經見效,那麼咱們能夠在梳理現有SQL邏輯的基礎上來評估是否能夠改造爲分佈式架構。
從改進後的效果來看,原來的QPS在近40萬,而改造後邏輯清晰簡單,在2萬左右,經過這個過程也讓我對架構優化有了新的理解,咱們不少時候都是但願可以作得更多,可是反過來卻發現可以簡化也是一種優化藝術,經過這個階段的改進以後,你們都充滿了信心。
策略7:業務分佈式架構改造
這個階段的演進是咱們架構改造的第二個里程碑,這個階段的改造咱們碰到了以下的問題:
咱們逐個說明一下。
1)問題1:高併發下的數據主鍵衝突和解決方案
業務邏輯中對於數據處理是以下圖所示的流程,好比id是主鍵,咱們要修改id=100的用戶屬性,增長10。
① 檢查記錄是否存在
select value from user where id=100;
② 若是記錄存在,則執行update操做
update user set id=value+10;
③ 若是記錄不存在,則執行insert操做
insert into user(id,value) values(100,10)
在併發量很大的狀況下,極可能線程1檢測數據不存在要執行insert操做的瞬間,線程2已經完成了insert操做,這樣一來就很容易拋出主鍵數據衝突。
對於這個問題的解決方案,咱們能夠充分使用MySQL的衝突檢測功能,即用insert on duplicate update key語法來解決,這個方案從索引維護的角度來看,在基於主鍵的條件下,實際上是不須要索引維護的,而相似的語法replace操做在delete+insert的過程當中是執行了兩條DML,從索引的維護代價來看要高一些。
相似下面的形式:
Insert into acc_data(id,value,mod_date) values(100,10,now()) on duplicate key update value=value+10,mod_date=now();
這種狀況不是最完美的,在少數狀況下會產生數據的髒讀,可是從數據生效的策略來看,咱們後續能夠在緩存層進行改進,因此這個問題算是基本解決了。
2)問題2:業務表數量巨大
對於業務表數量巨大的問題,在以前帳單業務的架構重構中,咱們已經有了借鑑的思路。因此咱們能夠經過配置化的方式提供幾個統一的數據入口,好比原來的業務的數據表爲:
app1_data,app2_data,app3_data... app500_data,
咱們能夠簡化爲一個或者少數訪問入口,好比:
app_group1_data(包含app1_data,app2_data... app100_data)
app_group2_data(包含app101_data,app102_data...app200_data),
以此類推。
經過配置化的方式對於應用來講不用關心數據存儲的細節,而數據的訪問入口能夠根據配置靈活定製。
通過相似的方式改進,咱們把系統架構統一改形成了三套分佈式架構,以下圖所示。
在總體改進以後,咱們查看如今的QPS,每一個分片節點均在5000左右,基本實現了水平擴展,並且從存儲容量上來看也是達到了預期的目標,到了這個階段,總體的架構已經逐步趨於穩定,可是咱們是面向業務的架構,還須要作後續的迭代。
5、性能優化階段
策略8:業務分片邏輯改造
咱們經過業務層的檢測發現,部分業務處理的延時在10毫秒左右,對於一個高併發的業務來講,這種結果是不能接受的,可是咱們已是分佈式架構,要進行優化能夠充分利用動態配置來實現。
好比某個數據入口包含10個表數據,其中有個表的數據量過大,致使這個分片的容量過大,這種狀況下咱們就能夠作一下中和,根據業務狀況來重構數據,把容量大的表儘量打散到不一樣的組中。
經過對數據量較大的表重構,修改分片的數據分佈以後,每一個分片節點上的文件大小從200M左右降爲70M左右,數據容量也控制在100萬條之內,下圖是其中一個分片節點的系統負載狀況,整體按照線上環境的部署狀況,單臺服務器的性能會控制在一個有效範圍以內,總體的性能提高了15%左右,而從業務的反饋來看,讀延遲優化到了1毫秒之內,寫延遲優化到了4毫秒之內。
後續業務關閉了數據緩存,這樣一來全部的查詢和寫入壓力都加在了現有的集羣中,從實際的效果來看QPS僅僅增長了不到15%(以下圖),而在後續作讀寫分離時這部分的壓力會徹底釋放。
6、架構里程碑和補充:基於分佈式架構的水平擴展方案
至此,咱們的分佈式集羣架構初步實現了業務需求,後續就是數據遷移的方案設計了,3套集羣的實例部署架構以下圖所示。
在這個基礎上須要考慮中間件的高可用,好比如今使用中間件服務,若是其中的一箇中間件服務發生異常宕機,那麼業務如何保證持續訪問,若是咱們考慮負載均衡,加入一個代理層,好比使用HAProxy,這就勢必帶來另一個問題,代理層的高可用如何保證,因此在這個架構設計中,咱們須要考慮得更可能是全局的設計。
咱們能夠考慮使用LVS+keepalived的組合方案,通過測試故障轉移對於應用層面來講幾乎無感知,整個方案的設計以下圖所示。
補充要點1:充分利用硬件性能和容量評估
根據上面的分佈式存儲架構演進和後端的數據監控,咱們能夠看到總體的集羣對於CPU的使用率並不高,而對於IO的需求更大,在這種狀況下,咱們須要基於硬件來完善咱們的IO吞吐量。
在基於SATA-SSD,PCIE-SSD等磁盤資源的測試,咱們設定了基於sysbench 的IO壓測基準:
通過對比測試,總體獲得了以下表所示的表格數據。
從以上的數據咱們能夠分析獲得以下圖所示的圖形。
其中,TPS:QPS大概是1:20,咱們對於性能測試狀況有了一個總體的認識,從成本和業務需求來看,目前SATA-SSD的資源配置可以徹底知足咱們的壓力場景。
補充要點2:須要考慮的服務器部署架構
對於總體架構設計方案已經具有交付條件,那麼線上環境的部署咱們還須要設計合理的架構,這個合理主要就是兩個邊界:
在這個基礎上進行了屢次討論和迭代,咱們梳理了以下圖所示的服務器部署架構,對於30多個實例,咱們最終採用了10臺物理服務器來支撐。
從機器的使用成原本說,MySQL的使用場景更偏向於PC服務,可是對於單機來講,CPU、內存、磁盤資源都會存在較大的冗餘,因此咱們考慮了單機多實例,交叉互備,從而提升資源使用效率,同時節省了大量的服務器資源成本。其中LVS服務能夠做爲通用的配置資源,故如上資源中無需重複申請。