開源OpenIM:高性能宿稀、可伸縮、易擴展的即時通訊架構(gòu)

本文屬于OpenIM技術(shù)團隊原創(chuàng)祝沸,轉(zhuǎn)載請注明出處越庇,謝謝

網(wǎng)上有很多關(guān)于IM的教程和技術(shù)博文奉狈,有億級用戶的IM架構(gòu),有各種淺談原創(chuàng)自研IM架構(gòu)仁期,也有微信技術(shù)團隊分享的技術(shù)文章,有些開發(fā)者想根據(jù)這些資料自研IM跛蛋。理想很豐滿,現(xiàn)實很骨感问芬,最后做出來的產(chǎn)品很難達(dá)到商用標(biāo)準(zhǔn)。事實上此衅,很多架構(gòu)沒有經(jīng)過海量用戶的考驗,當(dāng)然我們也不會評判某種架構(gòu)的好壞挡鞍,如果開發(fā)者企圖根據(jù)網(wǎng)上教程做出一個商用的IM,可能有點過于樂觀了墨微。本文主要從我個人角度深度剖析100%開源的OpenIM架構(gòu)。當(dāng)然翘县,世界上沒有最完美的架構(gòu),只有最合適的架構(gòu)锈麸,也沒有所謂的通用方案,不同的解決方案都有其優(yōu)缺點忘伞,只有最滿足業(yè)務(wù)的系統(tǒng)才是一個好的系統(tǒng)。而且氓奈,在有限的人力、物力舀奶,綜合考慮時間成本,通常需要做出很多權(quán)衡伪节。我們OpenIM的設(shè)計初衷绩鸣,充分考慮了中小企業(yè)的需求怀大,輕量級部署,同時也支持集群擴展化借,能支持幾萬用戶,也能輕松擴展到上億用戶蓖康,是一個可信賴的開源項目。

IM系統(tǒng)技術(shù)挑戰(zhàn)

可靠性

IM消息系統(tǒng)的可靠性蒜焊,通常就是指消息投遞的可靠性,即我們經(jīng)常聽到的“消息必達(dá)”泳梆,通常用消息的不丟失和不重復(fù)兩個技術(shù)指標(biāo)來表示。確保消息被發(fā)送后优妙,能被接收者收到。由于網(wǎng)絡(luò)環(huán)境的復(fù)雜性套硼,以及用戶在線的不確定性,消息的可靠性(不丟失邪意、不重復(fù))無疑是IM系統(tǒng)的核心指標(biāo),也是IM系統(tǒng)實現(xiàn)中的難點之一雾鬼∶戎欤總體來說呆贿,IM系統(tǒng)的消息“可靠性”嚷兔,通常就是指聊天消息投遞的可靠性(準(zhǔn)確的說做入,這個“消息”是廣義的,因為還存用戶看不見的各種指令和通知竟块,包括但不限于進(jìn)群退群通知、好友添加通知等浪秘,為了方便描述埠况,統(tǒng)稱“消息”)棵癣。

從消息發(fā)送者和接收者用戶行為來講,消息“可靠性”應(yīng)該分為以下幾種情況:

(1)發(fā)送失敗狈谊,對于這種情況IM系統(tǒng)必須要感知到,明確反饋發(fā)送方河劝。如果此消息沒有發(fā)送成功,發(fā)送方可以選擇重試或者稍后再試赎瞎。

(2)發(fā)送成功,如果接收方處在“在線”狀態(tài)务甥,應(yīng)該立即收到此消息。如果接收方處在“離線”狀態(tài)不能收到消息缓呛,一旦上線則立刻收到消息。

(3)消息不能重復(fù)哟绊,用數(shù)學(xué)術(shù)語表示:“有且僅有這條消息”因妙,如果重復(fù)了攀涵,可能表達(dá)的意思就變了∫怨剩總之,一個商用 IM系統(tǒng)怒详,必須包含消息“可靠性”邏輯,才能談基本可用昆烁,這是IM系統(tǒng)最基本也是最核心的邏輯。

有序性(一致性)

