如果技術棧不一致的同學請儘量看懂架構和概念,自己用自己的技術棧去架構自己的聊天室!
此項目爲前後端分離項目,基於vue+springboot構建
歡迎star
前端:以 vue 爲核心的 vue全家桶(vue+vuex-狀態管理+vue-router-路由管理+axios-HTTP庫),UI框架使用了iview+VueSax,打包和初始化項目工具使用 webpack 來代替vue-cli.圖片上傳使用了他人的 圖牀,發送框使用了 quill 富文本編輯器。
後端:使用了springboot 框架,數據持久層框架使用了 mybatis,數據庫使用mysql.
中間件:緩存中間件-redis,消息隊列-rabbitmq。
加密使用 AES 加密
通訊協議使用 WebSocket和HTTP協議
項目演示:
1.登錄註冊部分
由於登錄註冊太過於簡單,所以這裏直接給出賬號密碼名單使用
當此賬號已經登錄時,isOnline 開關會顯示打開狀態,點擊登錄會出現提示
切換另一個賬號即可,若無可用賬號,請等待 orz
2.聊天
注:這個項目裏只有羣聊,沒有設置單人聊天,因單人聊天無非是羣聊的一個特例,這裏不予實現。
主界面
項目裏給出了6個聊天室,分別對應不同實現方式。
–聊天室一:
僅使用WebSocket
實現,僅能在線聊天,離線用戶收不到消息且無法讀取未讀消息
如圖,前端通過http協議向後端發送數據,後端接收到數據後通過WebSocket協議向在線的人員發送數據。
–聊天室二:
使用`WebSocket+mysql`,提供了在線聊天,不在線用戶上線後可拉取未讀消息。
如圖,後端接收到聊天數據並給在線人員發送消息後再把數據按照一定的邏輯存在數據庫裏,當用戶上線後,拉取未讀消息即可,此時未讀消息能且僅能拉取一次,拉取完畢後,消息就成爲已讀消息,並不能重讀拉取。
–聊天室三:
緩存寫擴散:使用WebSocket+redis+mysql。
如圖,每個用戶維護一個 inBox,每條信息發給服務器時,給在線人員發生信息,並判斷該條消息所屬羣組,並找到該羣組下所有不在線人員,並把該消息寫入不在線人員下的inBox下,當用戶上線拉取消息時,只需從自己的 inBox 下拉取消息即可,該inBox是用redis的list實現的,是有序的(插入順序),所以不存在消息時間混亂問題,當用戶讀取消息後,立即刪除該inBox,保證了未讀消息的唯一性.其中mysql用於消息的備份。
–聊天室四:
緩存讀擴散:使用WebSocket+redis+mysql。
如圖,每個用戶需要維護一個outBox和一個已讀消息索引,用戶每發送一條信息,都存在自己的outBox裏面,並存入order順序表,當離線用戶上線拉取未讀消息時,首先讀取自己已讀消息索引和order順序表,根據順序表大小和已讀消息索引之差找到自己所有未讀消息,然後根據順序表裏的信息去找每一條信息發送者的outBox,並拉取每一條未讀消息,拉取完畢後更新自己的已讀消息索引即可。
–聊天室五:
緩存讀擴散改良版:使用WebSocket+redis+mysql。
在上一個聊天室中,由於每個用戶有一個outBox,信息存儲將是無序的,所以需要額外耗費空間來存儲順序,所以在改良版中,根據是羣組的信息發送,所以只需維護一個羣組的outBox,不再有個人的outBox,所以用戶拉取歷史消息時,只需比對自己消息索引和羣outBox索引即可
– 聊天室六
消息隊列:使用 WebSocket+Rabbitmq+mysql
這裏使用rabbitmq作爲消息中間件,每個羣組是一個fanout 類型交換器,這個交換器的特點在於,每接收一條消息,交換器都會給綁定的隊列發送這條消息,所以每個羣組綁定一個fanout類型交換器,每個交換器下都綁定這每個用戶的隊列,用戶每發送一條消息,每個隊列都會收到這條消息,在線的用戶把消息讀取出去,不在線的用戶上線拉取消息,這樣的方式減少了手動判斷用戶和減少關注消息的順序問題,統一交給消息隊列處理。
三、其他
- UI優化
登錄過期提醒
新消息提醒
發送圖片 - 消息存儲
前端所有消息均存在 localstorge中,當前後登錄賬戶不一致時,前一個賬戶消息將會被清空,如果前後登錄賬戶一致,消息不會清空
用戶登錄有效期在 30 分鐘內,超過30分鐘要重新登錄
四、jvm 優化
由圖,根據jvm GC 分析,可以看出此應用最多的是 “短命的對象”,因爲這個應用是一個 web應用,每一個請求完成後,此請求中所有創建的對象都是遊離的 無Root的對象,也就是馬上要被回收的對象,此時這個對象將在 Eden區存活,當Eden區滿,此時會觸發 minor Gc,此時所有已完成的請求中的對象均會在此銷燬,當用戶請求過多,即大量請求在幾秒內出現時,此時大量對象將在第一次 minor GC 時存活,移到 Survivor 1區,但是如圖所示,Old老年代區佔用率一直不變,說明大部分對象在經歷>=15次 minor GC 之前就被銷燬,所以我們要做的優化其實沒有很多,反過來看這個項目的構成,存活時間最長的應該是 WebSocket ,當用戶信息量過大時,還是有可能讓 WebSocket 相關對象度過>=15次的 minor GC ,但是由於項目的特性,幾乎不可能發生 Full GC,也就是說當WebSocket對象在老年代中死亡時,這將很難被清除,因爲無法進行Full GC,所以爲了避免這種情況,有兩種解決辦法均可解決
1.當WebScoket斷開連接時,主動置爲NULL,重寫 finalize方法,立即進行GC,而不用等待內存區滿再進行。
2.修改jvm參數 :-Xms500m,設置了jvm啓動內存(當時想着越大越好),-Xms500m(運行中最大內存),然而,本機運行卡死,服務器僅僅有1g的內存,必然帶不動,所以恢復默認值
歡迎大家留言討論
想要源碼的留言,我將私信給出github