[33] MQTT-概念&环境搭建
1. MQTT 入门#
1.1 MQTT 协议#
a. 什么是 MQTT#
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议)是一种基于发布/订阅(Publish/Subscribe)模式的"轻量级"通讯协议,该协议构建于 TCP/IP 协议上,由 IBM 在 1999 年发布。MQTT 最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

一个使用 MQTT 协议的应用程序或者设备,它总是建立到服务器的网络连接。客户端可以:
- 发布其他客户端可能会订阅的信息;
- 订阅其它客户端发布的消息;
- 退订或删除应用程序的消息;
- 断开与服务器连接。
MQTT 服务器以称为"消息代理"(Broker),可以是一个应用程序或一台设备。它是位于消息发布者和订阅者之间,它可以:
- 接受来自客户的网络连接;
- 接受客户发布的应用信息;
- 处理来自客户端的订阅和退订请求;
- 向订阅的客户转发应用程序消息。
b. 设计原则#
由于物联网的环境是非常特别的,所以 MQTT 遵循以下设计原则:
- 精简,不添加可有可无的功能;
- 发布/订阅(Pub/Sub)模式,方便消息在传感器之间传递,解耦 C/S 模式,带来的好处在于不必预先知道对方的存在(ip/port),不必同时运行;
- 允许用户动态创建主题(不需要预先创建主题),零运维成本;
- 把传输量降到最低以提高传输效率;
- 把低带宽、高延迟、不稳定的网络等因素考虑在内;
- 支持连续的会话保持和控制(心跳);
- 理解客户端计算能力可能很低;
- 提供服务质量(quality of service level:QoS)管理
- 不强求传输数据的类型与格式,保持灵活性(指的是应用层业务数据)。
c. 应用领域#
MQTT 协议广泛应用于物联网、移动互联网、智能硬件、车联网、电力能源等领域。
- 物联网 M2M 通信,物联网大数据采集
- Android 消息推送,WEB 消息推送
- 移动即时消息,例如 Facebook Messenger
- 智能硬件、智能家居、智能电器
- 车联网通信,电动车站桩采集
- 智慧城市、远程医疗、远程教育
- 电力、石油与能源等行业市场
d. 核心概念#
MQTT 客户端
实现 MQTT 协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT 协议中有三种身份:发布者(Publisher)、代理(Broker)、订阅者(Subscriber)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。
MQTT Broker
MQTT Broker 是负责处理客户端请求的关键组件,包括建立连接、断开连接、订阅和取消订阅等操作,同时还负责消息的转发。一个高效强大的 MQTT Broker 能够轻松应对海量连接和百万级消息吞吐量,从而帮助物联网服务提供商专注于业务发展,快速构建可靠的 MQTT 应用。
MQTT Topic
MQTT 主题本质上是一个 UTF-8 编码的字符串,是 MQTT 协议进行消息路由的基础。可以理解为就是通过主题「对消息进行分类」。MQTT 主题类似 URL 路径,使用斜杠 /
进行分层:
chat/room/1
sensor/10/temperature
为了避免歧义且易于理解,通常不建议主题以 /
开头或结尾,例如 /chat
或 chat/
。MQTT 主题不需要提前创建。MQTT 客户端在订阅或发布时即自动的创建了主题,开发者无需再关心主题的创建,并且也不需要手动删除主题。
1.2 EMQX#
EMQX 是一款实现了 MQTT 协议的、开源的 MQTT 消息代理软件。MQTT 定义了消息通讯的规则和流程,而 EMQX 则是遵循这些规则的软件,使得设备能够依据 MQTT 协议进行有效通信。在新版本的 EMQX 中同时支持 MQTT 3.1.1 和 5.0 协议。
a. Docker 部署#
docker 部署命令:
docker run -d --name emqx-enterprise \
-p 1883:1883 -p 8083:8083 \
-p 8084:8084 -p 8883:8883 \
-p 18083:18083 \
-v emqx_data:/opt/emqx/data \
-v emqx_log:/opt/emqx/log \
-v emqx_etc:/opt/emqx/etc \
emqx/emqx-enterprise:5.6.1
配了 aliyun 那个镜像加速,但还是拉不下来。在网上看的解决办法:向 /etc/docker/daemon.json 里加入下面的配置:
{
"registry-mirrors": [
"https://docker.registry.cyou",
"https://docker-cf.registry.cyou",
"https://dockercf.jsdelivr.fyi",
"https://docker.jsdelivr.fyi",
"https://dockertest.jsdelivr.fyi",
"https://mirror.aliyuncs.com",
"https://dockerproxy.com",
"https://mirror.baidubce.com",
"https://docker.m.daocloud.io",
"https://docker.nju.edu.cn",
"https://docker.mirrors.sjtug.sjtu.edu.cn",
"https://docker.mirrors.ustc.edu.cn",
"https://mirror.iscas.ac.cn",
"https://docker.rainbond.cc"
]
}
EMQ X Broker 提供了 Dashboard 以方便用户管理设备与监控相关指标,启动后我们通过访问服务端 18083 端口。
EMQX 常见端口如下:
端口号 | 说明 |
---|---|
1883 | TCP 端口 |
8083 | WebSocket 端口 |
8084 | WebSocket Secure 端口 |
8883 | SSL/TLS 端口 |
18083 | Broker 的 Dashboard 访问端口号 |
b. 目录结构#
不同安装方式得到的 EMQ X 其目录结构会有所不同,具体如下:
描述 | 使用 ZIP 压缩包安装 (同 Docker) | 使用二进制包安装 |
---|---|---|
可执行文件目录 | ./bin | /usr/lib/emqx/bin |
数据文件 | ./data | /var/lib/emqx/data |
Erlang 虚拟机文件 | ./erts-* | /usr/lib/emqx/erts-* |
配置文件目录 | ./etc | /etc/emqx |
依赖项目录 | ./lib | /usr/lib/emqx/lib |
日志文件 | ./log | /var/log/emqx |
启动相关的脚本、schema 文件 | ./releases | /usr/lib/emqx/releases |
以上目录中,用户经常接触与使用的是 bin
、etc
、data
、log
目录。
bin 目录
emqx、emqx.cmd:EMQ X 的可执行文件
emqx_ctl、emqx_ctl.cmd:EMQ X 管理命令的可执行文件
etc 目录
EMQ X 通过 etc
目录下配置文件进行设置,主要配置文件包括:
配置文件 | 说明 |
---|---|
emqx.conf | EMQ X 配置文件 |
acl.conf | EMQ X 默认 ACL 规则配置文件 |
plugins/*.conf | EMQ X 各类插件配置文件 |
certs/* | EMQ X SSL 证书文件 |
emqx.lic | License 文件仅限 EMQ X Enterprise |
data 目录
EMQ X 将运行数据存储在 data
目录下,主要的文件包括:
(1)configs/app.*.config
EMQ X 读取 etc/emqx.conf
和 etc/plugins/*.conf
中的配置后,转换为 Erlang 原生配置文件格式,并在运行时读取其中的配置。
(2)loaded_plugins
loaded_plugins
文件记录了 EMQ X 默认启动的插件列表,可以修改此文件以增删默认启动的插件。
$ cat loaded_plugins
emqx_management.
emqx_recon.
emqx_retainer.
emqx_dashboard.
emqx_rule_engine.
emqx_web_hook.
(3)mnesia
Mnesia 数据库是 Erlang 内置的一个分布式 DBMS,可以直接存储 Erlang 的各种数据结构。
EMQ X 使用 Mnesia 数据库存储自身运行数据,例如告警记录、规则引擎已创建的资源和规则、Dashbaord 用户信息等数据,这些数据都将被存储在 mnesia
目录下,因此一旦删除该目录,将导致 EMQ X 丢失所有业务数据。
c. Dashboard#
EMQ X 提供了 Dashboard 以方便用户管理设备与监控相关指标。通过 Dashboard 可以查看服务器基本信息、负载情况和统计数据,可以查看某个客户端的连接状态等信息甚至断开其连接,也可以动态加载和卸载指定插件。除此之外,EMQ X Dashboard 还提供了规则引擎的可视化操作界面,同时集成了一个简易的 MQTT 客户端工具供用户测试使用。
EMQ X Dashboard 是一个 Web 应用程序,你可以直接通过浏览器来访问它,无需安装任何其他软件。
当 EMQ X 成功运行在你的本地计算机上且 EMQ X Dashboard 被默认启用时,通过访问 http://localhost:18083 来查看 Dashboard,默认用户名/密码是 admin/public
。
为了使用户在操作和浏览中可以快速地定位和切换当前位置,EMQ X Dashboard 采用了侧边导航的模式,默认情况下 Dashboard 包含以下一级导航项目:
最新版本 EMQ X Broker 的 Dashboard 界面布局略有不同,增加了些导航,但基本都差不多。
导航项目 | 说明 |
---|---|
Monitor | 提供了服务端与客户端监控信息的展示页面 |
RULE ENGINE | 提供了规则引擎的可视化操作页面 |
MANAGEMENT | 提供了扩展插件与应用的管理页面 |
TOOLS | 提供了 WebSocket 客户端工具以及 HTTP API 速查页面 |
ADMIN | 提供了 Dashboard 用户管理和显示设置等页面 |
主要功能:
- 监控和管理 EMQX 中的相关信息与数据 :支持查看运行中的 EMQX 集群的整体连接数,订阅主题数,消息收发数量和流入流出速率,还包括节点列表和节点信息和一些系统指标信息,同时也可以对一些客户端连接与订阅数据进行查看与管理。
- 访问控制(认证与授权)管理:支持通过可视化的方式来新增和配置管理 EMQX 中的认证与授权机制。
- 数据集成:使用强大的基于 SQL 的规则引擎和数据桥或流量编辑器的可视化功能进行低代码数据处理和集成,以帮助实时提取、过滤、丰富、转换和存储 MQTT 数据。
- 在线配置热更新:支持在线修改和更新包括 MQTT、日志,监听器等配置项,更新成功后即刻生效。
首次访问:
用户名和密码:admin/pubic
可以通过 CLI 的 admins
命令进行密码重置:
./bin/emqx ctl admins passwd <Username> <Password>
1.3 MQTTX#
MQTTX 是 EMQX 开源的一款跨平台 MQTT 5.0 客户端工具,它支持 macOS, Linux 并且支持自定义脚本模拟测试、MQTT 消息格式转换、日志记录等多个功能。
MQTTX 包含三种类型的工具:
- MQTTX Desktop:MQTTX Desktop 是一款跨平台的 MQTT 桌面客户端工具
- MQTTX CLI:MQTTX CLI 是 EMQ 开源的一款 MQTT 5.0 命令行客户端工具
- MQTT Web:MQTTX Web 是一款基于浏览器访问客户端工具
官网地址:https://mqttx.app/zh
a. MQTTX Desktop#
下载地址:https://mqttx.app/zh/downloads
(1)新建连接
(2)创建新连接订阅主题
(3)输入主题名称发送消息
(4)可以通过 Dashboard 来管理链接和订阅信息
b. MQTTX CLI#
下载地址:https://mqttx.app/zh/downloads
(1)订阅主题:
$ mqttx-cli-win-x64.exe sub -t 'test/1' -h 192.168.136.147 -p 1883 -v
-t
:订阅主题-h
:服务器地址,填写对应监听器的 IP 地址,默认为localhost
-p
:服务器端口,默认为1883
-v
:在接收到的 Payload 前显示当前 Topic
(2)向主题发送消息
$ mqttx-cli-win-x64.exe pub -t 'test/1' -q 0 -h 192.168.136.147 -p 1883 -m "from MQTTX CLI"
-q
QoS 等级-m
消息内容
c. MQTTX Web#
使用 MQTTX Web 进行测试操作基本上与使用 MQTTX Desktop 相同。
$ docker pull emqx/mqttx-web
$ docker run -d --name mqttx-web -p 80:80 emqx/mqttx-web
2. MQTT 控制报文#
「报文」是网络中交换与传输的数据最小单元,通俗来讲就是站点一次性要发送的「数据块」。它包含了将要发送的完整数据信息,其长短不一致,长度不限且可变。MQTT 客户端和服务端通过交换控制报文来完成它们的工作,比如订阅主题和发布消息。
2.1 报文类型#
MQTT 目前定义了 15 种控制报文类型,如果按照功能进行分类,我们可以将这些报文分为连接、发布、订阅三个类别:
2.2 报文格式#
在 MQTT 中,无论是什么类型的控制报文,它们都由固定报头、可变报头和有效载荷三个部分组成。
固定报头固定存在于所有控制报文中,而可变报头和有效载荷是否存在以及它们的内容则取决于具体的报文类型。例如用于维持连接的 PINGREQ 报文就只有一个固定报头,用于传递应用消息的 PUBLISH 报文则完整地包含了这三个部分。
a. 固定报头#
固定报头由报文类型、标识位和报文剩余长度三个字段组成。
报文类型
占 4 bit,是一个无符号的整数。
常见的报文类型:https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901022
标识位
占 4 bit,不过到 MQTT 5.0 为止,只有 PUBLISH
报文的这 4bit 被赋予了明确的含义:
比特位 | 含义 | - |
---|---|---|
Bit 3 | DUP | 表示当前 PUBLISH 报文是否是一个重传的报文 |
Bit 2,1 | QoS | 表示当前 PUBLISH 报文使用的服务质量等级 |
Bit 0 | Retain | 表示当前 PUBLISH 报文是否是一个保留消息 |
其他所有的报文中,这 4 位都仍是保留的。
剩余长度
剩余长度指示了当前控制报文剩余部分的字节数,也就是「可变报头」和「有效载荷」这两个部分的长度。
MQTT 控制报文的总长度 = 固定报头的长度 + 剩余长度
b. 可变报头#
可变报头的内容取决于具体的报文类型。
举例:
- CONNECT 报文的可变报头按顺序包含了协议名、协议级别、连接标识、KeepAlive 和属性这五个字段;
- PUBLISH 报文的可变报头则按顺序包含了主题名、报文标识符和属性这三个字段。
属性是 MQTT 5.0 引入的一个概念。属性字段基本上都是可变报头的最后一部分,由属性长度和紧随其后的一组属性组成,这里的属性长度指的是后面所有属性的总长度。
所有的属性都是可选的,因为它们通常都有一个默认值,如果没有任何属性,那么属性长度的值就为 0。属性通常都是为了某个专门的用途而设计的,不同的报文所支持的属性都是不一样的。具体的对应情况可以查看官网地址:https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901027
c. 有效载荷#
有效载荷是用于实现对应报文的核心功能。
举例:
1、在 PUBLISH 报文中,Payload 用于承载具体的应用消息内容,这也是 PUBLISH 报文最核心的功能。
2、在 SUBSCRIBE 报文中,Payload 包含了想要订阅的主题以及对应的订阅选项,这也是 SUBSCRIBE 报文最主要的工作。
2.3 报文验证#
接下来我们通过 Wireshark 工具,抓取一下各种通讯操作所涉及到的报文。
连接相关报文验证:
发布相关报文:
上述发布的消息 QoS 的值设置为 0,因此看不到发布消息的时候的其他的报文数据,如果此时把消息的 QoS 等级设置为 1、2 就可以看到发送消息的时候其他的报文:
QoS 的值设置为 2 以后,抓取的报文数据如下所示:
订阅相关报文:
3. QoS 消息服务质量#
使用 MQTT 协议的设备大部分都是运行在网络受限的环境下,而只依靠底层的 TCP 传输协议,并不能完全保证消息的可靠到达。
MQTT 协议中规定了消息服务质量(Quality of Service),它保证了在不同的网络环境下消息传递的可靠性,QoS 的设计是 MQTT 协议里的重点。作为专为物联网场景设计的协议,MQTT 的运行场景不仅仅是 PC,而是更广泛的窄带宽网络和低功耗设备,如果能在协议层解决传输质量的问题,将为物联网应用的开发提供极大便利。
MQTT 提供了 QoS 机制,其核心是设计多种消息交互机制来提供不同的服务质量,来满足用户在各种场景下对消息可靠性的要求。
MQTT 定义了三个 QoS 等级,分别为:
等级 | 影响 | |
---|---|---|
QoS 0 | 最多交付一次 | 可能丢失消息 |
QoS 1 | 至少交付一次 | 可以保证收到消息,但消息可能重复 |
QoS 2 | 只交付一次 | 可以保证消息既不丢失也不重复 |
QoS 等级是由发布者在 PUBLISH 报文中指定的,大部分情况下 Broker 向订阅者转发消息时都会维持原始的 QoS 不变。不过也有一些例外的情况,根据订阅者的订阅要求,消息的 QoS 等级可能会在转发的时候发生降级。
例如,订阅者在订阅时要求 Broker 可以向其转发的消息的最大 QoS 等级为 QoS 1,那么后续所有 QoS 2 消息都会降级至 QoS 1 转发给此订阅者,而所有 QoS 0 和 QoS 1 消息则会保持原始的 QoS 等级转发。
不同情况下客户端收到的消息 QoS 可参考下表(木桶原理):
发布消息的 QoS | 主题订阅的 QoS | 接收消息的 QoS |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
0 | 2 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
1 | 2 | 1 |
2 | 0 | 0 |
2 | 1 | 1 |
2 | 2 | 2 |
3.1 QoS 0#
QoS 0 是最低的 QoS 等级。QoS 0 消息即发即弃,不需要等待确认,不需要存储和重传,因此对于接收方来说,永远都不需要担心收到重复的消息。
涉及到的相关报文:
QoS 0 消息丢失原因:
当我们使用 QoS 0 传递消息时,消息的可靠性完全依赖于底层的 TCP 协议。而 TCP 只能保证在连接稳定不关闭的情况下消息的可靠到达,一旦出现连接关闭、重置,仍有可能丢失当前处于网络链路或操作系统底层缓冲区中的消息。这也是 QoS 0 消息最主要的丢失场景。
3.2 QoS 1#
为了保证消息到达,QoS 1 加入了应答与重传机制,发送方只有在收到接收方的 PUBACK 报文以后,才能认为消息投递成功,在此之前,发送方需要存储该 PUBLISH 报文以便下次重传。
QoS 1 需要在 PUBLISH 报文中设置 Packet ID,而作为响应的 PUBACK 报文,则会使用与 PUBLISH 报文相同的 Packet ID,以便发送方收到后删除正确 PUBLISH 报文缓存。
涉及到的相关报文:
QoS 1 消息会重复原因:
对于发送方来说,没收到 PUBACK 报文分为以下两种情况:
- PUBLISH 未到达接收方
- PUBLISH 已经到达接收方,接收方的 PUBACK 报文还未到达发送方
在第一种情况下,发送方虽然重传了 PUBLISH 报文,但是对于接收方来说,实际上仍然仅收到了一次消息。在第二种情况下,在发送方重传时,接收方已经收到过了这个 PUBLISH 报文,这就导致接收方将收到重复的消息。
重传 PUBLISH 报文的时候,PUBLISH 中的 DUP 标志会被设置为 1,用以表示这是一个重传的报文。
3.3 QoS 2#
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 1 相同
消息不会重复原因:
快速回顾一下 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 消息的去重。
3.4 适用场景#
QoS 0
QoS 0 的缺点是可能会丢失消息,消息丢失的频率依赖于你所处的网络环境,并且可能使你错过断开连接期间的消息,不过优点是投递的效率较高。
所以我们通常选择使用 QoS 0 传输一些高频且不那么重要的数据,比如传感器数据,周期性更新,即使遗漏几个周期的数据也可以接受。
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 的使用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2020-11-09 10-索引优化分析(2)
2020-11-09 09-索引优化分析(1)