天天看點

1對多業務,資料庫水準切分架構一次搞定 | 架構師之路

本文将以“文章中心”為例,介紹“1對多”類業務,随着資料量的逐漸增大,資料庫性能顯著降低,資料庫水準切分相關的架構實踐:

如何來實施水準切分

水準切分後常見的問題

典型問題的優化思路及實踐

一、什麼是1對多關系

所謂的“1對1”,“1對多”,“多對多”,來自資料庫設計中的“實體-關系”ER模型,用來描述實體之間的映射關系。

1對1

一個使用者隻有一個登入名,一個登入名隻對應一個使用者

一個uid對應一個login_name,一個login_name隻對應一個uid

這是一個1對1的關系。

1對多

一個使用者可以發多條微網誌,一條微網誌隻有一個發送者

一個uid對應多個msg_id,一個msg_id隻對應一個uid

這是一個1對多的關系。

多對多

一個使用者可以關注多個使用者

一個使用者也可以被多個粉絲關注

這是一個多對多的關系。

二、文章中心業務分析

1對多業務,資料庫水準切分架構一次搞定 | 架構師之路

文章中心是一個典型的1對多業務。

1對多業務,資料庫水準切分架構一次搞定 | 架構師之路

一個使用者可以釋出多個文章,一個文章隻對應一個釋出者。

任何脫離業務的架構設計都是耍流氓,先來看看文章中心對應的業務需求。

文章中心,是一個提供文章釋出/修改/删除/檢視/搜尋的服務。

寫操作:

釋出(insert)文章

修改(update)文章

删除(delete)文章

讀操作:

通過tid查詢(select)文章實體,單行查詢

通過uid查詢(select)使用者釋出過的文章,清單查詢

文章檢索(search),例如通過時間、标題、内容搜尋符合條件的文章

在資料量較大,并發量較大的時候,通常通過中繼資料與索引資料分離的架構來滿足不同類型的需求:

1對多業務,資料庫水準切分架構一次搞定 | 架構師之路

架構中的幾個關鍵點:

tiezi-center:文章服務

tiezi-db:提供中繼資料存儲

tiezi-search:文章搜尋服務

tiezi-index:提供索引資料存儲

MQ:tiezi-center與tiezi-search通訊媒介,一般不直接使用RPC調用,而是通過MQ對兩個子系統解耦(為何這麼解耦,請參見《到底什麼時候該使用MQ?》)

其中,tiezi-center和tiezi-search分别滿足兩類不同的讀需求:

1對多業務,資料庫水準切分架構一次搞定 | 架構師之路

如上圖所示:

tid和uid上的查詢需求,可以由tiezi-center從中繼資料讀取并傳回

其他類檢索需求,可以由tiezi-search從索引資料檢索并傳回

對于寫需求:

1對多業務,資料庫水準切分架構一次搞定 | 架構師之路

增加,修改,删除的操作都會從tiezi-center發起

tiezi-center修改中繼資料

tiezi-center将資訊修改通知發送給MQ

tiezi-search從MQ接受修改資訊

tiezi-search修改索引資料

tiezi-search,搜尋架構不是本文的重點(外置索引架構設計,請參見《100億資料1萬屬性資料架構設計》),後文将重點描述文章中心中繼資料這一塊的水準切分設計。

三、文章中心中繼資料設計

通過文章中心業務分析,很容易了解到,其核心中繼資料為:

Tiezi(tid, uid, time, title, content, …);

其中:

tid為文章ID,主鍵

uid為使用者ID,發帖人

time, title, content …等為文章屬性

1對多業務,資料庫水準切分架構一次搞定 | 架構師之路

資料庫設計上,在業務初期,單庫就能滿足中繼資料存儲要求,其典型的架構設計為:

tiezi-center:文章中心服務,對調用者提供友好的RPC接口

tiezi-db:對文章資料進行存儲

在相關字段上建立索引,就能滿足相關業務需求:

文章記錄查詢,通過tid查詢,約占讀請求量90%

select * from t_tiezi where tid=$tid

文章清單查詢,通過uid查詢其釋出的所有文章,約占讀請求量10%

select * from t_tiezi where uid=$uid

四、文章中心水準切分-tid切分法

當資料量越來越大時,需要對文章資料的存儲進行線性擴充。

既然是文章中心,并且文章記錄查詢量占了總請求的90%,很容易想到通過tid字段取模來進行水準切分:

1對多業務,資料庫水準切分架構一次搞定 | 架構師之路

這個方法簡單直接,優點:

100%寫請求可以直接定位到庫

90%的讀請求可以直接定位到庫

缺點:

一個使用者釋出的所有文章可能會落到不同的庫上,10%的請求通過uid來查詢會比較麻煩

1對多業務,資料庫水準切分架構一次搞定 | 架構師之路

