当前位置: 首页 > news >正文

单体架构 IM 系统之长轮询方案设计

在上一篇技术短文(单体架构 IM 系统之核心业务功能实现)中,我们讨论了 “信箱模型” 在单体架构 IM 系统中的应用,“信箱模型” 见下图。

客户端 A 将 “信件” 投入到客户端 B 的 “信箱” 中,然后客户端 B 去自己的 “信箱” 中取回自己的 “信件”, “信箱模型” 故此得名。

“信箱模型” 中,所有动作由客户端触发,作为 “信箱” 的服务端被动响应即可; 所以 “信箱模型” 实现逻辑非常简单,但是对于 “信件” 的实时性不高; 客户端 B 想要尽快获取到自己的 “信件”,只能周期性地高频请求。我们知道,在 IM 系统中,所有客户端都在高频请求时,无效请求(即没有获取到自己的消息)是非常高的,这对服务器资源和网络资源来说是一种浪费。 在当前单体架构 IM 系统背景下(单体架构 IM 系统之架构设计),如何进行优化呢?

我们知道,http 是短连接协议,即一次客户端请求和服务端回复后,连接就断开了;在不更换协议(更换协议代价很大)的前提下,我们对其进行优化:服务端接收请求到回复客户端的时间,完全是可以控制的。 描述到这里,很多同学应该已经非常清楚了,即:将短连接的 http 访问优化为 http 长轮询方式,通过 http 长轮询方式模拟出 “长连接” 的效果。 http 长轮询见下图。

http-client 向 http-server 发出请求,http-server 拿到请求后,会立刻 hold 住该请求不返回; http-server 返回响应需要满足下面任何一个条件:

  1. 超过了一定时间,比如15秒,即超时了;(该条件减少了无效的 http 请求)

  2. 在超时之前产生了该 http-client 的数据。(该条件提高了消息的实时性)

http-client 收到响应后,会立刻再次向 http-server 发出请求,重复上述过程。

在单体架构 IM 系统的服务端,只需改造 http 部分,增加一个【http 长轮询】的插件即可大大提高消息的实时性,改造成本低,见效快! 那么 【http 长轮询】插件应该如何实现呢?我们分别介绍 “定时器” 和 “时间轮” 两种实现方案。

方案一、 定时器方案

定时器方案非常容易理解,即针对每一个 http 客户端,当服务端接收到请求后,就开启 15秒(假设超时时间是 15秒)的定时器;15秒内若产生了消息,则立刻返回,否则就等15秒后超时返回。我们基于 Go 语言代码进行描述,如下。

chTimeout := make(chan struct{}, 1)				//定义 “超时管道”
go func() {time.Sleep(time.Second * POLLING_TIMEOUT)	//定时器15秒超时fmt.Printf("INFO | [heartHandler] timeout")chTimeout <- struct{}{}						//超时后,向“超时管道” 中写入元素
}()select {case <-chTimeout:							//从 “超时管道” 中读数据fmt.Fprint(w, "nonthing")case msg := <-chPushMsg:					//从 “消息管道” 中读数据bs, _ := json.Marshal(msg)fmt.Fprint(w, string(bs))
}

通过 Go 语言代码实现定时器方案非常简单!

在 Go 语言中有一个类似于 “IO 多路复用” 的用法,即通过 select-case 语句实现对多个管道的监听,哪个管道先有了数据,就执行哪个 case 语句。

基于此,我们定义了两个管道,一个是用于 15 秒定时器超时的 “超时管道”,一个是传输消息的 “消息管道”; “超时管道” 在一个独立的协程中,由 “定时器” 控制,超时后向 “超时管道” 写入一个元素数据 (即 struct{},内容不重要,有数据即可表示超时)。select-case 语句非常巧妙地帮助我们实现了 要么15秒后超时返回,要么15秒内有消息即可返回的选择情况。

