為什么要分庫(kù)分表(設(shè)計(jì)高并發(fā)系統(tǒng)的時(shí)候,數(shù)據(jù)庫(kù)層面該如何設(shè)計(jì))?用過(guò)哪些分庫(kù)分表中間件?不同的分庫(kù)分表中間件都有什么優(yōu)點(diǎn)和缺點(diǎn)?你們具體是如何對(duì)數(shù)據(jù)庫(kù)如何進(jìn)行垂直拆分或水平拆分的?

一、概述

順勢(shì)而為,大數(shù)據(jù)高并發(fā)要有系統(tǒng)不斷的升級(jí),分庫(kù)分表便是其一。

二、為什么要分庫(kù)分表

場(chǎng)景:隨著互聯(lián)網(wǎng)技術(shù)的蓬勃發(fā)展,大數(shù)據(jù),高并發(fā)是很多公司遇到的情況。比如公司業(yè)務(wù)的發(fā)展,系統(tǒng)用戶量迅速增加,系統(tǒng)每天會(huì)多 10 萬(wàn)條數(shù)據(jù),一個(gè)月就多 300 萬(wàn)條數(shù)據(jù),單表已經(jīng)會(huì)達(dá)到幾百萬(wàn)數(shù)據(jù)量,高峰期請(qǐng)求達(dá)到 1000。處理方式是我們?cè)诰€上部署了幾臺(tái)機(jī)器,通過(guò)nginx做負(fù)載均衡,數(shù)據(jù)庫(kù)撐 1000QPS 也還將就。然而,數(shù)據(jù)量不斷在增長(zhǎng),接下來(lái)該如何處理呢?

當(dāng)每天活躍用戶數(shù)上千萬(wàn),每天單表新增數(shù)據(jù)多達(dá) 50 萬(wàn),目前一個(gè)表總數(shù)據(jù)量都已經(jīng)達(dá)到了兩三千萬(wàn)了,數(shù)據(jù)庫(kù)磁盤(pán)容量不斷消耗掉,高峰期并發(fā)達(dá)到驚人的?5000~8000QPS!此時(shí),單個(gè)數(shù)據(jù)庫(kù)已經(jīng)抗不住了。

三、分表

3.1、分表的方案

當(dāng)單表達(dá)到幾千萬(wàn)的時(shí)候,單表數(shù)據(jù)量太大,會(huì)極大影響 sql?執(zhí)行的性能,到了后面你的 sql 可能就跑的很慢了。一般來(lái)說(shuō),單表到幾百萬(wàn)的時(shí)候,性能就會(huì)相對(duì)差一些了,就得分表了。

根據(jù)數(shù)值取模

采用Id取模的方式來(lái)進(jìn)行分表。比如那customer表舉例,將customer 表根據(jù) cusno 字段切分到4個(gè)庫(kù)中,余數(shù)為0的放到第一個(gè)庫(kù),余數(shù)為1的放到第二個(gè)庫(kù),余數(shù)為2的放到第三個(gè)庫(kù),余數(shù)為3的放到第三個(gè)庫(kù)。這樣同一個(gè)用戶的數(shù)據(jù)會(huì)分散到不同的表中,如果查詢條件帶有cusno字段,則可明確定位到相應(yīng)表去查詢。

實(shí)例說(shuō)明:

首先創(chuàng)建三張表 customer0/ customer1/customer2/customer3, 然后我再創(chuàng)建 uuid表,該表的作用就是提供自增的id。

create?table?customer0(

????id?int?unsigned?primary?key?,

????name?varchar(32)?not?null?default?'',

????pwd?varchar(32)?not?null?default?''

)engine=myisam charset utf8;

create?table?customer1(

????id?int?unsigned?primary?key?,

????name?varchar(32)?not?null?default?'',

????pwd?varchar(32)?not?null?default?''

)engine=myisam charset utf8;

create?table?customer2(

????id?int?unsigned?primary?key?,

????name?varchar(32)?not?null?default?'',

????pwd?varchar(32)?not?null?default?''

)engine=myisam charset utf8;

