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