一、需求

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

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

二、技术实现

服务端消息中心采用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实现消息中心(一)思路整理的更多相关文章

  1. netty实现消息中心(二)基于netty搭建一个聊天室

    前言 上篇博文(netty实现消息中心(一)思路整理 )大概说了下netty websocket消息中心的设计思路,这篇文章主要说说简化版的netty聊天室代码实现,支持群聊和点对点聊天. 此demo ...

  2. html5引擎开发 -- 引擎消息中心和有限状态机 - 初步整理 一

    一 什么是有限状态机        FSM (finite-state machine),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型.他对于逻辑以及 ...

  3. 用代码打开通知中心(statusbar、通知栏、消息中心)

    我想用代码来打开android的消息中心,也叫做statusbar.通知栏.通知栏其实就是一个常驻的服务,至于原理这里就不多说了,简单说下思路和问题. 思路:API中没有实现的方法,那么就利用反射机制 ...

  4. 基于 SOA 概念 RPC 框架 的 消息中心 云部署 设计 漫谈

    一.背景 假设有一个系统的最大并发量有2000TPS左右.同时该系统有闲时和忙时,希望可以随时进行拓展和削减服务能力,以节省服务器费用开销. 该系统能提供站内消息.短信.app消息.邮箱的一个消息系统 ...

  5. eureka源码--服务的注册、服务续约、服务发现、服务下线、服务剔除、定时任务以及自定义注册中心的思路

    微服务注册后,在注册中心的注册表结构是一个map: ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>& ...

  6. Android 基于Netty的消息推送方案之对象的传递(四)

    在上一篇文章中<Android 基于Netty的消息推送方案之字符串的接收和发送(三)>我们介绍了Netty的字符串传递,我们知道了Netty的消息传递都是基于流,通过ChannelBuf ...

  7. Android 基于Netty的消息推送方案之字符串的接收和发送(三)

    在上一篇文章中<Android 基于Netty的消息推送方案之概念和工作原理(二)> ,我们介绍过一些关于Netty的概念和工作原理的内容,今天我们先来介绍一个叫做ChannelBuffe ...

  8. Android 基于Netty的消息推送方案之概念和工作原理(二)

    上一篇文章中我讲述了关于消息推送的方案以及一个基于Netty实现的一个简单的Hello World,为了更好的理解Hello World中的代码,今天我来讲解一下关于Netty中一些概念和工作原理的内 ...

  9. Angular2发布思路(整理官网Deployment页面)

    本文是按着ng2官网的高级内容“Deployment”的思路整理得出的,原文虽然在angular2的中文站下挂着,截止目前却还是英文版未翻译,笔者就在这里结合自己的理解给出原文的一点点整理.这是原文地 ...

随机推荐

  1. MySQL事务隔离之MVCC版本控制

    MVCC简介 MVCC是一种多版本并发控制机制. MVCC是为了解决什么问题? 大多数的MYSQL事务型存储引擎,如,InnoDB,Falcon以及PBXT都不使用一种简单的行锁机制.事实上,他们都和 ...

  2. 5.3 Go 匿名函数

    5.3 Go 匿名函数 Go支持匿名函数,顾名思义就是没名字的函数. 匿名函数一般用在,函数只运行一次,也可以多次调用. 匿名函数可以像普通变量一样被调用. 匿名函数由不带函数名字的函数声明与函数体组 ...

  3. 【MySQL】深入理解MySQL锁和事务隔离级别

    先看个小案例: 话不多说,上案例,先创建一个表 mysql> CREATE TABLE IF NOT EXISTS `account`( `id` INT UNSIGNED AUTO_INCRE ...

  4. Java操作XML(1)

    XML简介 XML(Extensible Markup Language)是一种非常有用的迷哦书结构化信息的技术.XML工具使处理和转化信息变得十分容易. 在程序中,可以使用属性文件(property ...

  5. 【万字图文-原创】 | 学会Java中的线程池,这一篇也许就够了!

    碎碎念 关于JDK源码相关的文章这已经是第四篇了,原创不易,粉丝从几十人到昨天的666人,真的很感谢之前帮我转发文章的一些朋友们. 从16年开始写技术文章,到现在博客园已经发表了222篇文章,大多数都 ...

  6. mysql中 Rank、DENSE_RANK()的区别

    相同点:RANK()和DENSE_RANK()的是排名函数 不同点:RANK()是跳跃排序,即如果有两条记录重复,接下来是第三级别 如:1 2 2 4,会跳过3 DENSE_RANK()是连续排序,即 ...

  7. return break 和continue在for循环中的不同作用

    平时自己经常在函数里见到return,在switch语句中使用break,而continue则用的不多. 其实这三者都能在for循环中发挥不同的作用,让代码更加灵活. 先说return return是 ...

  8. Linux suid 提权

    SUID (Set owner User ID up on execution) 是给予文件的一个特殊类型的文件权限.在 Linux/Unix中,当一个程序运行的时候, 程序将从登录用户处继承权限.S ...

  9. Rocket - debug - Periphery

    https://mp.weixin.qq.com/s/uGxn-Xec0LkwdaSsCtQBvw 简单介绍Periphery的实现. 1. ExportDebugDMI/ExportDebugJTA ...

  10. Bootstrap解决页面缩小变形的办法

    bootstrap布局是应用得很广泛的一种网页布局方法,例如:我们用一种中间内容很流行的布局分布:3-6-3式布局.代码如下 <style type="text/css"&g ...