【IM】如何保证消息可用性(一)
目录
- 1. 基本概念
- 1.1 长连接 和 短连接
- 1.2 PUSH模式和PULL模式
- 2. 背景介绍
- 2.1 理解端到端的思想
- 3. 方案选型
- 3.1 技术挑战
- 3.2 技术目标
1. 基本概念
在讲解消息可用性之前,需要理解几个通信领域的基本概念。
1.1 长连接 和 短连接
-
什么是长连接,短连接?
在HTTP/1.0中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。如果客户端浏览器访问的某个HTML或其他类型的 Web页中包含有其他的Web资源,如JavaScript文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话。但从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头有加入这行代码:
Connection:keep-alive在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的 TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接要客户端和服务端都支持长连接。
HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接
-
TCP的长连接和短连接
-
对于TCP短连接,一般由客户端发起三次握手,经过一次读写操作后,由客户端发起四次挥手,断开连接。短连接的优点在于管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段
-
对于TCP长连接,简单来说,client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。
连接本身是一种资源,连接建立后,server需要保存相关的状态信息,如果客户端已经关闭,此时的状态信息就白白占用了server的资源,因此需要保活功能的存在: 保活功能主要为服务器应用提供,服务器应用希望知道客户主机是否崩溃,从而可以代表客户使用资源。如果客户已经消失,使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,则服务器将应远等待客户端的数据,保活功能就是试图在服务 器端检测到这种半开放的连接。
如果一个给定的连接在两小时内没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于以下4个状态之一:正常运行,已经崩溃,客户主机崩溃并已经重新启动,客户机正常运行但是服务器不可达(弱网环境)
-
-
长短连接的选择
根据之前的介绍,我们已经能感受到长短连接的优缺点。长连接通常用于收发频繁的场景,尤其是点对点通信,比如IM,数据库;而短连接通常用于请求不频繁的情况,比如一般的WEB网站,对于网页资源的请求一般不会十分频繁。 -
简单的TCP长连接服务器DEMO
TCP的保活机制保证了传输层的连接可用性,但是如何保证应用层的连接,需要我们做跟多的工作,之后会单独讲解。package mainimport ("log""net""time" )func main() {// 创建 TCP 连接conn, err := net.Dial("tcp", "127.0.0.1:80")if err != nil {log.Fatal("Failed to connect:", err)}defer conn.Close()// 将连接转换为 TCPConn 类型tcpConn, ok := conn.(*net.TCPConn)if !ok {log.Fatal("Failed to convert to TCPConn")}// 设置 KeepAlive,启用 TCP 连接的 KeepAlive 功能if err := tcpConn.SetKeepAlive(true); err != nil {log.Fatal("Failed to set KeepAlive:", err)}// 设置 KeepAlive 周期,每隔一段时间发送一个 KeepAlive 包if err := tcpConn.SetKeepAlivePeriod(10 * time.Second); err != nil {log.Fatal("Failed to set KeepAlive period:", err)}// 在这里进行其他操作,保持 TCP 连接处于活跃状态 }
1.2 PUSH模式和PULL模式
这个比较简单,可以理解为GIT里的push和pull.服务器主动向客户端发数据叫做push,客户端向客户端请求数据叫做pull
2. 背景介绍
在IM场景下,消息的可用性包含两个方面:可靠性和一致性,可以总结为:可达有序,不重不漏。
- 可靠性:消息一旦显示发送成功,则一定到达对端
- 一致性:任何时刻消息保证由于发送端的发送顺序一致
2.1 理解端到端的思想
在设计IM系统的时候,最重要的思想是 端到端思维:
端到端原则是一种分布式系统中各个模块之间功能定位的设计原理,指的是从代价和性能的角度出发,在网络的最核心的部分应该只去做数据的传输而不能去做一些其他的应用,而数据是否正确传输则应该放到应用层去检查和判断,从而保证互联网核心的简单性,可维护性,可拓展性。
简单的理解端到端思想,可以以数据传输的可靠性为例子,在网络中传输文件,传输错误可能发生在端到端路线的多个节点,也许我们可以通过在每一个交换节点上都加入数据校验,超时,重传的机制来保证数据正确,但是,实际上,可能错误可能发生在端系统的自身,比如磁盘存取错误,缓存不足。这样一来,即使中间节点提供了数据的可靠,端系统也没有任何工作量的减少,依然需要进行正确性的检测。 因此,在中间节点只需要提供其能完全实现的功能,即路由转发即可。
OSI七层模型也是端到端思想的产物,主机网络的每一层都是一个端系统,只对本层负责,保证本层数据可靠,不保证上层数据可靠。这样的设计使得每一层功能不重叠,结构清晰:
- 数据链路层(第二层)和物理层(第一层): 这两层负责将数据转换为比特流并通过物理介质传输。在端到端原则下,这些层不应对数据进行处理,而应只负责将数据从一个点传输到另一个点。
- 网络层(第三层): 数据在网络中进行路由和转发,以便从计算机 A 到计算机 B。在端到端原则下,网络层应该提供最佳的数据传输路径,但不对数据进行修改或过滤
- 传输层(第四层): 数据被传输到传输层,以便在计算机 A 和 B 之间进行可靠的数据传输。在端到端原则下,传输层负责数据的分段、重传和错误检测,以保证数据的完整性和可靠性
由此我们可以总结:底层可靠性仅能保证底层可靠,不对上层负责。传输层已经帮我们做到了数据的完整和可靠,但是并不能说它能保证应用层的可靠了。
那么TCP到底做到了哪一步,我们又需要做哪些工作呢?

现在假设一个场景(应用层不做可用性处理):clientA发送msg1和msg2到server,两个消息通过一个长连接到达了服务端,此时可能会出现下面这些情况:
- msg1和msg2到达应用层,分别由一个线程去处理,msg2先落表成功,发送给clientB,造成数据乱序。
- msg1在server落盘失败,msg2成功,先发送给了clientB,造成数据丢失,乱序。
- 消息在由接入层到达业务层的时候,server进程崩溃,但是此时clientA认为已经送达,服务端业务无法感知,消息丢失。
因此对于IM这种三方通信来说,TCP/UDP的保证是远远不足够。
消息的端到端可用性 = 上行消息可用 + 服务端业务可用 + 下行消息可用
3. 方案选型
如何设计一个保证消息端到端可用性的协议是我们的本文的最重要任务。
3.1 技术挑战
- 三方通信,难以保证网络上的消息必达
- 没有全局时钟,那一确定唯一顺序,因果顺序
- 时钟零点漂移问题:时钟零点漂移通常指的是在计算机系统中时钟的偏移或漂移,这可能会导致时钟与实际时间之间存在误差。这种情况可能由多种因素引起,包括硬件时钟的不准确性、操作系统的时钟管理、系统负载等。
可行的解决方式有:- 使用网络时间协议(NTP): NTP 是一种用于同步计算机时钟的协议,可以从互联网上的时间服务器获取准确的时间信息,并调整本地时钟以匹配其时间。在大多数操作系统中,可以配置系统以自动与 NTP 服务器同步时间。
- 定期同步时间: 即使已启用自动同步时间功能,也建议定期手动同步一次时间,以确保时钟的准确性
- 减少系统负载: 高负载的系统可能会影响时钟同步的准确性。尽量减少系统的负载,特别是在进行时钟同步时
- 时钟零点漂移问题:时钟零点漂移通常指的是在计算机系统中时钟的偏移或漂移,这可能会导致时钟与实际时间之间存在误差。这种情况可能由多种因素引起,包括硬件时钟的不准确性、操作系统的时钟管理、系统负载等。
- 消息顺序性在多客户端,多服务端,多线程/协程下那一保持
3.2 技术目标
对消息可用性的进一步细化:
- 消息及时:实时接收,发送 (响应时间<=200ms,高峰期 <=1s)
- 消息可达:超时重试,ACK确认
- 消息幂等(每条消息只处理一次):分配seqId,服务端存储seqId
- 消息有序:seqId可比较,接收端可以按照发送端的顺序进行消息排序 (一个会话内保证消息有序即可)
在下一篇文章中,我们将探讨更加具体的实现方案。
相关文章:
【IM】如何保证消息可用性(一)
目录 1. 基本概念1.1 长连接 和 短连接1.2 PUSH模式和PULL模式 2. 背景介绍2.1 理解端到端的思想 3. 方案选型3.1 技术挑战3.2 技术目标 1. 基本概念 在讲解消息可用性之前,需要理解几个通信领域的基本概念。 1.1 长连接 和 短连接 什么是长连接,短连接…...
js直接下载附件和通过blob数据类型下载文件
js下载文件方式有使用a标签的,也有直接用window.open的,还有用form表单的;这里采用的是a标签的下载方式,一种是url直接下载,另一种是文件的blob数据类型进行下载。 文件blob数据类型的获取一般是后端返回文件的二进制流…...
第2章-神经网络的数学基础——python深度学习
第2章 神经网络的数学基础 2.1 初识神经网络 我们来看一个具体的神经网络示例,使用 Python 的 Keras 库 来学习手写数字分类。 我们这里要解决的问题是, 将手写数字的灰度图像(28 像素28 像素)划分到 10 个类别 中(0…...
【Docker】Docker学习⑧ - Docker仓库之分布式Harbor
【Docker】Docker学习⑧ - Docker仓库之分布式Harbor 一、Docker简介二、Docker安装及基础命令介绍三、Docker镜像管理四、Docker镜像与制作五、Docker数据管理六、网络部分七、Docker仓库之单机Dokcer Registry八、 Docker仓库之分布式Harbor1 Harbor功能官方介绍2 安装Harbor…...
一行命令在 wsl-ubuntu 中使用 Docker 启动 Windows
在 wsl-ubuntu 中使用 Docker 启动 Windows 0. 背景1. 验证我的系统是否支持 KVM?2. 使用 Docker 启动 Windows3. 访问 Docker 启动的 Windows4. Docker Hub 地址5. Github 地址 0. 背景 我们可以在 Windows 系统使用安装 wsl-ubuntu,今天玩玩在 wsl-ub…...
Datawhale 组队学习之大模型理论基础 Task7 分布式训练
第8章 分布式训练 8.1 为什么分布式训练越来越流行 近年来,模型规模越来越大,对硬件(算力、内存)的发展提出要求。因为内存墙的存在,单一设持续提高芯片的集成越来越困难,难以跟上模型扩大的需求。 为了…...
05-使用结构体构建相关数据
上一篇: 04-了解所有权 结构体(struct)是一种自定义数据类型,可以将多个相关值打包命名,组成一个有意义的组。如果你熟悉面向对象的语言,那么结构体就像是对象的数据属性。在本章中,我们将对元组…...
【Android】Android中的系统镜像由什么组成?
文章目录 总览Boot Loader 的加锁与解锁Boot 镜像内核RAM diskARM 中的设备树 (Device Tree) /System 和/Data 分区镜像参考 总览 各种Android设备都只能刷专门为相应型号的设备定制的镜像。 厂商会提供一套系统镜像把它作为“出厂默认”的 Android 系统刷在设备上。 一个完…...
仿真机器人-深度学习CV和激光雷达感知(项目2)day7【ROS关键组件】
文章目录 前言Launch 文件了解 XML 文件Launch 文件作用Launch 文件常用标签实例--作业1的 Launch 文件TF Tree介绍发布坐标变换--海龟例程获取坐标变换--海龟自动跟随例程rqt_工作箱前言 💫你好,我是辰chen,本文旨在准备考研复试或就业 💫本文内容是我为复试准备的第二个…...
解锁一些SQL注入的姿势
昨天课堂上布置了要去看一些sql注入的案例,以下是我的心得: 1.新方法 打了sqli的前十关,我发现一般都是联合查询,但是有没有不是联合查询的方法呢…...
Qt 拖拽事件示例
一、引子 拖拽这个动作,在桌面应用程序中是非常实用和具有很友好的交互体验的。我们常见的譬如有,将文件拖拽到某个窗口打开,或者拖拽文件到指定位置上传;在绘图软件中,选中某个模板、并拖拽到画布上,画布上变回绘制该模板的图像… 诸如此类,数不胜数。 那么,在Qt中我…...
Linux:命名管道及其实现原理
文章目录 命名管道指令级命名管道代码级命名管道 本篇要引入的内容是命名管道 命名管道 前面的总结中已经搞定了匿名管道,但是匿名管道有一个很严重的问题,它只允许具有血缘关系的进程进行通信,那如果是两个不相关的进程进行通信࿰…...
实习记录——第五天
今天我的心情不是很美丽,昨天晚上没怎么睡好,因为我一直在想离不离开实验室?该怎么说的事情?但是又觉得这个项目还没有完全结束,冒昧提这个事情是不是不好?最终也没得出一个结论,晚上睡得也不踏…...
Kotlin 教程(环境搭建)
Kotlin IntelliJ IDEA环境搭建 IntelliJ IDEA 免费的社区版下载地址:Download IntelliJ IDEA – The Leading Java and Kotlin IDE 下载安装后,我们就可以使用该工具来创建项目,创建过程需要选择 SDK, Kotlin 与 JDK 1.6 一起使…...
04.领域驱动设计:了解聚合和聚合根,怎样设计聚合-学习总结
目录 1、概述 2、聚合 3、聚合根 4、怎么设计聚合 4.1 聚合的构建过程主要步骤 第 1 步:采用事件风暴。 第 2 步:选出聚合根。 第 3 步:找出与聚合根关联的所有紧密依赖的实体和值对象。 第 4 步:画出对象的引用和依赖模型…...
cmake-find_package链接第三方库
文章目录 基本调用形式和模块模式使用方式 之前我们是使用了绝对路径来链接OpenCV第三方库,但是现在很多库一般会自己写一些cmake文件提供给用户,用户可以直接使用其中的内置变量即可。使用的命令就是find_package。 基本调用形式和模块模式 find_packa…...
obsidian阅读pdf和文献——与zotero连用
参考: 【基于Obsidian的pdf阅读、标注,构建笔记思维导图,实现笔记标签化、碎片化,便于检索和跳转】 工作流:如何在Obsidian中阅读PDF - Eleven的文章 - 知乎 https://zhuanlan.zhihu.com/p/409627700 操作步骤 基于O…...
走方格(动态规划)
解题思路: 找边界,即行为1,列为1。 拆分问题,拆分成一次走一步,只能向右或者向下走。 解题代码: public static void main(String[] args) {int [][]arrnew int[31][31];Scanner scnew Scanner(Sys…...
基于DataKit迁移MySQL到openGauss
📢📢📢📣📣📣 哈喽!大家好,我是【IT邦德】,江湖人称jeames007,10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】!😜&am…...
API网关-Apinto压缩包方式自动化安装配置教程
文章目录 前言一、Apinto安装教程1. 复制脚本2. 增加执行权限3. 执行脚本4. Apinto命令4.1 启动Apinto4.2 停止Apinto4.3 重启Apinto4.4 查看Apinto版本信息4.5 加入Apinto集群4.6 离开Apinto集群4.7 查看Apinto节点信息 5. 卸载Apinto 二、Apserver(Apinto Dashboard V3)安装教…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
