MQTT QoS

很多时候, 使用 MQTT 协议的设备都运行在网络受限的环境下, 而只依靠底层的 TCP 传输协议, 并不能
完全保证消息的可靠到达。 因此, MQTT 提供了 QoS 机制, 其核心是设计了多种消息交互机制来提供不
同的服务质量, 来满足用户在各种场景下对消息可靠性的要求

MQTT 定义了三个 QoS 等级, 分别为:

  • QoS 0, 最多交付一次。
  • QoS 1, 至少交付一次。
  • QoS 2, 只交付一次。

使用 QoS 0 可能丢失消息, 使用 QoS 1 可以保证收到消息, 但消息可能重复, 使用 QoS 2 可以保证消息既不丢失也不重复。 QoS 等级从低到高, 不仅意味着消息可靠性的提升, 也意味着传输复杂程度的提升。

在一个完整的从发布者到订阅者的消息投递流程中, QoS 等级是由发布者在 PUBLISH 报文中指定的, 大部分情况下 Broker 向订阅者转发消息时都会维持原始的 QoS 不变。 不过也有一些例外的情况, 根据订阅者的订阅要求, 消息的 QoS 等级可能会在转发的时候发生降级。

QoS 0 是最低的 QoS 等级。 QoS 0 消息即发即弃, 不需要等待确认, 不需要存储和重传, 因此对于接收方来说, 永远都不需要担心收到重复的消息。
为什么 QoS 0 消息会丢失?
当我们使用 QoS 0 传递消息时, 消息的可靠性完全依赖于底层的 TCP 协议。而 TCP 只能保证在连接稳定不关闭的情况下消息的可靠到达, 一旦出现连接关闭、 重置, 仍有可能丢失当前处于网络链路或操作系统底层缓冲区中的消息。 这也是QoS 0 消息最主要的丢失场景

为了保证消息到达, QoS 1 加入了应答与重传机制, 发送方只有在收到接收方的 PUBACK 报文以后, 才能认为消息投递成功, 在此之前, 发送方需要存储该 PUBLISH 报文以便下次重传。QoS 1 需要在 PUBLISH 报文中设置 Packet ID, 而作为响应的 PUBACK 报文, 则会使用与 PUBLISH报文相同的 Packet ID, 以便发送方收到后删除正确的 PUBLISH 报文缓存。

为什么 QoS 1 消息会重复?
对于发送方来说, 没收到 PUBACK 报文分为以下两种情况:

  • PUBLISH 未到达接收方
  • PUBLISH 已经到达接收方, 接收方的 PUBACK 报文还未到达发送方

在第一种情况下, 发送方虽然重传了 PUBLISH 报文, 但是对于接收方来说, 实际上仍然仅收到了一次消息。
但是在第二种情况下, 在发送方重传时, 接收方已经收到过了这个 PUBLISH 报文, 这就导致接收方将收到重复的消息

虽然重传时 PUBLISH 报文中的 DUP 标志会被设置为 1, 用以表示这是一个重传的报文。 但是接收方并不能因此假定自己曾经接收过这个消息, 仍然需要将其视作一个全新的消息。这是因为对于接收方来说, 可能存在以下两种情况:

第一种情况, 发送方由于没有收到 PUBACK 报文而重传了 PUBLISH 报文。 此时, 接收方收到的前后两个 PUBLISH 报文使用了相同的 Packet ID, 并且第二个 PUBLISH 报文的 DUP 标志为 1, 此时它确实是一个重复的消息。

第二种情况, 第一个 PUBLISH 报文已经完成了投递, 1024 这个 Packet ID 重新变为可用状态。 发送方使用这个 Packet ID 发送了一个全新的 PUBLISH 报文, 但这一次报文未能到达对端, 所以发送方后续重传了这个 PUBLISH 报文。 这就使得虽然接收方收到的第二个 PUBLISH 报文同样是相同的 Packet ID,并且 DUP 为 1, 但确实是一个全新的消息。由于我们无法区分这两种情况, 所以只能让接收方将这些 PUBLISH 报文都当作全新的消息来处理。 因此当我们使用 QoS 1 时, 消息的重复在协议层面上是无法避免的。

甚至在比较极端的情况下, 例如 Broker 从发布方收到了重复的 PUBLISH 报文, 而在将这些报文转发给订阅方的过程中, 再次发生重传, 这将导致订阅方最终收到更多的重复消息。

