一佳技术

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 StreamMatrix 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
OneWaysendOneWay(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 后端实现

ConnectorProducer 核心机制Consumer 核心机制顺序发送真实支持
RocketMQProducerTemplate.syncSend() / syncSendOrderly(hashKey)ConsumerContainerFactory 管理多 ConsumerContainer✅
RedisRedisTemplate.convertAndSend(topicPrefix + destination, json)RedisMessageListenerContainer + ChannelTopic❌
KafkaKafkaProducer.send(ProducerRecord) 异步回调 + flush单线程 poll loop❌
RabbitMQRabbitTemplate.convertAndSend(exchange, routingKey, json)SimpleMessageListenerContainer + MANUAL ack❌
ZeroMQZMQ.Socket(PUB).bind(pubAddress)ZMQ.Socket(SUB).connect() + poll loop❌
DBMSJdbcTemplate INSERT INTO easy_messaging_busScheduledExecutorService 轮询 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 方法签名