netty实现消息中心思路整理

一、需求

需要实现直播间的以下功能:

  • 群发消息(文本、图片、推荐商品)
  • 点对点私发消息(文本、图片、推荐商品)
  • 单个用户禁言
  • 全体用户禁言
  • 撤回消息
  • 聊天记录持久化

二、技术实现

服务端消息中心采用netty实现,
微站、小程序使用websocket与消息中心通信,
安卓端使用netty与消息中心通信。

服务器端每过一定时间会给客户端推送一条ping消息,客户端收到ping消息后回复pong消息,通过心跳验证存活客户端,定时断开未回复pong消息的链接,剔除服务端连接会话信息。
客户端因网络等问题断开链接后,客户端需要实现定时重连机制,先设定断开后每5秒尝试一次重连,这个时间后续可能会修改。

服务端链接地址:ws://{ip}:{端口}/websocket?liveId=123&userId=00b084ea98e24e80a7f8be3c4b8a64d0
liveId为直播id,userId为用户id

1.消息格式
消息为json格式字符串,有如下属性:

字段名 类型 含义 客户端是否必填
id string uuid,服务端生成 不填
liveId string 直播id 必填
code int 系统消息类型 必填
type int 业务消息类型 业务消息必填,心跳消息不填
msg string 消息内容 必填,心跳消息不填
sendUserId string 发送人用户id 必填
sendUserName string 发送人用户名 不填,服务器端返回
sendUserHeadImg string 发送人头像 不填,服务器端返回
receiveUserId string[] 接收人用户id数组 code为群发时无需填写,私聊需要填写
sendTime string 发送时间,服务端生成,格式:yyyy-MM-dd HH:mm:ss 不填
ext string 扩展信息 选填

消息code定义

code 含义
1 点对点
2 群发
3 ping消息,服务器端心跳发送到客户端
4 pong消息,客户端收到ping消息后回应pong消息

消息type定义

type 含义
1001 普通文本消息
1002 图片消息
1003 推荐商品消息
1004 单个用户禁言消息
1005 全体用户禁言消息
1006 撤回用户发言消息
1007 打赏消息

pong消息示例:
{
"code":4,
"liveId":"asfasda",
"sendUserId":"sfasdasdasd"
}

a.普通文本消息示例:
{
"id":"fasdasdasd",
"liveId":"fasdasdas",
"code":2,
"msg":"你好啊",
"sendUserId":"这是发送人的用户id",
"sendUserName":"asfasdas",
"sendUserHeadImg":"fasdasdasdsad.jpg",
"receiveUserId":"这是接收人的用户id",
"type":1001,
"sendTime":133548798798,
"ext":"{}"
}

b.带表情的普通文本消息示例:
{
"id":"fasdasdasd",
"liveId":"fasdasdas",
"code":10001,
"msg":"你好啊[微笑]",
"sendUserId":"这是发送人的用户id",
"sendUserName":"asfasdas",
"sendUserHeadImg":"fasdasdasdsad.jpg",
"receiveUserId":"这是接收人的用户id",
"type":1,
"sendTime":133548798798,
"ext":"{}"
}
备注:[微笑]为微笑表情图片的字符串标识,客户端收到这条消息后需要把表情标识替换为图片显示到聊天窗口。

c.图片消息示例:
先把图片文件进行客户端压缩,尽量控制到1M以内,然后调用上传图片接口得到图片相对路径,如果上传成功,组装websocket消息把路径放入msg:
{
"id":"fasdasd",
"liveId":"fasdasdas",
"code":2,
"msg":"/static/img/chat/2020-02-11/xxx.jpg",
"sendUserId":"这是发送人的用户id",
"sendUserName":"asfasdas",
"sendUserHeadImg":"fasdasdasdsad.jpg",
"receiveUserId":"这是接收人的用户id",
"type":1002,
"sendTime":133548798798,
"ext":"{}"
}

d.推荐商品消息示例:
推荐商品的msg字段为商品信息json
{
"id":"asfasdsdads",
"liveId":"fasdasdas",
"code":2,
"msg":"{"productId":"asdasfas","productType":1,"originPrice":100,"currentPrice":100,"productName":"sfasd", "coverImg":"asfasdasd.jpg"}",
"sendUserId":"这是发送人的用户id",
"sendUserName":"asfasdas",
"sendUserHeadImg":"fasdasdasdsad.jpg",
"receiveUserId":"这是接收人的用户id",
"type":1003,
"sendTime":133548798798
}

e.打赏消息示例:
{
"id":"fasdasd",
"liveId":"fasdasdas",
"code":2,
"msg":"/static/img/chat/2020-02-11/xxx.jpg",
"sendUserId":"这是发送人的用户id",
"receiveUserId":"",
"type":1007,
"sendTime":133548798798,
"ext":"{"name":"被打赏用户的用户名", "price":100}"
}

f.单用户禁言消息示例:
{
“id”:”fasdasd”,
“liveId”:”fasdasdas”,
“code”:2,
“msg”:”被禁言用户的id”,
“sendUserId”:”这是发送人的用户id”,
“receiveUserId”:”这是接收人的用户id”,
“type”:1004,
“sendTime”:133548798798,
“ext”:"{'name':'被禁言的用户名'}”
}