create?table?customer3(

????id?int?unsigned?primary?key?,

????name?varchar(32)?not?null?default?'',

????pwd?varchar(32)?not?null?default?''

)engine=myisam charset utf8;

create?table?uuid(

????id?int?unsigned?primary?key?auto_increment

)engine=myisam charset utf8;

利用以上創(chuàng)建的表進(jìn)行業(yè)務(wù)處理

@Service

public?class CustomerService {

????@Autowired

? ? ?private JdbcTemplate jdbcTemplate;

????/**

?????* 注冊(cè)的代碼

?????* @param?name

?????* @param pwd

?????* @return

?????*/

????public?String regiter(String?name,String pwd){

????????//1.生成cusno?,-? 先獲取到 自定增長(zhǎng)ID

????????String insertUUidSql =?"insert into uuid values(null)";//插入空數(shù)據(jù),這里的id是自動(dòng)增長(zhǎng)的

????????jdbcTemplate.update(insertUUidSql);//執(zhí)行

? ? ? ? ? //查詢到最近的添加的主鍵id

????????Long cusno = jdbcTemplate.queryForObject("select last_insert_id()", Long.class);

????????//2.存放具體的那張表中 - 也就是判斷存儲(chǔ)表名稱

????????String tableName =?"customer"?+ cusno?% 3;

????????//3.插入到具體的表中去- 注冊(cè)數(shù)據(jù)

????????String insertUserSql =?"INSERT INTO "?+ tableName +?" VALUES ('"?+ cusno?+?"','"?+?name?+?"','"?+ pwd +?"');";

????????System.out.println("insertUserSql:"?+ insertUserSql);

????????jdbcTemplate.update(insertUserSql);

????????return?"success";

????}

? ?/**

?????* 通過(guò)cusno查詢name

?????* @param userid

?????* @return

?????*/

????public?String get(Long cusno) {

????????//具體哪張表

????????String tableName =?"customer"?+ cusno?% 3;

????????String sql =?"select name from "?+ tableName +?" where id="+cusno;

????????System.out.println("SQL:"?+ sql);

????????return?jdbcTemplate.queryForObject(sql, String.class);//執(zhí)行查詢出name

????}

}

優(yōu)缺點(diǎn)總結(jié)

優(yōu)點(diǎn):

將一個(gè)數(shù)據(jù)表的數(shù)據(jù)分成多個(gè)表后,數(shù)據(jù)相對(duì)比較均勻,減輕來(lái)高并發(fā)訪問(wèn)帶來(lái)的數(shù)據(jù)庫(kù)壓力。

缺點(diǎn):

后期如果擴(kuò)容時(shí),需要遷移舊的數(shù)據(jù)重新計(jì)算。

跨分表查詢復(fù)雜性增加。比如上例中,如果頻繁用到的查詢條件中不帶cusno時(shí),將會(huì)導(dǎo)致無(wú)法定位數(shù)據(jù)庫(kù),從而需要同時(shí)向4個(gè)庫(kù)發(fā)起查詢,再在內(nèi)存中合并數(shù)據(jù),取最小集返回給應(yīng)用,分庫(kù)反而成為拖累。

根據(jù)數(shù)值范圍

為了解決后期集群擴(kuò)容需要遷移舊數(shù)據(jù)的問(wèn)題,可以使用按日期或者ID來(lái)進(jìn)行分表。例如:按日期將不同月甚至是日的數(shù)據(jù)分散到不同的表中;將cusno為1~9999的記錄分到第一個(gè)表,10000~20000的分到第二個(gè)表,以此類推。某種意義上,某些系統(tǒng)中使用的"冷熱數(shù)據(jù)分離",將一些使用較少的歷史數(shù)據(jù)遷移到其他庫(kù)中,業(yè)務(wù)功能上只提供熱點(diǎn)數(shù)據(jù)的查詢,也是類似的實(shí)踐。

優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

單表大小可控