IM系統(tǒng)中静尼,特別需要考慮消息時序問題,如果后發(fā)送的消息先顯示鼠渺,可能嚴(yán)重擾亂聊天消息所要表達(dá)的意義,會造成聊天語義不連貫拦盹,引起誤會。消息的時序性掌敬,也稱為消息收發(fā)一致性池磁,主要目標(biāo)是:保證聊天消息的絕對時序奔害。IM系統(tǒng)中消息時序的一致性問題看似簡單地熄,實則是非常有難度的技術(shù)熱點話題之一。為什么會出現(xiàn)時序問題? 1端考、分布式系統(tǒng)的出現(xiàn)導(dǎo)致時序不一致。IM系統(tǒng)模塊眾多却特,接入層、消息邏輯層等裂明、每層都分布式集群化,這些應(yīng)用分布在不同的機器上闽晦,如何保證時序是個難點。2仙蛉、網(wǎng)絡(luò)傳輸延遲導(dǎo)致時序不一致。不同用戶發(fā)送的消息到達(dá)服務(wù)器的延時差異較大荠瘪,給消息時序性帶來挑戰(zhàn)。

消息時序是分布式系統(tǒng)架構(gòu)設(shè)計中非常難的問題哀墓,一個分布式的IM系統(tǒng)必須要解決這個問題,如何高效麸祷、低成本解決這個問題,是我們OpenIM要考慮的方向。

實時性

實時性星瘾,即消息實時到達(dá)接收方,如果用戶在線琳状,則實時可達(dá),如果用戶不在線念逞,則登錄時可達(dá)。由于網(wǎng)絡(luò)波動边翁,以及移動端操作系統(tǒng)對應(yīng)用前后臺切換的管理,如何實現(xiàn)用戶連接管理符匾、消息實時推送,推送失敗的處理方式啊胶,客戶端重連機制,消息如何補齊等焰坪,都是需要IM系統(tǒng)考慮,同時要結(jié)合移動端的特點某饰,兼顧耗電量,網(wǎng)絡(luò)露乏,性能等。由于TCP開發(fā)略微復(fù)雜瘟仿,早期的基于HTTP短輪詢、長輪詢的低效的技術(shù)方案劳较,也無法達(dá)到實時性的要求。

擴展性

一般來說互聯(lián)網(wǎng)系統(tǒng)的擴展性包含多個含義观蜗,我們側(cè)重講解關(guān)于IM消息的擴展性。IM業(yè)務(wù)特性多墓捻,功能豐富,從聊天類型來看,分為:單聊环凿、群聊,聊天室等智听;從消息類型來看,分為:文本到推、圖片、視頻莉测、地理位置、自定義消息等悔雹;從消息功能來看,分為:撤回、在線狀態(tài)梯找、對方正在輸入唆阿、閱后即焚等驯鳖;從通知角度來看,分為:進(jìn)群浅辙、退群、添加好友记舆、驗證好友等各種通知。如何有效支撐泽腮、擴展功能,高效實現(xiàn)诊赊,是考驗IM擴展性的一個方面,也是對系統(tǒng)架構(gòu)設(shè)計能力的考驗碧磅。為了更好地提高數(shù)據(jù)通道對業(yè)務(wù)支撐的擴展性,我們首創(chuàng)了“一切皆消息”的消息模型鲸郊,即通訊雙方產(chǎn)生的所有消息、通知严望,服務(wù)端以消息統(tǒng)一處理,扮演了消息通道的角色像吻,客戶端針對不同消息類型做不同的UI展示峻黍,完美解決了擴展性問題姆涩。

IM系統(tǒng)術(shù)語以及本文檔專有名詞解釋

conversationId:會話Id,會話是指用戶和用戶之間骨饿,以及用戶和群之間,進(jìn)行通訊后產(chǎn)生的關(guān)聯(lián)宏赘。

userId:用戶Id:注冊使用IM的用戶Id,從消息的發(fā)送和接收來看有兩個身份:發(fā)送者和接收者

sendId:消息發(fā)送者Id

receiverId :消息接收者Id

msg:消息是指用戶之間的溝通內(nèi)容察署,一般指用戶主動產(chǎn)生的。同時也包括用戶看不見的各種指令和通知贴汪,包括但不限于進(jìn)群退群通知、好友添加通知等

inbox:用戶收件箱扳埂,給某人發(fā)送消息,實際上是往接收者“信箱”寫入消息阳懂,這個信箱就是收件箱

seq:用戶收件箱中消息序列號,分為local seq希太,和server seq,前者表示app本地消息seq誊辉,后者表示服務(wù)端消息seq,seq是連續(xù)且遞增的堕澄。