如上圖,一個uid通路需要周遊所有庫。

五、文章中心水準切分-uid切分法

有沒有一種切分方法,確定同一個使用者釋出的所有文章都落在同一個庫上,而在查詢一個使用者釋出的所有文章時,不需要去周遊所有的庫呢?

答:使用uid來分庫可以解決這個問題。

新出現的問題:如果使用uid來分庫,確定了一個使用者的文章資料落在同一個庫上,那通過tid來查詢,就不知道這個文章落在哪個庫上了,豈不是還需要周遊全庫,需要怎麼優化呢?

答:tid的查詢是單行記錄查詢,隻要在資料庫(或者緩存)記錄tid到uid的映射關系,就能解決這個問題。

新增一個索引庫:

t_mapping(tid, uid);

這個庫隻有兩列,可以承載很多資料

即使資料量過大,索引庫可以利用tid水準切分

這類kv形式的索引結構,可以很好的利用cache優化查詢性能

一旦文章釋出,tid和uid的映射關系就不會發生變化,cache的命中率會非常高

使用uid分庫,并增加索引庫記錄tid到uid的映射關系之後,每當有uid上的查詢:

1對多業務,資料庫水準切分架構一次搞定 | 架構師之路

可以通過uid直接定位到庫。

每當有tid上的查詢:

1對多業務,資料庫水準切分架構一次搞定 | 架構師之路

先查詢索引表,通過tid查詢到對應的uid

再通過uid定位到庫

這個方法的優點:

一個使用者釋出的是以文章落在同一個庫上

10%的請求過過uid來查詢清單,可以直接定位到庫

索引表cache命中率非常高,因為tid與uid的映射關系不會變

90%的tid請求,以及100%的修改請求,不能直接定位到庫,需要先進行一次索引表的查詢,當然這個查詢非常塊,通常在5ms内可以傳回

資料插入時需要操作中繼資料與索引表,可能引發潛在的一緻性問題

六、文章中心水準切分-基因法

有沒有一種方法,既能夠通過uid定位到庫,又不需要建立索引表來進行二次查詢呢,這就是本文要叙述的“1對多”業務分庫最佳實踐,基因法。

什麼是分庫基因?

通過uid分庫,假設分為16個庫,采用uid%16的方式來進行資料庫路由,這裡的uid%16,其本質是uid的最後4個bit決定這行資料落在哪個庫上,這4個bit,就是分庫基因。

什麼是基因法分庫?

在“1對多”的業務場景,使用“1”分庫,在“多”的資料id生成時,id末端加入分庫基因,就能同時滿足“1”和“多”的分庫查詢需求。

1對多業務,資料庫水準切分架構一次搞定 | 架構師之路

如上圖所示,uid=666的使用者釋出了一條文章(666的二進制表示為:1010011010):

使用uid%16分庫,決定這行資料要插入到哪個庫中

分庫基因是uid的最後4個bit,即1010

在生成tid時,先使用一種分布式ID生成算法生成前60bit(上圖中綠色部分)

将分庫基因加入到tid的最後4個bit(上圖中粉色部分)

拼裝成最終的64bit文章tid(上圖中藍色部分)

(怎麼生成60bit分布式唯一ID,請參見《分布式ID生成算法》)

這般,保證了同一個使用者釋出的所有文章的tid,都落在同一個庫上,tid的最後4個bit都相同,于是:

通過uid%16能夠定位到庫

通過tid%16也能定位到庫

潛在問題一:同一個uid釋出的tid落在同一個庫上,會不會出現資料不均衡?

答:隻要uid是均衡的,每個使用者釋出的平均文章數是均衡的,每個庫的資料就是均衡的。

潛在問題二:最開始分16庫,分庫基因是4bit,未來要擴充成32庫,分庫基因變成了5bit,那怎麼辦?

答:需要提前做好容量預估,例如事先規劃好5年内資料增長256庫足夠,就提前預留8bit基因。

七、總結

将以“文章中心”為典型的“1對多”類業務,在架構上,采用中繼資料與索引資料分離的架構設計方法:

文章服務,中繼資料滿足uid和tid的查詢需求

搜尋服務,索引資料滿足複雜搜尋尋求

對于中繼資料的存儲,在資料量較大的情況下,有三種常見的切分方法:

tid切分法,按照tid分庫,同一個使用者釋出的文章落在不同的庫上,通過uid來查詢要周遊所有庫

uid切分法,按照uid分庫,同一個使用者釋出的文章落在同一個庫上,需要通過索引表或者緩存來記錄tid與uid的映射關系,通過tid來查詢時,先查到uid,再通過uid定位庫

基因法,按照uid分庫,在生成tid裡加入uid上的分庫基因,保證通過uid和tid都能直接定位到庫

對于1對多的業務場景,分庫架構不再是瓶頸。