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