conn:登錄用戶的連接信息邀跃,用于消息推送蛙紫;

MQ:消息隊列,一般用來解決應(yīng)用解耦坑傅,異步消息,流量削峰等問題唁毒,實現(xiàn)高性能,高可用浆西,可伸縮和最終一致性架構(gòu),本文采用kafka組件近零。

OpenIM的誕生

隨著移動互聯(lián)網(wǎng)的蓬勃發(fā)展, IM 作為一種通訊能力久信,已經(jīng)成為互聯(lián)網(wǎng)上的基礎(chǔ)設(shè)施,也是許多 APP 不可或缺的功能入篮。如何讓每一個應(yīng)用都具備IM功能,同時考慮企業(yè)的接入成本潮售、服務(wù)器資源以及最重要的數(shù)據(jù)安全性和私密性酥诽。本人從微信離職后,創(chuàng)辦了開源OpenIM肮帐,是全球首家100%開源、免費項目训枢,并提供IMSDK,覆蓋所有主流開發(fā)平臺忘巧,iOS、Android砚嘴、Flutter涩拙、react native耸采、Windows、Linux虾宇、Unity、web嘱朽、小程序等。

開源IM現(xiàn)狀

github 上 IM 開源項目不少燥翅,但開發(fā)者卻難以使用,主要有幾點原因(1)個人項目居多森书,但近幾年都無人維護(hù),遇到問題無人解決凛膏,企業(yè)商業(yè)化產(chǎn)品不敢冒險使用(2)大部分項目不是 IM 技術(shù)專業(yè)團隊完成的,技術(shù)實力和技術(shù)架構(gòu)存疑猖毫,也沒有經(jīng)過大項目和海量用戶檢驗;(3)只開源服務(wù)端或者客戶端吁断,只開源某一端,需要開發(fā)者實現(xiàn)另外一端仔役,研發(fā)成本同樣不小,另外又兵,開源項目大部分都是以聊天app形式開源,開發(fā)者如何把 IM 集成到自身 app 中沛厨,同樣存在大量的修改和適配成本。(4)部分項目打著開源的旗號逆皮,社區(qū)版免費,但核心功能缺失页屠,商業(yè)版收費粹胯。

云服務(wù)商的弊端

IM 云服務(wù)商提供 IM SDK 和 API ,讓開發(fā)者簡單集成 IM 功能况鸣,當(dāng)然這里也存在明顯的問題(1)成本問題:企業(yè)每年額外支付上萬乃至數(shù)十萬的云服務(wù)費用,從長期來看是個不小的成本镐捧;(2)數(shù)據(jù)隱私問題:企業(yè)的用戶數(shù)據(jù)、聊天記錄等核心數(shù)據(jù)托管在 IM 云服務(wù)商懂酱,如何保證客戶的數(shù)據(jù)隱私和安全性;(3)需求定制問題:IM 需求多樣化誊抛,IM 功能只能由 IM 云服務(wù)商通過 SDK 的形式提供給大家使用,開發(fā)受限拗窃,所有功能都需要封裝成接口;(4)捆綁問題:一旦使用 IM 云服務(wù)随夸,形成捆綁關(guān)系,遷移成本高宾毒,受制于人。

自研的尷尬

IM 是一個看起來門檻很低的項目诈铛,網(wǎng)上有很多所謂的 IM 開發(fā)教程,甚至很多畢業(yè)設(shè)計也是做一個 IM 系統(tǒng)幢竹。由于這個誤區(qū)的存在,很多企業(yè)盲目樂觀組建 3-5 人的 IM 團隊妨退,歷時一年半載芭挽,最后只完成了一個 demo 版本。由于架構(gòu)設(shè)計不合理轻掩,demo 版本存在消息丟失幸乒、系統(tǒng)異常等 bug罕扎,無法達(dá)到商用的要求聚唐。IM系統(tǒng)除了面臨互聯(lián)網(wǎng)業(yè)務(wù)系統(tǒng)本身的挑戰(zhàn)腔召,還存在上文分析的可靠性、時序性臀蛛、擴展性等問題,所以浊仆,自研IM,對于中小企業(yè)來說抡柿,可能是最糟糕的選擇。

OpenIM的整體架構(gòu)


OpenIM分為兩大塊

