<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          以 B 站為例,聊聊站內(nèi)消息系統(tǒng)的設(shè)計

          共 4822字,需瀏覽 10分鐘

           ·

          2020-08-22 00:50

          本文來自 guang19 投稿(Github 同名,歡迎關(guān)注)。

          使用過簡書,知乎或 b 站的小伙伴應(yīng)該都有這樣的使用體驗:當(dāng)有其他用戶關(guān)注我們或者私信我們的行為時,我們會收到相關(guān)的消息。?雖然這些功能看上去簡單,但其背后的設(shè)計是非常復(fù)雜的,幾乎是一個完成的系統(tǒng),可以稱之為?站內(nèi)消息系統(tǒng)

          我以 b 站舉例(個人認(rèn)為 b 站的消息系統(tǒng)是我見過的非常完美的,UI 也最為人性化的):

          b站站內(nèi)消息

          可以看到 b 站把消息大致分為了三類:

          1. 系統(tǒng)推送的通知(System Notice);
          2. 回復(fù)、@、點贊等用戶行為產(chǎn)生的提醒(Remind);
          3. 用戶之間的私信(Chat)。

          這樣設(shè)計不僅分類明確,且處于同一個主體的事件提醒還會做一個聚合,極大的提高了用戶體驗,不讓用戶收到太多分散的消息。

          舉個例子:比如你在某個視頻或某篇文章下發(fā)表了評論,有 100 個人給你的評論點了贊,那么你希望消息頁面呈現(xiàn)的是一個一個用戶給你點贊的提醒,還是像以下聚合之后的提醒:

          消息的聚合

          我相信你大概率會選擇后者。

          我認(rèn)為對于很多應(yīng)用來說,這樣的設(shè)計都是非常合理的,接下來我寫寫我對于消息系統(tǒng)的設(shè)計。

          系統(tǒng)通知(System Notice)

          系統(tǒng)通知一般是由后臺管理員發(fā)出,然后指定某一類(全體,個人等)用戶接收?;诖嗽O(shè)想,可以把系統(tǒng)通知大致分為兩張表:

          1. t_manager_system_notice(管理員系統(tǒng)通知表)?:記錄管理員發(fā)出的通知 ;
          2. t_user_system_notice(用戶系統(tǒng)通知表)?:?存儲用戶接受的通知。

          t_manager_system_notice 結(jié)構(gòu)如下:

          字段名類型描述
          system_notice_idLONG系統(tǒng)通知 ID
          titleVARCHAR標(biāo)題
          contentTEXT內(nèi)容
          typeVARCHAR發(fā)給哪些用戶:單用戶 single;全體用戶 all,vip 用戶,具體類型各位小伙伴可以根據(jù)自己的需求選擇
          stateBOOLEAN是否已被拉取過,如果已經(jīng)拉取過,就無需再次拉取
          recipient_idLONG接受通知的用戶的 ID,如果 type 為單用戶,那么 recipient 為該用戶的 ID;否則 recipient 為 0
          manager_idLONG發(fā)布通知的管理員 ID
          publish_timeTIMESTAMP發(fā)布時間

          t_user_system_notice 結(jié)構(gòu)如下:

          字段名類型描述
          user_notice_idLONG主鍵 ID
          stateBOOLEAN是否已讀
          system_notice_idLONG系統(tǒng)通知的 ID
          recipient_idLONG接受通知的用戶的 ID
          pull_timeTIMESTAMP拉取通知的時間

          當(dāng)管理員發(fā)布一條通知后,將通知插入 t_manager_system_notice 表中,然后系統(tǒng)定時的從 t_manager_system_notice 表中拉取通知,然后根據(jù)通知的 type 將通知插入 t_user_system_notice 表中。

          如果通知的 type 是 single 的,那就只需要插入一條記錄到 t_user_system_notice 中。如果是全體用戶,那么就需要將一個通知批量根據(jù)不同的用戶 ID 插入到 t_user_system_notice 中,這個數(shù)據(jù)量就需要根據(jù)平臺的用戶量來計算。

          舉個例子:?管理員 A 發(fā)布了一個活動的通知,他需要將這個通知發(fā)布給全體用戶,當(dāng)拉取時間到來時,系統(tǒng)會將這一條通知取出。隨后系統(tǒng)到用戶表中查詢選取所有用戶的 ID,然后將這一條通知的信息根據(jù)所有用戶的 ID,批量插入 t_user_system_notice 中。用戶需要查看系統(tǒng)通知時,從 t_user_system_notice 表中查詢就行了。

          注意:

          1. 因為一次拉取的數(shù)據(jù)量可能很大,所以兩次拉取的時間間隔可以設(shè)置的長一些。
          2. 拉取 t_manager_system_notice 表中的通知時,需要判斷 state,如果已經(jīng)拉取過,就不需要重復(fù)拉取, 否則會造成重復(fù)消費。
          3. 當(dāng)一條通知需要發(fā)布給全體用戶時,我們應(yīng)該考慮到用戶的活躍度。因為如果有些用戶長期不活躍, 我們還將通知推送給他(她),這顯然會造成空間的浪費。?所以在選取用戶 ID 時,我們可以將用戶上次 登錄的時間與推送時間做一個比較,如果用戶一年未登陸或幾個月未登錄,我們就不選取其 ID,進(jìn)而避免 無謂的推送。
          4. 有的小伙伴可能有疑問:?某條通知已經(jīng)被拉取過的話,在其后注冊的用戶是不是不能再接收到這條通知??是的。但如果你想將已拉取過的通知推送給那些后注冊的用戶,也不是特別大的問題。?只需要再寫一個定時任務(wù),這個定時任務(wù)可以將通知的 push_time 與用戶的注冊時間比較一下,重新推送即可。

          以上就是系統(tǒng)通知的設(shè)計了,接下來再看看較難的提醒類型的消息。

          事件提醒(EventRemind)

          之所以稱提醒類型的消息為事件提醒,是因為此類消息均是通過用戶的行為產(chǎn)生的,如下:

          • xxx 在某個評論中@了你;
          • xxx 點贊了你的文章;
          • xxx 點贊了你的評論;
          • xxx 回復(fù)了你的文章;
          • xxx 回復(fù)了你的評論。

          諸如此類事件,我們以單詞 action 形容不同的事件(點贊,回復(fù),at)。?可以看到除了事件之外,我們還需要了解用戶是在哪個地方產(chǎn)生的事件,以便當(dāng)我們收到提醒時, 點擊這條消息就可以去到事件現(xiàn)場,從而增強用戶體驗,我以事件源 source 來形容事件發(fā)生的地方。

          • 當(dāng) action 為點贊,source 為文章時,我就知道:有用戶點贊了我的某篇文章;
          • 當(dāng) action 為點贊,source 為評論時,我就知道:有用戶點贊了我的某條評論;
          • 當(dāng) action 為@(at), source 為評論時,我就知道:有用戶在某條評論里@了我;
          • 當(dāng) action 為回復(fù),source 為文章時,我就知道:有用戶回復(fù)了我的某篇文章;
          • 當(dāng) action 為回復(fù),source 為評論時,我就知道:有用戶回復(fù)了我的某條評論;

          由此可以設(shè)計出事件提醒表 t_event_remind,其結(jié)構(gòu)如下:

          字段名類型描述
          event_remind_idLONG消息 ID
          actionVARCHAR動作類型,如點贊、at(@)、回復(fù)等
          source_idLONG事件源 ID,如評論 ID、文章 ID 等
          source_typeVARCHAR事件源類型:"Comment"、"Post"等
          source_contentVARCHAR事件源的內(nèi)容,比如回復(fù)的內(nèi)容,回復(fù)的評論等等
          urlVARCHAR事件所發(fā)生的地點鏈接 url
          stateBOOLEAN是否已讀
          sender_idLONG操作者的 ID,即誰關(guān)注了你,at 了你
          recipient_idLONG接受通知的用戶的 ID
          remind_timeTIMESTAMP提醒的時間

          消息聚合

          消息聚合只適用于事件提醒,以聚合之后的點贊消息來說:

          • 100 人 {點贊} 了你的 {文章 ID = 1} :《A》;
          • 100 人 {點贊} 了你的 {文章 ID = 2} :《B》;
          • 100 人 {點贊} 了你的 {評論 ID = 3} :《C》;

          聚合之后的消息明顯有兩個特征,即:action 和 source type,這是系統(tǒng)消息和私信都不具備的, 所以我個人認(rèn)為事件提醒的設(shè)計要稍微比系統(tǒng)消息和私信復(fù)雜。

          如何聚合?

          稍稍觀察下聚合的消息就可以發(fā)現(xiàn):某一類的聚合消息之間是按照 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;

          當(dāng)然,SQL 層面的結(jié)果集處理還是很麻煩的,所以我的想法先把用戶所有的點贊消息先查出來, 然后在程序里面進(jìn)行分組,這樣會簡單不少。

          拓展

          其實還有一種設(shè)計提醒表的做法,即按業(yè)務(wù)分類,不同的提醒存入不同的表,這樣可以分為:

          1. 點贊提醒表
          2. 回復(fù)提醒表
          3. at(@)提醒表。

          我認(rèn)為這種設(shè)計比第一種的更松耦合,不必所有類型的提醒都擠在一張表里,但是這也會帶來表數(shù)量的膨脹。?所以各位小伙伴可以自行選擇方案。

          私信

          站內(nèi)私信一般都是點到點的,且要求是實時的,服務(wù)端可以采用 Netty 等高性能網(wǎng)絡(luò)通信框架完成請求。?我們還是以 b 站為例,看看它是怎么設(shè)計的:

          站內(nèi)消息系統(tǒng)的設(shè)計

          b 站的私信部分可以分為兩部分:

          1. 左邊的與不同用戶的聊天室;
          2. 與當(dāng)前正在對話的用戶的對話框,顯示了當(dāng)前用戶與目標(biāo)用戶的所有消息。

          按照這個設(shè)計,我們可以先設(shè)計出聊天室表 t_private_chat,因為是一對一,所以聊天室表會包含對話的兩個用戶的信息:

          字段名類型描述
          private_chat_idLONG聊天室 ID
          user1_idLONG用戶 1 的 ID
          user2_idLONG用戶 2 的 ID
          last_messageVARCHAR最后一條消息的內(nèi)容

          這里 user1_id 和 user2_id 代表兩個用戶的 ID,并無特定的先后順序。

          接下來是私信表 t_private_message 了,私信自然和所屬的聊天室有聯(lián)系,且考慮到私信可以在記錄中刪除(刪除了只是不顯示記錄,但是對方會有記錄,撤回才是真正的刪除),就還需要記錄私信的狀態(tài),以下是我的設(shè)計:

          字段名類型描述
          private_message_idLONG私信 ID
          contentTEXT私信內(nèi)容
          stateBOOLEAN是否已讀
          sender_removeBOOLEAN發(fā)送消息的人是否把這條消息從聊天記錄中刪除了
          recipient_removeBOOLEAN接受人是否把這條消息從聊天記錄刪除了
          sender_idLONG發(fā)送者 ID
          recipient_idLONG接受者 ID
          send_timeTIMESTAMP發(fā)送時間

          消息設(shè)置

          消息設(shè)置一般都是針對提醒類型的消息的,且肯定是由用戶自己設(shè)置的。所以我想到一般有以下設(shè)置選項:

          1. 是否開啟點贊提醒;
          2. 是否開啟回復(fù)提醒;
          3. 是否開啟@提醒;

          下面是 b 站的消息設(shè)置:

          消息設(shè)置

          可以看到 b 站還添加了陌生人選項,也就是說如果給你發(fā)送私信的用戶不是你關(guān)注的用戶,那么視之為陌生人私信,就不接受。

          以下是我對于消息設(shè)置的設(shè)計:

          字段名類型描述
          user_idLONG用戶 ID
          like_messageBOOLEAN是否接收點贊消息
          reply_messageBOOLEAN是否接收回復(fù)消息
          at_messageBOOLEAN是否接收 at 消息
          stranger_messageBOOLEAN是否接收陌生人的私信

          總結(jié)

          以上就是我對于整個站內(nèi)消息系統(tǒng)的大概設(shè)計了,我參考了很多文章的內(nèi)容以及很多網(wǎng)站的設(shè)計,但實際項目的需求肯定與我所介紹的有很多出入,所以各位小伙伴可以酌情參考。

          最后

          文章有幫助可以點個「在看」或「分享」,都是支持,我都喜歡!

          我是 Guide 哥,Java后端開發(fā),會一點前端知識,喜歡烹飪,自由的少年。一個三觀比主角還正的技術(shù)人。我們下期再見!


          往期推薦



          來吧!手寫一個 RPC 框架。畢設(shè)/項目經(jīng)驗穩(wěn)了!

          我在華為外包一年的經(jīng)歷分享。

          我還在生產(chǎn)玩 JDK7,JDK 15 卻要來了!|新特性嘗鮮

          朋友入職中軟一個月(外包華為)就離職了!

          線上頻出MySQL死鎖問題!分享一下自己教科書般的排查和分析過程!

          6 個珍藏已久 IDEA 小技巧,這一波全部分享給你!


          瀏覽 46
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品你懂的在线观看 | 黄色录像网址 | 肏屄视频| 欧美另类欧美另类欧美另类 | 日韩欧美小视频 |