老讀者應該還記得我在去年國慶節前分享過一篇《技術乾貨:從零開始,教你設計一個百萬級的消息推送系統》,雖然我在文中有貼一些僞代碼,依然有些朋友但願能直接分享一些能夠運行的源碼。好吧,質疑我窮我無話可說(由於是真窮。。),懷疑我擼碼的能力那是絕對不行,因此此次準備拉起鍵盤大幹一場——徒手擼套分佈式IM出來!^_^!php
本文記錄了我開發的一款面向IM學習者的 IM系統——CIM(全稱:CROSS-IM),同時提供了一些組件幫助開發者構建一款屬於本身可水平擴展的 IM。html
經過學習本文和CIM代碼,你能夠得到如下知識:java
1)如何從頭開發一套IM(CIM的客戶有點弱,見諒見諒);node
2)如何設計分佈式的IM架構;git
3)如何將你的分佈式IM架構用代碼和相關技術實現出來。github
本文配套的CIM源碼地址:web
主要鏡像:https://github.com/crossoverJie/cimredis
備用鏡像:https://github.com/52im/cim算法
如下文章與本文相似或相關,一樣有助於您的IM開發入門:數據庫
* 友情提示:閱讀本文和CIM源碼,須要您具有必定的網絡編程、IM理論等知識等,若是您還不具有這些,請先閱讀《新手入門一篇就夠:從零開發移動端IM》,徹底來的及!
學習交流:
- 即時通信/推送技術開發交流5羣:215477170[推薦]
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
(本文同步發佈於:http://www.52im.net/thread-2775-1-1.html)
crossoverJie(陳杰): 90後,畢業於重慶信息工程學院,現供職於重慶豬八戒網絡有限公司。
本次特意錄了兩段視頻演示(羣聊、私聊),點擊下方連接能夠查看視頻版 Demo。
CIM 私聊視頻演示:https://www.bilibili.com/video/av39405821
CIM 羣聊視頻演示:https://www.bilibili.com/video/av39405501
下面來看看具體的架構設計:
架構說明:
1)CIM 中的各個組件均採用 SpringBoot 構建;
2)採用 Netty + Google Protocol Buffer 構建底層通訊;
3)Redis 存放各個客戶端的路由信息、帳號信息、在線狀態等;
4)Zookeeper 用於 IM-server 服務的註冊與發現。
總體主要由如下模塊組成:
1)cim-server——IM 服務端:用於接收 client 鏈接、消息透傳、消息推送等功能。支持集羣部署;
2)cim-forward-route——消息路由服務器:用於處理消息路由、消息轉發、用戶登陸、用戶下線以及一些運營工具(獲取在線用戶數等);
3)cim-client——IM 客戶端:給用戶使用的消息終端,一個命令便可啓動並向其餘人發起通信(羣聊、私聊);同時內置了一些經常使用命令方便使用。
總體的流程也比較簡單,流程圖以下:
流程解釋以下:
1)客戶端向 route 發起登陸;
2)登陸成功從 Zookeeper 中選擇可用 IM-server 返回給客戶端,並保存登陸、路由信息到 Redis;
3)客戶端向 IM-server 發起長鏈接,成功後保持心跳;
4)客戶端下線時經過 route 清除狀態信息。
因此當咱們本身部署時須要如下步驟:
2)部署 cim-server,這是真正的 IM 服務器,爲了知足性能需求因此支持水平擴展,只須要註冊到同一個 Zookeeper 便可;
3)部署 cim-forward-route,這是路由服務器,全部的消息都須要通過它。因爲它是無狀態的,因此也能夠利用 Nginx 代理提升可用性;
4)cim-client 真正面向用戶的客戶端;啓動以後會自動鏈接 IM 服務器即可以在控制檯收發消息了。
更多使用介紹能夠參考快速啓動。
接下來各章將重點看看具體的詳細設計實現,好比羣聊、私聊消息如何流轉;IM 服務端負載均衡;服務如何註冊發現等等。
先來看看服務端:主要是實現客戶端上下線、消息下發等功能。
首先是服務啓動:
因爲是在 SpringBoot 中搭建的,因此在應用啓動時須要啓動 Netty 服務。
從 pipline 中能夠看出使用了 Protobuf 的編解碼(具體報文在客戶端中分析,相關知識請見:《Protobuf通訊協議詳解:代碼演示、詳細原理介紹等》)。
須要知足 IM 服務端的水平擴展需求,因此 cim-server 是須要將自身數據發佈到註冊中心的。這裏參考以前分享的《搞定服務註冊與發現》有具體介紹。
因此在應用啓動成功後須要將自身數據註冊到 Zookeeper 中:
最主要的目的就是將當前應用的 ip + cim-server-port+ http-port 註冊上去:
上圖是我在演示環境中註冊的兩個 cim-server 實例(因爲在一臺服務器,因此只是端口不一樣)。這樣在客戶端(監聽這個 Zookeeper 節點)就能實時的知道目前可用的服務信息。
當客戶端請求 cim-forward-route 中的登陸接口(詳見下文)作完業務驗證(就至關於平常登陸其餘網站同樣)以後,客戶端會向服務端發起一個長鏈接。
如以前的流程所示:
這時客戶端會發送一個特殊報文,代表當前是登陸信息。服務端收到後就須要將該客戶端的 userID 和當前 Channel 通道關係保存起來。
同時也緩存了用戶的信息,也就是 userID 和 用戶名。
當客戶端斷線後也須要將剛纔緩存的信息清除掉。
同時也須要調用 route 接口清除相關信息(具體接口看下文)。
從架構圖中能夠看出,路由層是很是重要的一環;它提供了一系列的 HTTP 服務承接了客戶端和服務端。
目前主要是如下幾個接口。
因爲每個客戶端都是須要登陸才能使用的,因此第一步天然是註冊。
這裏就設計的比較簡單,直接利用 Redis 來存儲用戶信息;用戶信息也只有 ID 和 userName 而已。只是爲了方便查詢在 Redis 中的 KV 又反過來存儲了一份 VK,這樣 ID 和 userName 都必須惟一。
這裏的登陸和 cim-server 中的登陸不同,具備業務性質:
具體的流程:
1)登陸成功以後須要判斷是不是重複登陸(一個用戶只能運行一個客戶端);
2)登陸成功後須要從 Zookeeper 中獲取服務列表(cim-server)並根據某種算法選擇一臺服務返回給客戶端;
3)登陸成功以後還須要保存路由信息,也就是當前用戶分配的服務實例保存到 Redis 中。
爲了實現只能一個用戶登陸,使用了 Redis 中的 set 來保存登陸信息;利用 userID 做爲 key ,重複的登陸就會寫入失敗。
相似於 Java 中的 HashSet,只能去重保存。
獲取一臺可用的路由實例也比較簡單:
1)先從 Zookeeper 獲取全部的服務實例作一個內部緩存;
2)輪詢選擇一臺服務器(目前只有這一種算法,後續會新增)。
固然要獲取 Zookeeper 中的服務實例前天然是須要監聽 cim-server 以前註冊上去的那個節點。
具體代碼以下:
也是在應用啓動以後監聽 Zookeeper 中的路由節點,一旦發生變化就會更新內部緩存。這裏使用的是 Guava 的 cache,它基於 ConcurrentHashMap,因此能夠保證清除、新增緩存的原子性。
這是一個真正發消息的接口,實現的效果就是其中一個客戶端發消息,其他全部客戶端都能收到!流程確定是客戶端發送一條消息到服務端,服務端收到後在上文介紹的 SessionSocketHolder 中遍歷全部 Channel(通道)而後下發消息便可。服務端是單機倒也能夠,但如今是集羣設計。因此全部的客戶端會根據以前的輪詢算法分配到不一樣的 cim-server 實例中。
所以就須要路由層來發揮做用了。
路由接口收到消息後首先遍歷出全部的客戶端和服務實例的關係。
路由關係在 Redis 中的存放以下:
因爲 Redis 單線程的特質,當數據量大時;一旦使用 keys 匹配全部 cim-route:* 數據,會致使 Redis 不能處理其餘請求。因此這裏改成使用 scan 命令來遍歷全部的 cim-route:*。
接着會挨個調用每一個客戶端所在的服務端的 HTTP 接口用於推送消息。
在 cim-server 中的實現以下:
cim-server 收到消息後會在內部緩存中查詢該 userID 的通道,接着只須要發消息便可。
這是一個輔助接口,能夠查詢出當前在線用戶信息。
實現也很簡單,也就是查詢以前保存 」用戶登陸狀態的那個去重 set 「便可。
之因此說獲取在線用戶是一個輔助接口,其實就是用於輔助私聊使用的。通常咱們使用私聊的前提確定得知道當前哪些用戶在線,接着你纔會知道你要和誰進行私聊。
相似於這樣:
在咱們這個場景中,私聊的前提就是須要得到在線用戶的 userID:
因此私聊接口在收到消息後須要查詢到接收者所在的 cim-server 實例信息,後續的步驟就和羣聊一致了。調用接收者所在實例的 HTTP 接口下發信息。只是羣聊是遍歷全部的在線用戶,私聊只發送一個的區別。
一旦客戶端下線,咱們就須要將以前存放在 Redis 中的一些信息刪除掉(路由信息、登陸狀態)。
客戶端中的一些邏輯其實在上文已經談到一些了。
第一步也就是登陸,須要在啓動時調用 route 的登陸接口,得到 cim-server 信息再建立鏈接。
登陸過程當中 route 接口會判斷是否爲重複登陸,重複登陸則會直接退出程序。
接下來是利用 route 接口返回的 cim-server 實例信息(ip+port)建立鏈接。最後一步就是發送一個登陸標誌的信息到服務端,讓它保持客戶端和 Channel 的關係。
上文提到的一些登陸報文、真正的消息報文這些其實都是在咱們自定義協議中能夠區別出來的。因爲是使用 Google Protocol Buffer 編解碼,因此先看看原始格式。
其實這個協議中目前一共就三個字段:
1)requestId 能夠理解爲 userId;
2)reqMsg 就是真正的消息;
3)type 也就是上文提到的消息類別。
目前主要是三種類型,分別對應不一樣的業務:
爲了保持客戶端和服務端的鏈接,每隔一段時間沒有發送消息都須要自動的發送心跳。
目前的策略是每隔一分鐘就是發送一個心跳包到服務端:
這樣服務端每隔一分鐘沒有收到業務消息時就會收到 ping 的心跳包:
客戶端也內置了一些基本命令來方便使用。
好比輸入 :q 就會退出客戶端,同時會關閉一些系統資源。
當輸入 :olu(onlineUser 的簡寫)就會去調用 route 的獲取全部在線用戶接口。
羣聊的使用很是簡單,只須要在控制檯輸入消息回車便可。這時會去調用 route 的羣聊接口。
私聊也是同理,但前提是須要觸發關鍵字;使用 userId;;消息內容 這樣的格式纔會給某個用戶發送消息,因此通常都須要先使用
lu 命令獲取因此在線用戶才方便使用。
爲了知足一些定製需求,好比消息須要保存之類的。因此在客戶端收到消息以後會回調一個接口,在這個接口中能夠自定義實現。
所以先建立了一個 caller 的 bean,這個 bean 中包含了一個 CustomMsgHandleListener 接口,須要自行處理只須要實現此接口便可。
因爲我本身不怎麼會寫界面,但保不許有其餘大牛會寫。因此客戶端中的羣聊、私聊、獲取在線用戶、消息回調等業務(以及以後的業務)都是以接口形式提供。
也方便後面作頁面集成,只須要調這些接口就好了;具體實現不用怎麼關心。
cim 目前只是初版,BUG 多,功能少(只拉了幾個羣友作了測試);不事後續還會接着完善,至少這一版會給那些沒有相關經驗的朋友帶來一些思路。
後續計劃:
[1] 有關IM代碼實踐的文章:
《自已開發IM有那麼難嗎?手把手教你自擼一個Andriod版簡易IM (有源碼)》
《一種Android端IM智能心跳算法的設計與實現探討(含樣例代碼)》
《手把手教你用Netty實現網絡通訊程序的心跳機制、斷線重連機制》
《詳解Netty的安全性:原理介紹、代碼演示(上篇)》
《詳解Netty的安全性:原理介紹、代碼演示(下篇)》
《微信本地數據庫破解版(含iOS、Android),僅供學習研究 [附件下載]》
《Java NIO基礎視頻教程、MINA視頻教程、Netty快速入門視頻 [有源碼]》
《輕量級即時通信框架MobileIMSDK的iOS源碼(開源版)[附件下載]》
《開源IM工程「蘑菇街TeamTalk」2015年5月前未刪減版完整代碼 [附件下載]》
《微信本地數據庫破解版(含iOS、Android),僅供學習研究 [附件下載]》
《NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示 [附件下載]》
《NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示 [附件下載]》
《NIO框架入門(三):iOS與MINA二、Netty4的跨平臺UDP雙向通訊實戰 [附件下載]》
《NIO框架入門(四):Android與MINA二、Netty4的跨平臺UDP雙向通訊實戰 [附件下載]》
《用於IM中圖片壓縮的Android工具類源碼,效果可媲美微信 [附件下載]》
《高仿Android版手機QQ可拖拽未讀數小氣泡源碼 [附件下載]》
《一個WebSocket實時聊天室Demo:基於node.js+socket.io [附件下載]》
《Android聊天界面源碼:實現了聊天氣泡、表情圖標(可翻頁) [附件下載]》
《高仿Android版手機QQ首頁側滑菜單源碼 [附件下載]》
《開源libco庫:單機千萬鏈接、支撐微信8億用戶的後臺框架基石 [源碼下載]》
《分享java AMR音頻文件合併源碼,全網最全》
《微信團隊原創Android資源混淆工具:AndResGuard [有源碼]》
《一個基於MQTT通訊協議的完整Android推送Demo [附件下載]》
《Android版高仿微信聊天界面源碼 [附件下載]》
《高仿手機QQ的Android版鎖屏聊天消息提醒功能 [附件下載]》
《高仿iOS版手機QQ錄音及振幅動畫完整實現 [源碼下載]》
《Android端社交應用中的評論和回覆功能實戰分享[圖文+源碼]》
《Android端IM應用中的@人功能實現:仿微博、QQ、微信,零入侵、高可擴展[圖文+源碼]》
《仿微信的IM聊天時間顯示格式(含iOS/Android/Web實現)[圖文+源碼]》
《Android版仿微信朋友圈圖片拖拽返回效果 [源碼下載]》
《適合新手:從零開發一個IM服務端(基於Netty,有完整源碼)》
《拉起鍵盤就是幹:跟我一塊兒徒手擼一套分佈式IM系統》
>> 更多同類文章 ……
[2] 有關IM架構設計的文章:
《淺談IM系統的架構設計》
《簡述移動端IM開發的那些坑:架構設計、通訊協議和客戶端》
《一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)》
《一套原創分佈式即時通信(IM)系統理論架構方案》
《從零到卓越:京東客服即時通信系統的技術架構演進歷程》
《蘑菇街即時通信/IM服務器開發之架構選擇》
《騰訊QQ1.4億在線用戶的技術挑戰和架構演進之路PPT》
《微信後臺基於時間序的海量數據冷熱分級架構設計實踐》
《微信技術總監談架構:微信之道——大道至簡(演講全文)》
《如何解讀《微信技術總監談架構:微信之道——大道至簡》》
《快速裂變:見證微信強大後臺架構從0到1的演進歷程(一)》
《17年的實踐:騰訊海量產品的技術方法論》
《移動端IM中大規模羣消息的推送如何保證效率、實時性?》
《現代IM系統中聊天消息的同步和存儲方案探討》
《IM開發基礎知識補課(二):如何設計大量圖片文件的服務端存儲架構?》
《IM開發基礎知識補課(三):快速理解服務端數據庫讀寫分離原理及實踐建議》
《IM開發基礎知識補課(四):正確理解HTTP短鏈接中的Cookie、Session和Token》
《WhatsApp技術實踐分享:32人工程團隊創造的技術神話》
《微信朋友圈千億訪問量背後的技術挑戰和實踐總結》
《王者榮耀2億用戶量的背後:產品定位、技術架構、網絡方案等》
《IM系統的MQ消息中間件選型:Kafka仍是RabbitMQ?》
《騰訊資深架構師乾貨總結:一文讀懂大型分佈式系統設計的方方面面》
《以微博類應用場景爲例,總結海量社交系統的架構設計步驟》
《快速理解高性能HTTP服務端的負載均衡技術原理》
《子彈短信光鮮的背後:網易雲信首席架構師分享億級IM平臺的技術實踐》
《知乎技術分享:從單機到2000萬QPS併發的Redis高性能緩存實踐之路》
《IM開發基礎知識補課(五):通俗易懂,正確理解並用好MQ消息隊列》
《微信技術分享:微信的海量IM聊天消息序列號生成實踐(算法原理篇)》
《微信技術分享:微信的海量IM聊天消息序列號生成實踐(容災方案篇)》
《新手入門:零基礎理解大型分佈式架構的演進歷史、技術原理、最佳實踐》
《一套高可用、易伸縮、高併發的IM羣聊、單聊架構方案設計實踐》
《阿里技術分享:深度揭祕阿里數據庫技術方案的10年變遷史》
《阿里技術分享:阿里自研金融級數據庫OceanBase的艱辛成長之路》
《社交軟件紅包技術解密(一):全面解密QQ紅包技術方案——架構、技術實現等》
《社交軟件紅包技術解密(二):解密微信搖一搖紅包從0到1的技術演進》
《社交軟件紅包技術解密(三):微信搖一搖紅包雨背後的技術細節》
《社交軟件紅包技術解密(四):微信紅包系統是如何應對高併發的》
《社交軟件紅包技術解密(五):微信紅包系統是如何實現高可用性的》
《社交軟件紅包技術解密(六):微信紅包系統的存儲層架構演進實踐》
《社交軟件紅包技術解密(七):支付寶紅包的海量高併發技術實踐》
《社交軟件紅包技術解密(八):全面解密微博紅包技術方案》
《社交軟件紅包技術解密(九):談談手Q紅包的功能邏輯、容災、運維、架構等》
《即時通信新手入門:一文讀懂什麼是Nginx?它可否實現IM的負載均衡?》
《即時通信新手入門:快速理解RPC技術——基本概念、原理和用途》
《多維度對比5款主流分佈式MQ消息隊列,媽媽不再擔憂個人技術選型了》
《從游擊隊到正規軍:馬蜂窩旅遊網的IM系統架構演進之路》
《IM開發基礎知識補課(六):數據庫用NoSQL仍是SQL?讀這篇就夠了!》
>> 更多同類文章 ……
(本文同步發佈於:http://www.52im.net/thread-2775-1-1.html)