我们分析一下这个定时器实现方案:服务端需要针对每一次的 http 请求,分别启动一个 “定时器”;”定时器“ 在本质上是一个计算脉冲的计数器,达到设定值之后,通过软中断方式向 CPU 发起中断请求;当 http 客户端并发请求增大之后,服务端同时运行的 “定时器” 也会增多,于是软中断也增多,CPU 会经常性的停下手头工作去处理中断请求,CPU的工作效率会大大降低。那么,在 http 客户端数量不断增多的时候,如何进行优化呢? 下面的时间轮方案可以非常优雅地解决这个问题。

方案二、时间轮方案

在时间轮实现方案只需要一个每秒走一格的定时器即可,其核心思想是将同一秒内超时的所有客户端进行批量处理;见下图。

在该时间轮实现方案中,需要准备三个数据结构:

  1. 一个作为 “时间轮” 的循环队列,该时间轮的指针,每秒钟走一格,走一圈是一个完整的超时周期(图中超时时间是 13秒);

  2. 一个用户维度的 map<uid, 时间轮时间>,该 map 的 key 是用户 uid,value 是时间轮指针所指向的时间刻度;

  3. 一个时间轮时间维度的 map<时间轮时间, uid列表>,该 map 的 key 是时间轮的刻度,value 是这个时间刻度时所有发起 http 请求的 uid 列表。

当客户端发出 http 请求到服务端时,服务端将用户和当前时间刻度信息分别写入到上述的两个 map 中;在 13 秒超时之前,如果产生了用户的消息,则从上述两个 map 中删除用户和时间刻度信息;时间轮当前指针每走一格所指向的时间刻度,该时间刻度对应的用户列表就是 13 秒前发出 http 请求的用户列表,这些用户就是超时的客户端,需要超时返回,即返回空的 http 响应。

这样描述可能比较抽象,我们举一个例子:

  1. 假设当前时间轮指针指向了当前时间刻度 2,此时 有三个客户端分别是 101、102、103 发出 http 请求到服务端,服务端需要在 第一个 map 中分别写入 <101, 2> , <102, 2>,<103, 3>,在第二个 map 中写入 <2, [101, 102, 103]>;

  2. 三秒后,时间轮指针指向了当前时间刻度 5,此时产生了用户 102 的消息,服务端需要先从第一个 map 中删除元素 <102, 2>(同时记录下时间刻度 2,方便后续操作),再从第二个 map 中删除 102 的记录,删除后的map为 <2, [101, 103]>;

  3. 九秒后,在时间轮指针指向当前刻度 2 时,此时第二个 map 中,key 是 2 的所有的uid列表,即 [101, 103],就是所有超时的客户端列表,需要超时返回。

时间轮实现方案,通过一个定时器实现了对同一秒内超时的所有客户端的批量处理。

最后,对文中关键进行总结:

1、基于 http 周期轮询方式的 “信箱模型”,消息的实时性不高,可优化为 http 长轮询方式,通过 http 长轮询模拟出 “长连接” 的效果;

2、http 长轮询有两种实现方案:定时器方案和时间轮方案;

3、Go 语言实现的定时器方案,通过 select-case 语句实现了对多个管道的多路复用监听,达到了随时产生消息随时返回或超时返回的目的; 定时器方案适用于客户端数量较少的情况;

4、时间轮方案实现了对同一秒内所有超时客户端的批量处理,该方案需要三个数据结构:循环队列,map<uid,时间>, map<时间, uid列表>。

大家思考一下:

在该单体架构的 IM 系统中,http 短轮询方式优化成 http 长轮询方式后,点对点消息发送逻辑,需要调整吗?

(http 短轮询方式的消息发送逻辑参见技术短文:单体架构 IM 系统之核心业务功能实现)

相关文章:

单体架构 IM 系统之长轮询方案设计

在上一篇技术短文&#xff08;单体架构 IM 系统之核心业务功能实现&#xff09;中&#xff0c;我们讨论了 “信箱模型” 在单体架构 IM 系统中的应用&#xff0c;“信箱模型” 见下图。 客户端 A 将 “信件” 投入到客户端 B 的 “信箱” 中&#xff0c;然后客户端 B 去自己的 …...

Android Studio加载旧的安卓工程项目报错处理

