前言:
相對于傳統(tǒng)行業(yè)的相對服務(wù)時間9x9x6或者9x12x5,因為互聯(lián)網(wǎng)電子商務(wù)以及互聯(lián)網(wǎng)游戲的實時性,所以服務(wù)要求7*24小時,業(yè)務(wù)架構(gòu)不管是應(yīng)用還是數(shù)據(jù)庫,都需要容災(zāi)互備,在mysql的體系中,最好通過在最開始階段的數(shù)據(jù)庫架構(gòu)階段來實現(xiàn)容災(zāi)系統(tǒng)。所以這里從業(yè)務(wù)宏觀角度闡述下mysql架構(gòu)的方方面面。
一,MySQL架構(gòu)設(shè)計—業(yè)務(wù)分析
(1)讀多寫少
虛線表示跨機房部署,比如電子商務(wù)系統(tǒng),一個Master既有讀也有些寫,對讀數(shù)據(jù)一致性需要比較重要的,讀要放在Master上面。
M(R)僅僅是一個備庫,只有M(WR)掛了之后,才會切換到M(R)上,這個時候M(R)就變成了讀寫庫。比如游戲系統(tǒng),有很多Salve會掛載后面一個M(R)上面。
(2)讀多寫少MMS-電商
如果是電子商務(wù)類型的,這種讀多寫少的,一般是1個master拖上4到6個slave,所有slave掛載在一個master也足夠了。
切換的時候,把M1的讀寫業(yè)務(wù)切換到M2上面,然后把所有M1上的slave掛到M2上面去,如下所示:

(3)讀多寫少MMSS-游戲
如果是游戲行業(yè)的話,讀非常多蠻明顯的,會出現(xiàn)一般1個Master都會掛上10個以上的Slave的情況,所以這個時候,可以把一部分Slave掛載新的M(R)上面。至少會減少一些壓力,這樣至少服務(wù)器掛掉的時候,不會對所有的slave有影響,還有一部分在M(R)上的slave在繼續(xù),不會對所有的slave 受到影響,見圖3,

(4)讀少寫多
意味著讀并不會影響寫的效率,所以讀寫都可以放在一個M1(WR),而另外一個不提供讀也不提供寫,只提供standby冗余異地容災(zāi)。

這個異地容災(zāi)是非常重要的,否則如果是單機的,單邊的業(yè)務(wù),萬一idc機房故障了,一般就會影響在線業(yè)務(wù)的,這種 造成業(yè)務(wù)2小時無法應(yīng)用,對于在線電子商務(wù)交易來說,影響是蠻大的,所以為了最大限度的保證7*24小時,必須要做到異地容災(zāi),MM要跨idc機房。雖然對資源有一些要求,但是對HA來說是不可缺少的,一定要有這個MM機制。
做切換的時候,把所有的讀寫從M1直接切換到M2上就可以了。

(5)讀寫平分秋色
讀和寫差不多,但是讀不能影響寫的能力,把讀寫放在M1(WR)上,然后把一部分讀也放在M2(R)上面,當(dāng)然M1和M2也是跨機房部署的。
切換的時候,把一部分讀和全部寫從M1切換到M2上就可以了。
二:MySQL架構(gòu)設(shè)計—常見架構(gòu)
(1)強一致性
對讀一致性的權(quán)衡,如果是對讀寫實時性要求非常高的話,就將讀寫都放在M1上面,M2只是作為standby,就是采取和上面的一(4)的讀少寫多的一樣的架構(gòu)模式。
比如,訂單處理流程,那么對讀需要強一致性,實時寫實時讀,類似這種涉及交易的或者動態(tài)實時報表統(tǒng)計的都要采用這種架構(gòu)模式

(2)弱一致性
如果是弱一致性的話,可以通過在M2上面分擔(dān)一些讀壓力和流量,比如一些報表的讀取以及靜態(tài)配置數(shù)據(jù)的讀取模塊都可以放到M2上面。比如月統(tǒng)計報表,比如首頁推薦商品業(yè)務(wù)實時性要求不是很高,完全可以采用這種弱一致性的設(shè)計架構(gòu)模式。