QoS 2 解决了 QoS 0、 1 消息可能丢失或者重复的问题, 但相应地, 它也带来了最复杂的交互流程和最高的开销。 每一次的 QoS 2 消息投递, 都要求发送方与接收方进行至少两次请求/响应流程。

  • 首先, 发送方存储并发送 QoS 为 2 的 PUBLISH 报文以启动一次 QoS 2 消息的传输, 然后等待接收方回复 PUBREC 报文。 这一部分与 QoS 1 基本一致, 只是响应报文从 PUBACK 变成了PUBREC。

  • 当发送方收到 PUBREC 报文, 即可确认对端已经收到了 PUBLISH 报文, 发送方将不再需要重传这个报文, 并且也不能再重传这个报文。 所以此时发送方可以删除本地存储的 PUBLISH 报文, 然后发送一个 PUBREL 报文, 通知对端自己准备将本次使用的 Packet ID 标记为可用了。 与 PUBLISH 报文一样, 我们需要确保 PUBREL 报文到达对端, 所以也需要一个响应报文, 并且这个 PUBREL 报文需要被存储下来以便后续重传。

  • 当接收方收到 PUBREL 报文, 也可以确认在这一次的传输流程中不会再有重传的 PUBLISH 报文到达, 因此回复PUBCOMP 报文表示自己也准备好将当前的 Packet ID 用于新的消息了。

  • 当发送方收到 PUBCOMP 报文, 这一次的 QoS 2 消息传输就算正式完成了。 在这之后, 发送方可以再次使用当前的 Packet ID 发送新的消息, 而接收方再次收到使用这个 Packet ID 的 PUBLISH报文时, 也会将它视为一个全新的消息。

为什么 QoS 2 消息不会重复?
QoS 2 消息保证不会丢失的逻辑与 QoS 1 相同, 所以这里我们就不再重复了。与 QoS 1 相比, QoS 2 新增了 PUBREL 报文和 PUBCOMP 报文的流程, 也正是这个新增的流程带来了消息不会重复的保证

在我们更进一步之前, 我们先快速回顾一下 QoS 1 消息无法避免重复的原因:当我们使用 QoS 1 消息时, 对接收方来说, 回复完 PUBACK 这个响应报文以后 Packet ID 就重新可用了, 也不管响应是否确实已经到达了发送方。 所以就无法得知之后到达的, 携带了相同 Packet ID 的PUBLISH 报文, 到底是发送方因为没有收到响应而重传的, 还是发送方因为收到了响应所以重新使用了这个 Packet ID 发送了一个全新的消息。

所以, 消息去重的关键就在于, 通信双方如何正确地同步释放 Packet ID, 换句话说, 不管发送方是重传消息还是发布新消息, 一定是和对端达成共识了的。而 QoS 2 中增加的 PUBREL 流程, 正是提供了帮助通信双方协商 Packet ID 何时可以重用的能力。

QoS 2 规定, 发送方只有在收到 PUBREC 报文之前可以重传 PUBLISH 报文。 一旦收到 PUBREC 报文并发出 PUBREL 报文, 发送方就进入了 Packet ID 释放流程, 不可以再使用当前 Packet ID 重传PUBLISH 报文。 同时, 在收到对端回复的 PUBCOMP 报文确认双方都完成 Packet ID 释放之前, 也不可以使用当前 Packet ID 发送新的消息。

因此, 对于接收方来说, 能够以 PUBREL 报文为界限, 凡是在 PUBREL 报文之前到达的 PUBLISH 报文, 都必然是重复的消息; 而凡是在 PUBREL 报文之后到达的 PUBLISH 报文, 都必然是全新的消息。一旦有了这个前提, 我们就能够在协议层面完成 QoS 2 消息的去重。

不同 QoS 的适用场景和注意事项

QoS 0
QoS 0 的缺点是可能会丢失消息, 消息丢失的频率依赖于你所处的网络环境, 并且可能使你错过断开连接期间的消息, 不过优点是投递的效率较高。所以我们通常选择使用 QoS 0 传输一些高频且不那么重要的数据, 比如传感器数据, 周期性更新, 即使遗漏几个周期的数据也可以接受。

QoS 1
QoS 1 可以保证消息到达, 所以适合传输一些较为重要的数据, 比如下达关键指令、 更新重要的有实时性要求的状态等。
但因为 QoS 1 还可能会导致消息重复, 所以当我们选择使用 QoS 1 时, 还需要能够处理消息的重复, 或者能够允许消息的重复。在我们决定使用 QoS 1 并且不对其进行去重处理之前, 我们需要先了解, 允许消息的重复, 可能意味着什么。
如果我们不对 QoS 1 进行去重处理, 我们可能会遭遇这种情况, 发布方以 1、 2 的顺序发布消息, 但最终订阅方接收到的消息顺序可能是 1、 2、 1、 2。 如果 1 表示开灯指令, 2 表示关灯指令, 我想大部分用户都不会接受自己仅仅进行了开灯然后关灯的操作, 结果灯在开和关的状态来回变化。

