以 B 站为例,聊聊站内消息系统的设计
使用過簡書,知乎或 b 站的小伙伴應該都有這樣的使用體驗:當有其他用戶關注我們或者私信我們的行為時,我們會收到相關的消息。雖然這些功能看上去簡單,但其背后的設計是非常復雜的,幾乎是一個完成的系統(tǒng),可以稱之為站內消息系統(tǒng)。
我以 b 站舉例(個人認為 b 站的消息系統(tǒng)是我見過的非常完美的,UI 也最為人性化的):
b站站內消息
可以看到 b 站把消息大致分為了三類:
系統(tǒng)推送的通知(System Notice);
回復、@、點贊等用戶行為產生的提醒(Remind);
用戶之間的私信(Chat)。
這樣設計不僅分類明確,且處于同一個主體的事件提醒還會做一個聚合,極大的提高了用戶體驗,不讓用戶收到太多分散的消息。
舉個例子:比如你在某個視頻或某篇文章下發(fā)表了評論,有 100 個人給你的評論點了贊,那么你希望消息頁面呈現的是一個一個用戶給你點贊的提醒,還是像以下聚合之后的提醒:
消息的聚合
我相信你大概率會選擇后者。
我認為對于很多應用來說,這樣的設計都是非常合理的,接下來我寫寫我對于消息系統(tǒng)的設計。
系統(tǒng)通知(System Notice)
系統(tǒng)通知一般是由后臺管理員發(fā)出,然后指定某一類(全體,個人等)用戶接收。基于此設想,可以把系統(tǒng)通知大致分為兩張表:
t_manager_system_notice(管理員系統(tǒng)通知表):記錄管理員發(fā)出的通知 ;
t_user_system_notice(用戶系統(tǒng)通知表):存儲用戶接受的通知。
t_manager_system_notice 結構如下:
字段名類型描述system_notice_idLONG系統(tǒng)通知 IDtitleVARCHAR標題contentTEXT內容typeVARCHAR發(fā)給哪些用戶:單用戶 single;全體用戶 all,vip 用戶,具體類型各位小伙伴可以根據自己的需求選擇stateBOOLEAN是否已被拉取過,如果已經拉取過,就無需再次拉取recipient_idLONG接受通知的用戶的 ID,如果 type 為單用戶,那么 recipient 為該用戶的 ID;否則 recipient 為 0manager_idLONG發(fā)布通知的管理員 IDpublish_timeTIMESTAMP發(fā)布時間
t_user_system_notice 結構如下:
字段名類型描述user_notice_idLONG主鍵 IDstateBOOLEAN是否已讀system_notice_idLONG系統(tǒng)通知的 IDrecipient_idLONG接受通知的用戶的 IDpull_timeTIMESTAMP拉取通知的時間
當管理員發(fā)布一條通知后,將通知插入 t_manager_system_notice 表中,然后系統(tǒng)定時的從 t_manager_system_notice 表中拉取通知,然后根據通知的 type 將通知插入 t_user_system_notice 表中。
如果通知的 type 是 single 的,那就只需要插入一條記錄到 t_user_system_notice 中。如果是全體用戶,那么就需要將一個通知批量根據不同的用戶 ID 插入到 t_user_system_notice 中,這個數據量就需要根據平臺的用戶量來計算。
舉個例子:管理員 A 發(fā)布了一個活動的通知,他需要將這個通知發(fā)布給全體用戶,當拉取時間到來時,系統(tǒng)會將這一條通知取出。隨后系統(tǒng)到用戶表中查詢選取所有用戶的 ID,然后將這一條通知的信息根據所有用戶的 ID,批量插入 t_user_system_notice 中。用戶需要查看系統(tǒng)通知時,從 t_user_system_notice 表中查詢就行了。
注意:
因為一次拉取的數據量可能很大,所以兩次拉取的時間間隔可以設置的長一些。
拉取 t_manager_system_notice 表中的通知時,需要判斷 state,如果已經拉取過,就不需要重復拉取, 否則會造成重復消費。
當一條通知需要發(fā)布給全體用戶時,我們應該考慮到用戶的活躍度。因為如果有些用戶長期不活躍, 我們還將通知推送給他(她),這顯然會造成空間的浪費。所以在選取用戶 ID 時,我們可以將用戶上次 登錄的時間與推送時間做一個比較,如果用戶一年未登陸或幾個月未登錄,我們就不選取其 ID,進而避免 無謂的推送。
有的小伙伴可能有疑問:某條通知已經被拉取過的話,在其后注冊的用戶是不是不能再接收到這條通知?是的。但如果你想將已拉取過的通知推送給那些后注冊的用戶,也不是特別大的問題。只需要再寫一個定時任務,這個定時任務可以將通知的 push_time 與用戶的注冊時間比較一下,重新推送即可。
以上就是系統(tǒng)通知的設計了,接下來再看看較難的提醒類型的消息。
事件提醒(EventRemind)
之所以稱提醒類型的消息為事件提醒,是因為此類消息均是通過用戶的行為產生的,如下:
xxx 在某個評論中@了你;
xxx 點贊了你的文章;
xxx 點贊了你的評論;
xxx 回復了你的文章;
xxx 回復了你的評論。
諸如此類事件,我們以單詞 action 形容不同的事件(點贊,回復,at)。可以看到除了事件之外,我們還需要了解用戶是在哪個地方產生的事件,以便當我們收到提醒時, 點擊這條消息就可以去到事件現場,從而增強用戶體驗,我以事件源 source 來形容事件發(fā)生的地方。
當 action 為點贊,source 為文章時,我就知道:有用戶點贊了我的某篇文章;
當 action 為點贊,source 為評論時,我就知道:有用戶點贊了我的某條評論;
當 action 為@(at), source 為評論時,我就知道:有用戶在某條評論里@了我;
當 action 為回復,source 為文章時,我就知道:有用戶回復了我的某篇文章;
當 action 為回復,source 為評論時,我就知道:有用戶回復了我的某條評論;
由此可以設計出事件提醒表 t_event_remind,其結構如下:
字段名類型描述event_remind_idLONG消息 IDactionVARCHAR動作類型,如點贊、at(@)、回復等source_idLONG事件源 ID,如評論 ID、文章 ID 等source_typeVARCHAR事件源類型:"Comment"、"Post"等source_contentVARCHAR事件源的內容,比如回復的內容,回復的評論等等urlVARCHAR事件所發(fā)生的地點鏈接 urlstateBOOLEAN是否已讀sender_idLONG操作者的 ID,即誰關注了你,at 了你recipient_idLONG接受通知的用戶的 IDremind_timeTIMESTAMP提醒的時間
消息聚合
消息聚合只適用于事件提醒,以聚合之后的點贊消息來說:
100 人 {點贊} 了你的 {文章 ID = 1} :《A》;
100 人 {點贊} 了你的 {文章 ID = 2} :《B》;
100 人 {點贊} 了你的 {評論 ID = 3} :《C》;
聚合之后的消息明顯有兩個特征,即:action 和 source type,這是系統(tǒng)消息和私信都不具備的, 所以我個人認為事件提醒的設計要稍微比系統(tǒng)消息和私信復雜。
如何聚合?
稍稍觀察下聚合的消息就可以發(fā)現:某一類的聚合消息之間是按照 source type 和 source id 來分組的, 因此我們可以得出以下偽 SQL:
SELECT*FROMt_event_remindWHERErecipient_id=用戶ID
當然,SQL 層面的結果集處理還是很麻煩的,所以我的想法先把用戶所有的點贊消息先查出來, 然后在程序里面進行分組,這樣會簡單不少。
拓展
其實還有一種設計提醒表的做法,即按業(yè)務分類,不同的提醒存入不同的表,這樣可以分為:
點贊提醒表
回復提醒表
at(@)提醒表。
我認為這種設計比第一種的更松耦合,不必所有類型的提醒都擠在一張表里,但是這也會帶來表數量的膨脹。所以各位小伙伴可以自行選擇方案。
私信
站內私信一般都是點到點的,且要求是實時的,服務端可以采用 Netty 等高性能網絡通信框架完成請求。我們還是以 b 站為例,看看它是怎么設計的:
站內消息系統(tǒng)的設計
b 站的私信部分可以分為兩部分:
左邊的與不同用戶的聊天室;
與當前正在對話的用戶的對話框,顯示了當前用戶與目標用戶的所有消息。
按照這個設計,我們可以先設計出聊天室表 t_private_chat,因為是一對一,所以聊天室表會包含對話的兩個用戶的信息:
字段名類型描述private_chat_idLONG聊天室 IDuser1_idLONG用戶 1 的 IDuser2_idLONG用戶 2 的 IDlast_messageVARCHAR最后一條消息的內容
這里 user1_id 和 user2_id 代表兩個用戶的 ID,并無特定的先后順序。
接下來是私信表 t_private_message 了,私信自然和所屬的聊天室有聯系,且考慮到私信可以在記錄中刪除(刪除了只是不顯示記錄,但是對方會有記錄,撤回才是真正的刪除),就還需要記錄私信的狀態(tài),以下是我的設計:
字段名類型描述private_message_idLONG私信 IDcontentTEXT私信內容stateBOOLEAN是否已讀sender_removeBOOLEAN發(fā)送消息的人是否把這條消息從聊天記錄中刪除了recipient_removeBOOLEAN接受人是否把這條消息從聊天記錄刪除了sender_idLONG發(fā)送者 IDrecipient_idLONG接受者 IDsend_timeTIMESTAMP發(fā)送時間
消息設置
消息設置一般都是針對提醒類型的消息的,且肯定是由用戶自己設置的。所以我想到一般有以下設置選項:
是否開啟點贊提醒;
是否開啟回復提醒;
是否開啟@提醒;
下面是 b 站的消息設置:
消息設置
可以看到 b 站還添加了陌生人選項,也就是說如果給你發(fā)送私信的用戶不是你關注的用戶,那么視之為陌生人私信,就不接受。
以下是我對于消息設置的設計:
字段名類型描述user_idLONG用戶 IDlike_messageBOOLEAN是否接收點贊消息reply_messageBOOLEAN是否接收回復消息at_messageBOOLEAN是否接收 at 消息stranger_messageBOOLEAN是否接收陌生人的私信
總結
以上就是我對于整個站內消息系統(tǒng)的大概設計了,我參考了很多文章的內容以及很多網站的設計,但實際項目的需求肯定與我所介紹的有很多出入,所以各位小伙伴可以酌情參考。
總結
以上是生活随笔為你收集整理的以 B 站为例,聊聊站内消息系统的设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用 iPhone 拍照,哪些场景不适合
- 下一篇: 藏在 Siri 里的复联彩蛋是什么,如何