線上高併發應用重構(寫)填坑經驗分享(一)

        今年在公司重構(寫)了一個老項目,踩了無數的坑。

        中間好幾次遇到問題,甚至感覺項目可能要失敗了,好在最後終於成功上線了。

        雖然被坑的不要不要的,但也從中領悟到了不少東西,在這裏記錄一下,順便分享給大家樂呵樂呵。

 

        先簡單介紹下項目,一個面向C端用戶的服務,主要提供包括動態、評論、圈子、好友、關注、Feed等常見的社區功能,另外還有其他一些個性化的功能。

        日活比較高,整個服務QPS上萬。高頻業務,單個接口QPS上千。單項業務數據量過億,比如評論。

圖1.qps監控圖

        在上述高併發、海量數據的情況下,整個系統設計時需要注意的坑,和我總結的一些經驗:

數據庫層面

MySQL分庫分表

因爲是重寫整個項目,包括重新設計底層數據庫,必然要考慮到分庫分表。

最初在網上參考了一些分庫分表的原則,實際操作中,發現大部分資料都有些縹緲。

如果是簡單的應用怎麼分表,甚至不分都可以。所以這些原則你也不能說它是錯的,但在你最需要參考的時候,這些原則往往不夠深入。

分享下我個人總結的一些經驗:

先說分庫

分庫的主要目標,應該是緩解主庫(Master)的壓力。

絕大部分服務都是讀多寫少,在讀寫分離,1主1備N從的情況下,即便爲了保證一致性,部分讀請求路由到主庫,主庫壓力依舊很低。

通過監控服務的寫請求量和數據庫服務器的CPU壓力等性能指標,只要主庫壓力不大,就沒必要分庫。讀庫如果壓力大,直接加從庫實例即可。

一種極端的情況,就是分表數量過多了,一個庫裏表數量遞增,成萬上億了,那還是分庫的好。

還有一點,從運維的角度考慮,單庫冷備,數據不應該超過500GB。如果單庫數據量達到1個TB,運維也不好備份,爲了正常備份也要分庫。

 圖2.數據庫監控圖(圖中藍色線條代表主庫,基本上是趴着不動躺平的)

再說分表

在請求量不大 或 數據量不大的情況下,分不分表都無所謂。

考慮mysql的性能、樹的深度等,可以簡單的認爲單表500W左右即可。

但實際中往往需要結合具體的業務設計和查詢場景。

比如,1張幾千萬數據量的訂單表,如果業務上,只需要根據主鍵或唯一索引,每次查詢一條記錄,那麼不分表也是完全可行的。

但有時出於運維需要,分表會更方便一些,比如研發人員可能會想手寫一些SQL上去進行一些範圍查詢,爲排查問題提供一些方便。(這裏說的方便是指相對單表幾千萬,如果查詢字段沒有索引,範圍查詢基本不可用。當然從操作步驟上看,肯定比查1個表繁瑣了)

特別需要注意的是,如果一項業務數據需要高頻的用到 count語句查詢總數 或  order by進行排序,我建議分的表越多越好,管他3721先分1000張表再說。

多分表的好處就是,只要表中的數據量足夠少,即便你索引設計的不好,甚至查詢完全不走索引,也不容易產生慢查詢。哈哈哈!

小結:這次重構就被老系統的1000張表給坑了,因爲每張表只有幾萬條數據,我覺得太浪費了,  想當然的縮到了20張表。

           但又沒有很好的去分析查詢場景,設計索引。導致上線時,只放了1%的量,就崩了,看監控全部都是慢查詢。

           當然,最終我是通過優化索引來解決慢查詢,而不是加分表數量。但在有些情況下,這也是一種思路。

 

MySQL索引、字段設計

之前自己設計表,總喜歡加些固定字段,比如create_time, create_user, is_delete等,因爲運維方便。

重構了這個系統之後發現,在高併發海量數據的情況下,性能是首要問題,有時候多加這些字段反而成了負擔。(當然,大部分情況下,create_time還是必要的)

字段能少則少,名字能短則短,類型能用tinyint就不要用int。

 

 

 

“桌子有多小就要多小,椅子有多擠就要多擠,不要讓客人坐得那麼舒服,喫完就趕快走。吸管有多粗就要多粗,冰有多大塊就放多大塊,這樣汽水就可以一口喝完再買另一杯了。你是新來的嗎,這還要我教,一點變通都不會,笨蛋。”

——周星馳《食神》

 

索引這塊低頻小數據量無所謂,高頻海量數據務必所有查詢走索引。

再看一些實際例子,

1. is_delete 字段(邏輯刪除)

假設以評論爲例,單表500w,單條動態下平均上萬條評論。

業務場景中要查詢動態下的所有評論,where 子句要加上條件 is_delete = 0。

如果查詢出符合條件的結果集,有幾萬甚至十幾萬條,不把 is_delete 字段加到聯合索引中,這必將是一條慢查詢,再加上高併發,只要幾百的qps,很容易把服務打崩。

每個查詢加上這麼一個條件又有點畫蛇添足,除非運維需要,基本上不會有業務要查詢 is_delete = 1的情況。索性直接物理刪除,再加個歸檔表,要找回時,去歸檔表裏找。

這樣就不用在每個聯合索引裏多加一個字段了。

2. tinyint 和 int 

tinyint 主要用於一些狀態標誌位,比如 審覈狀態:0-未審覈 1-審覈通過 2-審覈未通過。

使用tinyint 一是節約空間,二是方便識別,一看就知道是標誌位。

另外這種標誌位經常出現在查詢條件中,但又不會單獨作爲查詢條件,因此建立索引時,必然是在聯合索引中出現。而聯合索引是有長度限制的,雖然大部分時候都不會遇到,但還是值得注意。

另外有的人標誌位喜歡用byte,但在代碼裏要轉型就很蛋疼了。

3.聯合索引的設計

就一個原則:查詢條件裏有的,都加進去。

除了要把 where 子句中的條件字段加進去外,在有order by 的情況下,還要把 order by 的字段加到最後。

比如:查詢動態id是123,狀態是審覈通過且上線的20條評論,按時間倒序排列。

select * from comment where news_id = 123 and audit_status = 1 and online_status = 1 order by ctime desc limit 20

那我們應該建立聯合索引 news_id,  audit_status, online_status, ctime

注意:在網上參考資料時,很多都說索引的建立原則,字段的區分度要高。

個人感覺這個原則並沒什麼道理,至少在建立聯合索引時不適用。

在建立單一索引時,我也沒有想到適用的具體場景。

比如有單表5千萬條身份信息,其中20條gender=1,5千萬條gender=0。

如果你就是要查詢gender=1的列表,如果不在gender列建立索引,即便只有20條數據,也必然是個慢查詢。

小結:索引的建立,必須針對具體的查詢語句。結合實際查詢場景,去考慮如何創建索引。

 


   歡迎關注我的公衆號,可以免費提供技術諮詢

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章