即時通訊服務器架構的一些思考

對於一個即時通訊服務器來講,在用戶量少的時候,一臺服務器就足以提供全部的服務。而這種架構也最簡單,舉個例子,用戶A與用戶B互爲好友,A向B發消息,服務器接收到消息時,解析出接收消息的人,直接轉發給B便可。但是當用戶數量愈來愈多時,一臺服務器已經沒法全部用戶的需求,這時就要進行服務擴容,進行分佈式部署redis

                                         

如圖所示,不一樣的用戶可能登陸到不一樣的服務器上,那麼用戶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,spa

   msgserverid設計

}server

假設這兩個數據都是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這樣數量用戶的系統,在全國分佈了不少個集羣。本文所討論的也僅僅侷限於一個集羣內的通訊設計,而集羣之間的通訊又如何通訊呢。每一個集羣的路由數據,若是全同步到其餘集羣,這種作法顯然不是最優。若是有更好的想法,也歡迎留言討論

相關文章
相關標籤/搜索