天然便于水平擴(kuò)展,后期如果想對(duì)整個(gè)分片集群擴(kuò)容時(shí),只需要添加節(jié)點(diǎn)即可,無(wú)需對(duì)其他分片的數(shù)據(jù)進(jìn)行遷移

使用分片字段進(jìn)行范圍查找時(shí),連續(xù)分片可快速定位分片進(jìn)行快速查詢,有效避免跨分片查詢的問(wèn)題。

缺點(diǎn):

熱點(diǎn)數(shù)據(jù)成為性能瓶頸。連續(xù)分片可能存在數(shù)據(jù)熱點(diǎn),例如按時(shí)間字段分片,有些分片存儲(chǔ)最近時(shí)間段內(nèi)的數(shù)據(jù),可能會(huì)被頻繁的讀寫(xiě),而有些分片存儲(chǔ)的歷史數(shù)據(jù),則很少被查詢。

四、分庫(kù)

就是你一個(gè)庫(kù)一般最多支撐到并發(fā) 2000,隨著查詢量的增加單臺(tái)數(shù)據(jù)庫(kù)服務(wù)器已經(jīng)沒(méi)辦法支撐,而且一個(gè)健康的單庫(kù)并發(fā)值你最好保持在每秒 1000 左右,不要太大,太大會(huì)導(dǎo)致單臺(tái)DB的存儲(chǔ)空間不夠。那么你就可以將一個(gè)庫(kù)的數(shù)據(jù)拆分到多個(gè)庫(kù)中,訪問(wèn)的時(shí)候就訪問(wèn)一個(gè)庫(kù)好了。

垂直拆分

垂直拆分意思就是處理數(shù)據(jù)庫(kù)的列,列和對(duì)應(yīng)的業(yè)務(wù)有關(guān)系,意思就是就是根據(jù)業(yè)務(wù)耦合性,將關(guān)聯(lián)度低的不同表存儲(chǔ)在不同的數(shù)據(jù)庫(kù)。

聯(lián)想到微服務(wù),做法與大系統(tǒng)拆分為多個(gè)小系統(tǒng)類似,按業(yè)務(wù)分類進(jìn)行獨(dú)立劃分,每個(gè)微服務(wù)使用單獨(dú)的一個(gè)數(shù)據(jù)庫(kù)。比如最初就一個(gè)數(shù)據(jù)庫(kù)為db_shop,db_shop庫(kù)包含user,product表,隨著公司業(yè)務(wù)的發(fā)展,技術(shù)團(tuán)隊(duì)人員也得到了擴(kuò)張,劃分為不同的技術(shù)小組,不同的小組負(fù)責(zé)不同的業(yè)務(wù)模塊。例如A小組負(fù)責(zé)用戶模塊,B小組負(fù)責(zé)產(chǎn)品模塊,拆分為db_user庫(kù)和db_product庫(kù)

需要解決的問(wèn)題:跨數(shù)據(jù)庫(kù)的事務(wù)、jion查詢等問(wèn)題。

水平拆分

按照規(guī)則劃分,一般水平分庫(kù)是在垂直分庫(kù)之后的。比如每天處理的訂單數(shù)量是海量的,可以按照一定的規(guī)則水平劃分。比如某張表太大,單個(gè)數(shù)據(jù)庫(kù)存儲(chǔ)不下或訪問(wèn)性能有壓力,把一張表拆成多張,每張表存放部分記錄,保存在不同的數(shù)據(jù)庫(kù)里,水平分庫(kù)需要對(duì)系統(tǒng)做大的改造。

1)Scale up,升級(jí)數(shù)據(jù)庫(kù)所在的物理機(jī),提升內(nèi)存/存儲(chǔ)/IO性能,但這種升級(jí)費(fèi)用昂貴,并且只能滿足短期需要。

2)Scale out,把訂單庫(kù)拆分為多個(gè)庫(kù),分散到多臺(tái)機(jī)器進(jìn)行存儲(chǔ)和訪問(wèn),這種做法支持水平擴(kuò)展,可以滿足長(zhǎng)遠(yuǎn)需要。

