微博的架构
http://blog.csdn.net/cleanfield/article/details/6339428
用戶信息表(t_user_info)
| 字段名稱 | 字節(jié)數(shù) | 類型 | 描述 |
| User_id | 4 | uint32 | 用戶編號(主鍵) |
| User_name | 20 | Char[20] | 名稱 |
| Msg_count | 4 | uint32 | 發(fā)布消息數(shù)量,可以作為t_msg_info水平切分新表的auto_increment |
| Fans_count | 4 | uint32 | 粉絲數(shù)量 |
| Follow_count | 4 | Uint32 | 關(guān)注對象數(shù)量 |
備注:以User_id取模分表
?
用戶之間關(guān)系表(t_user_relation),必須有關(guān)注與被關(guān)注的關(guān)系
| 字段名稱 | 字節(jié)數(shù) | 類型 | 描述 |
| User_id | 4 | uint32 | 用戶編號(聯(lián)合主鍵) |
| Follow_id | 4 | uint32 | 被關(guān)注者編號(聯(lián)合主鍵) |
| Type | 1 | Uint8 | 關(guān)系類型(0,粉絲;1,關(guān)注) |
備注:關(guān)系是單向的,以User_id取模分表
{要建立兩張表表示用戶之間的關(guān)系
1. follower表(user, follower)
2. following表(user, following)
由于用戶量巨大,所以要對這兩個表進(jìn)行切分庫,就是按照user的id進(jìn)行hash取模,如果要查詢什么人follow A ,就只要取模找到A所在的數(shù)據(jù)庫查找follower表,查詢A都是follow哪些人,對A取模,然后找到所在的數(shù)據(jù)庫,查找following表
}
?
用戶消息索引表(t_uer_msg_index)
| 字段名稱 | 字節(jié)數(shù) | 類型 | 描述 |
| User_id | 4 | uint32 | 用戶編號(聯(lián)合主鍵) |
| Author_id | 4 | uint32 | 消息發(fā)布者編號(可能是被關(guān)注者,也可能是自己)(聯(lián)合主鍵) |
| Msg_id | 4 | uint32 | 消息編號(由消息發(fā)布者的msg_count自增)(聯(lián)合主鍵) |
| Time_t | 4 | Uint32 | 發(fā)布時間(必須是消息元數(shù)據(jù)產(chǎn)生時間) |
備注:此表就是當(dāng)我們點擊“我的首頁”時拉取的消息列表,只是索引,Time_t對這些消息進(jìn)行排序
?
消息與消息關(guān)系表(t_msg_msg_relation)
| 字段名稱 | 字節(jié)數(shù) | 類型 | 描述 |
| Reference_id | 4 | uint32 | 引用消息用戶編號(聯(lián)合主鍵) |
| Reference?_msg_id | 4 | uint32 | 引用消息編號(聯(lián)合主鍵) |
| Referenced_id | 4 | uint32 | 消息發(fā)布者編號 |
| Referenced?_msg_id | 4 | uint32 | 被引用消息編號 |
| Type | 1 | Uint8 | 操作類型(1,評論;2,轉(zhuǎn)發(fā)) |
| Time_t | 4 | Uint32 | 發(fā)布時間 |
| Page_index | 4 | Uint32 | 轉(zhuǎn)發(fā)或者評論頁碼 |
備注:以Reference_id取模分表。
騰訊微博比新浪微博好的一點是一個消息的所有評論和轉(zhuǎn)發(fā)都是被固定頁碼,這樣在點擊看評論的時候搜索效率更高,因為多了一個where Page_index的定位條件,當(dāng)然帶來的問題就是可能看到有些頁的評論排版并不是滿頁,這就是因為標(biāo)識為這個Page_index的評論有刪除操作。
?
消息元數(shù)據(jù)表(t_msg_info)
| 字段名稱 | 字節(jié)數(shù) | 類型 | 描述 |
| User_id | 4 | uint32 | 發(fā)消息用戶編號(聯(lián)合主鍵) |
| Msg_id | 4 | uint32 | 消息編號(聯(lián)合主鍵) |
| Content | 140 | Char[140] | 消息內(nèi)容 |
| Type | 1 | Uint8 | 消息類型(0,原創(chuàng);1,評論;2,轉(zhuǎn)發(fā)) |
| Commented_count | 4 | Uint32 | 評論過數(shù)量(只增不減,刪除評論不影響此值,可以作為評論多頁顯示的頁碼) |
| Comment_count | 4 | Uint32 | 保留的評論數(shù)量 |
| Transferred_count | 4 | Uint32 | 轉(zhuǎn)發(fā)過數(shù)量(只增不減,刪除轉(zhuǎn)發(fā)不影響此值,可以作為轉(zhuǎn)發(fā)多頁顯示的頁碼) |
| Transfer_count | 4 | Uint32 | 保留的轉(zhuǎn)發(fā)數(shù)量 |
| Time_t | 4 | Uint32 | 發(fā)布時間 |
?備注:消息元數(shù)據(jù)中,content像可能存在圖片,這部分可以在分布式文件系統(tǒng)中存儲。在2011年數(shù)據(jù)庫大會上聽楊海潮的演講,對于nosql 也有涉及,本人能力有限,對這部分的職責(zé)還不清楚,希望高人指點。
?
非常推崇楊海潮ppt中的歸檔做法,因為微博是有時間軸線的,對于一定時間之前的記錄可以分層次歸檔,這樣在前端的最新的數(shù)據(jù)表的壓力就會減輕很多。
?
業(yè)務(wù)邏輯:
1.A關(guān)注B
1)在t_user_relation_A中添加
| A | B | 1 |
2)在t_user_relation_B中添加
| B | A | 0 |
2.原創(chuàng)發(fā)消息
1)在t_msg_info_A中添加這條元消息,type為0
2)更新t_user_info_A中Msg_count
3)在t_uer_msg_index_A中插入A發(fā)的這條消息的索引(A的編號和消息編號)
4)在t_user_relation_A中找到所有關(guān)注A的人,比如B,C,D,E,F等等,并發(fā)在這些用戶的t_uer_msg_index中插入A的這條信息索引,比如名人微博可以并發(fā)多個進(jìn)程來實現(xiàn)對粉絲的消息同步
3.A轉(zhuǎn)發(fā)B的消息msg_b
1)在t_msg_info_A中添加這條元消息msg_a,type為2
2)更新t_user_info_A中Msg_count
3)在t_uer_msg_index_A中插入A發(fā)的這條消息的索引(A的編號和消息編號)
4)在t_msg_info_B中更新msg_b的Transferred_count和Transfer_count
5)在t_msg_msg_relation中添加User_a,msg_a與User_b,msg_b的轉(zhuǎn)發(fā)關(guān)系,page_index為Transferred_count%page_count
4.A評論B的消息msg_b
1)在t_msg_info_A中添加這條元消息msg_a,type為1
2)更新t_user_info_A中Msg_count
3)在t_uer_msg_index_A中插入A發(fā)的這條消息的索引(A的編號和消息編號)
4)在t_msg_info_B中更新msg_b的Commented_count和Comment_count
5)在t_msg_msg_relation中添加User_a,msg_a與User_b,msg_b的評論關(guān)系,page_index為Commented_count%page_count
5.A刪除消息msg_a
1)刪除t_msg_info中的元數(shù)據(jù)msg_a
2)刪除t_uer_msg_index_A中的User_a,msg_a行記錄
3)備注:如果A的msg_a被別人評論或者引用,那么在對方查看評論或者轉(zhuǎn)發(fā)的時候會提示“原消息已被作者刪除”
6.A刪除轉(zhuǎn)發(fā)消息msg_a
1)刪除t_msg_info_A中的元數(shù)據(jù)msg_a
2)刪除t_uer_msg_index_A中的User_a,msg_a行記錄
3)在t_msg_msg_relation_A表中找到msg_a的源消息,即B的msg_b
4)刪除t_msg_msg_relation_A中user_a,msg_a和user_b,msg_b的轉(zhuǎn)發(fā)關(guān)系
5)更新t_msg_info_B中msg_b記錄的Transfer_count,減1
7.A刪除評論消息msg_a
1)刪除t_msg_info_A中的元數(shù)據(jù)msg_a
2)刪除t_uer_msg_index_A中的User_a,msg_a行記錄
3)在t_msg_msg_relation_A表中找到msg_a的源消息,即B的msg_b
4)刪除t_msg_msg_relation_A中user_a,msg_a和user_b,msg_b的評論關(guān)系
5)更新t_msg_info_B中msg_b記錄的Commecnt_count,減1
8.A拉取全部消息
1)從t_uer_msg_index_A中拉取Author_id,Msg_id,Time_t索引,并以Time_t排序
2)通過頁碼和每頁count控制返回結(jié)果數(shù)量,這樣避免了server io 壓力沖擊
?
5月25日更新:
1)條件允許的話,所有的index表可以放到內(nèi)存中,全部cache,而元數(shù)據(jù)直接ssd,這樣讀速度會提高很多,當(dāng)然也要做好熱備
2)t_user_relation表最好做合并存儲
?
5月27日更新:
1)在第二步原創(chuàng)發(fā)消息要通知給粉絲,這時如果是明星,那么推送的數(shù)量可能數(shù)百萬,新浪采取的做法是對這數(shù)百萬粉絲進(jìn)行區(qū)別對待,按照活躍度劃分為幾個層級,每個層級有一個推送時效限定,這樣可以做到最想看到這個信息的人能夠最及時的看到明星動態(tài)
2)用硬件來提升速度,將所有index表放在memory上,元數(shù)據(jù)放在ssd上,數(shù)據(jù)可以現(xiàn)在這兩層上做處理,并定時持久化到mysql中
3)提供批量處理接口,比如拉取最新更新索引
4)在一定限度上容忍不一樣,但要實現(xiàn)最終一致性
?
6月1日更新:
本文用的是push模式,關(guān)于微博的pull模式,請參見?http://blog.csdn.net/cleanfield/archive/2011/05/27/6450626.aspx
?
6月30日更新:
在新浪微博中,評論和轉(zhuǎn)發(fā)都與原創(chuàng)消息是一樣的獨立記錄,只不過多了一條消息關(guān)系記錄,在展現(xiàn)的時候除了要展現(xiàn)自己添加的轉(zhuǎn)發(fā)內(nèi)容或評論內(nèi)容之外,還需要將最原始的那條目標(biāo)消息取出來。
12月8日更新:
消息與消息關(guān)系表(t_msg_msg_relation)的備注中,應(yīng)該是以Referenced_id取模分裂
pull的實現(xiàn)
設(shè)計要點:
1)DB只作為持久化容器,一切操作在邏輯層完成
2)異步,前端的請求只要在中間server上完成就好,后續(xù)的持久化由LazyWriter完成(定時)
3)可以分布式實現(xiàn),中間邏輯的read和write可是分號段,以適應(yīng)批量操作,map/reduce
4)盡量做到全量cache,尤其是index
?
流程說明:
1)在A產(chǎn)生Feed的時候,更新index中A節(jié)點的最后更新時間,并標(biāo)記Feed_id(對于微博來說沒有必要做摘要);然后將content等詳細(xì)記錄寫入元數(shù)據(jù)存儲空間
2)B(A的粉絲)登錄拉取最新Feed時,由于數(shù)量限制(首頁有顯示空間限制,一般都要做成page_index+page_count)只能拉取所有關(guān)注對象中最新的N條Feed,這時先通過批量查詢對B的所有關(guān)注對象最新Feed做一個排序,因為完全在內(nèi)存中實現(xiàn),而且可以map/reduce,所以時間消耗很少,在生成了最新的Feed列表后,直接批量向元數(shù)據(jù)存儲空間拉取完成信息
?
pull模式的實時性比push模式要好,但是也會遇到關(guān)注對象太多時拉取慢的情況,無論pull還是push,最后都可以通過cache index實現(xiàn)快速索引的生成,通過map/reduce實現(xiàn)批量請求的分割與快速處理。
作為互聯(lián)網(wǎng)應(yīng)用來說,保證最終一致性才是最重要的,另外一點,對邏輯數(shù)據(jù)分層次處理,做優(yōu)先級劃分
另,關(guān)于用戶關(guān)系表,上面的做法,在邏輯上有寫復(fù)雜,也可以這樣做
基于上述面臨的問題,有人給我提供了一個擴展性的解決方案,同時也很好的解決了一個字段海量數(shù)據(jù)的問題。將方案一中的關(guān)注和被關(guān)注者表分解成兩張表,如下:
| 表名 | 關(guān)注者表 | ||
| 字段名 | 字段代碼 | 字段類型 | 描述 |
| 編號 | Id | Number | 主鍵 |
| 用戶名 | User_id | Varchar(20) | |
| 關(guān)注者編號 | Funs_id | Varchar(20) | ? |
| 表名 | 被關(guān)注者表 | ||
| 字段名 | 字段代碼 | 字段類型 | 描述 |
| 編號 | Id | Number | 主鍵 |
| 用戶名 | User_id | Varchar(20) | |
| 被關(guān)注者編號 | Wasfuns_id | Varchar(20) | ? |
我看到這樣的設(shè)計我很吃驚,試想一下,假如我一個用戶對應(yīng)有1W個關(guān)注者,那么該用戶就會在關(guān)注者表中存在一萬條他的記錄,這難道不是嚴(yán)重的數(shù)據(jù)冗余嗎?這甚至不符合數(shù)據(jù)庫的設(shè)計規(guī)范。
推和拉的方式
http://www.slideshare.net/thinkinlamp/feed-7582140
推:
對于u_feed以關(guān)注者的id進(jìn)行水平拆分
用戶1發(fā)表一條微博
找到用戶1的所有關(guān)注者
將這條微博根據(jù)關(guān)注這的id(hash)寫入對應(yīng)的u_feed表
拉:
對于u_feed以發(fā)布者的id進(jìn)行水平拆分
用戶1發(fā)表一條微博
根據(jù)發(fā)布者的id寫入對應(yīng)的u_feed中
對于關(guān)注者而言,需要一個輪詢,比如30s, 就進(jìn)行如下的操作
或者其關(guān)注的所有id, 獲取每一個id到u_feed中找發(fā)表的最新微博
可以看出,推模式的寫的數(shù)量量非常大,但是讀的性能很好
而拉模式的寫的負(fù)擔(dān)比較小,但是要處理好并發(fā)讀的問題
http://www.cnblogs.com/sunli/archive/2010/08/24/twitter_feeds_push_pull.html
拉模式的改進(jìn)主要是在feeds的存儲上,使用按照時間進(jìn)行分區(qū)存儲。分為最近時間段(比如最近一個小時),近期的,比較長時期等等。我們再來看下查詢的流程,比如姚晨登陸微博首頁,假設(shè)緩存中沒有任何數(shù)據(jù),那么我們可以查詢比較長時期的feeds表,然后進(jìn)入緩存。下一次查詢,通過查詢緩存中的數(shù)據(jù)的timeline,如果timeline還在最近一個小時內(nèi),那么只需要查詢最近一個小時的數(shù)據(jù)的feed表,最近一個小時的feeds表比圖四的feeds表可要小很多,查詢起來速度肯定快幾個數(shù)量級了。
?? ? ? ? ?改進(jìn)模式的重點在于feeds的時間分區(qū)存儲,根據(jù)上次查詢的timeline來決定查詢應(yīng)該落在那個表。一般情況下,經(jīng)常在線的用戶,頻繁使用的客戶端掃描操作,經(jīng)常登錄的用戶,都會落在最近的feeds表區(qū)間,查詢都是比較高效的。只有那些十天,半個月才登錄一次的用戶需要去查詢比較長時間的feeds大表,一旦查詢過了,就又會落在最近時間區(qū)域,所以效率也是非常高的。
總結(jié)