一億數(shù)據(jù)量,uid 分庫,tid 上的查詢?cè)撛趺崔k?
一個(gè)1對(duì)多的業(yè)務(wù)場(chǎng)景,一個(gè)屬性分庫,另一個(gè)屬性上的查詢?cè)趺崔k?
很多架構(gòu)師會(huì)面臨這樣的業(yè)務(wù)場(chǎng)景,今天就以帖子中心為例,聊聊這里面的架構(gòu)設(shè)計(jì)。
帖子中心,是互聯(lián)網(wǎng)業(yè)務(wù)中,一類典型的“1對(duì)多”業(yè)務(wù),即:一個(gè)用戶能發(fā)布多個(gè)帖子,一個(gè)帖子只有一個(gè)發(fā)布者。
任何脫離業(yè)務(wù)的架構(gòu)設(shè)計(jì)都是耍流氓,先來看看帖子中心對(duì)應(yīng)的業(yè)務(wù)需求,再來考慮它的分庫設(shè)計(jì),與架構(gòu)設(shè)計(jì)。
帖子中心,是一個(gè)提供帖子發(fā)布,修改,刪除,查看,搜索的服務(wù)。
帖子中心,有什么寫操作?
- 發(fā)布(insert)帖子;
- 修改(update)帖子;
- 刪除(delete)帖子;
帖子中心,有什么讀操作?
- 通過tid查詢(select)帖子實(shí)體,單行查詢;
- 通過uid查詢(select)用戶發(fā)布過的帖子,列表查詢;
- 帖子檢索(search),例如通過時(shí)間、標(biāo)題、內(nèi)容搜索符合條件的帖子;
在數(shù)據(jù)量較大,并發(fā)量較大的時(shí)候,架構(gòu)如何設(shè)計(jì)?
典型的,通常通過元數(shù)據(jù)與索引數(shù)據(jù)分離的架構(gòu)設(shè)計(jì)方法。
架構(gòu)中的幾個(gè)關(guān)鍵點(diǎn),如上圖所示:
- tiezi-center:帖子服務(wù);
- tiezi-db:提供元數(shù)據(jù)存儲(chǔ);
- tiezi-search:帖子搜索服務(wù);
- tiezi-index:提供索引數(shù)據(jù)存儲(chǔ);
- MQ:tiezi-center與tiezi-search通訊媒介,一般不直接使用RPC調(diào)用,而是通過MQ對(duì)兩個(gè)子系統(tǒng)解耦。
此時(shí),讀需求怎么滿足?
tiezi-center和tiezi-search分別滿足兩類不同的讀需求。
如上圖所示:
- tid和uid上的查詢需求,可以由tiezi-center從元數(shù)據(jù)讀取并返回;
- 其他類檢索需求,可以由tiezi-search從索引數(shù)據(jù)檢索并返回;
寫需求怎么辦呢?
至于寫需求,如上圖所示:
- 增加,修改,刪除的操作都會(huì)從tiezi-center發(fā)起;
- tiezi-center修改元數(shù)據(jù);
- tiezi-center將信息修改通知發(fā)送給MQ;
- tiezi-search從MQ接受修改信息;
- tiezi-search修改索引數(shù)據(jù);
tiezi-search,搜索架構(gòu)不是本文的重點(diǎn),不再展開,后文將重點(diǎn)描述帖子中心元數(shù)據(jù)水平切分設(shè)計(jì)。
帖子中心,數(shù)據(jù)庫元數(shù)據(jù)如何設(shè)計(jì)?
帖子中心業(yè)務(wù),很容易了解到,其核心元數(shù)據(jù)為:
t_tiezi(tid, uid, time, title, content, …);
其中:
- tid為帖子ID,主鍵;
- uid為用戶ID,發(fā)帖人;
- time, title, content …等為帖子屬性;
數(shù)據(jù)庫設(shè)計(jì)上,在業(yè)務(wù)初期,單庫就能滿足元數(shù)據(jù)存儲(chǔ)要求。
- tiezi-center:帖子中心服務(wù),對(duì)調(diào)用者提供友好的RPC接口;
- tiezi-db:對(duì)帖子數(shù)據(jù)進(jìn)行存儲(chǔ);
在相關(guān)字段上建立索引,就能滿足相關(guān)業(yè)務(wù)需求。
- 帖子記錄查詢,通過tid查詢,約占讀請(qǐng)求量90%;
select * from t_tiezi where tid=$tid
- 帖子列表查詢,通過uid查詢其發(fā)布的所有帖子,約占讀請(qǐng)求量10%;
select * from t_tiezi where uid=$uid
隨著數(shù)據(jù)量越來越大,如何進(jìn)行水平切分,對(duì)存儲(chǔ)容量進(jìn)行線性擴(kuò)展呢?
方案一:帖子ID切分法
既然是帖子中心,并且帖子記錄查詢量占了總請(qǐng)求的90%,很容易想到通過tid字段取模來進(jìn)行水平切分。
這個(gè)方法簡單直接,優(yōu)點(diǎn):
- 100%寫請(qǐng)求可以直接定位到庫;
- 90%的讀請(qǐng)求可以直接定位到庫;
缺點(diǎn)也很明顯:一個(gè)用戶發(fā)布的所有帖子可能會(huì)落到不同的庫上,10%的請(qǐng)求通過uid來查詢會(huì)比較麻煩;
如上圖,一個(gè)uid訪問需要遍歷所有庫。
有沒有一種切分方法,確保同一個(gè)用戶發(fā)布的所有帖子都落在同一個(gè)庫上,而在查詢一個(gè)用戶發(fā)布的所有帖子時(shí),不需要去遍歷所有的庫呢?
方案二:Mapping映射法
使用uid來分庫可以解決這個(gè)問題。
新的問題出現(xiàn)了:如果使用uid來分庫,確保了一個(gè)用戶的帖子數(shù)據(jù)落在同一個(gè)庫上,那通過tid來查詢,就不知道這個(gè)帖子落在哪個(gè)庫上了,豈不是還需要遍歷全庫,需要怎么優(yōu)化呢?
tid的查詢是單行記錄查詢,只要在數(shù)據(jù)庫(或者緩存)記錄tid到uid的映射關(guān)系,就能解決這個(gè)問題。
新增一個(gè)索引庫:
t_mapping(tid, uid);
- 這個(gè)庫只有兩列,可以承載很多數(shù)據(jù);
- 即使數(shù)據(jù)量過大,索引庫可以利用tid水平切分;
- 這類kv形式的索引結(jié)構(gòu),可以很好的利用cache優(yōu)化查詢性能;
- 一旦帖子發(fā)布,tid和uid的映射關(guān)系就不會(huì)發(fā)生變化,cache的命中率會(huì)非常高;
使用uid分庫,并增加索引庫記錄tid到uid的映射關(guān)系之后,每當(dāng)有uid上的查詢,可以通過uid直接定位到庫。
每當(dāng)有tid上的查詢,可以先查mapping表得到uid,再通過uid定位到庫。
這個(gè)方法的優(yōu)點(diǎn)是:
- 一個(gè)用戶發(fā)布的所有帖子落在同一個(gè)庫上;
- 10%的請(qǐng)求通過uid來查詢列表,可以直接定位到庫;
- 索引表cache命中率非常高,因?yàn)閠id與uid的映射關(guān)系不會(huì)變;
缺點(diǎn)也很明顯:
- 90%的tid請(qǐng)求,以及100%的修改請(qǐng)求,不能直接定位到庫,需要先進(jìn)行一次索引表的查詢,當(dāng)然這個(gè)查詢非???,通常在5ms內(nèi)可以返回;
- 數(shù)據(jù)插入時(shí)需要操作元數(shù)據(jù)與索引表,可能引發(fā)潛在的一致性問題;
有沒有一種方法,既能夠通過uid定位到庫,又不需要建立索引表來進(jìn)行二次查詢呢,使得uid和tid都能夠直接一次命中的方案呢?
方案三:基因法
什么是分庫基因?
通過uid分庫,假設(shè)分為16個(gè)庫,采用uid%16的方式來進(jìn)行數(shù)據(jù)庫路由,這里的uid%16,其本質(zhì)是uid的最后4個(gè)bit決定這行數(shù)據(jù)落在哪個(gè)庫上,這4個(gè)bit,就是分庫基因。
什么是基因法分庫?
在“1對(duì)多”的業(yè)務(wù)場(chǎng)景,使用“1”分庫,在“多”的數(shù)據(jù)id生成時(shí),id末端加入分庫基因,就能同時(shí)滿足“1”和“多”的分庫查詢需求。
如上圖所示,uid=666的用戶發(fā)布了一條帖子(666的二進(jìn)制表示為:1010011010):
- 使用uid%16分庫,決定這行數(shù)據(jù)要插入到哪個(gè)庫中;
- 分庫基因是uid的最后4個(gè)bit,即1010;
- 在生成tid時(shí),先使用一種分布式ID生成算法生成前60bit(上圖中綠色部分);
- 將分庫基因加入到tid的最后4個(gè)bit(上圖中粉色部分);
- 拼裝成最終的64bit帖子tid(上圖中藍(lán)色部分);
這般,保證了同一個(gè)用戶發(fā)布的所有帖子的tid,都落在同一個(gè)庫上,tid的最后4個(gè)bit都相同,于是:
- 通過uid%16能夠定位到庫;
- 通過tid%16也能定位到庫;
有人要問了,同一個(gè)uid發(fā)布的tid落在同一個(gè)庫上,會(huì)不會(huì)出現(xiàn)數(shù)據(jù)不均衡?
只要uid是均衡的,每個(gè)用戶發(fā)布的平均帖子數(shù)是均衡的,每個(gè)庫的數(shù)據(jù)就是均衡的。
總結(jié)
將以“帖子中心”為典型的“1對(duì)多”類業(yè)務(wù),在架構(gòu)上,采用元數(shù)據(jù)與索引數(shù)據(jù)分離的架構(gòu)設(shè)計(jì)方法:
- 帖子服務(wù),元數(shù)據(jù)滿足uid和tid的查詢需求;
- 搜索服務(wù),索引數(shù)據(jù)滿足復(fù)雜搜索尋求;
對(duì)于元數(shù)據(jù)的存儲(chǔ),在數(shù)據(jù)量較大的情況下,有三種常見的切分方法:
- tid切分法,按照tid分庫,同一個(gè)用戶發(fā)布的帖子落在不同的庫上,通過uid來查詢要遍歷所有庫;
- Mapping映射法,按照uid分庫,同一個(gè)用戶發(fā)布的帖子落在同一個(gè)庫上,需要通過索引表或者緩存來記錄tid與uid的映射關(guān)系,通過tid來查詢時(shí),先查到uid,再通過uid定位庫;
- 基因法,按照uid分庫,在生成tid里加入uid上的分庫基因,保證通過uid和tid都能直接定位到庫;
知其然,知其所以然。
思路比結(jié)論更重要。