帖子中心,是互聯網業務中,一類典型的「1對多」業務,即:一個用戶能發佈多個帖子,一個帖子只有一個發佈者。算法
隨着數據量的逐步增大,併發量的逐步增大,帖子中心這種「1對多」業務,架構應該如何設計,有哪些因素須要考慮,是本文將要系統性討論的問題。數據庫
什麼是x對x?
所謂的「1對1」,「1對多」,「多對多」,來自數據庫設計中的「實體-關係」ER模型,用來描述實體之間的映射關係。緩存
什麼是「1對1」業務?
用戶中心,一個用戶只有一個登陸名,一個登陸名只對應一個用戶,這是典型的1對1業務。架構
什麼是「1對多」業務?
帖子中心,一個用戶能夠發多條微博,一條微博只有一個發送者,這是典型的1對多業務。併發
什麼是「多對多」業務?
feed關注,一個用戶能夠關注多個用戶,一個用戶也能夠被多個用戶關注,這是典型的多對多業務。app
帖子中心是個什麼業務,有什麼典型的業務需求?
帖子中心是一個典型的1對多業務。
數據庫設計
一個用戶能夠發佈多個帖子,一個帖子只對應一個發佈者。分佈式
任何脫離業務的架構設計都是耍流氓,先來看看帖子中心對應的業務需求。ide
帖子中心,是一個提供帖子發佈,修改,刪除,查看,搜索的服務。性能
帖子中心,有什麼寫操做?
(1)發佈(insert)帖子;
(2)修改(update)帖子;
(3)刪除(delete)帖子;
帖子中心,有什麼讀操做?
(1)經過tid查詢(select)帖子實體,單行查詢;
(2)經過uid查詢(select)用戶發佈過的帖子,列表查詢;
(3)帖子檢索(search),例如經過時間、標題、內容搜索符合條件的帖子;
在數據量較大,併發量較大的時候,架構如何設計?
典型的,一般經過元數據與索引數據分離的架構設計方法。
架構中的幾個關鍵點,如上圖所示:
(1)tiezi-center:帖子服務;
(2)tiezi-db:提供元數據存儲;
(3)tiezi-search:帖子搜索服務;
(4)tiezi-index:提供索引數據存儲;
(5)MQ:tiezi-center與tiezi-search通信媒介,通常不直接使用RPC調用,而是經過MQ對兩個子系統解耦。
此時,讀需求怎麼知足?
tiezi-center和tiezi-search分別知足兩類不一樣的讀需求。
如上圖所示:
(1)tid和uid上的查詢需求,能夠由tiezi-center從元數據讀取並返回;
(2)其餘類檢索需求,能夠由tiezi-search從索引數據檢索並返回;
寫需求怎麼辦呢?
至於寫需求,如上圖所示:
(1)增長,修改,刪除的操做都會從tiezi-center發起;
(2)tiezi-center修改元數據;
(3)tiezi-center將信息修改通知發送給MQ;
(4)tiezi-search從MQ接受修改信息;
(5)tiezi-search修改索引數據;
tiezi-search,搜索架構不是本文的重點,再也不展開,後文將重點描述帖子中心元數據水平切分設計。
帖子中心,數據庫元數據如何設計?
帖子中心業務,很容易瞭解到,其核心元數據爲:
t_tiezi(tid, uid, time, title, content, …);
其中:
(1)tid爲帖子ID,主鍵;
(2)uid爲用戶ID,發帖人;
(3)time, title, content …等爲帖子屬性;
數據庫設計上,在業務初期,單庫就能知足元數據存儲要求。
(1)tiezi-center:帖子中心服務,對調用者提供友好的RPC接口;
(2)tiezi-db:對帖子數據進行存儲;
在相關字段上創建索引,就能知足相關業務需求。
(1)帖子記錄查詢,經過tid查詢,約佔讀請求量90%;
select from t_tiezi where tid=$tid
(2)帖子列表查詢,經過uid查詢其發佈的全部帖子,約佔讀請求量10%;
select
from t_tiezi where uid=$uid
隨着數據量愈來愈大,如何進行水平切分,對存儲容量進行線性擴展呢?
方案一:帖子ID切分法
既然是帖子中心,而且帖子記錄查詢量佔了總請求的90%,很容易想到經過tid字段取模來進行水平切分。
這個方法簡單直接,優勢:
(1)100%寫請求能夠直接定位到庫;
(2)90%的讀請求能夠直接定位到庫;
缺點也很明顯:
(1)一個用戶發佈的全部帖子可能會落到不一樣的庫上,10%的請求經過uid來查詢會比較麻煩;
如上圖,一個uid訪問須要遍歷全部庫。
有沒有一種切分方法,確保同一個用戶發佈的全部帖子都落在同一個庫上,而在查詢一個用戶發佈的全部帖子時,不須要去遍歷全部的庫呢?
方案二:用戶ID切分法
使用uid來分庫能夠解決這個問題。
新的問題出現了:若是使用uid來分庫,確保了一個用戶的帖子數據落在同一個庫上,那經過tid來查詢,就不知道這個帖子落在哪一個庫上了,豈不是還須要遍歷全庫,須要怎麼優化呢?
tid的查詢是單行記錄查詢,只要在數據庫(或者緩存)記錄tid到uid的映射關係,就能解決這個問題。
新增一個索引庫:
t_mapping(tid, uid);
(1)這個庫只有兩列,能夠承載不少數據;
(2)即便數據量過大,索引庫能夠利用tid水平切分;
(3)這類kv形式的索引結構,能夠很好的利用cache優化查詢性能;
(4)一旦帖子發佈,tid和uid的映射關係就不會發生變化,cache的命中率會很是高;
使用uid分庫,並增長索引庫記錄tid到uid的映射關係以後,每當有uid上的查詢,能夠經過uid直接定位到庫。
每當有tid上的查詢,能夠先查mapping表獲得uid,再經過uid定位到庫。
這個方法的優勢是:
(1)一個用戶發佈的因此帖子落在同一個庫上;
(2)10%的請求過過uid來查詢列表,能夠直接定位到庫;
(3)索引表cache命中率很是高,由於tid與uid的映射關係不會變;
缺點也很明顯:
(1)90%的tid請求,以及100%的修改請求,不能直接定位到庫,須要先進行一次索引表的查詢,固然這個查詢很是塊,一般在5ms內能夠返回;
(2)數據插入時須要操做元數據與索引表,可能引起潛在的一致性問題;
有沒有一種方法,既可以經過uid定位到庫,又不須要創建索引表來進行二次查詢呢,使得uid和tid都可以直接一次命中的方案呢?
方案三:基因法
什麼是分庫基因?
經過uid分庫,假設分爲16個庫,採用uid%16的方式來進行數據庫路由,這裏的uid%16,其本質是uid的最後4個bit決定這行數據落在哪一個庫上,這4個bit,就是分庫基因。
什麼是基因法分庫?
在「1對多」的業務場景,使用「1」分庫,在「多」的數據id生成時,id末端加入分庫基因,就能同時知足「1」和「多」的分庫查詢需求。
如上圖所示,uid=666的用戶發佈了一條帖子(666的二進制表示爲:1010011010):
(1)使用uid%16分庫,決定這行數據要插入到哪一個庫中;
(2)分庫基因是uid的最後4個bit,即1010;
(3)在生成tid時,先使用一種分佈式ID生成算法生成前60bit(上圖中綠色部分);
(4)將分庫基因加入到tid的最後4個bit(上圖中粉色部分);
(5)拼裝成最終的64bit帖子tid(上圖中藍色部分);
這般,保證了同一個用戶發佈的全部帖子的tid,都落在同一個庫上,tid的最後4個bit都相同,因而:
(1)經過uid%16可以定位到庫;
(2)經過tid%16也能定位到庫;
有人要問了,同一個uid發佈的tid落在同一個庫上,會不會出現數據不均衡?
只要uid是均衡的,每一個用戶發佈的平均帖子數是均衡的,每一個庫的數據就是均衡的。
又有人要問了,最開始分16庫,分庫基因是4bit,將來要擴充成32庫,分庫基因變成了5bit,那怎麼辦?
須要提早作好容量預估,例如事先規劃好5年內數據增加256庫足夠,就提早預留8bit基因。
總結
將以「帖子中心」爲典型的「1對多」類業務,在架構上,採用元數據與索引數據分離的架構設計方法:
(1)帖子服務,元數據知足uid和tid的查詢需求;
(2)搜索服務,索引數據知足複雜搜索尋求;
對於元數據的存儲,在數據量較大的狀況下,有三種常見的切分方法:
(1)tid切分法,按照tid分庫,同一個用戶發佈的帖子落在不一樣的庫上,經過uid來查詢要遍歷全部庫;
(2)uid切分法,按照uid分庫,同一個用戶發佈的帖子落在同一個庫上,須要經過索引表或者緩存來記錄tid與uid的映射關係,經過tid來查詢時,先查到uid,再經過uid定位庫;
(3)基因法,按照uid分庫,在生成tid里加入uid上的分庫基因,保證經過uid和tid都能直接定位到庫;
架構師訓練營,歡迎來玩相關文章:《用戶中心,1億數據,架構如何設計?》