vault backup: 2026-05-24 20:51:06
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
---
|
||||
tags:
|
||||
- MQ
|
||||
create time: 2026-05-24 19:52
|
||||
---
|
||||
|
||||
# MQ 消息协议总览
|
||||
|
||||
## 概述
|
||||
|
||||
消息协议是消息队列系统中 Producer、Broker、Consumer 之间的"通信语言"。不同的 MQ 产品使用不同的协议,协议的选择直接影响互操作性、性能和适用场景。本文梳理主流消息协议的设计定位与演进脉络。
|
||||
|
||||
## 正文
|
||||
|
||||
### 为什么需要消息协议
|
||||
|
||||
试想一个场景:你用 RabbitMQ 的客户端库去连接 Kafka,能成功吗?显然不能。因为它们"说的不是同一种语言"。消息协议就是定义客户端与 Broker 之间如何交换数据的规范——消息怎么编码、命令怎么发送、连接怎么建立、确认怎么回传,全都由协议约定。
|
||||
|
||||
协议存在的意义在于两点:**互操作性**和**标准化**。有了统一的协议,不同厂商的客户端和 Broker 可以互相通信;没有协议(或协议私有),就只能绑定在单一产品上。
|
||||
|
||||
> [!question]
|
||||
> 如果你是一家公司的架构师,需要同时接入 RabbitMQ 和 Kafka,你会选择统一协议层还是各自接入?为什么?
|
||||
|
||||
### 协议栈层次模型
|
||||
|
||||
消息协议工作在应用层,底层依赖 TCP 或 WebSocket 等传输协议。层次关系如下:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
AppLayer["应用层协议: AMQP, MQTT, STOMP, JMS, OpenMessaging"]
|
||||
TransLayer["传输层: TCP / WebSocket / TLS"]
|
||||
NetLayer["网络层: IP"]
|
||||
|
||||
AppLayer --> TransLayer
|
||||
TransLayer --> NetLayer
|
||||
```
|
||||
|
||||
关键点:**应用层协议决定了消息的语义和格式,传输层只负责字节流的可靠传输**。这也意味着同一个应用层协议可以跑在 TCP 上(性能优先),也可以跑在 WebSocket 上(Web 场景)。
|
||||
|
||||
### 主流协议一览
|
||||
|
||||
| 协议 | 定位 | 设计目标 | 典型场景 | 代表产品 |
|
||||
|------|------|----------|----------|----------|
|
||||
| **AMQP 0-9-1** | 企业级消息中间件标准 | 可靠投递、灵活路由、事务支持 | 金融交易、企业集成 | RabbitMQ |
|
||||
| **AMQP 1.0** | AMQP 的正式 ISO 标准版本 | 跨平台互操作、去掉 Broker 强依赖 | 云原生、跨组织通信 | Azure Service Bus, ActiveMQ |
|
||||
| **MQTT** | IoT 轻量级协议 | 低带宽、低功耗、离线支持 | 物联网、移动推送 | EMQX, Mosquitto |
|
||||
| **STOMP** | 简单文本协议 | 易于调试、人可读 | WebSocket 消息、简单队列 | RabbitMQ (STOMP 插件) |
|
||||
| **JMS** | Java 消息服务 API 规范 | Java 生态统一编程接口 | Java 企业应用 | ActiveMQ, IBM MQ |
|
||||
| **OpenMessaging** | 跨语言跨平台标准 | 云原生、厂商中立 | 多云部署 | 阿里云 RocketMQ |
|
||||
| **Kafka 私有协议** | 高吞吐流处理 | 追求极致性能,协议与实现强绑定 | 日志收集、实时流计算 | Apache Kafka |
|
||||
|
||||
> [!question]
|
||||
> 观察这张表,你会发现"标准化"和"性能优化"之间似乎存在矛盾——Kafka 放弃了标准化却获得了极致性能。你觉得这种取舍合理吗?
|
||||
|
||||
### 协议的设计哲学差异
|
||||
|
||||
协议之间的差异不只是格式不同,背后是**设计哲学的分歧**:
|
||||
|
||||
- **AMQP** 走的是"标准化 + 丰富语义"路线。它定义了 Exchange、Queue、Binding 等抽象模型,Broker 承担了大量路由和过滤工作。好处是功能强大,代价是协议复杂。
|
||||
- **MQTT** 走的是"极简 + 弱网适应"路线。协议报文最小只有 2 字节,支持 QoS 0/1/2 三级投递保障,专门为带宽受限的 IoT 设备设计。
|
||||
- **Kafka** 走的是"性能优先"路线。它的私有协议直接操作文件系统的 offset,零拷贝传输,协议与存储引擎深度耦合。标准化反而会成为性能瓶颈。
|
||||
- **STOMP** 走的是"简单可读"路线。纯文本协议,用 `telnet` 就能手动发送消息,调试极其方便,但性能和功能都比较有限。
|
||||
|
||||
### 协议演进趋势
|
||||
|
||||
消息协议的演进大致经历了三个阶段:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Phase1["阶段1: 私有协议"]
|
||||
Phase2["阶段2: 标准化"]
|
||||
Phase3["阶段3: 跨平台云原生"]
|
||||
|
||||
Phase1 -->|"JMS 规范 Java 生态"| Phase2
|
||||
Phase2 -->|"AMQP 0-9-1 业界采用"| Phase3
|
||||
Phase3 -->|"AMQP 1.0 / OpenMessaging"| Future["未来: 协议互操作"]
|
||||
```
|
||||
|
||||
1. **私有协议时代**:每个 MQ 厂商各搞一套,JMS 虽然是规范但限于 Java 生态,且只定义 API 不定义线路协议(wire protocol),不同实现之间依然不互通。
|
||||
2. **标准化时代**:AMQP 0-9-1 的出现让 RabbitMQ 成为事实标准。协议定义了线路层格式,真正实现了"一个客户端连多个 Broker"的可能性。
|
||||
3. **跨平台云原生时代**:AMQP 1.0 成为 ISO/IEC 标准,去掉了对 Broker 模型的强依赖;OpenMessaging 由中国厂商主导,面向多云和 Serverless 场景。
|
||||
|
||||
> [!question]
|
||||
> OpenMessaging 是由阿里等中国厂商主导的协议标准。你觉得在消息中间件领域,中国厂商能否主导下一个通用标准?需要具备哪些条件?
|
||||
|
||||
### 如何选择协议
|
||||
|
||||
选协议本质上是选场景。几个判断维度:
|
||||
|
||||
- **需要 IoT/移动端?** → MQTT(轻量、离线消息)
|
||||
- **需要企业级可靠投递?** → AMQP(确认机制、事务、灵活路由)
|
||||
- **需要高吞吐流处理?** → Kafka 私有协议(性能无可替代)
|
||||
- **需要快速调试或 WebSocket 场景?** → STOMP(文本协议、简单直观)
|
||||
- **需要跨云厂商中立?** → OpenMessaging 或 AMQP 1.0
|
||||
|
||||
没有银弹,理解每种协议的设计意图,才能做出合理的技术选型。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[5-AMQP-协议]]
|
||||
@@ -0,0 +1,238 @@
|
||||
---
|
||||
tags:
|
||||
- MQ
|
||||
create time: 2026-05-24 19:52
|
||||
---
|
||||
|
||||
# AMQP 协议
|
||||
|
||||
## 概述
|
||||
|
||||
AMQP(Advanced Message Queuing Protocol)是应用最广泛的消息队列开放标准之一。本文重点解析 AMQP 0-9-1 版本的核心模型、Exchange 路由机制,并与 1.0 版本进行对比,最后给出 Go 语言的完整收发示例。
|
||||
|
||||
## 正文
|
||||
|
||||
### AMQP 的版本线
|
||||
|
||||
AMQP 并非只有一个版本,理解版本差异是正确使用它的前提:
|
||||
|
||||
- **AMQP 0-8**:早期版本,功能有限,已基本淘汰。
|
||||
- **AMQP 0-9-1**:2008 年发布,是 RabbitMQ 实现的版本。定义了完整的 Broker 模型(Exchange/Queue/Binding),是目前**使用最广泛**的版本。严格来说它不是 OASIS 正式标准,而是社区规范。
|
||||
- **AMQP 0-10**:由另一组人维护,与 0-9-1 差异较大,采用者较少(如 Apache Qpid)。
|
||||
- **AMQP 1.0**:2012 年成为 OASIS 标准,后纳入 ISO/IEC 19464。这是一个**完全重新设计**的协议,去掉了 Exchange 等抽象概念,引入 Link/Terminus 模型。Azure Service Bus、ActiveMQ Artemis 等支持此版本。
|
||||
|
||||
> [!question]
|
||||
> 0-9-1 和 1.0 看起来像是两个完全不同的协议。为什么同一个标准组织会允许如此大的断裂?你觉得这是技术进步还是标准碎片化?
|
||||
|
||||
### AMQP 0-9-1 核心模型
|
||||
|
||||
AMQP 0-9-1 的消息流转模型是理解 RabbitMQ 的基础:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Producer["Producer"]
|
||||
Exchange["Exchange"]
|
||||
QueueA["Queue A"]
|
||||
QueueB["Queue B"]
|
||||
ConsumerA["Consumer A"]
|
||||
ConsumerB["Consumer B"]
|
||||
|
||||
Producer -->|"发送消息 + Routing Key"| Exchange
|
||||
Exchange -->|"Binding Key: order.*"| QueueA
|
||||
Exchange -->|"Binding Key: log.#"| QueueB
|
||||
QueueA --> ConsumerA
|
||||
QueueB --> ConsumerB
|
||||
```
|
||||
|
||||
核心组件及其职责:
|
||||
|
||||
| 组件 | 职责 |
|
||||
|------|------|
|
||||
| **Connection** | TCP 长连接,负责认证和加密 |
|
||||
| **Channel** | Connection 内的虚拟连接,多路复用,避免频繁建 TCP |
|
||||
| **Exchange** | 消息路由引擎,根据规则将消息分发到 Queue |
|
||||
| **Queue** | 消息存储容器,消费者从这里拉取或推送消息 |
|
||||
| **Binding** | Exchange 和 Queue 之间的绑定关系,携带路由规则 |
|
||||
|
||||
消息投递流程:Producer 将消息发送到 Exchange(不是直接发到 Queue),Exchange 根据自身的类型和 Binding 规则决定消息去往哪些 Queue。**这种间接路由模型是 AMQP 最核心的设计思想。**
|
||||
|
||||
### Connection 与 Channel
|
||||
|
||||
一个 TCP Connection 上可以复用多个 Channel,这是 AMQP 的性能优化手段:
|
||||
|
||||
```go
|
||||
// 建立一个 Connection
|
||||
conn, _ := amqp.Dial("amqp://guest:guest@localhost:5672/")
|
||||
defer conn.Close()
|
||||
|
||||
// 在同一 Connection 上打开两个 Channel
|
||||
ch1, _ := conn.Channel()
|
||||
ch2, _ := conn.Channel()
|
||||
// ch1 和 ch2 互不干扰,可以并行操作
|
||||
```
|
||||
|
||||
为什么需要 Channel?因为每个线程/协程持有独立的 Channel 可以避免锁竞争,而且建立 TCP 连接的开销远大于创建 Channel。
|
||||
|
||||
### 四种 Exchange 类型
|
||||
|
||||
Exchange 是消息路由的核心,不同类型的 Exchange 适用于不同的路由策略。
|
||||
|
||||
#### Direct Exchange
|
||||
|
||||
精确匹配 Routing Key 和 Binding Key。消息只会到达 Binding Key 完全匹配的 Queue。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
P["Producer"] -->|"RoutingKey: order.created"| Ex["Direct Exchange"]
|
||||
Ex -->|"BindKey: order.created"| Q1["Order Queue"]
|
||||
Ex -->|"BindKey: payment.created"| Q2["Payment Queue"]
|
||||
```
|
||||
|
||||
适用场景:点对点的消息分发,比如将订单消息路由到订单处理队列。
|
||||
|
||||
#### Fanout Exchange
|
||||
|
||||
忽略 Routing Key,将消息广播到所有绑定的 Queue。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
P["Producer"] -->|"RoutingKey: (忽略)"| Ex["Fanout Exchange"]
|
||||
Ex --> Q1["Analytics Queue"]
|
||||
Ex --> Q2["Logging Queue"]
|
||||
Ex --> Q3["Audit Queue"]
|
||||
```
|
||||
|
||||
适用场景:日志广播、事件通知,一份消息多个消费者各自处理。
|
||||
|
||||
#### Topic Exchange
|
||||
|
||||
基于模式匹配的路由。Binding Key 和 Routing Key 都用 `.` 分隔单词,支持两个通配符:`*` 匹配一个单词,`#` 匹配零或多个单词。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
P["Producer"] -->|"RoutingKey: order.pay.success"| Ex["Topic Exchange"]
|
||||
Ex -->|"BindKey: order.*.success"| Q1["Success Queue"]
|
||||
Ex -->|"BindKey: order.#"| Q2["All Orders Queue"]
|
||||
Ex -->|"BindKey: log.#"| Q3["Log Queue"]
|
||||
```
|
||||
|
||||
适用场景:灵活的事件订阅,消费者可以按需订阅感兴趣的消息子集。
|
||||
|
||||
#### Headers Exchange
|
||||
|
||||
不依赖 Routing Key,而是根据消息 Headers 中的键值对进行匹配。绑定时指定一组键值对和匹配规则(`x-match=all` 表示全部匹配,`x-match=any` 表示任一匹配)。
|
||||
|
||||
适用场景:消息属性复杂、需要多维度过滤的场景。实际使用较少,因为 Topic Exchange 在大多数情况下已经够用。
|
||||
|
||||
> [!question]
|
||||
> 四种 Exchange 类型中,Fanout 最简单、Headers 最复杂。但在实际项目中,Direct 和 Topic 的使用频率远高于另外两种。你觉得这是为什么?
|
||||
|
||||
### Virtual Host:多租户隔离
|
||||
|
||||
Virtual Host(vhost)是 AMQP 中的逻辑隔离单元。每个 vhost 拥有独立的 Exchange、Queue 和 Binding 集合,不同 vhost 之间的资源完全隔离。
|
||||
|
||||
典型用途:
|
||||
- **环境隔离**:`/dev`、`/staging`、`/prod` 各用一个 vhost
|
||||
- **租户隔离**:SaaS 平台为每个租户分配独立 vhost
|
||||
- **权限控制**:可以按 vhost 粒度分配用户权限
|
||||
|
||||
```go
|
||||
// 连接时指定 vhost
|
||||
conn, _ := amqp.Dial("amqp://user:pass@localhost:5672/production")
|
||||
```
|
||||
|
||||
### AMQP 1.0 vs 0-9-1 的关键差异
|
||||
|
||||
AMQP 1.0 并非 0-9-1 的升级版,而是**重新设计**。核心差异:
|
||||
|
||||
| 维度 | 0-9-1 | 1.0 |
|
||||
|------|-------|-----|
|
||||
| 路由模型 | Exchange + Binding + Queue | Link + Terminus(Source/Target) |
|
||||
| Broker 角色 | Broker 负责路由和存储 | Broker 是可选的,可以点对点 |
|
||||
| 协议复杂度 | 中等,概念清晰 | 较高,状态机复杂 |
|
||||
| 生态 | RabbitMQ 生态完善 | Azure、ActiveMQ 支持 |
|
||||
| 标准化 | 非正式标准 | ISO/IEC 19464 |
|
||||
|
||||
1.0 中 Producer 通过 Link 直接连接到 Target(类似 Queue),不再经过 Exchange 路由。这简化了点对点通信,但失去了 0-9-1 灵活的路由能力。
|
||||
|
||||
> [!question]
|
||||
> AMQP 1.0 去掉了 Exchange 概念。如果让你重新设计,你会保留 Exchange 还是去掉它?Exchange 的灵活性和复杂度之间如何权衡?
|
||||
|
||||
### Go 代码:完整收发示例
|
||||
|
||||
使用 `amqp091-go` 库(RabbitMQ 官方维护的 Go 客户端)演示 Topic Exchange 的消息收发。
|
||||
|
||||
**发送端**:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conn, _ := amqp.Dial("amqp://guest:guest@localhost:5672/")
|
||||
defer conn.Close()
|
||||
|
||||
ch, _ := conn.Channel()
|
||||
defer ch.Close()
|
||||
|
||||
// 声明 Topic Exchange
|
||||
ch.ExchangeDeclare("events", "topic", true, false, false, false, nil)
|
||||
|
||||
// 声明两个队列
|
||||
q1, _ := ch.QueueDeclare("order-events", true, false, false, false, nil)
|
||||
q2, _ := ch.QueueDeclare("log-events", true, false, false, false, nil)
|
||||
|
||||
// 绑定:order 相关事件 → order-events 队列
|
||||
ch.QueueBind(q1.Name, "order.*", "events", false, nil)
|
||||
// 绑定:所有事件 → log-events 队列
|
||||
ch.QueueBind(q2.Name, "#", "events", false, nil)
|
||||
|
||||
// 发送一条消息,Routing Key 为 order.created
|
||||
ch.Publish("events", "order.created", false, false,
|
||||
amqp.Publishing{
|
||||
ContentType: "application/json",
|
||||
Body: []byte(`{"order_id": 12345, "amount": 99.9}`),
|
||||
})
|
||||
log.Println("消息已发送")
|
||||
}
|
||||
```
|
||||
|
||||
**接收端**:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conn, _ := amqp.Dial("amqp://guest:guest@localhost:5672/")
|
||||
defer conn.Close()
|
||||
|
||||
ch, _ := conn.Channel()
|
||||
defer ch.Close()
|
||||
|
||||
// 消费 order-events 队列
|
||||
msgs, _ := ch.Consume("order-events", "", false, false, false, false, nil)
|
||||
|
||||
for msg := range msgs {
|
||||
log.Printf("收到订单消息: %s", msg.Body)
|
||||
msg.Ack(false) // 手动确认,确保消息不丢失
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
代码要点:
|
||||
- `ExchangeDeclare` 声明 Exchange,`true` 表示持久化,Broker 重启后 Exchange 依然存在。
|
||||
- `QueueBind` 将 Queue 绑定到 Exchange 并指定 Binding Key,这里用 `order.*` 匹配 `order.created`、`order.paid` 等。
|
||||
- `Consume` 的 `autoAck` 设为 `false`,配合 `msg.Ack(false)` 实现手动确认,避免消费者崩溃时消息丢失。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[4-MQ-消息协议总览]]
|
||||
@@ -0,0 +1,170 @@
|
||||
---
|
||||
tags: [MQ, MQTT, IoT, 协议]
|
||||
create time: 2026-05-24 19:52
|
||||
---
|
||||
|
||||
# MQTT 协议
|
||||
|
||||
## 概述
|
||||
|
||||
MQTT(Message Queuing Telemetry Transport)是专为物联网(IoT)场景设计的轻量级消息协议。它的设计哲学极其明确:在低带宽、高延迟、不可靠的网络环境下,用最小的资源开销实现设备间的消息传递。从智能家居传感器到工业 SCADA 系统,MQTT 已经成为 IoT 通信的事实标准。
|
||||
|
||||
## 正文
|
||||
|
||||
### 为什么需要 MQTT?
|
||||
|
||||
想象一个场景:成千上万个温度传感器部署在偏远地区,它们通过 2G/3G 网络上报数据。这些设备 CPU 弱、内存小、电量有限、网络时断时续。在这种约束下,HTTP 的冗长头部和请求-响应模型就成了灾难。MQTT 就是为解决这类问题而生的——它把协议开销压缩到极致(最小报文仅 2 字节),支持持久连接,提供灵活的消息可靠性保障。
|
||||
|
||||
### 报文结构
|
||||
|
||||
MQTT 的每条报文由三部分组成:
|
||||
|
||||
```
|
||||
+------------------+--------------------+----------+
|
||||
| Fixed Header | Variable Header | Payload |
|
||||
+------------------+--------------------+----------+
|
||||
```
|
||||
|
||||
- **Fixed Header**(必选):包含报文类型(4 bit)、标志位(4 bit)和剩余长度(可变字节编码)。报文类型定义了 14 种操作,如 CONNECT、PUBLISH、SUBSCRIBE 等。
|
||||
- **Variable Header**(可选):根据报文类型不同而变化,比如 PUBLISH 报文里放 Topic 名称,CONNECT 报文里放协议版本和连接标志。
|
||||
- **Payload**(可选):实际消息内容。CONNECT 报文的 Payload 是 Client ID 和遗嘱消息,PUBLISH 报文的 Payload 就是应用数据。
|
||||
|
||||
> [!question] 为什么 Fixed Header 的"剩余长度"要用可变字节编码(Variable Byte Encoding)?
|
||||
> 因为 IoT 场景下大部分消息体很小(几十字节),如果用固定 4 字节表示长度就太浪费了。可变编码让 0-127 字节的长度只占 1 字节,128-16383 字节占 2 字节,以此类推——小消息用小开销,大消息也能表示。
|
||||
|
||||
### QoS 等级:三种可靠性语义
|
||||
|
||||
MQTT 最精妙的设计之一就是三个 QoS(Quality of Service)等级,让发布者按需选择投递保证,而不是"一刀切"。
|
||||
|
||||
**QoS 0 - 最多一次(At Most Once)**
|
||||
|
||||
fire-and-forget,发送即忘。没有确认机制,消息可能丢失。适合传感器周期性上报——丢一帧温度数据无所谓,下一帧马上就来。
|
||||
|
||||
**QoS 1 - 至少一次(At Least Once)**
|
||||
|
||||
发布者收到 PUBACK 后才确认发送完成。如果超时未收到 ACK,会重发消息(DUP 标志置 1)。这意味着消息不会丢,但可能重复投递——消费者需要自行处理幂等。
|
||||
|
||||
**QoS 2 - 恰好一次(Exactly Once)**
|
||||
|
||||
通过四次握手保证消息恰好送达一次,是最高等级但也最重的语义。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant P as "Publisher"
|
||||
participant B as "Broker"
|
||||
participant C as "Consumer"
|
||||
|
||||
Note over P,B: "QoS 0: fire and forget"
|
||||
P->>B: "PUBLISH QoS 0"
|
||||
B->>C: "PUBLISH QoS 0"
|
||||
|
||||
Note over P,B: "QoS 1: at least once"
|
||||
P->>B: "PUBLISH QoS 1"
|
||||
B-->>P: "PUBACK"
|
||||
B->>C: "PUBLISH QoS 1"
|
||||
C-->>B: "PUBACK"
|
||||
|
||||
Note over P,B: "QoS 2: exactly once"
|
||||
P->>B: "PUBLISH QoS 2"
|
||||
B-->>P: "PUBREC"
|
||||
P->>B: "PUBREL"
|
||||
B-->>P: "PUBCOMP"
|
||||
B->>C: "PUBLISH QoS 2"
|
||||
C-->>B: "PUBREC"
|
||||
B->>C: "PUBREL"
|
||||
C-->>B: "PUBCOMP"
|
||||
```
|
||||
|
||||
> [!question] QoS 2 的四次握手会不会很慢?在什么场景下值得用?
|
||||
> 相比 QoS 0/1,QoS 2 确实多了额外的往返开销。但在需要精确计数的场景下(如扣费指令、库存变更、IoT 设备的命令下发),一条消息重复执行可能导致严重后果。实际部署中,很多系统选择 QoS 1 + 消费端幂等的组合来平衡性能和可靠性,只在真正"不能重复"的关键路径上才用 QoS 2。
|
||||
|
||||
### 核心概念
|
||||
|
||||
#### Topic 与通配符
|
||||
|
||||
MQTT 使用层级结构的 Topic 进行消息路由,用 `/` 分隔层级,例如 `home/livingroom/temperature`。
|
||||
|
||||
订阅时支持两种通配符:
|
||||
- `+`:匹配单个层级。`home/+/temperature` 匹配 `home/livingroom/temperature` 和 `home/bedroom/temperature`。
|
||||
- `#`:匹配多个层级(必须放在末尾)。`home/#` 匹配 `home/` 下所有 Topic。
|
||||
|
||||
#### 遗嘱消息(Will Message)
|
||||
|
||||
客户端在连接时可以设置一条"遗嘱消息"。如果客户端异常断线(没有发送 DISCONNECT),Broker 会自动将这条遗嘱消息发布到指定 Topic。这对 IoT 设备的在线状态监控非常有用——订阅遗嘱 Topic 就能实时知道哪些设备掉线了。
|
||||
|
||||
#### 保留消息(Retain)
|
||||
|
||||
当 PUBLISH 报文的 Retain 标志置 1 时,Broker 会保存这条消息。后续任何新订阅该 Topic 的客户端会立即收到这条保留消息,而不是等到下一次发布。典型场景:传感器发布当前温度为保留消息,新上线的监控端一订阅就能拿到最新值,不用等下一次上报。
|
||||
|
||||
#### Clean Session
|
||||
|
||||
- **Clean Session = 1**:每次连接都是全新会话,Broker 不保留任何订阅关系和未确认消息。适合无状态客户端。
|
||||
- **Clean Session = 0**:Broker 为客户端持久化会话(订阅列表 + QoS 1/2 的未确认消息)。客户端断线重连后能恢复之前的订阅并收到离线期间的消息。
|
||||
|
||||
### MQTT 5.0 新特性
|
||||
|
||||
MQTT 5.0(2019 年发布)是一次重大升级,引入了多项企业级特性:
|
||||
|
||||
- **共享订阅(Shared Subscriptions)**:多个客户端组成一个订阅组,消息在组内负载均衡分发。这解决了 MQTT 之前缺乏 Consumer Group 的痛点,类似 Kafka 的分区分配。
|
||||
- **用户属性(User Properties)**:在报文头部附加自定义键值对,可用于传递链路追踪 ID、业务上下文等元信息,而不用修改消息体。
|
||||
- **原因码(Reason Codes)**:所有响应报文都携带结构化的原因码,取代了之前模糊的返回码,方便客户端精确处理错误(如"Topic 名称不合法" vs "鉴权失败")。
|
||||
- **Session Expiry Interval**:可以设置会话过期时间,不再只有"立即过期"和"永不过期"两种选择。
|
||||
- **请求-响应模式**:引入 Response Topic 和 Correlation Data 属性,让 MQTT 原生支持请求-响应模式。
|
||||
|
||||
### Go 代码示例
|
||||
|
||||
使用 Eclipse Paho MQTT Go 客户端实现消息的发布和订阅。
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建客户端选项
|
||||
opts := mqtt.NewClientOptions().
|
||||
AddBroker("tcp://localhost:1883").
|
||||
SetClientID("go-mqtt-demo").
|
||||
SetCleanSession(true)
|
||||
|
||||
// 设置连接成功后的订阅回调
|
||||
opts.OnConnect = func(c mqtt.Client) {
|
||||
// 订阅 Topic,QoS 1
|
||||
c.Subscribe("sensor/temperature", 1, func(_ mqtt.Client, msg mqtt.Message) {
|
||||
fmt.Printf("收到消息: topic=%s payload=%s\n", msg.Topic(), string(msg.Payload()))
|
||||
})
|
||||
}
|
||||
|
||||
client := mqtt.NewClient(opts)
|
||||
if token := client.Connect(); token.Wait() && token.Error() != nil {
|
||||
panic(token.Error())
|
||||
}
|
||||
|
||||
// 发布消息,QoS 1,Retain 标志设为 false
|
||||
token := client.Publish("sensor/temperature", 1, false, "23.5°C")
|
||||
token.Wait()
|
||||
|
||||
time.Sleep(time.Second)
|
||||
client.Disconnect(250)
|
||||
}
|
||||
```
|
||||
|
||||
这段代码做了三件事:连接 Broker、订阅 `sensor/temperature` Topic、发布一条温度消息。`OnConnect` 回调确保断线重连后会重新订阅,这是 MQTT 客户端开发的最佳实践。
|
||||
|
||||
---
|
||||
|
||||
> [!tip] 实践建议
|
||||
> - IoT 设备资源紧张时优先用 QoS 0,需要可靠投递用 QoS 1,关键控制指令才用 QoS 2。
|
||||
> - 生产环境务必启用 TLS 加密。MQTT 默认端口 1883 是明文的,安全端口是 8883。
|
||||
> - Client ID 必须全局唯一,否则会踢掉前一个同 ID 的连接。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[03-协议与标准/4-MQ-消息协议总览|MQ 消息协议总览]]
|
||||
- [[03-协议与标准/5-AMQP-协议|AMQP 协议]]
|
||||
- [[01-基础概念/2-MQ-适用场景与选型原则|MQ 适用场景与选型原则]]
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
tags: [MQ, JMS, OpenMessaging, Java, 协议]
|
||||
create time: 2026-05-24 19:52
|
||||
---
|
||||
|
||||
# JMS 与 OpenMessaging
|
||||
|
||||
## 概述
|
||||
|
||||
JMS(Java Message Service)是 Java 生态中消息中间件的接口规范,它不实现任何消息传输逻辑,而是定义了一套统一的 API,让应用代码可以在不同 MQ 产品之间切换。OpenMessaging 则是阿里巴巴发起的下一代开放标准,试图突破 JMS 的 Java 绑定和厂商碎片化问题,构建跨语言、跨平台、云原生的消息规范。
|
||||
|
||||
## 正文
|
||||
|
||||
### JMS 是什么?
|
||||
|
||||
JMS 不是一个 MQ 产品,而是一份"接口契约"。就像 JDBC 定义了数据库访问的标准接口,JMS 定义了消息通信的标准接口。你的代码面向 JMS API 编写,底层可以换成 ActiveMQ、IBM MQ、TIBCO 等任何 JMS 实现,理论上不需要改一行业务代码。
|
||||
|
||||
这个设计思路在 Java 一统天下的企业级开发时代非常有吸引力——应用服务器(如 WebLogic、WebSphere)内置 JMS 实现,开发者只需要注入 `ConnectionFactory` 就能开始收发消息。
|
||||
|
||||
### 两种消息模型
|
||||
|
||||
JMS 定义了两种经典的消息通信模型:
|
||||
|
||||
**Point-to-Point(队列模型)**
|
||||
|
||||
一条消息只被一个消费者消费。生产者把消息发到 Queue,消费者从 Queue 拉取。消费后消息从 Queue 中移除(或标记已消费)。典型场景:订单处理、任务分发。
|
||||
|
||||
**Pub/Sub(发布订阅模型)**
|
||||
|
||||
一条消息可以被多个订阅者同时接收。生产者发布到 Topic,所有订阅该 Topic 的消费者都会收到副本。典型场景:事件广播、通知推送。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
P1["Producer"] -->|"send"| Q["Queue"]
|
||||
Q -->|"consume"| C1["Consumer A"]
|
||||
Q -->|"consume"| C2["Consumer B"]
|
||||
|
||||
P2["Publisher"] -->|"publish"| T["Topic"]
|
||||
T -->|"deliver"| S1["Subscriber X"]
|
||||
T -->|"deliver"| S2["Subscriber Y"]
|
||||
T -->|"deliver"| S3["Subscriber Z"]
|
||||
|
||||
style Q fill:#4A90D9,color:#fff
|
||||
style T fill:#F5A623,color:#fff
|
||||
```
|
||||
|
||||
> [!question] Queue 模型中,如果多个消费者监听同一个 Queue,消息会被谁收到?
|
||||
> JMS 规范本身没有规定负载均衡策略,具体由实现厂商决定。但通常的行为是:消息在多个消费者之间轮询(round-robin)分发,每条消息只投递给其中一个消费者。这和 Kafka Consumer Group 的行为类似。
|
||||
|
||||
### JMS API 核心接口
|
||||
|
||||
JMS 的编程模型围绕以下几个核心接口展开:
|
||||
|
||||
| 接口 | 职责 |
|
||||
|------|------|
|
||||
| **ConnectionFactory** | 创建 Connection 的工厂,通常由 JMS 实现提供,配置了 Broker 地址和认证信息 |
|
||||
| **Connection** | 到 Broker 的 TCP 连接,支持异步消息分发和连接恢复 |
|
||||
| **Session** | 单线程上下文,用于创建 Producer/Consumer 和管理事务 |
|
||||
| **MessageProducer** | 发送消息到 Queue 或 Topic |
|
||||
| **MessageConsumer** | 从 Queue 或 Topic 接收消息,支持同步 `receive()` 和异步 `setMessageListener()` |
|
||||
| **Message** | 消息本体,包含 Header(JMSDestination、JMSDeliveryMode 等)、Properties(自定义键值对)和 Body |
|
||||
|
||||
### 消息类型
|
||||
|
||||
JMS 规范定义了五种消息体类型,覆盖了常见的数据传输需求:
|
||||
|
||||
| 类型 | 说明 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| **TextMessage** | 字符串文本(通常为 XML/JSON) | 最常用,Web 服务间通信 |
|
||||
| **ObjectMessage** | 序列化的 Java 对象 | Java 生态内部传输,但有安全风险 |
|
||||
| **MapMessage** | 键值对集合(类似 Map) | 结构化但不需要完整对象的场景 |
|
||||
| **BytesMessage** | 原始字节数组 | 二进制数据、跨语言场景 |
|
||||
| **StreamMessage** | 顺序读写的原始类型流 | 需要流式处理的场景 |
|
||||
|
||||
> [!question] ObjectMessage 看起来很方便,为什么生产中反而不推荐?
|
||||
> 因为 ObjectMessage 依赖 Java 序列化,存在严重的安全漏洞(反序列化攻击链)。而且它和 Java 绑死,跨语言几乎不可能。现代实践更倾向用 TextMessage + JSON/Protobuf 编码。
|
||||
|
||||
### JMS 的局限性
|
||||
|
||||
JMS 在 Java 企业级开发中统治了十多年,但它有几个根深蒂固的问题:
|
||||
|
||||
1. **Java 绑定**:JMS 是纯 Java API,无法直接被 Go、Python、C++ 等语言使用。在微服务和多语言架构兴起后,这个限制变得越来越致命。
|
||||
2. **缺乏跨语言互操作**:不同 JMS 实现之间的有线协议(wire protocol)不统一,ActiveMQ 用 OpenWire,IBM MQ 用 MQI,互相不能直连。
|
||||
3. **厂商实现碎片化**:虽然有统一规范,但各厂商在扩展特性(如延迟消息、事务消息、死信队列)上的实现差异很大,迁移成本并不低。
|
||||
4. **规范更新缓慢**:JMS 2.0(2013 年)才引入简化 API(JMSContext),在此之前用 JMS 写代码要处理大量的样板代码(try-finally 关闭资源)。
|
||||
|
||||
### OpenMessaging:面向云原生的开放标准
|
||||
|
||||
2017 年,阿里巴巴联合 Apache RocketMQ 团队发起了 OpenMessaging 项目,目标很明确:定义一个厂商中立、语言无关、云原生的消息和流标准规范。
|
||||
|
||||
OpenMessaging 的核心设计原则:
|
||||
|
||||
- **跨语言**:规范不限定编程语言,提供 Java、Go、C++ 等多语言 Binding。
|
||||
- **跨平台**:不绑定任何特定 MQ 产品,Kafka、RocketMQ、Pulsar 等都可以实现 OpenMessaging 接口。
|
||||
- **云原生**:原生支持分区、事务、消息追踪、延迟消息等现代需求,而不是事后打补丁。
|
||||
- **标准化扩展**:通过 Namespace、Region 等概念支持多租户和跨地域部署。
|
||||
|
||||
### OpenMessaging vs JMS 对比
|
||||
|
||||
| 维度 | JMS | OpenMessaging |
|
||||
|------|-----|---------------|
|
||||
| 发起方 | Sun Microsystems / Oracle | 阿里巴巴 + 社区 |
|
||||
| 语言绑定 | Java Only | 多语言(Java、Go、C++) |
|
||||
| 消息模型 | Queue + Topic | Partitioned Queue + Topic + Streaming |
|
||||
| 有线协议 | 不统一(厂商各自实现) | 标准化 RPC 协议 |
|
||||
| 事务支持 | JTA 事务集成 | 原生分布式事务 |
|
||||
| 云原生 | 无原生支持 | Namespace、多租户、弹性伸缩 |
|
||||
| 生态成熟度 | 非常成熟,20+ 年积累 | 仍在发展中,落地项目较少 |
|
||||
| 典型实现 | ActiveMQ、IBM MQ | Apache RocketMQ(部分支持) |
|
||||
|
||||
> [!question] OpenMessaging 提出了很好的愿景,为什么它的落地进展相对缓慢?
|
||||
> 一方面,Kafka 和 RocketMQ 各自已经形成了庞大生态,应用层集成稳定,切换标准的动力不足。另一方面,消息协议不像 HTTP 那样有极强的网络效应——MQ 的抽象层通常在 SDK 内部,业务开发者感知不到底层协议差异。标准化的收益没有数据库连接池(如 HikariCP 替换 DBCP)那么直接。
|
||||
|
||||
### Go 代码:JMS 思路的对等实现
|
||||
|
||||
JMS 没有官方 Go SDK,但我们可以用 Go 的接口抽象来实现类似 JMS 的分层设计。以下示例展示如何用面向接口的方式构建消息客户端,底层可插拔不同 MQ 实现。
|
||||
|
||||
```go
|
||||
package mq
|
||||
|
||||
// Message 对应 JMS 的 Message 接口
|
||||
type Message interface {
|
||||
Topic() string
|
||||
Body() []byte
|
||||
Properties() map[string]string
|
||||
}
|
||||
|
||||
// Producer 对应 JMS 的 MessageProducer
|
||||
type Producer interface {
|
||||
Send(topic string, body []byte, props map[string]string) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Consumer 对应 JMS 的 MessageConsumer
|
||||
type Consumer interface {
|
||||
Subscribe(topic string, handler func(Message)) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// ClientFactory 对应 JMS 的 ConnectionFactory
|
||||
// 不同 MQ 产品实现这个接口即可插拔替换
|
||||
type ClientFactory interface {
|
||||
NewProducer() (Producer, error)
|
||||
NewConsumer(group string) (Consumer, error)
|
||||
}
|
||||
```
|
||||
|
||||
这段代码的核心思想和 JMS 一模一样:面向接口编程。业务代码依赖 `Producer` / `Consumer` 接口,不关心底层是 Kafka、RocketMQ 还是 NATS。替换实现只需要换一个 `ClientFactory`,这正是 JMS 当年设计的初衷——只不过 JMS 把它限定在了 Java 生态里。
|
||||
|
||||
---
|
||||
|
||||
> [!tip] 实践建议
|
||||
> - 如果你的系统是纯 Java 栈,JMS 仍然是成熟可靠的选择,配合 Spring JMS Template 能大幅简化开发。
|
||||
> - 跨语言微服务架构下,优先考虑 Kafka 或 RocketMQ 的原生 SDK,它们的 Go/Python/Node.js 支持都很成熟。
|
||||
> - 不要为了"标准化"而标准化。协议的价值在于互操作性——如果你的系统不需要对接多个 MQ 产品,直接用原生 SDK 反而更简单。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[03-协议与标准/4-MQ-消息协议总览|MQ 消息协议总览]]
|
||||
- [[03-协议与标准/5-AMQP-协议|AMQP 协议]]
|
||||
- [[03-协议与标准/6-MQTT-协议|MQTT 协议]]
|
||||
- [[12-架构与实战/47-MQ-与微服务|MQ 与微服务]]
|
||||
Reference in New Issue
Block a user