f.单用户取消禁言消息示例:
{
“id”:”fasdasd”,
“liveId”:”fasdasdas”,
“code”:2,
“msg”:”被取消禁言用户的id”,
“sendUserId”:”这是发送人的用户id”,
“receiveUserId”:”这是接收人的用户id”,
“type”:1010,
“sendTime”:133548798798,
“ext”:"{'name':'被禁言的用户名'}”
}

f.撤回消息示例:
{
“id”:”fasdasd”,
“liveId”:”fasdasdas”,
“code”:2,
“msg”:”被撤销的消息id”,
“sendUserId”:”这是发送人的用户id”,
“receiveUserId”:”这是接收人的用户id”,
“type”:1006,
“sendTime”:133548798798,
“ext”:””
}

2.消息交互流程

image.png

接收消息步骤:

a.当用户打开直播介绍页面时,向注册中心发起建立连接请求,监听消息中心推送过来的消息。

b.当消息中心推送过来的消息触发了监听事件函数,判断type是普通文本消息、系统消息、推荐商品消息其中的某一种,然后执行对应的逻辑处理与展示。

发送消息步骤:

a.当用户打开直播介绍页面时,向注册中心发起建立连接请求,监听消息中心推送过来的消息。

b.如果是普通聊天文本消息或系统消息按约定好的消息格式组装好消息json,如果是图片消息则先调用图片上传接口得到url并把url组装到消息json,调用websocket或者netty sdk向消息中心发送消息。

撤回消息步骤:

a.助教端、ibos点击撤回时,组装一条type为撤回、code为群发类型、msg字段为需要撤回的消息id的消息,发送到消息中心。

b.消息中心收到这条消息后,将mongodb中的消息状态改为撤回,并广播撤回消息到所有客户端。

c.被广播的客户端收到这条消息后,按消息id隐藏对应的消息。

单个用户禁言步骤

a.助教端、ibos点击用户禁言时,组装一条type为禁言、code为点对点私聊类型、msg字段为需要禁言的用户id的消息,发送到消息中心。

b.消息中心收到这条消息后,将用户的禁言状态持久化到数据库,并在缓存中记录直播和用户的禁言关系。如果这个用户的客户端还保持会话连接,点对点推送这个被禁言用户的客户端。

c.被禁言用户客户端收到消息后,文本框禁用并提示用户已被禁言。

备注:
当用户刷新页面时,会从数据库中获取到最新的禁言状态并禁用/启用文本框。极端情况用户发出消息,消息经过消息中心时会查询一下缓存中是否有直播用户禁言关系,有的话该消息不予推送。
取消单个用户禁言的交互步骤和以上步骤相同,只是消息的type为取消禁言。

全体用户禁言步骤

a.助教端、ibos点击全体用户禁言时,将直播的禁言状态持久化到数据库,并在缓存中记录直播的禁言状态。组装一条type为全体禁言、code为广播的消息,发送到消息中心。

b.消息中心将消息广播到观看该直播的所有客户端。

c.客户端收到消息后,文本框禁用。

备注:
当用户刷新页面时,会从数据库中获取到最新的直播禁言状态并禁用/启用文本框。极端情况用户发出消息,消息经过消息中心时会查询一下缓存中是否有直播禁言状态,有的话该消息不予推送。
取消全体用户禁言的交互步骤和以上步骤相同,只是消息的type为取消全体禁言。

3.消息存储

在消息中心所在服务器的本地内存加一个队列作为缓冲区,经过消息中心的聊天记录会追加到缓冲区。开启异步任务定时检查缓冲区是否达到阈值,达到缓冲区阈值后批量存储到mongodb聊天记录表。

mongodb表结构

字段名 含义
id 主键
zbId 直播id
msgId 消息id
sendUserId 发送人id
receiveUserId 接收人id,多个逗号分隔
sendTime 发送时间
code 消息发送类型
type 消息类型
msg 消息内容
ext 消息扩展内容
createTime 保存时间

4.鉴权

在消息中心对用户的发言状态做验证。

5.扩展
如果需要支持热部署和扩容,需要解决如下的坑:
1.消息中心的客户端会话管理代码需要改造成分布式会话管理,例如使用redis做存储,但是连接数过大时网络请求传输的数据量也会增大,不妥。

2.集群需要解决负载均衡问题,目前只能做客户端负载均衡,无法像nginx转发http请求一样实现服务端负载均衡。

3.目前市面上没有成熟标准的解决以上问题的方案,需要自己结合一些零散的思路做一些尝试。

网上找到的消息服务集群思路:
如果从自己编程方面考虑socket集群,那么是有困难的。告诉你一个我曾使用过的架构模型。

1、HTTP服务做集群。

2、socket服务器启用后直接访问HTTP服务,主动告知有一个新的socket服务,socket服务状态用中间缓存层保存,具体服务状态可以使用HTTP心跳轮询检测,此部分为socket服务的主动发现、装载服务、卸载服务。

3、客户端请求HTTP服务,HTTP服务分析保存在其上的各个socket服务的存活状态和负载情况,然后返回给客户端最优的socket服务地址。

4、客户端获得最优负载的socket服务地址后连接对应的socket服务。

5、各服务之间的数据交换,可以添加一台socket服务作为socket服务的中转站,这种方式不太可靠,强依赖于中转服务的存活状态。

6、各socket服务的数据必须能保证全局共享,用于客户端之间数据的共通性,使用户在感知上就像完全连接在一台socket服务之上。

下一篇为netty实现消息推送的代码实现:netty实现消息中心(二)基于netty搭建一个聊天室

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342