今年在公司重構(gòu)(寫)了一個老項目浪讳,踩了無數(shù)的坑。
中間好幾次遇到問題淹遵,甚至感覺項目可能要失敗了,好在最后終于成功上線了济炎。
雖然被坑的不要不要的辐真,但也從中領(lǐng)悟到了不少東西,在這里記錄一下,順便分享給大家樂呵樂呵倘感。
先簡單介紹下項目,一個面向C端用戶的服務(wù)淤年,主要提供包括動態(tài)蜡豹、評論、圈子弄诲、好友、關(guān)注齐遵、Feed等常見的社區(qū)功能,另外還有其他一些個性化的功能拓哟。
日活比較高伶授,整個服務(wù)QPS上萬。高頻業(yè)務(wù)违诗,單個接口QPS上千景图。單項業(yè)務(wù)數(shù)據(jù)量過億,比如評論挚币。
在上述高并發(fā)妆毕、海量數(shù)據(jù)的情況下,整個系統(tǒng)設(shè)計時需要注意的坑笛粘,和我總結(jié)的一些經(jīng)驗:
數(shù)據(jù)庫層面
MySQL分庫分表
因為是重寫整個項目薪前,包括重新設(shè)計底層數(shù)據(jù)庫,必然要考慮到分庫分表示括。
最初在網(wǎng)上參考了一些分庫分表的原則,實際操作中鳍侣,發(fā)現(xiàn)大部分資料都有些縹緲吼拥。
如果是簡單的應(yīng)用怎么分表,甚至不分都可以惑折。所以這些原則你也不能說它是錯的,但在你最需要參考的時候唬复,這些原則往往不夠深入。
分享下我個人總結(jié)的一些經(jīng)驗:
先說分庫, 分庫的主要目標(biāo)棘捣,應(yīng)該是緩解主庫(Master)的壓力休建。
絕大部分服務(wù)都是讀多寫少,在讀寫分離茵烈,1主1備N從的情況下砌些,即便為了保證一致性,部分讀請求路由到主庫存璃,主庫壓力依舊很低纵东。
通過監(jiān)控服務(wù)的寫請求量和數(shù)據(jù)庫服務(wù)器的CPU壓力等性能指標(biāo),只要主庫壓力不大偎球,就沒必要分庫。讀庫如果壓力大袍冷,直接加從庫實例即可猫牡。
一種極端的情況,就是分表數(shù)量過多了,一個庫里表數(shù)量遞增褂痰,成萬上億了,那還是分庫的好归薛。
還有一點,從運維的角度考慮习贫,單庫冷備千元,數(shù)據(jù)不應(yīng)該超過500GB。如果單庫數(shù)據(jù)量達(dá)到1個TB幸海,運維也不好備份,為了正常備份也要分庫袜硫。
再說分表, 在請求量不大 或 數(shù)據(jù)量不大的情況下官研,分不分表都無所謂。
考慮mysql的性能阀参、樹的深度等,可以簡單的認(rèn)為單表500W左右即可杏瞻。
但實際中往往需要結(jié)合具體的業(yè)務(wù)設(shè)計和查詢場景衙荐。
比如,1張幾千萬數(shù)據(jù)量的訂單表砌函,如果業(yè)務(wù)上溜族,只需要根據(jù)主鍵或唯一索引,每次查詢一條記錄仍劈,那么不分表也是完全可行的寡壮。
但有時出于運維需要讹弯,分表會更方便一些这溅,比如研發(fā)人員可能會想手寫一些SQL上去進(jìn)行一些范圍查詢,為排查問題提供一些方便臭胜。(這里說的方便是指相對單表幾千萬对竣,如果查詢字段沒有索引,范圍查詢基本不可用否纬。當(dāng)然從操作步驟上看临燃,肯定比查1個表繁瑣了)
特別需要注意的是,如果一項業(yè)務(wù)數(shù)據(jù)需要高頻的用到 count語句查詢總數(shù) 或 order by進(jìn)行排序膜廊,我建議分的表越多越好,管他3721先分1000張表再說蹬跃。
多分表的好處就是铆铆,只要表中的數(shù)據(jù)量足夠少,即便你索引設(shè)計的不好翁都,甚至查詢完全不走索引谅猾,也不容易產(chǎn)生慢查詢。哈哈哈坐搔!
小結(jié):這次重構(gòu)就被老系統(tǒng)的1000張表給坑了敬矩,因為每張表只有幾萬條數(shù)據(jù),我覺得太浪費了, 想當(dāng)然的縮到了20張表占锯。
但又沒有很好的去分析查詢場景缩筛,設(shè)計索引。導(dǎo)致上線時瞎抛,只放了1%的量,就崩了胎撤,看監(jiān)控全部都是慢查詢断凶。
當(dāng)然,最終我是通過優(yōu)化索引來解決慢查詢肿男,而不是加分表數(shù)量却嗡。但在有些情況下,這也是一種思路如庭。
MySQL索引撼港、字段設(shè)計
之前自己設(shè)計表,總喜歡加些固定字段哟楷,比如create_time, create_user, is_delete等否灾,因為運維方便。
重構(gòu)了這個系統(tǒng)之后發(fā)現(xiàn)墨技,在高并發(fā)海量數(shù)據(jù)的情況下扣汪,性能是首要問題,有時候多加這些字段反而成了負(fù)擔(dān)崭别。(當(dāng)然,大部分情況下舞痰,create_time還是必要的)
字段能少則少,名字能短則短玷禽,類型能用tinyint就不要用int呀打。
索引這塊查詢低頻少量數(shù)據(jù)無所謂,高頻海量數(shù)據(jù)務(wù)必所有查詢走索引撩银。
再看一些實際例子,
1. is_delete 字段(邏輯刪除)
假設(shè)以評論為例豺憔,單表500w,單條動態(tài)下平均上萬條評論咪啡。
業(yè)務(wù)場景中要查詢動態(tài)下的所有評論暮屡,where 子句要加上條件 is_delete = 0。
如果查詢出符合條件的結(jié)果集准夷,有幾萬甚至十幾萬條莺掠,不把 is_delete 字段加到聯(lián)合索引中,這必將是一條慢查詢楔绞,再加上高并發(fā)唇兑,只要幾百的qps,很容易把服務(wù)打崩蔫耽。
每個查詢加上這么一個條件又有點畫蛇添足留夜,除非運維需要图甜,基本上不會有業(yè)務(wù)要查詢 is_delete = 1的情況鳖眼。
索性直接物理刪除具帮,再加個歸檔表低斋,要找回時,去歸檔表里找掘猿。
這樣就不用在每個聯(lián)合索引里多加一個字段了唇跨。
2. tinyint 和 int
tinyint 主要用于一些狀態(tài)標(biāo)志位,比如 審核狀態(tài):0-未審核 1-審核通過 2-審核未通過改橘。
使用tinyint 一是節(jié)約空間玉控,二是方便識別,一看就知道是標(biāo)志位碌识。
另外這種標(biāo)志位經(jīng)常出現(xiàn)在查詢條件中虱而,但又不會單獨作為查詢條件,因此建立索引時魁瞪,必然是在聯(lián)合索引中出現(xiàn)惠呼。
而聯(lián)合索引是有長度限制的,雖然大部分時候都不會遇到趟畏,但還是值得注意滩租。
有的人標(biāo)志位喜歡用byte利朵,但在代碼里要轉(zhuǎn)型就很蛋疼了猎莲。
3.聯(lián)合索引的設(shè)計
就一個原則:查詢條件里有的,都加進(jìn)去樟遣。
除了要把 where 子句中的條件字段加進(jìn)去外身笤,在有order by 的情況下,還要把 order by 的字段加到最后液荸。
比如:查詢動態(tài)id是123,狀態(tài)是審核通過且上線的20條評論娇钱,按時間倒序排列伤柄。
select * from comment where news_id = 123 and audit_status = 1 and online_status = 1 order by ctime desc limit 20
那我們應(yīng)該建立聯(lián)合索引 (news_id, audit_status, online_status, ctime)
注意:在網(wǎng)上參考資料時,很多都說索引的建立原則文搂,字段的區(qū)分度要高适刀。
個人感覺這個原則并沒什么道理,至少在建立聯(lián)合索引時不適用煤蹭。
在建立單一索引時蔗彤,也沒有想到適用的具體場景。
比如有單表5千萬條身份信息疯兼,其中20條gender=1然遏,5千萬條gender=0。
如果你就是要查詢gender=1的列表吧彪,如果不在gender列建立索引,即便只有20條數(shù)據(jù)姨裸,也必然是個慢查詢秧倾。 小結(jié):索引的建立,必須針對具體的查詢語句傀缩。結(jié)合實際查詢場景那先,去考慮如何創(chuàng)建索引。