零七嫌、目錄#
1液样、概述####
1-1殴泰、業(yè)務(wù)場(chǎng)景
1-2芒涡、整體架構(gòu)
2、IM通道詳細(xì)分析####
2-1物延、整體實(shí)現(xiàn)
2-2宣旱、通信協(xié)議
2-3、DeviceId和NodeId生成方法
2-4叛薯、消息流轉(zhuǎn)過(guò)程
2-5浑吟、對(duì)點(diǎn)對(duì)點(diǎn)聊天的支持
3笙纤、消息推送與離線存儲(chǔ)(通常方案)####
3-1、消息持久化
3-2组力、消息在線推送與送達(dá)確認(rèn)
3-3省容、未送達(dá)消息離線拉取
4、消息推送與離線存儲(chǔ)(有贊客服系統(tǒng))####
4-1燎字、消息持久化
4-2腥椒、未讀數(shù)標(biāo)識(shí)與已讀確認(rèn)
4-3、方案優(yōu)缺點(diǎn)
一轩触、概述#
1-1寞酿、業(yè)務(wù)場(chǎng)景####
客服系統(tǒng)家夺,為有贊店鋪和買(mǎi)家之間提供消息溝通渠道脱柱。
- 每個(gè)有贊店鋪有自己的唯一ID標(biāo)識(shí)——kdtId;
- 店鋪的每個(gè)買(mǎi)家將產(chǎn)生一個(gè)會(huì)話用于消息溝通,每個(gè)會(huì)話有唯一ID標(biāo)識(shí)——conversationId拉馋,會(huì)話由“店鋪+買(mǎi)家”唯一確定;
- 一個(gè)店鋪中可以有多個(gè)客服人員榨为;
- 每個(gè)會(huì)話中包含買(mǎi)家和一個(gè)或多個(gè)客服人員;
- 會(huì)話中任意成員發(fā)出的消息將被會(huì)話中所有成員看到煌茴;
- 目前消息通道包含:與有贊客服系統(tǒng)直連的店鋪客服随闺,與有贊客服系統(tǒng)直連的店鋪買(mǎi)家,通過(guò)微信公眾號(hào)聯(lián)系店鋪的微信買(mǎi)家蔓腐。
1-2矩乐、整體架構(gòu)####
圖中:
- 進(jìn)入系統(tǒng)的消息帶有from和to標(biāo)識(shí)。from代表發(fā)送消息的成員回论,由sender_uid唯一確定散罕;to代表接收消息的店鋪,由kdtId最終轉(zhuǎn)換為conversation_id唯一確定傀蓉;
- 系統(tǒng)推送給IM客戶端的消息包含以下信息:消息所屬會(huì)話(conversation_id,show_nickname,show_avatar)欧漱,消息發(fā)送者信息(sender_uid,sender_nickname,sender_avatar),消息具體內(nèi)容葬燎;
- 系統(tǒng)推送給微信的消息僅有客服給買(mǎi)家發(fā)消息的場(chǎng)景误甚,通過(guò)調(diào)用微信API完成:發(fā)送消息的店鋪公眾號(hào)(sender_openId),接收消息的微信粉絲(receiver_openId)谱净,消息具體內(nèi)容窑邦;
- sender_uid本身包含消息發(fā)送者類型(微信粉絲、IM粉絲壕探、客服)冈钦,Logic層根據(jù)該類型進(jìn)行相應(yīng)的業(yè)務(wù)處理(對(duì)粉絲消息執(zhí)行客服調(diào)度邏輯,對(duì)客服消息執(zhí)行接待及統(tǒng)計(jì)邏輯)浩蓉;
- recv_uid本身包含消息接收者類型(微信粉絲派继,IM粉絲宾袜,客服),Channel層根據(jù)其類型區(qū)分通道完成消息推送驾窟;
1)Channel層:通道層
負(fù)責(zé)系統(tǒng)與外部通道(socket連接庆猫,微信)的直接通信交互,消息的統(tǒng)一格式绅络、推送月培、接收。
- IM設(shè)備連接管理恩急;
- IM通道消息路由推送杉畜;
- 微信通道消息格式統(tǒng)一轉(zhuǎn)換;
- 微信通道消息推送衷恭;
2)Msg-Bus:消息總線
系統(tǒng)Channel層與Logic層的通信此叠,消息流轉(zhuǎn)的樞紐。
利用消息總線進(jìn)行系統(tǒng)解耦随珠,QoS灭袁。
3)Logic:業(yè)務(wù)層
客服系統(tǒng)具體業(yè)務(wù)實(shí)現(xiàn)。
- 消息離線存儲(chǔ)窗看;
- 消息業(yè)務(wù)維度拆分茸歧;
- 客服調(diào)度業(yè)務(wù)具體實(shí)現(xiàn);
二显沈、IM通道詳細(xì)分析#
2-1软瞎、整體實(shí)現(xiàn)####
1)Connector:socket連接層
職責(zé)包括:
- IM設(shè)備的連接管理;
- 用戶設(shè)備的注冊(cè)登錄拉讯;
- 用戶設(shè)備維度的消息推送涤浇;
2)Channel:通道層
職責(zé)包括:
- 用戶維度的消息路由;
- 消息流轉(zhuǎn)格式轉(zhuǎn)換遂唧;
3)集群結(jié)構(gòu)
- Channel節(jié)點(diǎn)向zk注冊(cè)自己的信息芙代;
- Connector節(jié)點(diǎn)通過(guò)zk獲取Channel節(jié)點(diǎn)的信息并與其連接,每個(gè)Connector節(jié)點(diǎn)跟集群中所有Channel節(jié)點(diǎn)建立socket連接進(jìn)行通信盖彭;
- 外部IM設(shè)備與Connector節(jié)點(diǎn)直接建立socket連接進(jìn)行通信纹烹;
- 每個(gè)Connector節(jié)點(diǎn)本地維護(hù)自己管理的“設(shè)備DeviceId<-->連接信息Session”的關(guān)系;
- 全局維護(hù)"IM設(shè)備<-->Connector節(jié)點(diǎn)"關(guān)系表召边,用于記錄設(shè)備登錄情況铺呵;
- 全局維護(hù)"用戶<-->設(shè)備連接"路由表,用于用戶維度的消息推送隧熙;
2-2片挂、通信協(xié)議####
1)外部IM設(shè)備與Connector節(jié)點(diǎn)間采用websocket協(xié)議進(jìn)行通信
2)Connector節(jié)點(diǎn)與Channel節(jié)點(diǎn)之間采用私有定制協(xié)議進(jìn)行通信,具體協(xié)議格式及編解碼過(guò)程略。
2-3音念、DeviceId和NodeId生成方法####
1)DeviceId生成方法
DeviceId用于唯一標(biāo)識(shí)一個(gè)用戶登錄到系統(tǒng)的連接設(shè)備沪饺。
使用64位(即java中的long類型)表示一個(gè)DeviceId,其生成方法參考snowflake分布式ID生成法闷愤。
[0]空缺
[1 - 3]節(jié)點(diǎn)類型
- 000 -> 外部IM客戶端
- 001 -> Connector節(jié)點(diǎn)
- 010 -> Logic節(jié)點(diǎn)
[4 - 15]集群號(hào)整葡,空缺不用
[16 - 17]Device類型,目前僅有[00]一種默認(rèn)類型讥脐,留作后續(xù)擴(kuò)展
[18 - 47]時(shí)間戳
[48 - 63]16位自增ID
2)NodeId生成方法
NodeId用于標(biāo)識(shí)系統(tǒng)中一個(gè)服務(wù)節(jié)點(diǎn)遭居,作為系統(tǒng)集群節(jié)點(diǎn)內(nèi)部通信的唯一標(biāo)識(shí)。
使用64位(即java中的long類型)表示一個(gè)NodeId旬渠,其生成方法參考snowflake分布式ID生成法俱萍。
[0]空缺
[1 - 3]節(jié)點(diǎn)類型
- 000 -> 外部IM客戶端
- 001 -> Connector節(jié)點(diǎn)
- 010 -> Logic節(jié)點(diǎn)
[4 - 15]集群號(hào),每個(gè)節(jié)點(diǎn)在集群中有個(gè)唯一節(jié)點(diǎn)號(hào)告丢,目前使用節(jié)點(diǎn)IP后4位
[16 - 47]32位節(jié)點(diǎn)IP信息
[48 - 63]16位節(jié)點(diǎn)Port信息
2-3枪蘑、用戶設(shè)備登錄/注冊(cè)過(guò)程&連接信息綁定####
1)幾個(gè)關(guān)鍵存儲(chǔ)
2)用戶設(shè)備登錄/注冊(cè)
a、客戶端建連
- 客戶端發(fā)起連接到Connector節(jié)點(diǎn)芋齿,Connector服務(wù)端生成連接上下文Session腥寇,并作為Attachment綁定到對(duì)應(yīng)Channel上;
b觅捆、用戶設(shè)備登錄/注冊(cè)
- *注:userId為有贊統(tǒng)一賬戶體系,因此使用統(tǒng)一服務(wù)不做獨(dú)立存儲(chǔ)麻敌;
- 客戶端發(fā)起用戶設(shè)備登錄請(qǐng)求栅炒,攜帶userId和deviceType信息;
- Connector節(jié)點(diǎn)處理登錄請(qǐng)求术羔,由userId和deviceType生成hashcode(目前是直接拼接)作為查詢鍵赢赊,查詢"DeviceInfo表"對(duì)應(yīng)設(shè)備是否已登錄/注冊(cè)過(guò)。若注冊(cè)過(guò)直接拿到對(duì)應(yīng)DeviceId级历,若沒(méi)注冊(cè)過(guò)則新生成DeviceId并存入"DeviceInfo表"释移;
- 將DeviceId填充入連接上下文Session的index字段;
- Connector節(jié)點(diǎn)本地存儲(chǔ)"DeviceId<-->Session映射表"寥殖;
- 全局存儲(chǔ)"設(shè)備連接映射表"和"用戶設(shè)備路由表"玩讳;
2-4、消息流轉(zhuǎn)過(guò)程####
客戶端建連&用戶設(shè)備登錄/注冊(cè)過(guò)程嚼贡,由圖文“綠色”過(guò)程描述熏纯。
客戶端請(qǐng)求&服務(wù)端響應(yīng)過(guò)程,由圖中“粉紫色”過(guò)程描述粤策。
服務(wù)端推送消息到客戶端樟澜,由圖中“藍(lán)色”過(guò)程描述。
2-5、對(duì)點(diǎn)對(duì)點(diǎn)聊天的支持####
1)目前業(yè)務(wù)現(xiàn)狀
目前客服系統(tǒng)業(yè)務(wù)場(chǎng)景僅存在買(mǎi)家與店鋪溝通的情況秩贰,即以“買(mǎi)家+店鋪”作為會(huì)話維度(由conversationId唯一標(biāo)識(shí))霹俺。
消息接收維度均為“會(huì)話”,即會(huì)話內(nèi)某成員發(fā)送一條消息會(huì)被推送給會(huì)話內(nèi)所有其他成員毒费。
消息離線存儲(chǔ)也以會(huì)話維度進(jìn)行存儲(chǔ)及查詢吭服。
2)點(diǎn)對(duì)點(diǎn)支持
若要支持點(diǎn)對(duì)點(diǎn)聊天(客服間聊天),只需將消息接收維度擴(kuò)展為“用戶”蝗罗。
消息離線存儲(chǔ)也需單獨(dú)實(shí)現(xiàn)為“消息接收者——消息發(fā)送者”維度進(jìn)行存儲(chǔ)及查詢艇棕。
三、消息推送與離線存儲(chǔ)(通常方案)#
3-1串塑、消息持久化####
1)職責(zé)劃分
a沼琉、用戶各會(huì)話消息的存儲(chǔ)與歷史消息拉取由客戶端本地存儲(chǔ)實(shí)現(xiàn);
b桩匪、用戶各會(huì)話的未讀消息數(shù)紅點(diǎn)提示由客戶端本地累加記錄打瘪;
c、服務(wù)端保證對(duì)建連客戶端消息的實(shí)時(shí)推送傻昙;
d闺骚、服務(wù)端提供未送達(dá)消息拉取接口,對(duì)因客戶端不在線或因網(wǎng)絡(luò)異常丟失的消息妆档,當(dāng)客戶端下次建連登錄時(shí)通過(guò)調(diào)用拉取接口獲取未送達(dá)消息僻爽;
2)消息ID
a、消息量不大時(shí)贾惦,可通過(guò)集中發(fā)號(hào)器對(duì)每個(gè)會(huì)話維度的消息生成連續(xù)自增消息ID胸梆,并以“會(huì)話+消息ID”全局唯一標(biāo)識(shí)一條消息。
b须板、消息量大的情況下碰镜,集中發(fā)號(hào)器將存在性能瓶頸。此時(shí)可通過(guò)snowflake方法分布式生成全局唯一消息ID习瑰,且消息ID趨勢(shì)有序绪颖。
3)消息存儲(chǔ)維度
a、對(duì)一般點(diǎn)對(duì)點(diǎn)單聊場(chǎng)景甜奄,消息以“消息接收者”作為“查詢主鍵”進(jìn)行存儲(chǔ)與查詢柠横。
b、對(duì)特殊業(yè)務(wù)場(chǎng)景贺嫂,需明確定義會(huì)話維度滓鸠,并以“會(huì)話”作為“查詢主鍵”進(jìn)行存儲(chǔ)與查詢。
3-2第喳、消息在線推送與送達(dá)確認(rèn)####
3-3糜俗、未送達(dá)消息離線拉取####
由于im-server對(duì)每條消息維護(hù)“送達(dá)”狀態(tài),配合“未送達(dá)消息離線拉取接口”可保證消息100%理論上不丟失。
四悠抹、消息推送與離線存儲(chǔ)(有贊客服系統(tǒng))#
4-1珠月、消息持久化####
1)職責(zé)劃分
由于系統(tǒng)客戶端大部分為web端,并不擅長(zhǎng)做本地存儲(chǔ)楔敌,因此持久化存儲(chǔ)部分由服務(wù)端完成啤挎。
由于會(huì)話內(nèi)消息Id連續(xù)遞增,服務(wù)端對(duì)每個(gè)會(huì)話采用游標(biāo)的方式記錄最新消息位置及各用戶已讀消息位置卵凑。
a庆聘、會(huì)話消息的存儲(chǔ)與歷史消息拉取由服務(wù)端實(shí)現(xiàn);
b勺卢、用戶在各會(huì)話的未讀消息游標(biāo)由服務(wù)端存儲(chǔ)伙判,客戶端調(diào)用服務(wù)端接口更新用戶在某會(huì)話內(nèi)已讀消息游標(biāo);
c黑忱、服務(wù)端保證對(duì)建連客戶端消息的實(shí)時(shí)推送宴抚;
d、服務(wù)端提供歷史消息拉取接口甫煞,由客戶端調(diào)用分頁(yè)拉取會(huì)話內(nèi)歷史消息菇曲;
2)消息ID
通過(guò)集中發(fā)號(hào)器對(duì)每個(gè)會(huì)話維度的消息生成連續(xù)自增消息ID,并以“會(huì)話+消息ID”全局唯一標(biāo)識(shí)一條消息抚吠。
集中發(fā)號(hào)器目前使用aerospike實(shí)現(xiàn)常潮。
3)消息存儲(chǔ)維度
以會(huì)話(conversationId)作為“查詢主鍵”進(jìn)行消息存儲(chǔ)與查詢。
4-2埃跷、未讀數(shù)標(biāo)識(shí)與已讀確認(rèn)####
因?yàn)橄到y(tǒng)客戶端不進(jìn)行消息持久存儲(chǔ)蕊玷,因此系統(tǒng)將維護(hù)消息的“已讀狀態(tài)”,而非“送達(dá)狀態(tài)”弥雹。
a、系統(tǒng)為每個(gè)會(huì)話記錄當(dāng)前最大消息游標(biāo)cursor延届。
b剪勿、系統(tǒng)為會(huì)話內(nèi)每個(gè)成員記錄其在會(huì)話中當(dāng)前已讀的最大消息游標(biāo)。
c方庭、用戶客戶端登錄拉取會(huì)話列表時(shí)厕吉,后端系統(tǒng)會(huì)同時(shí)返回會(huì)話列表中每個(gè)會(huì)話的用戶未讀消息個(gè)數(shù)。
d械念、用戶客戶端在會(huì)話中讀取了消息時(shí)头朱,需給予后端系統(tǒng)反饋,后端系統(tǒng)會(huì)更新用戶在對(duì)應(yīng)會(huì)話中的已讀游標(biāo)龄减。
4-3项钮、方案優(yōu)缺點(diǎn)####
優(yōu):方案根據(jù)系統(tǒng)業(yè)務(wù)特性使用“用戶+店鋪”作為會(huì)話維度存儲(chǔ)消息,會(huì)話內(nèi)消息由集中發(fā)號(hào)器生成連續(xù)自增ID。使得系統(tǒng)實(shí)現(xiàn)復(fù)雜度降低烁巫,歷史消息分頁(yè)拉取及未讀消息數(shù)功能實(shí)現(xiàn)方便署隘。
缺:消息存儲(chǔ)維度與業(yè)務(wù)緊密關(guān)聯(lián),不便于系統(tǒng)通用擴(kuò)展亚隙。
缺:集中發(fā)號(hào)器產(chǎn)生“會(huì)話內(nèi)連續(xù)消息ID”的方式存在性能瓶頸風(fēng)險(xiǎn)磁餐。