天天看點

即時通信伺服器架構的一些思考

即時通信,伺服器,架構

對于一個即時通信伺服器來說,在使用者量少的時候,一台伺服器就足以提供所有的服務。而這種架構也最簡單,舉個例子,使用者A與使用者B互為好友,A向B發消息,伺服器接收到消息時,解析出接收消息的人,直接轉發給B即可。可是當使用者數量越來越多時,一台伺服器已經無法所有使用者的需求,這時就要進行服務擴容,進行分布式部署

即時通信伺服器架構的一些思考

如圖所示,不同的使用者可能登入到不同的伺服器上,那麼使用者A給使用者B發消息時,伺服器收到消息,首先判斷B是否也登入在本伺服器上,如果是,那麼直接轉發消息即可。如果B不在本伺服器上,那應該往哪裡轉發這條消息呢?最簡單的做法就是向伺服器叢集中的其他伺服器廣播這條消息,對于每個收到這條消息的伺服器,首先判斷消息的目的使用者是否登入在自己身上,如果不是,直接忽略該消息。如果是,那麼向目的使用者轉發該消息。固然,這種暴力粗犷的做法是最簡單直接的,但是會産生很多無效的消息轉發,對于伺服器性能産生很大的影響。曾看過蘑菇街開源的即時通信軟體Teamtalk的代碼,伺服器就是這種實作方式。其伺服器架構如下:

即時通信伺服器架構的一些思考

不同的msg伺服器連接配接到同一台route server上,所有msg伺服器之間的轉發全部通過route server。這無疑會加重route server的負載。即時msg server部署的再多,根據木桶理論,一個系統的性能是由其最薄弱的環節所決定的。是以也注定這樣的架構,其系統容量也是有限的。那麼如何改善這種系統呢,很明顯伺服器之間的消息轉發不能直接全部廣播,而應該有一套明确的路由系統,即伺服器在轉發消息時,應該知道這條消息應該轉發到哪一台伺服器,這樣就不需要每條消息都在所有伺服器之間廣播了。

那麼如何實作這樣一套路由系統呢?

簡單的做法是,每個使用者上線時,通過其連接配接的msg server向其他所有msg server廣播自己的登入資訊,告知其他伺服器自己登入在哪台伺服器上面。這樣當某個使用者向其好友發消息時,首先通過好友id檢視其登入的msg server。如果好友與自己是同一台伺服器,那麼直接轉發即可;如果不是,伺服器向route server發送轉發該消息,并且帶上目标msg server的id.這樣route server 收到消息後,解析出目标的msg server,進行一次轉發即可,省去了大量的廣播消息。這種方式雖然解決了廣播消息的問題,但是在每台msg server上都要儲存所有使用者的路由資訊。當所有使用者都登入時,幾乎就退化成了單點模型,msg server肯定承受不了。

那麼如何解決這個問題呢?試想一下,既然所有的msg server上都儲存着同樣的路由資訊,那麼我們可以把這些資料從msg server剝離出來,存在一個單獨的伺服器上,供msg server查詢。我們暫且把這個伺服器叫做route info server(路由資訊伺服器).對于一個使用者要存儲的資料為

{

   userid,

   msgserverid

}

假設這兩個資料都是32Byte,那麼存儲一億個使用者需要的記憶體32B*10^8=3.2G。目前好點的伺服器都有50G的記憶體,很顯然記憶體不是問題。那麼就剩通路量的問題。如果所有的msg server都從這一台伺服器上讀取資料, 肯定會影響整個系統的性能。是以路由資訊伺服器不能采用這種單點模型。考慮到這種路由資訊的特點,很明顯是一種讀多寫少的資料。一個使用者隻有在登入的時候才會寫一次路由資訊,其他時候就是轉發消息的時候讀取路由資訊了。那麼可以采用類似資料庫的主備模型,主伺服器用來寫路由資訊,備伺服器用于查詢路由資訊。而且可以設定多台備伺服器,分擔msg server的讀壓力。其實我們也可以使用一些成熟的緩存系統來完成路由資訊伺服器的功能,比如redis. redis擁有現成的主備方案,隻是像這種通用的緩存伺服器在存儲資料時,消耗的記憶體會大些。研究過redis源碼的,大多都能了解。其key value都存儲在redisobject的結構體當中,有一些附加的資訊,是以比自己寫一個這樣的服務所消耗的記憶體肯定會大些。

OK,說完了路由資訊伺服器,我們再回到msg server上來。那麼msg server在轉發消息時,首先根據目的使用者的id 到路由資訊伺服器上查找其所在的msg server用于消息轉發。但是這也會存在一個,每次轉發消息時,都要查詢一次路由資訊,這無疑會影響消息的轉發速度,而且也會增大路由資訊伺服器的通路壓力。如果在發送消息之後,将路由資訊儲存到本地,那麼下次發送消息,就無需再去路由資訊伺服器重複查詢了。但是也不能把所有的路由全部儲存到本地,那樣又會嚴重消耗msg server的記憶體。于是,就有我們想到一種折中的方案,使用一個lru的緩存隊列,在需要儲存新的路由資訊時,首先檢視緩存隊列是否已滿,如果未滿,直接插入到隊首,如果隊列已滿,淘汰到隊尾的資料。緩存列隊大小可根據記憶體大小靈活設定。考慮到在我們平時在使用qq時,大部分人都登入着,但是發消息的人并不多。對于路由資訊,在其首次轉發消息是,從路由資訊伺服器查詢一次路由,在其整個回話過程中,路由資訊都緩存在本地。在其會話結束後,将最近最久未使用的路由資料淘汰出去,這種做法再考慮到記憶體使用的同時,又大大減少了伺服器的通路次數,算是一種較好的折中方案. 在完成了路由資訊系統之後,route server也可以進行水準擴充,route server要做的僅僅是轉發消息,并不需要存儲資料,擴充起來非常友善。最終的系統架構如下:

即時通信伺服器架構的一些思考

總結:

1. 本文所描述的即時通信伺服器架構,着重讨論的是消息如何路由的問題,但這并不代表一個完整的即時通信伺服器系統,諸如注冊,登入,離線消息,檔案等功能這些都未在本文的讨論範圍之類

2. 本文中所提的方案也是一種設想,并未真正進行實作,肯定也有很多細節問題沒有考慮到。歡迎大家留言讨論

3. 對于本文所提的系統,可稱之為一個服務叢集。而像qq這樣數量使用者的系統,在全國分布了很多個叢集。本文所讨論的也僅僅局限于一個叢集内的通信設計,而叢集之間的通信又如何通信呢。每個叢集的路由資料,如果全同步到其他叢集,這種做法顯然不是最優。如果有更好的想法,也歡迎留言讨論

繼續閱讀