在很多網站系統(如CMS系統,SNS系統等),都有“站內信”的功能。
“站內信”不同於電子郵件,電子郵件通過專門的郵件服務器發送、保存。而“站內信”是系統內的消息,說白了,“站內信”的實現,就是通過數據庫插入記錄來實現的。
“站內信”有兩個基本功能。一:點到點的消息傳送。用戶給用戶發送站內信;管理員給用戶發送站內信。二:點到面的消息傳送。管理員給用戶(指定滿足某一條件的用戶群)群發消息。點到點的消息傳送很容易實現,本文不再詳述。下面將根據不同的情況,來說說“站內信”的群發是如何實現的。
第一種情況,站內的用戶是少量級別的。(幾十到上百)
這種情況,由於用戶的數量非常少,因此,沒有必要過多的考慮數據庫的優化,采用簡單的表格,對系統的設計也來的簡單,后期也比較容易維護,是典型的用空間換時間的做法。
數據庫的設計如下:表名:Message
ID:編號;SendID:發送者編號;RecID:接受者編號(如為0,則接受者為所有人);Message:站內信內容;Statue:站內信的查看狀態;PDate:站內信發送時間;
如果,某一個管理員要給所有人發站內信,則先遍歷用戶表,再按照用戶表中的所有用戶依次將站內信插入到Message表中。這樣,如果有56個用戶,則群發一條站內信要執行56個插入操作。這個理解上比較簡單,比較耗損空間。
某一個用戶登陸后,查看站內信的語句則為:
Select * FROM Message Where RecID=‘ID’ OR RecID=0
第二種情況,站內的用戶中量級別的(上千到上萬)。
如果還是按照第一種情況的思路。那發一條站內信的后果基本上就是后台崩潰了。因為,發一條站內信,得重復上千個插入記錄,這還不是最主要的,關鍵是上千乃至上萬條記錄,Message字段的內容是一樣的,而Message有大量的占用存儲空間。比方說,Message字段有100個漢字,占用200個字節,那么5萬條,就占用200×50000=10000000個字節=10M。簡單的一份站內信,就占用10M,這還讓不讓人活了。
因此,將原先的表格拆分為兩個表,將Message的主體放在一個表內,節省空間的占用
數據庫的設計如下:
表名:Message
ID:編號;SendID:發送者編號;RecID:接受者編號(如為0,則接受者為所有人);MessageID:站內信編號;Statue:站內信的查看狀態;
表名:MessageText
ID:編號;Message:站內信的內容;PDate:站內信發送時間;
在管理員發一封站內信的時候,執行兩步操作。先在MessageText表中,插入站內信的內容。然后在Message表中給所有的用戶插入一條記錄,標識有一封站內信。
這樣的設計,將重復的站內信的主體信息(站內信的內容,發送時間)放在一個表內,大量的節省存儲空間。不過,在查詢的時候,要比第一種情況來的復雜。
第三種情況,站內的用戶是大量級的(上百萬),並且活躍的用戶只占其中的一部分。
大家都有這樣的經歷,某日看一個網站比較好,一時心情澎湃,就注冊了一個用戶。過了一段時間,由於種種原因,就忘記了注冊時的用戶名和密碼,也就不再登陸了。那么這個用戶就稱為不活躍的。從實際來看,不活躍的用戶占着不小的比例。
我們以注冊用戶2百萬,其中活躍用戶只占其中的10%。
就算是按照第二種的情況,發一封“站內信”,那得執行2百萬個插入操作。但是其中的有效操作只有10%,因為另外的90%的用戶可能永遠都不會再登陸了。
在這種情況下,我們還得把思路換換。
數據庫的設計和第二種情況一樣:
表名:Message
ID:編號;SendID:發送者編號;RecID:接受者編號(如為0,則接受者為所有人);MessageID:站內信編號;Statue:站內信的查看狀態;
表名:MessageText
ID:編號;Message:站內信的內容;PDate:站內信發送時間;
管理員發站內信的時候,只在MessageText插入站內信的主體內容。Message里不插入記錄。
那么,用戶在登錄以后,首先查詢MessageText中的那些沒有在Message中有記錄的記錄,表示是未讀的站內信。在查閱站內信的內容時,再將相關的記錄插入到Message中。
這個方法和第二種的比較起來。如果,活躍用戶是100%。兩者效率是一樣的。而活躍用戶的比例越低,越能體現第三種的優越來。只插入有效的記錄,那些不活躍的,就不再占用空間了。
以上,是我對群發“站內信”的實現的想法。
href:http://www.cnblogs.com/grenet/archive/2010/03/08/1680655.html
百萬級用戶量的站內信群發數據庫設計
隨着WEB2.0的發展,用戶之間的信息交互也變得十分龐大,而且實時性要求越來越高。現在很多SNS網站和一部分CMS網站都廣泛地應 用了站內信這一模塊,這個看似簡單的東西其實背后隱藏着很多需要設計師重視的設計細節,要做好這個“郵遞員”是很不容易的。為什么這么說呢?下面我們就一 步步來探索設計一個百萬級用戶量的站內信群發數據庫,看完以后你就會明白什么是真正可靠高效的“郵遞員”。
1、幾十——幾百的用戶量
這樣的網站規模最小,可能是一個中小企業的CMS系統,面對這樣的用戶量,我們就不必要考慮短消息數據量太大的問題了,所以按照怎么方便怎么來的原 則,群發就每人復制一條消息數據,這樣用戶可以自己管理自己的消息,可以非常方便進行“已讀、未讀、刪除”等操作。按照這個思路,我們的數據庫設計如下:
表T_Message
Id bigint --消息ID
SenderId bigint --發送者ID
ReceiverId bigint --接收者ID
SendTime datetime --發送時間
ReadFlag tinyint --已讀標志
MessageText text --消息正文
這樣,我們接受自己的消息時只要做如下查詢:
SELECT * FROM T_Message WHERE ReceiverId=myid
查詢自己的未讀消息只要做如下查詢:
SELECT * FROM T_Message WHERE ReceiverId=myid and ReadFlag=0
這種方法很簡單,可能是我們第一個想到的,對於這樣的用戶量的情況這樣的設計確實也足夠了。
2、幾千——幾萬的用戶量
用戶量到了這樣的級哦別,這個網站應該算是比較大了,筆者估計,可能是一個地區性的SNS網站。那么面對這樣的用戶量,我們又該如何來設計站內信群 發呢?上面第一種思路還行得通嗎?應該這樣說,如果勉強要用上面那種設計,也是可以的,只不過T_Message可能要考慮分區。但是,大家會不會覺得消 息正文復制那么多條對於這樣的用戶量來講空間浪費太大,因為考慮到接收者一般是不修改消息正文的,所以我們可以讓所有接收者共享一條消息正文。具體數據庫 設計方法和上面大同小異:
T_Message
Id bigint --消息ID
SenderId bigint --發送者ID
ReceiverId bigint --接收者ID
SendTime datetime --發送時間
ReadFlag tinyint --已讀標志
MessageTextId bigint --這里把消息正文內容換成消息正文Id
T_MessageText
Id bigint --ID標識
SenderId bigint --發送者ID
MessageText text --消息正文
這樣,我們就大大節省了消息的存儲空間,但是查詢的時候就稍微麻煩一點,就需要進行聯合查詢了,查詢自己的未讀消息可以這樣(意思一下,可能還有更高效的查詢方式):
SELECT T_Message.*,T_MessageText.* FROM T_Message
INNER JOIN T_MessageText ON T_Message.MessageTextId=T_MessageText.Id
WHERE T_Message.ReceiverId=myid AND T_Message.ReadFlag=0
用這種方法除了正文我們不能隨便刪除外,用戶還是可以自己管理自己的消息。
3、百萬級大用戶量
如果一個網站到了百萬級的用戶量了,那我不得不膜拜該網站和網站經營者了,因為經營這樣的網站一直是筆者的夢想:)好了,回歸正題,如果這樣的系統 放你面前,讓你設計一個站內信群發數據庫,你該何去何從,總之,上面兩種常規的辦法肯定是行不通了的,因為龐大的數據量會讓消息表撐爆,即使你分區也無濟 於事。這時候作為一個系統架構師的你,可能不僅僅要從技術的角度去考慮這個問題,更要從用戶實際情況去着手尋找解決問題的辦法。這里,有一個概念叫“活躍 用戶”,即經常登錄網站的用戶,相對於那些一時沖動注冊而接下來又從來不登錄的用戶來說,活躍用戶對網站的忠誠度很高,從商業的角度來講,忠誠的客戶享受 更高端的服務。
根據這個思路,我們來探索一種方法。假設網站有500萬注冊用戶,其中活躍用戶為60萬(這個比例真很不錯了),現在我們要對所有用戶群發一封致謝信。還是上面兩張表,首先我們可以先往消息表中插入一條群發標識為-1的 消息,這里我們用字段SourceMessageId(原始消息)來標識(-1為原始群發消息本身,其他則是原始消息id),這樣其實群發的工作已經完成 了,用戶可以看到這條公共的消息了。但是用戶需要有消息的控制權,所以必須讓每個用戶擁有一條自己的消息。要達到這個目的,我們可以讓用戶登錄時檢查是否 已經拷貝原始消息,如果沒有拷貝,則拷貝一份原始消息並插入消息表,群發標識為原始消息的id;如果已經存在原始消息的拷貝,則什么都不做。這樣,我們就只要為這60萬活躍用戶消耗消息空間就可以了。具體數據庫設計如下:
T_Message
Id bigint --消息ID
SenderId bigint --發送者ID
ReceiverId bigint --接收者ID,如果為原始群發消息則為-1
SendTime datetime --發送時間
ReadFlag tinyint --已讀標志,如果為原始群發消息則統一為0未讀
SourceMessageId bigint
一篇文章:
站內短信很常見,比如系統需要發消息給用戶,用戶登錄之后可以看到這些消息。
Msg表,字段如下:
id int 自增長id
senderid int 外鍵關聯發送者id
title varchar(128) 短信標題
content varchar(512) 短信內容
createTime datatime 發信時間
status tinyint 發件箱中的狀態:0--普通;1--刪除
一張user_has_msg表,字段如下:
id int
departmentid int 部門群發的時候外鍵關聯部門id,可以為空
receverid int 外鍵關聯收信人,可以為空
msgid int 外鍵關聯短信息
status tinyint 收件箱狀態:0--普通;1--刪除
readStatus tinyint 閱讀狀態:0--未讀;1--已讀
這樣設計是基於如下考慮的:
首先,msg表包含了發件箱所需要的所有信息,程序的時候寫發件箱的時候可以只考慮操作一張數據庫表。
第二,user_has_msg中,departmentid主要考慮的是存在大量的按照部門群發的可能,這樣的話,群發給一個部門的時候之需要在兩張表上個記錄一條數據,而不需要在user_has_msg中記錄該部門員工數條記錄。
但是,后來這個方案被我自己和同事討論后否決了,原因如下:
首先,departmentid的存在使得沒有用戶可以刪除收件箱中的站內信,因為刪除了,其他人的收件箱里也看不到。
第二,msg表不能保證顯示完所有的發件箱所需要的數據,因為只有着一張表的是后讀不出來收件人信息。
修改后的版本是:
將msg修改為只保純粹的信息的表:
id int 自增長id
title varchar(128) 短信標題
content varchar(512) 短信內容
createTime datatime 發信時間
將user_has_msg修改為保存各種關系和狀態的表:
id int
senderid int 外鍵關聯發送者id
receverid int 外鍵關聯收信人
msgid int 外鍵關聯短信息
sendStatus tinyint 發件箱中的狀態:0--普通;1--刪除
receveStatus tinyint 收件箱狀態:0--普通;1--刪除
readStatus tinyint 閱讀狀態:0--未讀;1--已讀
進一步:http://blog.rexsong.com/?p=1202
http://www.nowamagic.net/librarys/veda/detail/431
http://www.cnblogs.com/hejiaquan/archive/2012/04/07/2435817.html
http://baiyuxiong.iteye.com/blog/876211
http://huoding.com/2012/09/28/174
---------------------------------
一篇文章:
NO1.給所有的用戶‘真的’發送一個信息。
NO2.在用戶登錄時,獲取程序的待辦任務。
發布消息的並發不會太大,數據量大可以按時間分表,只關心近期的
也可以使用redis