1. 前言
1.1. 项目简要说明
VMS平台是视频监控管理平台,是一个基于服务器,依托于数据库和网络的服务系统,是中大型视频监控方案的中央管理平台,能够实现设备接入管理、用户权限管理、流媒体管理、报警管理、录像管理等。
vms-api-gateway作为VMS的网关,负责请求和响应的鉴权、路由等操作,其中的WebSocket功能,在实际使用中存在一系列体验和性能问题:
(1)WebSocket内容格式不统一;
(2)推送进度流程问题;
(3)乱序问题;
(4)VMS local大数据量推送下断连问题。
因此,本项目是针对上述存在的问题进行优化,达到体验优化的效果。
1.2. 任务概述
本任务是针对VMS的vms-api-gateway模块下web端WebSocket功能的优化,为了不影响之前的功能,要求尽量不改变源代码,以新增功能的方式进行开发。
1.3. 可用资源
1.VMS软件,包括:
(1)VMS api-gateway原有组件,包括web端的WebSocket的实现;
(2)VMS manager原有组件,包括进度推送类任务的实现。
2.ipc-simulator-port-local-1.1-SNAPSHOT:实现模拟设备(包括IPC、NVR)的添加和激活。
1.4. 术语定义
术语 定义
WebSocket 一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层;VMS通过该协议进行消息主动推送。
STOMP STOMP(Simple Text Oriented Messaging Protocol,简单文本定向消息协议)是一个面向文本的协议,但消息负载可以是文本或二进制。VMS的web端依靠该协议实现WebSocket。
VMS
Video Management System,视频监控管理平台,是一个基于服务器,依托于数据库和网络的服务系统,是中大型视频监控方案的中央管理平台,能够实现设备接入管理、用户权限管理、流媒体管理、报警管理、录像管理等
1.5. 参考资料
VMS - websocket 优化方案 - EBG-Internal-Shared - Confluence,该文档展示了在性能测试中暴露出来的一些问题以及解决方案的设计;
STOMP Protocol Specification,STOMP1.2基本格式、特性、用法说明;
STOMP :: Spring 框架 - Spring 框架,Spirng框架下的STOMP特性。
2. 需求分析
需求分析文档:【需求分析】VMS WebSocket功能优化 - CRD_EP_Software_Service - Confluence
3. 原理概述
3.1. 协议分析
VMS的Web端的WebSocket依托STOMP框架实现,本节结合业务对该协议进行简要分析,基于分析和需求进行统一封装。
3.1.1. 协议描述
STOMP(Simple/Streaming Text Oriented Messaging Protocol)是一种基于帧的轻量级消息传输协议,专为消息中间件(Message-Oriented Middleware, MOM)设计,旨在简化客户端与消息代理(Broker)之间的异步通信。
3.1.2. 协议数据格式
1.帧(Frame)结构:
TOMP消息以文本帧形式组织,包含三个部分:
命令(Command):定义操作类型(如CONNECT、SEND、SUBSCRIBE等)。
头部(Headers):键值对,传递元数据(如目标地址destination、消息类型content-type)。
消息体(Body):可包含文本或二进制数据,长度由content-length头指定。
示例:
STOMP标准格式
COMMAND
header1:value1
header2:value2
Body^@
其中,需要对“Body”部分进行说明,按照官网文档,如图所示:
该说明指出只有SEND、MESSAGE、ERROR三个消息可以有消息体(body)。
2.目前格式设计
本部分的协议数据格式的封装设计主要针对body部分,现在消息推送、订阅响应都属于COMMAND=MESSAGE的范畴,但是目前二者的body由各个业务决定,没有统一的格式。
目前,由StompDestination枚举类中有49条常量可知,WebSocket其中的Topic有49个。
其中可以分为两种类型:进度类推送、事件类推送。除此之外,网关也会向Web发送订阅响应。因此,设计标准格式需要考虑这三种类型:
(1)进度类推送:通常用于耗时较长的事件,通过WebSocket连接不断向web端返回执行进度信息,比如“local设备添加进度推送”、“批量执行指令进度推送”等。
以“local设备进度添加”为例,目前返回数据格式如下:
local设备添加进度推送
{
"errorCode": "number",
"message": "string",
"result": {
"success": "number",
"fail": "number",
"total": "number",
"status": "number",
"macConflict": "boolean"
"taskId":"string"
"failList": {
"devId": "string",
"name": "string",
"ipString": "string",
"mac": “string”,
"hasPassword": "boolean",
"errorCode": "integer",
"message": "string",
"secLeft": “string”
}
}
}
result中的taskId指的是用于后续删除mac地址重合的设备,仅有重合时有值。
(2)事件类推送:服务器主动向web端推送特定事件或通知,通常用于实时通知客户端某些业务事件的发生,比如“设备信息变更推送”等。
设备信息变更推送
{
"diviceId": "string",
"status": "integer",
"name": "string"
"isDelete": "boolean",
"oldSiteId": "number",
"siteId": "number",
"upgradeStatus": "number",
"timezoneDstChange": "boolean"
}
(3)订阅响应:网关收到订阅之后返回给web端的订阅成功的消息。
订阅响应
{
"message": "success",
"errorCode": 0,
"data": null
}
综上,可以看到三种发给Web端的消息格式不一。
3.2. 标准格式设计
1.标准格式
标准格式设计示例如下:
消息载荷内容设计
{
"type": "string",
"errorCode": "number",
"taskId": "string",
"status": "string",
"sequenceNumber": "number",
"data": "object"
}
其中,字段说明如表所示:
字段 进度类推送 事件类推送 订阅响应 备注
type MESSAGE SUBSCRIBE SUBSCRIBE 任务类型,SUBSCRIBE/MESSAGE,用以标识是订阅响应还是推送消息
errorCode 批量任务中有失败时置为非0 默认0 默认0 错误码,前端在COMPLETE时通过errorCode判断是否执行失败逻辑,即发起任务结果查询
taskId XXXX 无 无 进度类推送任务topic中的uuid,用以标识不同推送任务
status RUNNING/COMPLETE COMPLETE COMPLETE 任务状态,RUNNING/COMPLETE;非进度类推送默认COMPLETE
sequenceNumber 0 无 无
消息编号,每个Topic从0开始递增;SUBSCIRBE默认无sequenceNumber;
使用此字段时需要确保topic有taskId区分每次任务,需要各业务在处理时每次加1后推送。
无taskId的topic使用时保持sequenceNumber字段未空,前端不执行乱序处理逻辑
data 对象 对象 无 业务对象,需控制大小
2.封装后的示例如下:
(1)进度类推送,以“local批量设备添加进度推送”为例:
local批量设备添加进度推送
{
"type": "MESSAGE",
"errorCode": 0,
"taskId": "唯一字符",
"status": "RUNNING",
"sequenceNumber": 5,
"data": {
"success": a,
"fail": b,
"total": c ,
"macConflict":
"taskId":"string"
}
}
(2)事件类推送,以“设备信息变更推送”为例:
设备信息变更推送
{
"type": "MESSAGE",
"errorCode": 0,
"status": "COMPLETE",
"data": {
"diviceId": "string",
"status": "integer",
"name": "string",
"isDelete": "boolean",
"oldSiteId": "number",
"siteId": "number",
"upgradeStatus": "number",
"timezoneDstChange": "boolean"
}
}
(3)订阅响应:
订阅响应
{
"type": "SUBSCRIBE",
"status": "COMPLETE",
"errorCode": 0,
"data": null
}
3.3 官方的乱序解决
参考消息顺序 :: Spring Framework - Spring 框架的内容,如下所示:
该描述中提到消息乱序的原因之一是消息发出的时候使用了多线程,同时提出了解决方案,也告诉我们该方案有一定的性能损耗。
因此,对该具体实现进行研究。
具体实现是OrderMessageChannelDecorator类,该类通过ConcurrentLinkedQueue 保证多线程环境下消息入队/出队的原子性,AtomicBoolean sendInProgress 确保同一时间仅一个线程执行发送操作。
与单线程的对比:
单线程 多线程下setPreservePublishOrder=true
入队 单线程 多线程
出队(发送) 单线程 单线程
回调 单线程 多线程
通过“并发入队+串行发送”的混合架构,在保障消息顺序的同时释放了多线程并发处理能力。其中,多线程可快速填充队列提升吞吐量,而sendInProgress原子锁与回调链机制确保发送环节严格有序,既避免了单线程的阻塞瓶颈,又通过动态可配置的拦截器实现按需顺序保障,兼顾高并发性能与顺序可靠性。
4. 系统架构描述
4.1. 概述
主要是在vms-api-gateway和vms-manager两个模块进行开发。
4.2. 模块结构
整体模块图如图所示:
4.3. 模块描述和建模
模块 功能名称 功能描述
WebSocket消息代理模块 WebSocket消息解析 WebSocket消息解析,解析前端的消息。
WebSocket消息封装 WebSocket消息封装,得到http请求。
WebSocket消息路由 WebSocket消息路由,路由到manager的controller。
WebSocket消息格式标准化模块 WebSocket消息格式标准化 定义统一格式。
设备添加进度推送模块 设备添加进度推送 往代理推送设备添加进度的消息。
查询任务结果模块 查询WebSocket任务分页结果 查询WebSocket推送进度类任务的结果,以分页或者列表的形式展示。
4.4. 流程设计
4.4.1. local设备添加流程
以“local设备添加流程”为例,该部分可分为四部分:
a.请求解析、封装、路由功能
b.设备添加功能
c.添加进度推送功能
d.查询任务结果功能
整体的时序图如下所示:
对send的消息格式、内容进行说明:
将addDiscoveredDevices原有的HTTP请求包含的业务参数放进send的body里面,示例如下:
Send消息格式
COMMAND:SEND
destination: /api/v1/vms/{vmsId}/local/sites/{siteId}/devices/addDiscoveredDevices
content-type:application/json
vmsId:{vmsId}
siteId:{siteId}
CSRF-TOKEN:{token}
SourceType:vms_web
{
"username": "string",
"password": "string",
"initUsername": "string",
"initPassword": "string",
"resetPwdEmail": "string",
"selectAll": false,
"discoverType": "string",
"uuid": "string",
"language": "string",
"country": "string",
"powerLineFre": "string",
"zoneId": "string",
"timeZone": "string",
"followSiteTime": false,
"searchValue": "string",
"question1": "string",
"whetherRsa": false,
"answer1": "string",
"answer2": "string",
"question2": "string",
"answer3": "string",
"question3": "string",
"devIds": ["id1", "id2"]
}^@
【说明】:
(1)本次的uuid与subscirbe中的taskId相同,用以标识是同一任务。
(2)该send内容在MessageinboundInterceptor.presend方法中解析,封装成HTTP请求,异步发送到/api/v1/vms/{vmsId}/local/sites/{siteId}/devices/addDiscoveredDevices。
4.4.2. 任务结果查询流程
对查询任务结果流程再细化说明。
当前该功能的前端展示依赖UI设计,当前UI支持列表展示,因此设计WebSocket任务结果列表查询的流程。
【扩展性保障】但是设备数量在大规模添加情况可能会很大,因此设计分页接口支持未来可能的扩展需求,同时也更符合业务情况。
【接口灵活性】前端通过当前的UI设计判断使用哪一种查询接口,接口分离实现解耦。
流程图如下。
以分页查询为例,时序图如下所示:
5. 数据库及中间件设计
为了实现单独的查询任务结果的接口,需要设计缓存用以缓存WebSocket任务执行结果。
目前VMS的缓存都是通过Redis实现,参考VMS Cache和Lock Key记录表,设计具体如下:
服务-模块 key 缓存类型 value示例 过期时间 用途
manager websocket:result:{vmsId}:{sn} WebSocketResultDTO
{
"success":number,
"fail":number,
"total":number,
"content":[Object,...]
}
30min websocket任务执行结果缓存
6. 接口概要设计
6.1. 概述
对“设备添加进度推送”、“查询WebSocket任务分页结果”、“查询WebSocket任务列表结果”的接口进行设计。
6.2. 接口分类与功能
Yapi链接如下:
1.设备添加进度推送:websocket - local设备添加进度推送v2
2.查询WebSocket任务分页结果:websocket - 任务结果分页查询请求
3.查询WebSocket任务列表结果:websocket - 任务结果列表查询请求
6.3. 兼容性说明
目前是针对现有功能进行优化,为了兼容之前的实现,新建Topic v2版本。
进度推送时,需要往两个Topic同时推送。待后续版本考虑删除旧Topic。
改动如下:
枚举 含义 原Topic 新Topic 备注
DEVICE_ADD_PROGRESS 设备批量添加实时进度推送 /vms/{vmsId}/users/{userId}/device-add-progress/{uuid} /vms/{vmsId}/users/{userId}/device-add-progress/{uuid}/v2 加了v2后缀(这是概要设计,有什么需要修改、补充的)
最新发布