(3)中間一致性
如果既不是很強的一致性又不是很弱的一致性,那么我們就采取中間的策略,就是在同機房再部署一個S1(R),作為備庫,提供讀取服務(wù),減少M1(WR)的壓力,而另外一個idc機房的M2只做standby容災(zāi)方式的用途。
當(dāng)然這里會用到3臺數(shù)據(jù)庫服務(wù)器,也許會增加采購壓力,但是我們可以提供更好的對外數(shù)據(jù)服務(wù)的能力和途徑,實際中盡可能兩者兼顧。

(4)統(tǒng)計業(yè)務(wù)
比如PV、UV操作、頁數(shù)的統(tǒng)計、流量的統(tǒng)計、數(shù)據(jù)的匯總等等,都可以劃歸為統(tǒng)計類型的業(yè)務(wù)。
數(shù)據(jù)庫上做大查詢的統(tǒng)計是非常消耗資源的。統(tǒng)計分為實時的統(tǒng)計和非實時的統(tǒng)計,由于mysql主從是邏輯sql的模式,所以不能達到100%的實時,如果是online 要嚴(yán)格的非常實時的統(tǒng)計比如像火車票以及金融異地結(jié)算等的統(tǒng)計,mysql這塊不是它的強項,就只有查詢M1主庫來實現(xiàn)了。
A,但是對于不是嚴(yán)格的實時性的統(tǒng)計,mysql有個很好的機制是binlog,我們可以通過binlog進行解析Parser,解析出來寫入統(tǒng)計表進行統(tǒng)計或者發(fā)消息給應(yīng)用端程序來進行統(tǒng)計。這種是準(zhǔn)實時的統(tǒng)計操作,有一定的短暫的可接受的統(tǒng)計延遲現(xiàn)象,如果要100%實時性統(tǒng)計只有查詢M1主庫了。
通過 binlog的方式實現(xiàn)統(tǒng)計,在互聯(lián)網(wǎng)行業(yè),尤其是電商和游戲這塊,差不多可以解決90%以上的統(tǒng)計業(yè)務(wù)。有時候如果用戶或者客戶提出要實時read- time了,大家可以溝通一下為什么需要實時,了解具體的業(yè)務(wù)場景,有些可能真的不需要實時統(tǒng)計,需要有所權(quán)衡,需要跟用戶和客戶多次有效溝通,做出比較適合業(yè)務(wù)的統(tǒng)計架構(gòu)模型。
B,還有一種offline統(tǒng)計業(yè)務(wù),比如月份報表年報表統(tǒng)計等,這種完全可以把數(shù)據(jù)放到數(shù)據(jù)倉庫里面或者第三方Nosql里面進行統(tǒng)計。

(5)歷史數(shù)據(jù)遷移
歷史數(shù)據(jù)遷移,需要盡量不影響現(xiàn)在線上的業(yè)務(wù),盡量不影響現(xiàn)在線上的查詢寫入操作,為什么要做歷史數(shù)據(jù)遷移?因為有些業(yè)務(wù)的數(shù)據(jù)是有時效性的,比如電商中的已經(jīng)完成的歷史訂單等,不會再有更新操作了,只有很簡單的查詢操作,而且查詢也不會很頻繁,甚至可能一天都不會查詢一次。
如果這時候歷史數(shù)據(jù)還在online庫里面或者online表里面,那么就會影響online的性能,所以對于這種,可以把數(shù)據(jù)遷移到新的歷史數(shù)據(jù)庫上,這個歷史數(shù)據(jù)庫可以是mysql也可以是nosql,也可以是數(shù)據(jù)倉庫甚至hbase大數(shù)據(jù)等。
實現(xiàn)途徑是通過 slave庫查詢出所有的數(shù)據(jù),然后根據(jù)業(yè)務(wù)規(guī)則比如時間、某一個緯度等過濾篩選出數(shù)據(jù),放入歷史數(shù)據(jù)庫(History Databases)里面。遷移完了,再回到主庫M1上,刪除掉這些歷史數(shù)據(jù)。這樣在業(yè)務(wù)層面,查詢就要兼顧現(xiàn)在實時數(shù)據(jù)和歷史數(shù)據(jù),可以在filter 上面根據(jù)遷移規(guī)則把online查詢和history查詢對接起來。比如說一個月之內(nèi)的在online庫查詢一個月之前的在history庫查詢,可以把這個規(guī)則放在DB的遷移filter層和應(yīng)用查詢業(yè)務(wù)模塊層。如果可以的話,還可以配置更細化,通過應(yīng)用查詢業(yè)務(wù)模塊層來影響DB的遷移filter層,比如以前查詢分為一個月為基準(zhǔn),現(xiàn)在查詢業(yè)務(wù)變化了,以15天為基準(zhǔn),那么應(yīng)用查詢業(yè)務(wù)模塊層變化會自動讓DB的filter層也變化,實現(xiàn)半個自動化,更加智能一些。