訂單庫(kù)主要包括訂單主表/訂單明細(xì)表(記錄商品明細(xì))/訂單擴(kuò)展表,水平分庫(kù)即把這3張表的記錄分到多個(gè)數(shù)據(jù)庫(kù)中,訂單水平分庫(kù)效果如下圖所示:


分庫(kù)策略:和水平分表類似

分庫(kù)維度確定后,如何把記錄分到各個(gè)庫(kù)里呢?一般有兩種方式:

根據(jù)數(shù)值范圍,比如用戶Id為1-9999的記錄分到第一個(gè)庫(kù),10000-20000的分到第二個(gè)庫(kù),以此類推。

根據(jù)數(shù)值取模,比如用戶Id mod n,余數(shù)為0的記錄放到第一個(gè)庫(kù),余數(shù)為1的放到第二個(gè)庫(kù),以此類推。

需要解決的問(wèn)題:數(shù)據(jù)路由、組裝。

讀寫(xiě)分離

對(duì)于時(shí)效性不高的數(shù)據(jù),可以通過(guò)讀寫(xiě)分離緩解數(shù)據(jù)庫(kù)壓力。

需要解決的問(wèn)題:在業(yè)務(wù)上區(qū)分哪些業(yè)務(wù)上是允許一定時(shí)間延遲的,以及數(shù)據(jù)同步問(wèn)題。

垂直分庫(kù)-->水平分庫(kù)-->讀寫(xiě)分離

五、拆分后面臨新的問(wèn)題的解決方案

常用的解決方案:站在巨人的肩膀上能省力很多,目前分庫(kù)分表已經(jīng)有一些較為成熟的開(kāi)源解決方案。

選用第三方的數(shù)據(jù)庫(kù)中間件(Atlas,Mycat,TDDL,DRDS),同時(shí)業(yè)務(wù)系統(tǒng)需要配合數(shù)據(jù)存儲(chǔ)的升級(jí)。綜合考慮,現(xiàn)在其實(shí)建議考量的,就是 Sharding-jdbc 和 Mycat,這兩個(gè)都可以去考慮使用。

Sharding-jdbc 這種 client 層方案的優(yōu)點(diǎn)在于不用部署,運(yùn)維成本低,不需要代理層的二次轉(zhuǎn)發(fā)請(qǐng)求,性能很高,但是如果遇到升級(jí)啥的需要各個(gè)系統(tǒng)都重新升級(jí)版本再發(fā)布,各個(gè)系統(tǒng)都需要耦合?Sharding-jdbc 的依賴;

Mycat 這種 proxy 層方案的缺點(diǎn)在于需要部署,自己運(yùn)維一套中間件,運(yùn)維成本高,但是好處在于對(duì)于各個(gè)項(xiàng)目是透明的,如果遇到升級(jí)之類的都是自己中間件那里搞就行了。

通常來(lái)說(shuō),這兩個(gè)方案其實(shí)都可以選用,但是我個(gè)人建議中小型公司選用 Sharding-jdbc,client 層方案輕便,而且維護(hù)成本低,不需要額外增派人手,而且中小型公司系統(tǒng)復(fù)雜度會(huì)低一些,項(xiàng)目也沒(méi)那么多;但是中大型公司最好還是選用 Mycat 這類 proxy 層方案,因?yàn)榭赡艽蠊鞠到y(tǒng)和項(xiàng)目非常多,團(tuán)隊(duì)很大,人員充足,那么最好是專門(mén)弄個(gè)人來(lái)研究和維護(hù) Mycat,然后大量項(xiàng)目直接透明使用即可。

附:

負(fù)載均衡:汽車超載,多臺(tái)汽車運(yùn)送物質(zhì)。有個(gè)總調(diào)度中心,分配每輛車的權(quán)重,分別拉多少貨物。在系統(tǒng)里面,這個(gè)總調(diào)度中心可以是nginx,通過(guò)配置多臺(tái)服務(wù)器的ip,進(jìn)行權(quán)重分配,使用輪詢算法,實(shí)現(xiàn)應(yīng)對(duì)并發(fā)量大的策略。

主鍵生成策略:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容