需求分析
技術(shù)需求分為分布式架構(gòu)技術(shù)需求和IM技術(shù)需求
高可用:IM server花式宕機(jī)、分布式Job
高性能:單節(jié)點(diǎn)至少10萬級長連接(c100k)和毫秒級響應(yīng)速度
擴(kuò)展性:可以自由擴(kuò)展計算和存儲能力
監(jiān)控:IM服務(wù)具備自監(jiān)控能力覆醇,降低對外監(jiān)控服務(wù)的依賴
易運(yùn)維
統(tǒng)一的websocket技術(shù)選型:兼容局限于7層協(xié)議web終端永脓,7層相對4層的性能損耗也不是很大常摧。
在線用戶狀態(tài):在線終端與IM server節(jié)點(diǎn)的websocket長連接對應(yīng)關(guān)系的狀態(tài)維護(hù)
圖片壓縮技術(shù)
消息持久化存儲
消息序列化和反序列化
im管理后臺
消息可靠性落午、即時性:不丟消息肚豺、毫秒觸達(dá)详炬。
通信加密
消息存儲加密
文件發(fā)送和存儲
“正在輸入”狀態(tài)
“消息撤回”
“忙碌”等狀態(tài)
群組
用戶關(guān)系(可見性)
消息類型(文字呛谜、音頻隐岛、視頻瓷翻、圖片齐帚、地理位置对妄、自定義類型)
消息評論(類似飛書)
消息待辦
歷史消息
聊天室
多終端同時登陸
音視頻通話
企業(yè)im組織架構(gòu)管理
集團(tuán)im多組織隔離
互聯(lián)網(wǎng)im多租戶
多級管理員(系統(tǒng)管理員剪菱、租戶管理員、組織管理員蚓哩、部門管理員)
本地賬密認(rèn)證和第三方賬密認(rèn)證
架構(gòu)分析
語言方面依然是Java8
vip虛擬ip+dns輪詢岸梨,實現(xiàn)負(fù)載器集群的高可用
多NGINX/負(fù)載均衡器(我個人認(rèn)為根本沒必要花錢去使用商業(yè)版的負(fù)載均衡器盛嘿,NGINX集群真的完全完全充足夠用括袒!人傻錢多才去買F5等昂貴的負(fù)載器)
去注冊中心的raft+gossip,這樣可以降低運(yùn)維成本芥炭,因為不再需要維護(hù)一個注冊中心集群(如zookeeper园蝠、etcd等)
分布式j(luò)ob(我們計劃參考Raft+Gossip算法來實現(xiàn)一套分布式調(diào)度任務(wù)彪薛,摒棄使用任何第三方的job調(diào)度器)
chaos monkey(這個名字很有名的善延,搞應(yīng)用架構(gòu)的肯定都知道易遣,有了它豆茫,程序想不健壯都不行了)
高性能
事實上netty被證明單節(jié)點(diǎn)可以穩(wěn)定hold住數(shù)十萬連接揩魂。
業(yè)務(wù)層使用非阻塞的reactive框架來開發(fā)
充分利用非阻塞的線程威力
我們準(zhǔn)備采用我們非常熟悉的rxjava2來作為開發(fā)框架
高性能netty網(wǎng)絡(luò)框架實現(xiàn)http API server
我們計劃基于netty網(wǎng)絡(luò)框架火脉,采用http1.1無狀態(tài)短連接忘分。
?servlet http server(spring mvc、spring boot重斑、單純的servlet容器)
?netty http server 他們之間的性能差異,我這里就不描述了肯骇,懂的人自然懂。
?OAuth2.0?授權(quán)碼模式(Authorization Code)笛丙、密碼模式(password)漾脂、?Client模式(Client Credentials)(OAuth2.0需要redis集中存儲狀態(tài)數(shù)據(jù))
?JWT 一次性授權(quán)票據(jù)、輕應(yīng)用/小程序用完即走場景坦冠,服務(wù)端不需要集中存儲session,是完全無狀態(tài)的判呕。
dao層我們選型使用reactive postgresql
不再使用jdbc這種阻塞業(yè)務(wù)線程直到數(shù)據(jù)庫返回結(jié)果的線程模型,什么mybatis梦抢、hibernate哼蛆、jooq一律不在考慮范圍內(nèi)腮介! 如果不基于jdbc,那么還需要提供一套自有的orm了,我們這邊也有一定的技術(shù)積累可以復(fù)用抵代。
字符串?dāng)?shù)據(jù)的序列化和反序列化,我們暫時使用熟悉的fastjson
即時通訊網(wǎng)絡(luò)協(xié)議,如上文統(tǒng)一使用websocket應(yīng)用層長連接
消息體payload的格式規(guī)范詳見接口規(guī)范文檔
websocket長連接中的消息ack協(xié)議:
我們不是單純地把消息payload丟到長連接上去就完了
我們還需要一套ack機(jī)制來保證消息是已經(jīng)被終端收到晦嵌。
我們還需要一套ack機(jī)制來保終端消息是已經(jīng)被服務(wù)器持久化到數(shù)據(jù)庫中了。
消息去重機(jī)制/協(xié)議:message id協(xié)議。
在確定的IM通信協(xié)議前提下伴挚,我們要提供一套完善的全方位的壓測客戶端,以確定各種規(guī)模的服務(wù)端配置能給出多高的極限并發(fā)量田弥。
擴(kuò)展性
上文提到我們基于raft+gossip去中心化的服務(wù)治理方案,我們的服務(wù)端im-server應(yīng)用擴(kuò)展直接通過增加服務(wù)器增加應(yīng)用實例數(shù)量來實現(xiàn)計算能力擴(kuò)展。
如何克服關(guān)系型數(shù)據(jù)庫存儲的瓶頸問題
使用數(shù)據(jù)庫中件(這個方案由于維護(hù)成本太高而放棄)
使用云廠商的數(shù)據(jù)庫云服務(wù)postgresql(這個視情況而定)
使用分布式關(guān)系型數(shù)據(jù)庫
基于上文提到的reactive-dao的選型,我們選型的是postgresql型數(shù)據(jù)庫
決定采用開源的cockroachdb十绑,是由于它是完整的postgresql數(shù)據(jù)庫協(xié)議的實現(xiàn)晚岭,而且支持完美的寫擴(kuò)展能力,開源和高可用的支持片择,基于postgresql生態(tài),云原生的支持,不依賴某個特定的云廠商硫戈。
這里為什么要提自監(jiān)控呢?主要是因為我們想要實現(xiàn)一款解耦對特定監(jiān)控系統(tǒng)的依賴關(guān)系。 設(shè)計思路是:
實現(xiàn)一套通用監(jiān)控數(shù)據(jù)的metric接口,將IM服務(wù)器的狀態(tài)全部提供出來
任何外部的監(jiān)控系統(tǒng)如果要與之對接琢感,則再實現(xiàn)一個適配器對接二方數(shù)據(jù)接口即可。
如果要降低im集群的維護(hù)難度,原則就是:
盡量避免或少使用外部中間件和多重數(shù)據(jù)庫種類。
我們見過很多系統(tǒng)撤防,為了性能和為了功能豐富无牵,MySQL主從集群克懊、redis集群、MongoDB集群扮念、NGINX輪詢谈为、zookeeper集群粘茄、fastdfs集群吠架、kafka/rabbitMQ集群磺平,等等,一堆一堆地用赊舶。這運(yùn)維的酸爽感舔痪,你們不搞運(yùn)維的體會不了。
你的軟件,它如果是可以一鍵傻瓜運(yùn)行、可實施性特別簡單傻瓜是不是接受度會更高?特別是是在小型企業(yè)內(nèi)部斋枢,以及學(xué)術(shù)技術(shù)研究方面涩赢。
Linux、windows澄步、Mac OS
docker或者更進(jìn)一步kubernetes
IM相關(guān)技術(shù)實現(xiàn)
統(tǒng)一的websocket技術(shù)選型:兼容局限于7層協(xié)議web終端,7層相對4層的性能損耗也不是很大。
技術(shù)選型如上文,我們使用netty來實現(xiàn)websocket server
海量在線用戶終端是與多個IM server建立著長連接的,我們向終端推送消息必須定位到是哪個im server的哪個websocket連接。如果有一個簡單而搞笑的定位算法那就是極好的。一般我們首先想到的是使用redis等緩存技術(shù)去集中存儲這些狀態(tài)。但是,我們是否有更優(yōu)秀的方案呢嗜浮?
hash slot算法
raft一致性算法 步驟如下:
我們通過raft一致性算法得到IM server節(jié)點(diǎn)列表
我們通過hash slot算法可以快速高效地得到用戶對應(yīng)的IM server節(jié)點(diǎn),由于一致性哈希算法的一致性保證,我們可以保證每次得到的都是一致的結(jié)果,即我們可以保證每次定位到100%相同的長連接所在的節(jié)點(diǎn)。
hash slot結(jié)合raft一致性算法撮胧,支持花式宕機(jī)后session長連接動態(tài)調(diào)整到一致性哈希對應(yīng)的落點(diǎn)锻离,節(jié)點(diǎn)越多需要新建的session長連接越少疏虫;同理啤呼,我們擴(kuò)展im計算能力卧秘,新增session節(jié)點(diǎn)后,部分存量session長連接需要斷開并與新節(jié)點(diǎn)建立的長連接數(shù)量越少官扣。
腦裂問題一直是一個經(jīng)典問題翅敌,我只能說你們把機(jī)房基礎(chǔ)網(wǎng)絡(luò)做穩(wěn)了,這是前提惕蹄。
跨機(jī)房集群蚯涮,機(jī)房間網(wǎng)絡(luò)斷了,我們又該怎么整呢卖陵? 0. 首先國內(nèi)就沒有幾家企業(yè)有機(jī)會讓他們的IM達(dá)到跨機(jī)房部署應(yīng)用集群的條件遭顶。如果達(dá)到了,我們繼續(xù)往下看泪蔫。
不知道大家有沒有玩過elasticsearch棒旗?
elasticsearch有一個minimum_master_nodes參數(shù),用來規(guī)避腦裂時被隔離的小集群內(nèi)某個節(jié)點(diǎn)被提升為leader撩荣。所以你們的懂的铣揉,這里不再多說了饶深。
先空著
上文已經(jīng)提到了,我們基于最單純數(shù)據(jù)庫和中間件選型原則逛拱,選用最少的數(shù)量類型的數(shù)據(jù)庫技術(shù): 目前市面上最合適的產(chǎn)品經(jīng)過實踐評估即是cockroachdb敌厘,即小強(qiáng)db,云原生的異地多活分布式關(guān)系型數(shù)據(jù)庫解決方案朽合。 之所以是cockroachdb:
上文已經(jīng)講到過postgresql的reactive驅(qū)動的生態(tài)俱两。
跟tidb一樣,參考Google f1論文理論基礎(chǔ)旁舰,對標(biāo)Google spanner數(shù)據(jù)庫的產(chǎn)品锋华,但是比tidb更簡潔的運(yùn)維+云原生嗡官;(我憑什么說比tidb運(yùn)維更簡單呢箭窜?你們自己玩一下這兩個數(shù)據(jù)庫就知道了。)
我心目中的明星協(xié)議:一致性協(xié)議raft
完全開源
有人說Protobuf通信協(xié)議是最快的序列化和傳輸方案衍腥。但是protobuf不是最通用的序列化方案磺樱。考慮到我們對json的熟悉程度更高于protobuf婆咸,而且基于字符串的序列化性能竹捉,json不比protobuf慢。而IM傳輸?shù)膒ayload不就是文本嘛尚骄?有興趣可以參考這篇關(guān)于protobuf和json性能對比的文章
所以块差,我們固執(zhí)地選擇fastjson作為序列化和反序列化方案
im管理后臺
即后臺界面與管理接口配套的微服務(wù)化方案。說白了就是復(fù)用倔丈,就是將來誰想要復(fù)用我們的局部服務(wù)能力憨闰,我可以把它摳出去給他復(fù)用。
考慮到微服務(wù)化需五,我們需要將im核心進(jìn)行細(xì)粒度的拆分實現(xiàn)鹉动。im核心對應(yīng)的各個功能模塊必然對應(yīng)各個管理接口,管理接口對應(yīng)著不同的管理后臺頁面宏邮。我們的設(shè)計會將這些頁面也進(jìn)行微服務(wù)化分離管理泽示。聰明的您理解得肯定沒錯,我們將會使用多站點(diǎn)方案:
單點(diǎn)登錄蜜氨,身份認(rèn)證
集中權(quán)限管理機(jī)制:權(quán)限注冊中心+權(quán)限單元自治械筛。有興趣的你可以參考阿里云RAM和騰訊云ACM。
有些老牌的后端開發(fā)工程師喜歡用模板框架飒炎,比如很有名的freemarker埋哟,Velocity。但是這些東東真的被證明是拖慢前后端開發(fā)者進(jìn)行協(xié)作開發(fā)的“利器”厌丑。當(dāng)然定欧,你們團(tuán)隊都是后端工程師出身的“全椨婧牵”小伙子的情況除外。
前端技術(shù)選型呢砍鸠,交給我們的前端小伙子去決定吧扩氢,后端模板技術(shù),我們是不會選擇的爷辱。
后端選型
既然是多站點(diǎn)录豺,那么是需要提供單點(diǎn)登錄能力的,這里我們將抽象一層單點(diǎn)登錄客戶端饭弓,去對接統(tǒng)一單點(diǎn)登錄服務(wù)SSO/內(nèi)置的簡單的SSO服務(wù)
將來考慮支持移動B端管理能力双饥,同樣也是需要集成SSO的。
后端API服務(wù)能力弟断,上文已經(jīng)提到咏花,我們使用netty實現(xiàn)API server,什么spring mvc阀趴、springboot都一邊站吧昏翰。
api doc:因為是前后端分離所以簡潔清晰的API文檔是必不可少的,不然你讓你的前端基友讀你的Java源碼來理解API接口該如何調(diào)用嘛刘急?而且API文檔最好是“半自動化”的棚菊,“文檔代碼一致性”的。
IM功能性設(shè)計
發(fā)送方終端 --> 服務(wù)端 --> 數(shù)據(jù)庫 --入庫成功ack--> 發(fā)送方終端?
--> 收方終端 --成功接收ack--> 服務(wù)端 --更新狀態(tài)已發(fā)送--> 數(shù)據(jù)庫?
(是不是感覺這個不清晰叔汁,我們可以)
我們采用經(jīng)典的ack和message id去重機(jī)制
ack保證消息不丟失,關(guān)于ack我們在上文中已經(jīng)簡單地提到過据块,它屬于websocket session會話通信協(xié)議的一部分:
發(fā)送方終端-->服務(wù)端-->持久化成功--ack-->發(fā)送方終端
服務(wù)端--推送-->接收端終端--接收并ack-->服務(wù)端
message id去重機(jī)制保證
服務(wù)端消息僅入庫存儲唯一一條码邻,我們將message id在數(shù)據(jù)庫設(shè)置唯一性約束。
終端接收消息后推送瑰钮,都會根據(jù)id去重冒滩,保證UI只呈現(xiàn)那唯一一條消息。
message id由im-sdk生成(消息推送restful接口除外)
messageId結(jié)構(gòu)規(guī)范定義:版本號 + 發(fā)送方id(比如userId)+ 終端id + 時間戳 + 隨機(jī)數(shù)
消息即時性浪谴,其實就是IM服務(wù)性能可靠保證前提和網(wǎng)絡(luò)通暢的前提
性能保證:上文已經(jīng)提到开睡,監(jiān)控和彈性是保證IM服務(wù)可靠性的關(guān)鍵。
網(wǎng)絡(luò)可靠性:網(wǎng)絡(luò)可靠性還是相對依賴網(wǎng)絡(luò)基礎(chǔ)設(shè)施了苟耻,應(yīng)用層會輔助對應(yīng)用流量進(jìn)行監(jiān)控篇恒,以降低對網(wǎng)絡(luò)監(jiān)控的強(qiáng)依賴。
什么樣的IM才能讓領(lǐng)導(dǎo)用著放心凶杖?
聊天消息在老板自己的機(jī)房里面存著胁艰,別人無權(quán)獲取,也無權(quán)進(jìn)行監(jiān)控,更不談對你的隱私進(jìn)行窺探和大數(shù)據(jù)分析了
聊天的內(nèi)容通過互聯(lián)網(wǎng)傳輸時腾么,別人難以截聽奈梳,即使截聽也難以破解