QoS 2
QoS 2 既可以保证消息到达, 也可以保证消息不会重复, 但传输成本最高。 如果我们不愿意自行实现去重方案, 并且能够接受 QoS 2 带来的额外开销, 那么 QoS 2 将是一个合适的选择。 通常我们会在金融、 航空等行业场景下会更多地见到 QoS 2 的使用。

如何为 QoS 1 消息去重?
在我们介绍 QoS 1 的时候讲到, QoS 1 消息的重复在协议层面上是无法避免的。 所以如果我们想要对 QoS1 消息进行去重, 只能从业务层面入手。一个比较常用且简单的方法是, 在每个 PUBLISH 报文的 Payload 中都带上一个时间戳或者一个单调递增的计数, 这样上层业务就可以根据当前收到消息中的时间戳或计数是否大于自己上一次接收的消息中的时间戳或计数来判断这是否是一个新消息。

何时向后分发 QoS 2 消息?
我们已经了解到, QoS 2 的流程是非常长的, 为了不影响消息的实时性, 我们可以在第一次收到 PUBLISH报文时, 就启动消息的向后分发。 当然一旦开始向后分发, 后续收到在 PUBREL 报文之前到达的 PUBLISH报文, 都不能再重复分发操作, 以免消息重复。
不同 QoS 的性能有差距么?
以 EMQX 为例, 在相同的硬件配置下进行点对点通信, 通常 QoS 0 与 QoS 1 能够达到的吞吐比较接近,不过 QoS 1 的 CPU 占用会略高于 QoS 0, 负载较高时, QoS 1 的消息延迟也会进一步增加。 而 QoS 2能够达到的吞吐一般仅为 QoS 0、 1 的一半左右。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/757634.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

游戏防沉迷系统相关内容

网站地址&#xff1a;网络游戏防沉迷实名认证系统 PHP代码&#xff1a; 创建对应文件&#xff0c;在需要的位置get传参请求即可&#xff0c;具体参数参考 网络游戏防沉迷实名认证系统接口对接技术规范v2.0 1、上传信息 <?php $url "https://wlc.nppa.gov.cn/test…

HCIA复习实验题(静态路由综合实验)

一、实验拓扑 二、实验划分 三、实验需求 如上图所见&#xff1b; 四、实验结果 1.实现内网通&#xff1b; &#xff08;1&#xff09;配置R1~R5的接口IP&#xff08;以及环回接口&#xff09;&#xff1b; R1接口&#xff1a; [R1]int GigabitEthernet 0/0/0 [R1-Gigab…

低代码 + 大模型,让业务系统智能化,加速想法落地

1 AI 时代下的应用 大模型来了&#xff0c;每个行业/领域都在融合大模型。作为低代码开发的探索者&#xff0c;今天我们来聊聊业务系统是如何融合大模型实现智能化的。 我们用百度内部实际应用场景来举例。比如请假&#xff0c;智能业务助手可以帮你&#xff1a; 了解…

SpringDataJpa大坑——一对多级联修改问题

前言 寒假接手一个项目&#xff0c;甲方提出了这样一个功能——需要一个商品有多张图片。可以进行滑动观看。这个需求很简单&#xff0c;前端只要做一个轮播图&#xff0c;后端只要涉及一个商品下有多组照片即可&#xff08;一对多关系&#xff09;。 项目后端选型 框架springb…

从零开始写 Docker(七)---实现 mydocker commit 打包容器成镜像

本文为从零开始写 Docker 系列第七篇&#xff0c;实现类似 docker commit 的功能&#xff0c;把运行状态的容器存储成镜像保存下来。 完整代码见&#xff1a;https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识&#xff1a; 核心原…

Jmeter-基础元件使用(二)

一、Jmeter属性 当我们想要在不同线程组中使用某变量&#xff0c;就需要使用属&#xff0c;此时Jmeter属性的设置需要函数来进行set和get操作 1.创建set函数 2.然后采用Beanshell取样器进行函数执行 3.调用全局变量pro_id 4.将上面生成的函数字符串粘贴到另一个线程组即可…

合合信息扫描全能王亮相静安区3·15活动,AI扫描带来绿色消费新体验

保护消费者的合法权益&#xff0c;是全社会的共同责任。为优化消费环境、促进品质消费高地建设&#xff0c;打造安全优质和谐的消费环境&#xff0c;上海静安区消保委于3月15日举办静安区2024年“315”国际消费者权益日活动。 “激发消费活力&#xff0c;绿色低碳同行”是本次3…

Linux 常用命令精简版---文件编辑查找级压缩(适合新手)

--------------------------------------------文件编辑查找--------------------------------------------------------- 1、查找文件 find /etc/ -type f -name cims &#xff08;f参数为常规文件&#xff0c;例如文本图片等&#xff09; 2、模糊查询一类文件 find /e…