(一)Open-IM-SDK-Core? 采用golang實現(xiàn)客戶端邏輯备蚓,主要負(fù)責(zé)本地db存儲及更新;斷網(wǎng)重連及管理星著;消息及各種通知回調(diào)。本地消息粗悯、會話等數(shù)據(jù)存儲,通過通知機制完成本地數(shù)據(jù)實時同步样傍,同時兼顧客戶端緩存的作用,有效緩解了服務(wù)端壓力衫哥。另外,golang跨平臺的特性撤逢,使得各移動平臺都能無縫調(diào)用,開發(fā)者只需根據(jù)產(chǎn)品需求編寫UI界面蚊荣,通過回調(diào)機制和SDK完成數(shù)據(jù)交互和通知。

(二)Open-IM-Server 由接入層互例、邏輯層和存儲層組成,好處在于各層能夠依據(jù)業(yè)務(wù)特點專注于自己的事情媳叨,提高系統(tǒng)復(fù)用性关顷,降低業(yè)務(wù)間的耦合武福。

(1)接入層:消息通過 websocket 協(xié)議接入,其他業(yè)務(wù)通過 http/https 協(xié)議提供REST API實現(xiàn)艘儒。消息是高頻及核心功能,通過雙協(xié)議路由界睁,體現(xiàn)了輕重分離的設(shè)計思想。

(2)邏輯層:通過 rpc 實現(xiàn)無狀態(tài)邏輯服務(wù)翻斟,易于平行擴展,模塊通過 MQ 解耦访惜。

(3)存儲層:redis 存儲 token 和 seq;mongodb 存儲離線消息债热,并定時刪除 14 天內(nèi)(可自行配置)數(shù)據(jù);mysql 存儲全量歷史消息以及用戶相關(guān)資料窒篱。數(shù)據(jù)分層存儲,充分利用不同存儲組件的特性墙杯。

(4)Etcd:服務(wù)注冊和發(fā)現(xiàn)、以及分布式配置中心高镐。

消息網(wǎng)關(guān)msg_gateway

消息接入層,采用websocket協(xié)議接入嫉髓,import gorilla具體實現(xiàn),服務(wù)模塊無狀態(tài)算行,柔性伸縮,運維簡單纱意。通過MQ讓業(yè)務(wù)模塊之間解耦鲸阔,消息寫入MQ即表示發(fā)送成功迄委。

(1)負(fù)責(zé)用戶連接管理类少,保持長連接,存儲uid->conn映射關(guān)系硫狞;

(2)負(fù)責(zé)消息接收落地,成功寫入MQ后給客戶端返回成功残吩;

(3)負(fù)責(zé)把消息推送給在線狀態(tài)的接收者;

下圖是客戶端發(fā)送消息流程


消息轉(zhuǎn)發(fā)msg_transfer

消息處理rpc泣侮,作為消費者從MQ中消費(讀取)消息活尊,遞增接收者收件箱seq,關(guān)聯(lián)seq和msg蛹锰,并存儲到mongodb。全量歷史消息無收件箱概念铜犬,消息作為流水記錄落地mysql即可,兩者通過協(xié)程獨立處理翎苫,雙方互不影響。msg作為無狀態(tài)服務(wù)節(jié)點煎谍,如果消息量增加,可以啟動冗余節(jié)點服務(wù)呐粘,加快消息處理流程。

(1)負(fù)責(zé)消費MQ中的消息作岖,作為消費者,實時感知新信息達(dá)到痘儡,并觸發(fā)回調(diào)邏輯;

(2)生成msgId作為全局消息Id;

(3)讀取receiver userId渐尿,并通過redis的incr操作遞增服務(wù)端對應(yīng)的seq;

(4)關(guān)聯(lián)seq和msgid砖茸,并存入以receiver userid為key的mongodb中,作為離線消息凉夯,一般在14天后會刪除;

(5)同時劲够,把消息作為歷史記錄存入mysql中,作為消息備份再沧,或其他用途。

(4)和(5)是兩個獨立的協(xié)程并行執(zhí)行的炒瘸,mysql寫入快慢不會影響mongodb的寫入,這樣既完成了冷熱數(shù)據(jù)分離顷扩,也充分利用了機器資源。

下圖是消息處理入庫流程


消息推送push