文章目录 Invalid Gradle JDK configuration foundNDK not configuredCMake 3.10.2 was not found安装cmake适配cmake版本号 com.intellij.openapi.externalSystem.model.ExternalSystemExceptiongradle版本过低或下载不了下载gradle与依赖库超时替换gradle国内源替换Maven 仓库…...

阿里公告:停止 EasyExcel 更新与维护

最近&#xff0c;阿里发布公告通知&#xff0c;将停止对知名 Java Excel 工具库 EasyExcel 的更新和维护。EasyExcel 由阿里巴巴开源&#xff0c;作者是玉箫&#xff0c;在 GitHub 上拥有 30k stars、7.5k forks 的高人气。 据悉&#xff0c;EasyExcel 作者玉箫去年已从阿里离…...

Spring 中的 BeanWrapper

BeanWrapper 是 Spring 框架中的一个接口&#xff0c;它提供了一种方式来设置和获取 JavaBean 的属性。JavaBean 是一种特殊的 Java 类&#xff0c;遵循特定的编码约定&#xff08;例如&#xff0c;私有属性和公共的 getter/setter 方法&#xff09;&#xff0c;通常用于封装数…...

2024鹏城杯msic部分WP

MISC 网安第一课 查找字符key&#xff0c;发现key1&#xff0c;但是没看到key2 后缀改为zip&#xff0c;打开以后发现不一样的地方&#xff0c;三张图片和一个misc文件夹 图片放到010看一眼 编号为1的图片在文件尾发现key2 misc文件夹中是一个out.pcb&#xff0c;放到010发现…...

DAY23|回溯算法Part02|LeetCode: 39. 组合总和 、40.组合总和II 、131.分割回文串

目录 LeetCode: 39. 组合总和 基本思路 C代码 LeetCode: 40.组合总和II 基本思路 C代码 LeetCode: 131.分割回文串 基本思路 C代码 LeetCode: 39. 组合总和 力扣代码链接 文字讲解&#xff1a;LeetCode: 39. 组合总和 视频讲解&#xff1a;带你学透回溯算法-组合总和…...

go map

1、数据结构 // A header for a Go map. type hmap struct {// Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.// Make sure this stays in sync with the compilers definition.count int // # live cells size of map.…...

三十七、Python基础语法(异常)

在 Python 中&#xff0c;异常是在程序执行过程中发生的错误情况。当出现异常时&#xff0c;程序的正常执行流程会被中断&#xff0c;并尝试寻找相应的异常处理机制来处理这个错误。 一、异常的类型 Python 中有很多内置的异常类型&#xff0c;例如&#xff1a; ZeroDivision…...

ThreadLocal的熟悉与使用

目录 1.ThreadLocal介绍2.ThreadLocal源码解析2.1 常用方法2.2 结构设计2.3 类图2.4 源码分析2.4.1 set方法分析2.4.2 get方法分析2.4.3 remove方法分析 3.ThreadLocal内存泄漏分析3.1 相关概念3.1.1 内存溢出3.1.2 内存泄漏3.1.3 强引用3.1.4 弱引用 3.2 内存泄漏是否和key使用…...

如何使用 Puppeteer 和 Browserless 抓取亚马逊产品数据?

您可以在亚马逊上找到所有有关产品、卖家、评论、评分、特价、新闻等的相关且有价值的信息。无论是卖家进行市场调研还是个人收集数据&#xff0c;使用高质量、便捷且快速的工具将极大地帮助您准确地抓取亚马逊上的各种信息。 为什么抓取亚马逊产品数据很重要&#xff1f; 亚…...

使用Python求解经典“三门问题”,揭示概率的奇妙之处

三门问题&#xff08;Monty Hall Problem&#xff09;是经典的概率问题&#xff0c;描述了一位游戏选手在三个门中选择一扇门&#xff0c;其中一扇门后有奖品&#xff0c;其余两扇门后是空的。选手做出选择后&#xff0c;主持人会打开另一扇空门&#xff0c;然后给选手一次更改…...

数据库基础(6) . DDL

3.2.DDL 数据定义语言 DDL : Data Definition Language 用于创建新的数据库、模式&#xff08;schema&#xff09;、表&#xff08;tables&#xff09;、视图&#xff08;views&#xff09;以及索引&#xff08;indexes&#xff09;等。 常见的DDL语句包括SHOW、CREATE、DRO…...