C语言字符函数和字符串函数详解

Hello, 大家好&#xff0c;我是一代&#xff0c;今天给大家带来有关字符函数和字符串函数的有关知识 所属专栏&#xff1a;C语言 创作不易&#xff0c;望得到各位佬们的互三呦 一.字符函数 在C语言中有一些函数是专门为字符设计的&#xff0c;这些函数的使用都需要包含一个头文…

管理类联考–复试–英文面试–问题–WhatWhyHow--纯英文汇总版

文章目录 Do you have any hobbies? What are you interested in? What do you usually do in your spare time? Could you tell me something about your family&#xff1f; Could you briefly introduce your family? What is your hometown like? Please tell me so…

在Arm 虚拟硬件(AVH)部署深度学习OCR算法

AI算法的嵌入式部署 AI算法在独立的设备上运行其实就是行业内的嵌入式AI的概念, 大致过程如下: 开发AI模型, 2.对数据集进行处理, 3.训练AI模型并验证效果, 4.转成ONNX格式(ONNX:万金油中间格式,给模型优化和部署带来了更多可能性)或者借助libtorch或者TensorFlow来部署C++版…

Prometheus+Grafana 监控Tongweb7

文章目录 1.准备工作2.Tongweb7部署3.Prometheus部署4.上传jar包并配置Tongweb75.Prometheus配置6.安装和配置Grafana 1.准备工作 本次参考&#xff1a;Prometheus监控Tongweb容器 1.使用虚拟机ip&#xff1a;192.168.10.51&#xff08;tongweb&#xff09;&#xff0c;192.1…

day14-SpringBoot 原理篇

一、配置优先级 SpringBoot 中支持三种格式的配置文件&#xff1a; 注意事项 虽然 springboot 支持多种格式配置文件&#xff0c;但是在项目开发时&#xff0c;推荐统一使用一种格式的配置 &#xff08;yml 是主流&#xff09;。 配置文件优先级排名&#xff08;从高到低&…

golang 根据某个特定字段对结构体的顺序进行排序

文章目录 方法一方法二方法三 在Go语言中&#xff0c;我们可以使用 sort.Slice() 函数对结构体进行排序。假设你有一个结构体&#xff0c;并且希望根据其中的某个字段进行排序&#xff0c;你可以使用自定义的排序函数。 方法一 下面是一个示例代码&#xff0c;假设有一个包含…

【设计模式】Java 设计模式之模板命令模式(Command)

命令模式&#xff08;Command&#xff09;的深入分析与实战解读 一、概述 命令模式是一种将请求封装为对象从而使你可用不同的请求把客户端与接受请求的对象解耦的模式。在命令模式中&#xff0c;命令对象使得发送者与接收者之间解耦&#xff0c;发送者通过命令对象来执行请求…

HuggingFists系统介绍+视频链接

之前通过系列文章介绍了HugggingFists系统&#xff0c;这次给出视频&#xff0c;方便大家更直观的了解如何使用HuggingFists系统。视频链接如下&#xff1a; 《HuggingFists--低代码AI/LLM数据科学工具介绍》 相关的文章系列链接如下&#xff1a; 《HuggingFists系统功能介绍…

大数据面试总结 五

1、使用spark统计worldcount val words lines.flatMap(line > line.split(" ")) val pairs words.map(word > (word, 1)) val wordCounts pairs.reduceByKey(_ _)2、什么是clickhouse&#xff0c;优势是什么 clickhouse是列式存储关系里系统&#xff0c;…

嵌套循环实现九九乘法表

大家好&#xff1a; 衷心希望各位点赞。 您的问题请留在评论区&#xff0c;我会及时回答。 案例描述 利用嵌套循环&#xff0c;实现九九乘法表。 代码 #include <iostream> #include <Windows.h>using namespace std;int main(void) {//外层循环执行一次&#…

v-bind 绑定 class 与 style 基础用法

使用 v-bind 指令绑定 class 和 style 时语法相对复杂一些&#xff0c;这两者是可以互相替代的&#xff0c;均用于响应更新HTML元素的属性&#xff0c; v-bind 绑定 class 属性可以改写成绑定 style 属性&#xff0c;只是 css 属性位置变了而已。 1. 绑定 class 属性 1.1 数组…

学了 Python 但又感觉没学 Python 不如重学 Python - day4(数据类型:列表)

目录 1、创建列表 2、列表基本操作 3、索引与分片 4、矩阵 5、常用列表方法 &#xff08;1&#xff09;append 与 extend 方法 &#xff08;2&#xff09;insert 方法 &#xff08;3&#xff09;remove 与 pop 方法 &#xff08;4&#xff09;del 语句与 clear 方法 …