msg_transfer完成存儲消息到后隘截,向push發(fā)起消息推送任務(wù),msg_gateway查詢本地userId->conn表婶芭,如果用戶在線則推送給接收者,對于msg_gateway的推送架構(gòu)設(shè)計犀农,做成了“半狀態(tài)”服務(wù),即在節(jié)點本地存儲了用戶連接信息呵哨,作為局部信息,沒有通過redis全局共享孟害。push推送消息時,向所有msg_gateway發(fā)送推送請求挨务,帶來一定的“驚群效應(yīng)”玉组,由于msg_gateway節(jié)點不多果漾,所以影響有限,帶來的好處則是在不影響性能的前提下绒障,msg_gateway設(shè)計和實現(xiàn)簡單捍歪,運維也更簡單。

(1)msg_transfer把消息寫入mongodb后糙臼,發(fā)送push消息推送請求;

(2)push提供rpc推送服務(wù)变逃,通過etcd找到所有注冊的msg_gateway,并發(fā)送推送請求揽乱;

(3)msg_gateway從本節(jié)點內(nèi)存中查詢userId->conn,如果找到conn凰棉,則向客戶端推送消息;

(4)如果消息接收者不在線撒犀,msg_gateway無法推送消息,但客戶端網(wǎng)絡(luò)重連時會及時同步歷史消息或舞,進(jìn)行消息補齊;

下圖是消息實時推送流程:


消息同步及對齊seq

由于網(wǎng)絡(luò)的波動以及負(fù)責(zé)的網(wǎng)絡(luò)環(huán)境映凳,導(dǎo)致消息推送存在不確定性。OpenIM采用local seq和server seq消息對齊魏宽,同時結(jié)合拉取和推送的方式,簡單高效地解決了消息的可靠性問題队询。這里分兩種場景進(jìn)行表述:

(1)客戶端接收推送消息時,比如客戶端收到推送消息的seq為100蚌斩,如果local seq為99,因為seq遞增且連續(xù),所以消息正常顯示即可员魏。如果local seq大于100,說明重復(fù)推送了消息撕阎,拋棄此消息即可。如果local seq小于99虏束,說明中間有歷史消息丟失,拉取(local seq+1, 100)的消息镇匀,進(jìn)行補齊即可;

(2)用戶在登錄汗侵、或者斷網(wǎng)重連時,客戶端會從服務(wù)端拉取最大seq(max seq)晰韵,讀取客戶端本地seq(local seq),如果local seq 小于 max seq宫屠,說明存在歷史消息未同步的情況,調(diào)用接口同步自身收件箱[local seq+1, max seq]的數(shù)據(jù)完成消息對齊浪蹂。

下圖是消息同步流程圖


本文主要簡單闡述了OpenIM的架構(gòu)以及消息流程,讓開發(fā)者對其有初步認(rèn)識坤次,在接下來的文章中,我們會詳細(xì)講解OpenIM服務(wù)端消息架構(gòu)缰猴,OpenIM客戶端架構(gòu),同時會詳細(xì)分析OpenIM如何簡單高效解決消息的可靠性滑绒、實時性闷堡、一致性和擴展性問題杠览。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市纵势,隨后出現(xiàn)的幾起案子管钳,更是在濱河造成了極大的恐慌软舌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佛点,死亡現(xiàn)場離奇詭異,居然都是意外死亡超营,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門糟描,熙熙樓的掌柜王于貴愁眉苦臉地迎上來书妻,“玉大人,你說我怎么就攤上這事躲履。” “怎么了工猜?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長篷帅。 經(jīng)常有香客問我,道長魏身,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任箭昵,我火速辦了婚禮,結(jié)果婚禮上家制,老公的妹妹穿的比我還像新娘。我一直安慰自己颤殴,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布诅病。 她就那樣靜靜地躺著粥烁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪讨阻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天钝吮,我揣著相機與錄音,去河邊找鬼板辽。 笑死,一個胖子當(dāng)著我的面吹牛劲弦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播邑跪,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼画畅!你這毒婦竟也來了轴踱?” 一聲冷哼從身側(cè)響起淫僻,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤嘁傀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后细办,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡笑撞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了茴肥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡瓤狐,死狀恐怖批幌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情荧缘,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布截粗,位于F島的核電站,受9級特大地震影響绸罗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜豆瘫,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望外驱。 院中可真熱鬧,春花似錦略步、人聲如沸定页。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽羡铲。三九已至儡毕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腰湾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工倒槐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留两残,地道東北人人弓。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓滨嘱,卻偏偏與公主長得像吟榴,于是被迫代替她去往敵國和親吩翻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容