一個像 Line 的聊天群設計 ~ 安安你好 ~
instant messaging
Lastmod: 2019-12-15

本篇文章中,我們講要說明,如何開發一個簡單的聊天群系統,這個東東雖然我們很常見到,到和我們平常開發的一些 WEB 有很大的差別。

差別在那呢 ? 假設我們開一個todolist功能,事實上大部份的工作就是crud的事情,每當要新增一個 todo 時,只要發送 http 到後端新增資料到資料庫裡去,然後在回傳結果就好了,但聊天群這種,如果你每發送一個訊息都使用 http 那一定爆掉的。

像聊天群這樣類型的,我們稱為InstantMessaging IM中文為即時通訊,本篇文章我們將會說明要建立這種IM應用所需要的基本知識。

開始吧 ~

從 Web 到 IM 的通信過程轉變

在最開始時瀏覽器它沒有辦法直接連接到另一個瀏覽器的通信功能,也就是說你不能從 A client 直接傳送訊息到 B client 去,我們只能在它們的中間,建立一個 server ,來將 A 要傳送的訊息儲放起來,然後 B 在自已去 server 取得資料,如下圖 :

這種做不行嗎 ?

說實話,功能是有做出來 ~ 但浪費太多的資源,你想想,根據上面的說法,當 A 發送訊息到 server 後,你 B 要如何知道 server 有你的訊息 ? 記好 http 只能從client發送到server,不能反之,所以這也代表這你 B 只能定時的去 server 問問看,說有沒有我的資料啊 ~

很明顯的,你可能問了十次,只有一次才有你要的訊息,那其它九次,不就都浪費掉了,這也代表你的 IM 系統有 90 % 的效能在處理沒用的事情

當然中間處理的其它方法先不說,後來 html5 提出了一個應用層的協議websocket,來解決這事兒 ~

Hello WebSocket

這個協議可以幫助我們可以實現,從 server 端推送資料到 client 端,而且從建立的通道是持久連接,在 http 1.0 時你每發送一次請求,都需要做一次 tcp 握手,而 http 1.1 時,則可以多次使用一個 tcp,但在 websocket 你就只要建立一次,當完成握手後,就會產生一個全雙工的通道。

全雙工代表這,可以從任何一方傳送資料或接受資料。

它的 server 與 client 的運作圖如下 :

最簡單的聊天群架構

從上面的概念中我們知道,我們這邊主要需要使用的東西是 websocket,接下來我們可以來開始的規畫我們聊天群的架構。

我們先從最簡單的來看,如下圖 :

Business Server : 用來處理用戶需要用到 http 的操作,例如用戶登入、登出、加入聊天群、離開聊天群之類的,並且每當使用者要加入聊天群時,會發送訊息給 message server 然後它會在往 client 端推送說 ~ 某用戶加入聊天群囉 ~

Message Server : 用來發送訊息與接受訊息。

上面的架構很簡單,就是 client 對一個 server,但我們來想想會發生什麼問題 ? 首先假設我們這個系統有多少人,server 就需要建立幾條的 websocket,那我想問,一台 server 可以接受幾個 websocket 連線呢 ?

一台 server 可以有多少連線呢 ?

這個問題說實話很難回答,因為還要考慮 server 性能、程式碼撰寫等,不過我這邊會簡單的根據,來大概的算出可能的連線數。

要理解這個問題,我們需要先理解下面這個知識,

file hanle 的限制

在 unix 中每一個 tcp 連線都要占用一個file descriptor,而它有一定的限制數量,當使用完後,新的 tcp 連線到來就會發生錯誤以下的錯誤訊息 :

Socket/File: Can't open so many files。

那一個 process 我們可以開啟幾個檔案呢 ? 我們可以用以下的指令來看看 :

ulimit -n

像我這台 mac pro 的預設是 :

4864

而如果是AWS EC2那在最基本版本,什麼都沒動過的則為 :

1024

所以白話文的說,如果我沒有修改預設,我在這邊最多同時間,只能連線4864個 tcp 。 它當然可以改預設,不過每一個系統一定都是有上限的,記憶中有些系統的上線大約 1百萬 到2百萬之間,這邊到不確定。

簡單的總結一下,一個 server 可以有多少連線呢 ? 直說我很難回答正確的答案,但我只能說,如果你沒調整預設的 process 限制,那必定只能連這個系統的上限(大概)。

但是每個系統有設定限制,就代表它預設一定有它的理由,所以不確定修改是不是個好方法。

聊天群架構 V-2

上面的架構非常非常的簡單,但它有什麼問題呢 ? 我們想想以下這個場景 :

我們某個聊天群裡總共 4 人,其中 A、B、C 三人目前在線上,而 D 不在線上,那麼 ~ 要著麼確保 D 上線時,可以看到其它三人所發送的訊息呢 ?

目前大概的想法為,增加兩個服務,其中第一個為使用 redies 建立的用來保存用戶狀態(上線與未上線)的服務,每當使用者登入或登出時,都會透過business server發送訊息來更新redies狀態。

然後每當有訊息準備要傳送時,message server會先到redies取得聊天群內的用戶資訊,然後如果在線上的,則進行傳送,不在線上的則將知存放到temp message server裡。

然後每當用戶上線時,business server會發個訊息到messge server去,然後去看看temp message server裡有沒有離線訊息,有的話就傳送出去。

聊天群架構 V-3

上面的架構看似沒問題,但是它事實上有個缺點,有沒有注意到,很多的邏輯運算都在message server裡面做,我原本是將它定義成只用來收發訊息,但是我現在很多判斷都在裡面做,雖然在量不大時,還沒什麼問題,但如果量很大的話message server會上天堂的。

所以我決定在增加一個服務,就取名為Logical Server,架構圖如下 :

它主要的工作就是要處理所有要送出與收到的邏輯運算,像我們剛剛上面說到,要更新使用者狀態也改成從這裡運作,而決定那些訊息要存放到離線 server 也是在這處理,而在每一次使用者登入時,要傳送的訊息也都是從這裡處理好,然後在去通知message server和他說,該送貨囉 ~~

聊天群架構 V-4

上面的架構中,我們基本上已經可以處理不少需求了,但我們在思考看看,有沒有那個地方可以改進,我們從一個角度來想,流量,我們仔細想一下那一個地方的流量是最大的 ? 嗯就是message server,假設我們一台message serverhold 不住要著麼辦呢 ?

這時就是針對message server進行擴充,架構會變成如下 :

上面新增加了兩個服務 :

Proxy : 用來決定要使用那台message serverclient進行連線,事實上就是 load balance 的功能。

Dispatch : 用來決定訊息要傳送到那一個message server

我們每一次要建立 websocket 連線時,都會需要先連到proxy由它來決定你要去那個message server建立 websocket 連線。

然後每當有訊息進來後,會到一個名為dispatch的服務,先將該訊息進行包裝,並且也會注明該訊息是從那個 message server 過來的,然後再傳送到 logical server 中,判斷那將該訊息送到那些用戶上,最後在回送到dispatch上,由它來決定要送到那個message server 上,最後再由它回傳給用戶上。

結論

在這篇文章中,我們所討論到的聊天群架構,事實上到 V-4 版本,已經可以處理大致上WEB!聊天群會用的功能,下一篇文章中我們將要來討論如果來設計聊天室

參考資料

comments powered by Disqus