easy-messaging 消息总线组件设计
本文档描述 easy-messaging 组件的设计与实现。该组件提供后端中立的消息发送/消费抽象层,通过 SPI 接口(
BackendProducer/BackendConsumer)支持 7 种 MQ 后端(RocketMQ / Redis / Kafka / RabbitMQ / ZeroMQ / DBMS / AutoMQ),上层门面MessageSender<T>/MessageListener<T>屏蔽后端差异。所有后端均为 optional scope,应用按需引入;自动配置通过@AutoConfigureBefore(MessagingAutoConfiguration.class)汇聚锚点模式保证顺序。easy-event-bus Adapter 层通过EasyMessagingProducerAdapter桥接到本组件。
一、背景与问题
1.1 为什么要独立消息抽象层
Matrix Framework 需要跨进程通信能力(领域事件、网关设备下行、实时通知等),但不同项目对 MQ 的选择不同:
| MQ | 适用场景 | 选择者 |
|---|---|---|
| RocketMQ | 金融级可靠投递、大吞吐 | 生产用户 |
| Redis | 小项目快速接入、无独立 MQ 基础设施 | 中小项目 |
| DBMS | 零外部依赖、测试环境 | POC / 测试 |
| Kafka | 超大吞吐、流式处理 | 大数据场景 |
| RabbitMQ | 功能完善、社区活跃 | 通用场景 |
| ZeroMQ | 高性能点对点 | 低延迟场景 |
如果业务层硬编码 MQ 客户端(DefaultMQProducer / KafkaProducer),切换成本极高。easy-messaging 通过 SPI 把 MQ 差异隔离在 Connector 层。
1.2 与 Spring Cloud Stream 的区别
| 维度 | Spring Cloud Stream | Matrix easy-messaging |
|---|---|---|
| 目标 | 统一编程模型(Binder 抽象) | 直接 SPI 抽象 MQ 原生 API |
| 复杂度 | 高(Binder / Function / Supplier 三层) | 低(一层 SPI) |
| 依赖 | Spring Cloud 全家桶 | 仅 spring-messaging |
| 后端 | Binder 社区维护 | 内置 7 种 |
| 命令/事件语义 | 无 | 无(事件总线层定义) |
easy-messaging 选择轻量 SPI 路线,因为 Matrix Framework 已经在事件总线层提供事件语义(CQS 范式),messaging 层只需做”管道”。
二、组件定位与边界
定位:后端中立的消息管道。业务层通过 MessageSender<T> 发送、MessageListener<T> 消费,后端 SPI 自动注入唯一可用的 MQ Connector。
边界:
| 做 | 不做 |
|---|---|
| SPI 抽象(BackendProducer / BackendConsumer) | 不实现事件语义(由 easy-event-bus 定义) |
| 7 种 MQ Connector 实现 | 不做消息持久化(MQ 后端负责) |
| 注解驱动(@Producer / @Consumer) | 不做分布式事务 / 一致性协议 |
| 统一消息序列化格式(非 RocketMQ 后端用 JSON wrapper) | 不做消息幂等(由消费方实现) |
| 自动配置汇聚锚点(MessagingAutoConfiguration) | 不支持多后端并存(互斥 @ConditionalOnMissingBean) |
约束:
- 同一应用只应启用一个 MQ 后端——所有 Backend AutoConfiguration 都有
@ConditionalOnMissingBean(BackendProducer.class)+@ConditionalOnMissingBean(BackendConsumer.class) - 只有 RocketMQ 有真正的顺序发送——其余 6 个后端
sendOrderly/sendOneWayOrderly的hashKey被忽略 - DBMS Connector Producer/Consumer 默认表名不一致——Producer 默认
easy_messaging_bus,Consumer 默认event_message,需显式配置统一 - Kafka Consumer group.id 硬编码为
"easy-messaging-consumer-group" - 所有 MQ 后端均为 optional scope
三、总体架构
flowchart TD
subgraph "用户层"
A1["MessageSender<T> (接口)"]
A2["AbstractMessageSender<T> (薄代理)"]
A3["MessageListener<T> (抽象类)"]
A4["@Producer / @Consumer 注解"]
A5["Callback / Headers / Result / ConsumeMode"]
end
subgraph "SPI 层"
B1["BackendProducer (12 + 1 方法)"]
B2["BackendConsumer (4 方法)"]
end
subgraph "Connector 层(7 后端,互斥)"
C1["RocketMQ<br/>原生 Message + ProducerTemplate"]
C2["Redis<br/>Pub/Sub + JSON wrapper"]
C3["Kafka<br/>ProducerRecord + JSON wrapper"]
C4["RabbitMQ<br/>convertAndSend + JSON wrapper"]
C5["ZeroMQ<br/>PUB/SUB socket + pipe 分隔 frame"]
C6["DBMS<br/>INSERT + SELECT轮询"]
C7["AutoMQ<br/>继承 Kafka"]
end
subgraph "自动配置层"
D1["各 Backend AutoConfiguration<br/>@AutoConfigureBefore(MessagingAutoConfiguration.class)<br/>+ @ConditionalOnMissingBean 互斥"]
D2["MessagingAutoConfiguration<br/>汇聚锚点<br/>注册 MessageListenerBeanPostProcessor"]
end
A2 --> B1
A3 --> B2
D1 -->|"注入 BackendProducer / BackendConsumer"| B1
D1 --> B2
B1 --> C1 & C2 & C3 & C4 & C5 & C6 & C7
B2 --> C1 & C2 & C3 & C4 & C5 & C6 & C7
D1 --> D2
消息格式差异:
- RocketMQ:用原生
org.apache.rocketmq.common.message.Message,不做 wrapper - 其余 6 后端:统一 JSON wrapper
{"headers": {...}, "messageId": "uuid", "payload": "json-string"}
四、核心 API 草图
MessageSender<T>(15 方法)
覆盖 同步/异步/one-way × 普通/顺序 × 三种入参形式:
| 类别 | 方法 | 返回值 |
|---|---|---|
| 同步 | send(T) / send(Headers, T) / send(Message<T>) | Result |
| 同步顺序 | sendOrderly(T, hashKey) / sendOrderly(Message<T>, hashKey) | Result |
| 异步 | sendAsync(T, Callback) / sendAsync(Headers, T, Callback) / sendAsync(Message<T>, Callback) | void |
| 异步顺序 | sendAsyncOrderly(T, Callback, hashKey) | void |
| OneWay | sendOneWay(T) / sendOneWay(Message<T>) | void |
| OneWay 顺序 | sendOneWayOrderly(T, hashKey) | void |
AbstractMessageSender<T>
- 持有
@Autowired BackendProducer backendProducer+ProduceConfig config - 所有方法实现是一行薄代理:
backendProducer.syncSend(config.toDestination(), msg)
MessageListener<T>
public abstract class MessageListener<T> implements InitializingBean {
protected Type messageType; // 反射从泛型 T 解析
protected ConsumeConfig config; // @Consumer 注解注入
@Autowired private BackendConsumer backendConsumer;
public abstract void onMessage(Headers headers, T message) throws Exception;
@Override
public void afterPropertiesSet() {
backendConsumer.registerMessageListener(config, this);
backendConsumer.start();
}
}
BackendProducer SPI(12 + 1 方法)
Result syncSend(String destination, Message<?> msg);
Result syncSend(String destination, Object payload);
Result syncSendOrderly(String, Message<?>, String hashKey);
Result syncSendOrderly(String, Object, String hashKey);
void asyncSend(String, Message<?>, Callback);
void asyncSend(String, Object, Callback);
void asyncSendOrderly(String, Message<?>, Callback, String hashKey);
void asyncSendOrderly(String, Object, Callback, String hashKey);
void sendOneWay(String, Message<?>);
void sendOneWay(String, Object);
void sendOneWayOrderly(String, Message<?>, String);
void sendOneWayOrderly(String, Object, String);
void shutdown();
BackendConsumer SPI(4 方法)
void registerMessageListener(ConsumeConfig config, MessageListener<?> listener);
void start();
void stop();
void shutdown();
注解
@Producer(topic = "order-events", tag = "created") // → AbstractMessageSender 子类
@Consumer(topic = "order-events", tag = "*", threadMin = 4, threadMax = 16) // → MessageListener 子类
@Consumer 由 MessageListenerBeanPostProcessor(@Order(HIGHEST_PRECEDENCE))在 postProcessBeforeInitialization 阶段注入 ConsumeConfig。
五、SPI 层与 7 后端实现
| Connector | Producer 核心机制 | Consumer 核心机制 | 顺序发送真实支持 |
|---|---|---|---|
| RocketMQ | ProducerTemplate.syncSend() / syncSendOrderly(hashKey) | ConsumerContainerFactory 管理多 ConsumerContainer | ✅ |
| Redis | RedisTemplate.convertAndSend(topicPrefix + destination, json) | RedisMessageListenerContainer + ChannelTopic | ❌ |
| Kafka | KafkaProducer.send(ProducerRecord) 异步回调 + flush | 单线程 poll loop | ❌ |
| RabbitMQ | RabbitTemplate.convertAndSend(exchange, routingKey, json) | SimpleMessageListenerContainer + MANUAL ack | ❌ |
| ZeroMQ | ZMQ.Socket(PUB).bind(pubAddress) | ZMQ.Socket(SUB).connect() + poll loop | ❌ |
| DBMS | JdbcTemplate INSERT INTO easy_messaging_bus | ScheduledExecutorService 轮询 event_message | ❌ |
| AutoMQ | 继承 KafkaBackendProducer | 继承 KafkaBackendConsumer | ❌ |
六、自动配置体系
flowchart TD
subgraph "Backend AutoConfiguration(互斥 @ConditionalOnMissingBean)"
B1["RocketMqMessagingAutoConfiguration<br/>@ConditionalOnProperty(easy.rocketmq.enabled)"]
B2["RedisMessagingAutoConfiguration<br/>@ConditionalOnProperty(easy.redis.enabled)"]
B3["KafkaMessagingAutoConfiguration<br/>@ConditionalOnProperty(easy.kafka.enabled)"]
B4["RabbitMqMessagingAutoConfiguration"]
B5["ZeroMqMessagingAutoConfiguration"]
B6["DbmsMessagingAutoConfiguration"]
B7["AutoMqMessagingAutoConfiguration"]
end
M["MessagingAutoConfiguration<br/>汇聚锚点<br/>注册 MessageListenerBeanPostProcessor"]
B1 -->|"@AutoConfigureBefore"| M
B2 -->|"@AutoConfigureBefore"| M
B3 -->|"@AutoConfigureBefore"| M
B4 -->|"@AutoConfigureBefore"| M
B5 -->|"@AutoConfigureBefore"| M
B6 -->|"@AutoConfigureBefore"| M
B7 -->|"@AutoConfigureBefore"| M
M -->|"@AutoConfigureAfter"| EVENT_BUS["CqrsMessagingAutoConfiguration<br/>easy-event-bus 适配层(类名为历史命名)"]
互斥设计:所有 Backend AutoConfiguration 的 Bean 定义都有 @ConditionalOnMissingBean(BackendProducer.class) + @ConditionalOnMissingBean(BackendConsumer.class)。第一个匹配完成后,后续 Bean 全部跳过。
七、消息序列化与反序列化
Producer 侧
flowchart LR
P1["AbstractMessageSender.send(T)"] --> P2["toMessageHeaders()"]
P2 --> P3{"Connector 类型"}
P3 -- "RocketMQ" --> P4["RocketMQ 原生 Message"]
P3 -- "其余 6 后端" --> P5["JsonMapper.toJson(payload)<br/>+ 构造 JSON wrapper"]
P5 --> P6["发送到 MQ"]
JSON wrapper 格式:{"headers": {...}, "messageId": "uuid", "payload": "json-string"}
Consumer 侧
flowchart LR
C1["MessageListenerBeanPostProcessor<br/>注入 ConsumeConfig"] --> C2["MessageListener.afterPropertiesSet()"]
C2 --> C3["backendConsumer.registerMessageListener()"]
C3 --> C4["MQ 收到消息"]
C4 --> C5{"Connector 类型"}
C5 -- "RocketMQ" --> C6["原生 Message.getBody() → JsonMapper.fromJson()"]
C5 -- "其余 6 后端" --> C7["先解 wrapper JSON<br/>→ Class.forName(messageType)<br/>→ JsonMapper.fromJson(payload)"]
C6 --> C8["onMessage(headers, T)"]
C7 --> C8
八、已知限制
| 限制 | 严重度 | 说明 |
|---|---|---|
| 顺序发送仅 RocketMQ 有真实 hashKey 路由 | 中 | 其余 6 后端忽略 hashKey |
| DBMS Producer/Consumer 默认表名不一致 | 高 | Producer 默认 easy_messaging_bus,Consumer 默认 event_message |
| Kafka Consumer group.id 硬编码 | 低 | "easy-messaging-consumer-group",无法自定义 |
| ConsumeMode.ORDERLY 未使用 | 低 | toConsumeGroup() 硬编码 CONCURRENTLY |
| 消息反序列化依赖 Class.forName | 中 | 仅对有默认构造函数的 POJO 有效 |
| 不支持多后端并存 | 中 | 互斥条件,需要绕开或修改 |
| Headers 类型限制 | 低 | 统一 Map<String, String> |
| AutoMQ 零增量代码 | 低 | 纯协议兼容继承 Kafka |
九、关键设计决策
| 决策 | 内容 | 理由 |
|---|---|---|
| 轻量 SPI 路线 vs Spring Cloud Stream | 不引入 Spring Cloud 全家桶,一层 SPI 抽象 | Matrix 只需管道,事件语义在上层事件总线 |
| @AutoConfigureBefore 汇聚锚点 | 各 Backend 自动配置先于 MessagingAutoConfiguration | 锚点注册依赖 Backend Producer/Consumer 已就绪 |
| 互斥后端 @ConditionalOnMissingBean | 同一应用只启用一个后端 | 简化配置、避免资源冲突 |
| JSON wrapper 统一消息格式 | 除 RocketMQ 外 6 个后端统一 | 序列化/反序列化逻辑一致,减少分支 |
| MessageListenerBeanPostProcessor | 最高优先级 + postProcessBeforeInitialization | 保证 @Consumer 注解注入在 afterPropertiesSet 之前 |
| ProducerTemplate 封装 | RocketMQ 不直连 DefaultMQProducer | 统一 sync/async/one-way 方法签名 |