(6)MySQL Sharding
像oracle這種基于rac基于共享存儲的方式,不需要sharding只需要擴從rac存儲就能實現(xiàn)了。但是這種代價相對會比較高一些,共享存儲一般都比較貴,隨著業(yè)務(wù)的擴展數(shù)據(jù)的爆炸式增長,你會不停累計你的成本,甚至達到一個天文數(shù)字。
目前這種share disk的方式,除了oracle的業(yè)務(wù)邏輯層做的非常完善之外其他的解決方案都還不是很完美。
Mysql的sharding也有其局限性,sharding之后的數(shù)據(jù)查詢訪問以及統(tǒng)計都會有很大的問題,mysql的sharding是解決share nothing的存儲的一種分布式的方法,大體上分為垂直拆分和水平拆分。
(6.1)垂直拆分
可以橫向拆分,可以縱向拆分,可以橫向縱向拆分,還可以按照業(yè)務(wù)拆分。
6.1.1橫向拆分
Mysql庫里面的橫向拆分是指,每一個數(shù)據(jù)庫實例里面都有很多個db庫,每一個db庫里面都有A表B表,比如db1庫有A表B表,db2庫里也有A表和B表,那么我們把db1、db2庫的A表B表拆分出來,把一個庫分成2個,就拆分成db1、db2、db3、db4,其中db1庫和db2庫放A表數(shù)據(jù),db3庫和 db4庫放B表的數(shù)據(jù),db1、db2庫里面只有A表數(shù)據(jù),db3、db4庫里面只有B表的數(shù)據(jù)。
打個比方,作為電商來說,每個庫里面都有日志表和訂單表,假如A表是日志表log表,B表是訂單表Order表,一般說來寫日志和寫訂單沒有強關(guān)聯(lián)性,我們可以講A表日志表和B表訂單表拆分出來。那么這個時候就做了一次橫向的拆分工作,將A表日志表和B表訂單表拆分開來放在不同的庫,當(dāng)然A表和B表所在的數(shù)據(jù)庫名也可以保持一致(PS:在不同的實例里面),如下圖所示:

PS:這種拆分主要針對于不同的業(yè)務(wù)對表的影響不大,表之間的業(yè)務(wù)關(guān)聯(lián)很弱或者基本上沒有業(yè)務(wù)關(guān)聯(lián)。拆分的好處是不相關(guān)的數(shù)據(jù)表拆分到不同的實例里面,對數(shù)據(jù)庫的容量擴展和性能提高的均衡來說,都是蠻有好處的。
6.1.2縱向拆分
把同一個實例上的不同的db庫拆分出來,放入單獨的不同實例中。這種拆分的適應(yīng)場景和要求是db1和db2是沒有多少業(yè)務(wù)聯(lián)系的,類似6.1.2里面的A表和B表那樣。如果你用到了跨庫業(yè)務(wù)同時使用db1和db2的話,個人建議要重新考慮下業(yè)務(wù),重新梳理下盡量把一個模塊的表放在一個庫里面,不要垮庫操作。
這種庫縱向拆分里面,單獨的庫db1,表A和表B是強關(guān)聯(lián)的。如下圖所示:

PS:看到很多使用mysql的人,總是把很多沒有業(yè)務(wù)關(guān)聯(lián)性的表放在一個庫里面,或者總是把很多個的db庫放在同一個實例里面,就像使用oracle那樣就一個 instance的概念而已。Mysql的使用一大原則就是簡單,盡量單一,簡單的去使用mysql,庫要嚴(yán)格的分開;表沒有關(guān)系的,要嚴(yán)格拆分成庫。這樣的話擴展我們的業(yè)務(wù)就非常方便簡單了,只需要把業(yè)務(wù)模塊所在的db拆分出來,放入新的數(shù)據(jù)庫服務(wù)器上即可。
6.1.3 橫向縱向拆分
有些剛起步的,開始為了快速出產(chǎn)品,就把所以的庫所有的表都放在一個實例上,等業(yè)務(wù)發(fā)展后,就面臨著數(shù)據(jù)拆分,這里就會把橫向縱向拆分結(jié)合起來,一起實現(xiàn),如下圖所示:

6.1.4 業(yè)務(wù)拆分
跟水平拆分有點類似,但是有不同的地方。比如一個供應(yīng)商,可能整個網(wǎng)站上有10個供應(yīng)商,一個網(wǎng)站上面每一個供應(yīng)商都有一定的量,而且供應(yīng)商之間的數(shù)據(jù)量規(guī)模都差不多的規(guī)模,那么這個時候就可以使用供應(yīng)商的緯度來做拆分。
比如usern庫中,a、b、c表都是強關(guān)聯(lián)的,都有完整的業(yè)務(wù)邏輯存在,這里只有用戶(供應(yīng)商)緯度是沒有關(guān)聯(lián)的,那這個時候就可以把數(shù)據(jù)以用戶的緯度來進行拆分。
就是用戶1和用戶2各自都有一套完整的業(yè)務(wù)邏輯,而且彼此之間不關(guān)聯(lián),所以就可以把用戶1和用戶2數(shù)據(jù)拆分到不同的數(shù)據(jù)庫實例上面。目前很多互聯(lián)網(wǎng)公司或者游戲公司有很多業(yè)務(wù)都是以用戶緯度進行拆分的,比如qunaer、sohu game、sina等。

(6.2) 水平拆分
水平拆分相對要簡單一些,但是難度偏大,會導(dǎo)致分布式的情況、跨數(shù)據(jù)的情況、跨事務(wù)的情況可以分為大概三類,1是歷史數(shù)據(jù)和實時數(shù)據(jù)拆分,2是單庫多表拆分,3是多庫多表拆分。
6.2.1 實時數(shù)據(jù)歷史數(shù)據(jù)的拆分
和歷史數(shù)據(jù)遷移是一樣的邏輯,就是要將online庫的數(shù)據(jù)遷移到listory的數(shù)據(jù)庫里面,對于實時的讀寫來說,數(shù)據(jù)是放在online db庫里面,對于時間較遠的數(shù)據(jù)來說,是放在歷史History DB記錄庫里面的,這里的歷史庫可以是mysql也可以是別的nosql庫等。

6.2.2 單庫多表拆分
主要不是解決容量問題,而是解決性能問題而擴展的,加入當(dāng)前實例只有一個DB,有一個大表,一個大表就把整個實例占滿了,這個時候就不能拆分db了,因為只有一個單表,這個時候我們就只能拆表了,拆表的方式主要是解決性能問題,因為單個表越大,對于mysql來說遍歷表的樹形結(jié)構(gòu)遍歷數(shù)據(jù)會消耗更多的資源,有時候一個簡單的查詢就可能會引起整個db的很多葉子節(jié)點都要變動。表的insert、update、delete操作都會引起幾乎所有節(jié)點的變更,此時操作量會非常大,操作的時候讀寫性能都會很低,這個時候我們就可以考慮把大表拆分成多個小表,工作經(jīng)歷中是按照hash取模打散成16個小表,也有按照id主鍵/50 取模打散到50個小表當(dāng)中,下圖實例是打散成2個小表。

