163 lines
8.4 KiB
Markdown
163 lines
8.4 KiB
Markdown
---
|
||
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 与微服务]]
|