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.消息交互流程
接收消息步骤:
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搭建一个聊天室
netty实现消息中心(一)思路整理的更多相关文章
- netty实现消息中心(二)基于netty搭建一个聊天室
前言 上篇博文(netty实现消息中心(一)思路整理 )大概说了下netty websocket消息中心的设计思路,这篇文章主要说说简化版的netty聊天室代码实现,支持群聊和点对点聊天. 此demo ...
- html5引擎开发 -- 引擎消息中心和有限状态机 - 初步整理 一
一 什么是有限状态机 FSM (finite-state machine),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型.他对于逻辑以及 ...
- 用代码打开通知中心(statusbar、通知栏、消息中心)
我想用代码来打开android的消息中心,也叫做statusbar.通知栏.通知栏其实就是一个常驻的服务,至于原理这里就不多说了,简单说下思路和问题. 思路:API中没有实现的方法,那么就利用反射机制 ...
- 基于 SOA 概念 RPC 框架 的 消息中心 云部署 设计 漫谈
一.背景 假设有一个系统的最大并发量有2000TPS左右.同时该系统有闲时和忙时,希望可以随时进行拓展和削减服务能力,以节省服务器费用开销. 该系统能提供站内消息.短信.app消息.邮箱的一个消息系统 ...
- eureka源码--服务的注册、服务续约、服务发现、服务下线、服务剔除、定时任务以及自定义注册中心的思路
微服务注册后,在注册中心的注册表结构是一个map: ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>& ...
- Android 基于Netty的消息推送方案之对象的传递(四)
在上一篇文章中<Android 基于Netty的消息推送方案之字符串的接收和发送(三)>我们介绍了Netty的字符串传递,我们知道了Netty的消息传递都是基于流,通过ChannelBuf ...
- Android 基于Netty的消息推送方案之字符串的接收和发送(三)
在上一篇文章中<Android 基于Netty的消息推送方案之概念和工作原理(二)> ,我们介绍过一些关于Netty的概念和工作原理的内容,今天我们先来介绍一个叫做ChannelBuffe ...
- Android 基于Netty的消息推送方案之概念和工作原理(二)
上一篇文章中我讲述了关于消息推送的方案以及一个基于Netty实现的一个简单的Hello World,为了更好的理解Hello World中的代码,今天我来讲解一下关于Netty中一些概念和工作原理的内 ...
- Angular2发布思路(整理官网Deployment页面)
本文是按着ng2官网的高级内容“Deployment”的思路整理得出的,原文虽然在angular2的中文站下挂着,截止目前却还是英文版未翻译,笔者就在这里结合自己的理解给出原文的一点点整理.这是原文地 ...
随机推荐
- 3.11 Go Struct结构体
3.11 Go Struct结构体 Golang支持OOP面向对象编程. Go的结构体struct如同python的class. Go基于struct实现OOP特性,只有组合composition这个 ...
- kudu_遇到的一些问题
最近在研究,自己搭建kudu遇到的一些问题,及解决方法,供大家参考. 1.java连接kudu,出现超时的问题,是因为kudu开启了认证模式: 通过查找 ...
- jsp循环map map的key值不固定
<c:if test="${not empty parammap}"> <c:forEach items="${parammap }" var ...
- Vue刷新页面的三种方式
我们在写项目的时候,经常会遇到,用户执行完某个动作,改变了某些状态,需要重新刷新页面,以此来重新渲染页面 1.原始方法: location.reload(); 2.vue自带的路由跳转: this.$ ...
- Hyperledger Fabric Node SDK和应用开发
Hyperledger Fabric 提供了多种语言的SDK版本,其中提出比较早.比较稳定而全面的是Node.js版本的SDK. 前面提到的fabric示例(如first-network和e2e-cl ...
- DFS序--一般都要转化为顶点到每个点
There is a rooted tree with n nodes, number from 1-n. Root’s number is 1.Each node has a value ai. I ...
- 初窥 BB-Framework
- 第一次写js轮播图
仿小米首页轮播图(注意事项) 布局部分 1.用ul包裹li再包裹a的形式来装图片,建立focus类: <div class="focus"> <ul> &l ...
- HTTP 冷知识 | HTTP 请求中,空格应该被编码为 %20 还是 + ?
HTTP 请求中,空格应该被编码为什么?今天我们走进 RFC 文档和 W3C 文档,了解一下这个「史诗级」大坑. 1.%20 还是 + ? 开始讲解前先看个小测试,在浏览器里输入 blank test ...
- 开心一下-实现基于Java一个中文编程语言
https://mp.weixin.qq.com/s/TsTiLVF5D07-wbDMk9bsyQ 这不是认真的,不是真的要去实现一个中文编程语言. 多年以前,有位同学把Java代码发给我说帮 ...