6.2.3 多庫多表拆分
在單庫多表的基礎(chǔ)上,如果單庫空間資源已經(jīng)不足以提供業(yè)務(wù)支撐的話,可以考慮多庫多表的方式來做,解決了空間問題和性能問題,不過會有一個問題就是跨庫查詢操作,查詢就會有另外的策略,比如說加一個logic db層來實現(xiàn)跨庫跨實例自動查詢,簡單如下圖所示:

6.2.4水平拆分小結(jié)
水平拆分原則:
– a. 盡量均勻的拆分維度。
– b. 盡量避免跨庫事務(wù)。
– c. 盡量避免跨庫查詢。
設(shè)計:
–a根據(jù)拆分維度,做mod進行數(shù)據(jù)表拆分,大部分都是取模的拆分機制,比如hash的16模原則等。
–b根據(jù)數(shù)據(jù)容量,劃分?jǐn)?shù)據(jù)庫拆分
數(shù)據(jù)操作
–a跨事務(wù)操作:分布式事務(wù),通過預(yù)寫日志的方式來間接地實現(xiàn)。
–b跨庫查詢:數(shù)據(jù)匯總or消息服務(wù)
6.2.5 案例說明
u 案例:
– 按照用戶維度進行拆分成64個分庫,1024個分表
- user_id%1024 拆分到1024張分表中
- 每個分庫中存放1024/64張分表
- 取模的時候,可以用id的最后4位數(shù)據(jù)或者3位數(shù)字來取模就可以了。

u 操作1:采用Configure DB
– 拆分之后的查詢操作,做一個Configure DB,這個DB存放的是所有實例的庫表的映射關(guān)系,當(dāng)我APP發(fā)來有一個請求查詢user1的數(shù)據(jù),那么這個user1的數(shù)據(jù)是存放在上千個實例中的哪一個庫表呢?這個關(guān)聯(lián)信息就在Configure DB里面,APP先去Configure DB里面取得user1的關(guān)聯(lián)系信息(比如是存放在d_01庫上的t_0016表里面),然后APP根據(jù)關(guān)聯(lián)信息直接去查詢對應(yīng)的d_01實例的 t_0016表里面取得數(shù)據(jù)。

u 操作2:采用Proxy
– 拆分之后的查詢操作,做一個Proxy,APP訪問Proxy,Proxy根據(jù)訪問規(guī)則就可以直接路由到具體的db實例,生成新的sql去操作對應(yīng)的db實例,然后通過Proxy協(xié)議進行操作把操作結(jié)果返回給APP。
– 優(yōu)勢是Proxy和db實例是在一個網(wǎng)段,這樣Proxy與db實例的操作的時間是非常短的。

u 操作3:采用Data Engine
– 拆分之后的查詢操作,有一個Data Engine Service,這個DES里面配置了所有數(shù)據(jù)庫實例的映射關(guān)系,需要在APP應(yīng)用端安裝一個Agent,是同步邏輯,在JDBC層實現(xiàn),DES可以實現(xiàn)讀寫分離,原理可以參考TDDL的實現(xiàn)。
6.3 集群管理
縱向擴容:一個實例拆分成多個實例,縱向拆分比較簡單,修改的東西比較少,拆分的時候要通知到Configure DB或者DES,以免拆分之后查詢不到數(shù)據(jù)或者數(shù)據(jù)錄入不到新的db上面,如下圖所示:
橫向擴容:比較復(fù)雜,在縱向擴容成2個庫的基礎(chǔ)之上,再一次對庫的表進行擴容,所以需要及時通知Configure DB和DES更細庫和表的路由連接信息。