2024 年度分布式电力推进(DEP)系统发展探究

分布式电力推进 &#xff08;DEP&#xff09; 的发明是为了尝试和改进现代飞机&#xff1a;我们如何提高飞机的效率&#xff1f;提高它的机动性&#xff1f;缩短它的起飞和着陆距离&#xff1f; DEP 概念有望在提高性能的同时减少燃料消耗&#xff0c;在我们孜孜不倦地努力使航…...

vue通过iframe方式嵌套grafana图表

文章目录 前言一、iframe方式实现xxx.xxx.com拒绝连接登录不跳转Cookie 的SameSite问题解决不显示额外区域(kiosk1) 前言 我们的前端是vue实现的&#xff0c;监控图表是在grafana中的&#xff0c;需要在项目web页面直接显示grafana图表 一、iframe方式实现 xxx.xxx.com拒绝连…...

简单介绍下 Java 中的 @Validated 和 @Valid 注解的区别?

文章目录 Valid&#xff1a;专注单个对象的深度验证适用场景使用示例小结 Validated&#xff1a;聚焦接口分组的批量验证适用场景使用示例小结 主要区别总结如何选择&#xff1f;总结推荐阅读文章 在 Java 开发中&#xff0c;为了确保输入数据符合我们的要求&#xff0c;少不了…...

SpringBoot配置Rabbit中的MessageConverter对象

SpringAMQP默认使用SimpleMessageConverter组件对消息内容进行转换 SimpleMessageConverter&#xff1a; only supports String, byte[] and Serializable payloads仅仅支持String、Byte[]和Serializable对象Jackson2JsonMessageConverter&#xff1a;was expecting (JSON Str…...

C++ 错题本--duplicate symbol问题

顾名思义, duplicate symbol是重复符号的意思! 代码是用来做什么的(问题缘由 & 代码结构) 写排序算法, 提出了一个公共的头文件用来写一些工具方法, 比如打印数组内容. 以便于不同文件代码需要打印数组内容的时候,直接引入相关头文件即可, 但是编译时出现了 duplicate sym…...

Cursor的chat与composer的使用体验分享

经过一段时间的试用&#xff0c;下面对 Composer 与 Chat 的使用差别进行总结&#xff1a; 一、长文本及程序文件处理方面 Composer 在处理长文本时表现较为稳定&#xff0c;可以对长文进行更改而不会出现内容丢失的情况。而 Chat 在更改长的程序文件时&#xff0c;有时会删除…...

【优选算法 — 滑动窗口】最大连续1的个数 将 x 减到0的最小操作数

最大连续1的个数 最大连续1的个数 题目描述 题目解析 给我们一个元素全是0或者1的数组&#xff0c;和一个整数 k &#xff0c;然后让我们在数组选出最多的 k 个0&#xff1b;这里翻转最多 k 个0的意思&#xff0c;是翻转 0 的个数< k&#xff0c;而不是一定要翻转 k …...

《TCP/IP网络编程》学习笔记 | Chapter 8:域名及网络地址

《TCP/IP网络编程》学习笔记 | Chapter 8&#xff1a;域名及网络地址 《TCP/IP网络编程》学习笔记 | Chapter 8&#xff1a;域名及网络地址域名系统什么是域名&#xff1f;DNS 服务器IP 地址和域名之间的转换使用域名的必要性利用域名获取 IP 地址利用 IP 地址获取域名 基于 Wi…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用

1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用

在工业制造领域&#xff0c;无损检测&#xff08;NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统&#xff0c;以非接触式光学麦克风技术为核心&#xff0c;打破传统检测瓶颈&#xff0c;为半导体、航空航天、汽车制造等行业提供了高灵敏…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅

目录 前言 操作系统与驱动程序 是什么&#xff0c;为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中&#xff0c;我们在使用电子设备时&#xff0c;我们所输入执行的每一条指令最终大多都会作用到硬件上&#xff0c;比如下载一款软件最终会下载到硬盘上&am…...