轉(zhuǎn)載:https://mp.weixin.qq.com/s/5TqKKRfdC5CGiiFlfb_LEA
隨著業(yè)務(wù)的快速發(fā)展,做到未雨綢繆很重要,在提升關(guān)系型數(shù)據(jù)庫的擴(kuò)展性和高可用性方面需要提前布局,MySQL方案雖然不是萬金油,卻是架構(gòu)演進(jìn)中的一種典型方案,也是建設(shè)MySQL分布式存儲(chǔ)平臺(tái)一個(gè)很好的切入點(diǎn)。
本文會(huì)著重討論遷移到MySQL架構(gòu)體系的演進(jìn)過程,相信大大小小的公司在不同的發(fā)展階段都會(huì)碰到其中一些共性的問題。
我們先來簡(jiǎn)單介紹一下系統(tǒng)遷移的背景,在這個(gè)過程中我們不會(huì)刻意強(qiáng)調(diào)源數(shù)據(jù)庫的一些功能性差異,相對(duì)來說是一種更通用的架構(gòu)改進(jìn)方式。
一、架構(gòu)改造背景和演進(jìn)策略
遷移前,我們做了業(yè)務(wù)梳理,整體的系統(tǒng)現(xiàn)狀梳理如下表,可以發(fā)現(xiàn)這個(gè)業(yè)務(wù)其實(shí)可以劃分為兩個(gè)大類,一個(gè)是數(shù)據(jù)業(yè)務(wù),一個(gè)是賬單業(yè)務(wù)。數(shù)據(jù)業(yè)務(wù)負(fù)責(zé)事務(wù)性數(shù)據(jù),而賬單業(yè)務(wù)是狀態(tài)數(shù)據(jù)的操作歷史。
改造前架構(gòu)如下圖所示,對(duì)數(shù)據(jù)做了過濾,整體上庫里面的表有上萬張,雖然是多個(gè)獨(dú)立的業(yè)務(wù)單元,但是狀態(tài)數(shù)據(jù)和流水?dāng)?shù)據(jù)是彼此通過存儲(chǔ)過程級(jí)聯(lián)調(diào)用。
對(duì)這樣一個(gè)系統(tǒng)做整體的改造,存在大量存儲(chǔ)過程,在業(yè)務(wù)耦合度較高的情況下,要拆分為分布式架構(gòu)是很困難的,主要體現(xiàn)在3個(gè)地方:
(1)研發(fā)和運(yùn)維對(duì)于分布式架構(gòu)的理解有限,認(rèn)為改造雖然可行,但是改動(dòng)量極大,基本會(huì)在做和不做之間搖擺。
(2)對(duì)于大家的常規(guī)理解來說,希望達(dá)到的效果是一種透明平移的狀態(tài),即原來的存儲(chǔ)過程我們都無縫的平移過來,在MySQL分布式的架構(gòu)下,這種方案顯然是不可行的,而且如果硬著頭皮做完,效果也肯定不好。
(3)對(duì)于分布式的理解,不是僅僅把業(yè)務(wù)拆開那么簡(jiǎn)單,我們心中始終要有一個(gè)平衡點(diǎn),并不是所有業(yè)務(wù)都需要拆分做成分布式。分布式雖能帶來好處,但是同時(shí)分布式也會(huì)帶來維護(hù)的復(fù)雜成本。
所以對(duì)于架構(gòu)的改進(jìn),我們?yōu)榱四軌蚵涞?,要在這個(gè)過程中盡可能和研發(fā)團(tuán)隊(duì)保持架構(gòu)的同步迭代,整體上走過了如下圖所示的4個(gè)階段。
(1)功能階段:梳理需求,對(duì)存儲(chǔ)過程進(jìn)行轉(zhuǎn)移,適配MySQL方向。
(2)架構(gòu)階段:對(duì)系統(tǒng)架構(gòu)和業(yè)務(wù)架構(gòu)進(jìn)行改進(jìn)設(shè)計(jì),支持分布式擴(kuò)展。
(3)性能階段:對(duì)系統(tǒng)壓力進(jìn)行增量測(cè)試和全量測(cè)試,全面優(yōu)化性能問題。
(4)遷移階段:設(shè)計(jì)數(shù)據(jù)遷移方案,完成線上環(huán)境到MySQL分布式環(huán)境的遷移。
我們主要討論上面前3個(gè)階段,我總結(jié)為8個(gè)架構(gòu)演進(jìn)策略,我們逐個(gè)來說一下。
二、功能設(shè)計(jì)階段
策略1:功能平移
對(duì)于一個(gè)已經(jīng)運(yùn)行穩(wěn)定的商業(yè)數(shù)據(jù)庫系統(tǒng),如果要把它改造為基于MySQL分布式架構(gòu),很自然會(huì)存在一種距離感,這是一種重要但不緊急的事情,而且從改進(jìn)的步調(diào)來說,是很難一步到位的。所以我們?cè)谶@里實(shí)行的是迭代的方案,如下圖所示。
如同大家預(yù)期的那樣,既然里面有大量的存儲(chǔ)過程邏輯,我們是不是把存儲(chǔ)過程轉(zhuǎn)移到MySQL里面就可以了呢。
在沒有做完這件事情之前,大家誰都不敢這么說,況且MySQL單機(jī)的性能和商業(yè)數(shù)據(jù)庫相比本身存在差距,在搖擺不定中,我們還是選擇既有的思維來進(jìn)行存儲(chǔ)過程轉(zhuǎn)移。
在初始階段,這部分的時(shí)間投入會(huì)略大一些,在功能和調(diào)用方式上,我們需要做到盡可能讓應(yīng)用層少改動(dòng)或者不改動(dòng)邏輯代碼。
存儲(chǔ)過程轉(zhuǎn)移之后,我們的架構(gòu)演進(jìn)才算是走入了軌道,接下來我們要做的是系統(tǒng)拆分。
三、系統(tǒng)架構(gòu)演進(jìn)階段
策略2:系統(tǒng)架構(gòu)拆分
我們之前做業(yè)務(wù)梳理時(shí)清楚地知道:系統(tǒng)分為數(shù)據(jù)業(yè)務(wù)和賬單業(yè)務(wù),那么我們下一步的改造目標(biāo)也很明確了。
首先的切入點(diǎn)是數(shù)據(jù)庫的存儲(chǔ)容量,如果一個(gè)TB級(jí)別的MySQL庫,存在著上萬張表,而且業(yè)務(wù)的請(qǐng)求極高,很明顯單機(jī)存在著較大的風(fēng)險(xiǎn),系統(tǒng)拆分是把原來的一個(gè)實(shí)例拆成兩個(gè),通過這種拆分就能夠強(qiáng)行把存儲(chǔ)過程的依賴解耦。
而拆分的核心思路是對(duì)于賬單數(shù)據(jù)的寫入從實(shí)時(shí)轉(zhuǎn)為異步,這樣對(duì)于前端的響應(yīng)就會(huì)更加高效。
拆分后的架構(gòu)如下圖所示。
當(dāng)然拆分后,新的問題出現(xiàn)了,賬單業(yè)務(wù)的寫入量按照規(guī)劃是很高的,無論單機(jī)的寫入性能和存儲(chǔ)容量都難以擴(kuò)展,所以我們需要想出新的解決方案。
策略3:寫入水平擴(kuò)展
賬單數(shù)據(jù)在業(yè)務(wù)模型上屬于流水型數(shù)據(jù),不存在事務(wù),所以我們的改進(jìn)就是把賬單業(yè)務(wù)的存儲(chǔ)過程轉(zhuǎn)變?yōu)閕nsert語句,在轉(zhuǎn)換之后,我們把賬單數(shù)據(jù)庫改造為基于中間件的分布式架構(gòu),這個(gè)過程對(duì)于應(yīng)用同學(xué)來說是透明的,因?yàn)樗恼{(diào)用方式依然是SQL。
同時(shí)因?yàn)橹暗馁~單數(shù)據(jù)有大量的表,數(shù)據(jù)分布參差不齊,表結(jié)構(gòu)都相同,所以我們也借此機(jī)會(huì)把數(shù)據(jù)入口做了統(tǒng)一,根據(jù)業(yè)務(wù)模型梳理了幾個(gè)固定的數(shù)據(jù)入口。
這樣一來,對(duì)于應(yīng)用來說,數(shù)據(jù)寫入方式就更簡(jiǎn)單,更清晰了,改造后的架構(gòu)如下圖所示。
這個(gè)改造對(duì)于應(yīng)用同學(xué)的收益是很大的,因?yàn)檫@個(gè)架構(gòu)改造讓他們直接感受到:不用修改任何邏輯和代碼,數(shù)據(jù)庫層就能夠快速實(shí)現(xiàn)存儲(chǔ)容量和性能的水平擴(kuò)展。
賬單的改進(jìn)暫時(shí)告一段落,我們開始聚焦于數(shù)據(jù)業(yè)務(wù),發(fā)現(xiàn)這部分的讀請(qǐng)求非常高,讀寫比例可以達(dá)到8:1左右,我們繼續(xù)架構(gòu)的改進(jìn)。
策略4:讀寫分離擴(kuò)展
這部分的改進(jìn)方案相對(duì)清晰,我們可以根據(jù)業(yè)務(wù)特點(diǎn)創(chuàng)建多個(gè)從庫來對(duì)讀請(qǐng)求做負(fù)載均衡。這個(gè)時(shí)候數(shù)據(jù)庫業(yè)務(wù)的數(shù)據(jù)庫中依然有大量的存儲(chǔ)過程。
所以做讀寫分離,使用中間件來完成還是存在瓶頸,業(yè)務(wù)層有自己的中間件方案,所以讀寫分離的模式是通過存儲(chǔ)過程調(diào)用查詢數(shù)據(jù)。這雖然不是我們理想中的解決方案,但是它會(huì)比較有效,如下圖所示。通過這種方式分流了大概50%的查詢流量。
現(xiàn)在整體來看,業(yè)務(wù)的壓力都在數(shù)據(jù)業(yè)務(wù)方向,有的同學(xué)看到這種情況可能會(huì)有疑問:為什么不直接把存儲(chǔ)過程重構(gòu)為應(yīng)用層的SQL呢,在目前的情況下,具有說服力的方案是滿足已有的需求,而且目前要業(yè)務(wù)配合改進(jìn)還存在一定的困難和風(fēng)險(xiǎn)。我們接下來繼續(xù)開始演進(jìn)。
四、業(yè)務(wù)架構(gòu)演進(jìn)階段
策略5:業(yè)務(wù)拆分
因?yàn)閿?shù)據(jù)業(yè)務(wù)的壓力現(xiàn)在是整個(gè)系統(tǒng)的瓶頸,所以一種思路就是先仔細(xì)梳理數(shù)據(jù)業(yè)務(wù)的情況,我們發(fā)現(xiàn)其實(shí)可以把數(shù)據(jù)業(yè)務(wù)拆分為平臺(tái)業(yè)務(wù)和應(yīng)用業(yè)務(wù),平臺(tái)業(yè)務(wù)更加統(tǒng)一,是全局的,應(yīng)用業(yè)務(wù)相對(duì)來說種類會(huì)多一些。
做這個(gè)拆分對(duì)于應(yīng)用層來說工作量也會(huì)少一些,而且也能夠快速驗(yàn)證改進(jìn)效果。改進(jìn)后的架構(gòu)如下圖所示。
這個(gè)階段的改進(jìn)可以說是架構(gòu)演進(jìn)的一個(gè)里程碑,根據(jù)模擬測(cè)試的結(jié)果來看,數(shù)據(jù)庫的QPS指標(biāo)總體在9萬左右,而整體的壓力經(jīng)過估算會(huì)是目前的20倍以上,所以毫無疑問,目前的改造是存在瓶頸的,簡(jiǎn)單來說,就是不具備真實(shí)業(yè)務(wù)的上線條件。
這個(gè)時(shí)候大家的壓力都很大,要打破目前的僵局,目前可見的方案就是對(duì)于存儲(chǔ)過程邏輯進(jìn)行改造,這是不得已而為之的事情,也是整個(gè)架構(gòu)改進(jìn)的關(guān)鍵,這個(gè)階段的改進(jìn),我們稱之為事務(wù)降維。
策略6:事務(wù)降維
事務(wù)降維的過程是在經(jīng)過這些階段的演進(jìn)之后,整體的業(yè)務(wù)邏輯脈絡(luò)已經(jīng)清晰,改動(dòng)的過程竟然比想象的還要快很多,經(jīng)過改進(jìn)后的方案對(duì)原來的大量復(fù)雜邏輯校驗(yàn)做了取舍,也經(jīng)過了反復(fù)迭代,最終是基于SQL的調(diào)用方案,大家在此的最大顧慮是原來使用存儲(chǔ)過程應(yīng)用層只需要一次請(qǐng)求,而現(xiàn)在的邏輯改造后需要3次請(qǐng)求,可能從數(shù)據(jù)流量上會(huì)帶給集群很大的壓力,后來經(jīng)過數(shù)據(jù)驗(yàn)證這種顧慮消除了。改進(jìn)后的架構(gòu)如下圖所示,目前已經(jīng)是完全基于應(yīng)用層的架構(gòu)方式了。
在這個(gè)基礎(chǔ)之上,我們的梳理就進(jìn)入了快車道,既然改造為應(yīng)用邏輯的方式已經(jīng)見效,那么我們可以在梳理現(xiàn)有SQL邏輯的基礎(chǔ)上來評(píng)估是否可以改造為分布式架構(gòu)。
從改進(jìn)后的效果來看,原來的QPS在近40萬,而改造后邏輯清晰簡(jiǎn)單,在2萬左右,通過這個(gè)過程也讓我對(duì)架構(gòu)優(yōu)化有了新的理解,我們很多時(shí)候都是希望能夠做得更多,但是反過來卻發(fā)現(xiàn)能夠簡(jiǎn)化也是一種優(yōu)化藝術(shù),通過這個(gè)階段的改進(jìn)之后,大家都充滿了信心。
策略7:業(yè)務(wù)分布式架構(gòu)改造
這個(gè)階段的演進(jìn)是我們架構(gòu)改造的第二個(gè)里程碑,這個(gè)階段的改造我們碰到了如下的問題:
高并發(fā)下的數(shù)據(jù)主鍵沖突;
業(yè)務(wù)表數(shù)量巨大。
我們逐個(gè)說明一下。
1)問題1:高并發(fā)下的數(shù)據(jù)主鍵沖突和解決方案
業(yè)務(wù)邏輯中對(duì)于數(shù)據(jù)處理是如下圖所示的流程,比如id是主鍵,我們要修改id=100的用戶屬性,增加10。
** ① 檢查記錄是否存在**
select value from user where id=100;
** ② 如果記錄存在,則執(zhí)行update操作**
update user set id=value+10;
** ③ 如果記錄不存在,則執(zhí)行insert操作**
insert into user(id,value) values(100,10)
在并發(fā)量很大的情況下,很可能線程1檢測(cè)數(shù)據(jù)不存在要執(zhí)行insert操作的瞬間,線程2已經(jīng)完成了insert操作,這樣一來就很容易拋出主鍵數(shù)據(jù)沖突。
對(duì)于這個(gè)問題的解決方案,我們可以充分使用MySQL的沖突檢測(cè)功能,即用insert on duplicate update key語法來解決,這個(gè)方案從索引維護(hù)的角度來看,在基于主鍵的條件下,其實(shí)是不需要索引維護(hù)的,而類似的語法replace操作在delete+insert的過程中是執(zhí)行了兩條DML,從索引的維護(hù)代價(jià)來看要高一些。
類似下面的形式:
Insert into acc_data(id,value,mod_date) values(100,10,now()) on duplicate key update value=value+10,mod_date=now();
這種情況不是最完美的,在少數(shù)情況下會(huì)產(chǎn)生數(shù)據(jù)的臟讀,但是從數(shù)據(jù)生效的策略來看,我們后續(xù)可以在緩存層進(jìn)行改進(jìn),所以這個(gè)問題算是基本解決了。
2)問題2:業(yè)務(wù)表數(shù)量巨大
對(duì)于業(yè)務(wù)表數(shù)量巨大的問題,在之前賬單業(yè)務(wù)的架構(gòu)重構(gòu)中,我們已經(jīng)有了借鑒的思路。所以我們可以通過配置化的方式提供幾個(gè)統(tǒng)一的數(shù)據(jù)入口,比如原來的業(yè)務(wù)的數(shù)據(jù)表為:
app1_data,app2_data,app3_data... app500_data,
我們可以簡(jiǎn)化為一個(gè)或者少數(shù)訪問入口,比如:
app_group1_data(包含app1_data,app2_data... app100_data)
app_group2_data(包含app101_data,app102_data...app200_data),
以此類推。
通過配置化的方式對(duì)于應(yīng)用來說不用關(guān)心數(shù)據(jù)存儲(chǔ)的細(xì)節(jié),而數(shù)據(jù)的訪問入口可以根據(jù)配置靈活定制。
經(jīng)過類似的方式改進(jìn),我們把系統(tǒng)架構(gòu)統(tǒng)一改造成了三套分布式架構(gòu),如下圖所示。
在整體改進(jìn)之后,我們查看現(xiàn)在的QPS,每個(gè)分片節(jié)點(diǎn)均在5000左右,基本實(shí)現(xiàn)了水平擴(kuò)展,而且從存儲(chǔ)容量上來看也是達(dá)到了預(yù)期的目標(biāo),到了這個(gè)階段,整體的架構(gòu)已經(jīng)逐步趨于穩(wěn)定,但是我們是面向業(yè)務(wù)的架構(gòu),還需要做后續(xù)的迭代。
五、性能優(yōu)化階段
策略8:業(yè)務(wù)分片邏輯改造
我們通過業(yè)務(wù)層的檢測(cè)發(fā)現(xiàn),部分業(yè)務(wù)處理的延時(shí)在10毫秒左右,對(duì)于一個(gè)高并發(fā)的業(yè)務(wù)來說,這種結(jié)果是不能接受的,但是我們已經(jīng)是分布式架構(gòu),要進(jìn)行優(yōu)化可以充分利用動(dòng)態(tài)配置來實(shí)現(xiàn)。
比如某個(gè)數(shù)據(jù)入口包含10個(gè)表數(shù)據(jù),其中有個(gè)表的數(shù)據(jù)量過大,導(dǎo)致這個(gè)分片的容量過大,這種情況下我們就可以做一下中和,根據(jù)業(yè)務(wù)情況來重構(gòu)數(shù)據(jù),把容量大的表盡可能打散到不同的組中。
通過對(duì)數(shù)據(jù)量較大的表重構(gòu),修改分片的數(shù)據(jù)分布之后,每個(gè)分片節(jié)點(diǎn)上的文件大小從200M左右降為70M左右,數(shù)據(jù)容量也控制在100萬條以內(nèi),下圖是其中一個(gè)分片節(jié)點(diǎn)的系統(tǒng)負(fù)載情況,總體按照線上環(huán)境的部署情況,單臺(tái)服務(wù)器的性能會(huì)控制在一個(gè)有效范圍之內(nèi),整體的性能提升了15%左右,而從業(yè)務(wù)的反饋來看,讀延遲優(yōu)化到了1毫秒以內(nèi),寫延遲優(yōu)化到了4毫秒以內(nèi)。
后續(xù)業(yè)務(wù)關(guān)閉了數(shù)據(jù)緩存,這樣一來所有的查詢和寫入壓力都加在了現(xiàn)有的集群中,從實(shí)際的效果來看QPS僅僅增加了不到15%(如下圖),而在后續(xù)做讀寫分離時(shí)這部分的壓力會(huì)完全釋放。
六、架構(gòu)里程碑和補(bǔ)充:基于分布式架構(gòu)的水平擴(kuò)展方案
至此,我們的分布式集群架構(gòu)初步實(shí)現(xiàn)了業(yè)務(wù)需求,后續(xù)就是數(shù)據(jù)遷移的方案設(shè)計(jì)了,3套集群的實(shí)例部署架構(gòu)如下圖所示。
在這個(gè)基礎(chǔ)上需要考慮中間件的高可用,比如現(xiàn)在使用中間件服務(wù),如果其中的一個(gè)中間件服務(wù)發(fā)生異常宕機(jī),那么業(yè)務(wù)如何保證持續(xù)訪問,如果我們考慮負(fù)載均衡,加入一個(gè)代理層,比如使用HAProxy,這就勢(shì)必帶來另外一個(gè)問題,代理層的高可用如何保證,所以在這個(gè)架構(gòu)設(shè)計(jì)中,我們需要考慮得更多是全局的設(shè)計(jì)。
我們可以考慮使用LVS+keepalived的組合方案,經(jīng)過測(cè)試故障轉(zhuǎn)移對(duì)于應(yīng)用層面來說幾乎無感知,整個(gè)方案的設(shè)計(jì)如下圖所示。
補(bǔ)充要點(diǎn)1:充分利用硬件性能和容量評(píng)估
根據(jù)上面的分布式存儲(chǔ)架構(gòu)演進(jìn)和后端的數(shù)據(jù)監(jiān)控,我們可以看到整體的集群對(duì)于CPU的使用率并不高,而對(duì)于IO的需求更大,在這種情況下,我們需要基于硬件來完善我們的IO吞吐量。
在基于SATA-SSD,PCIE-SSD等磁盤資源的測(cè)試,我們?cè)O(shè)定了基于sysbench 的IO壓測(cè)基準(zhǔn):
調(diào)度策略:cfq
測(cè)試用例:oltp_read_write.lua
壓測(cè)表數(shù)量:10
單表?xiàng)l目數(shù):50000000
壓測(cè)時(shí)長:3600s
經(jīng)過對(duì)比測(cè)試,整體得到了如下表所示的表格數(shù)據(jù)。
從以上的數(shù)據(jù)我們可以分析得到如下圖所示的圖形。
其中,TPS:QPS大概是1:20,我們對(duì)于性能測(cè)試情況有了一個(gè)整體的認(rèn)識(shí),從成本和業(yè)務(wù)需求來看,目前SATA-SSD的資源配置能夠完全滿足我們的壓力場(chǎng)景。
補(bǔ)充要點(diǎn)2:需要考慮的服務(wù)器部署架構(gòu)
對(duì)于整體架構(gòu)設(shè)計(jì)方案已經(jīng)具備交付條件,那么線上環(huán)境的部署我們還需要設(shè)計(jì)合理的架構(gòu),這個(gè)合理主要就是兩個(gè)邊界:
滿足現(xiàn)有的性能,能夠支撐指數(shù)級(jí)的壓力支撐;
成本合理。
在這個(gè)基礎(chǔ)上進(jìn)行了多次討論和迭代,我們梳理了如下圖所示的服務(wù)器部署架構(gòu),對(duì)于30多個(gè)實(shí)例,我們最終采用了10臺(tái)物理服務(wù)器來支撐。
從機(jī)器的使用成本來說,MySQL的使用場(chǎng)景更偏向于PC服務(wù),但是對(duì)于單機(jī)來說,CPU、內(nèi)存、磁盤資源都會(huì)存在較大的冗余,所以我們考慮了單機(jī)多實(shí)例,交叉互備,從而提高資源使用效率,同時(shí)節(jié)省了大量的服務(wù)器資源成本。其中LVS服務(wù)可以作為通用的配置資源,故如上資源中無需重復(fù)申請(qǐng)。