为什么你应该在 MQ 里用多个消费者,而不是一个

有一段时间,产品提新需求,我们的第一反应是:这个回调能不能不加?

不是不想做,是这个 MQ 消费者已经改不动了。每次加一个新 type,都要走一遍:改代码、测试全部 case、发版。战战兢兢,生怕碰坏已有的逻辑。

到后来,我们真的不想在这个 MQ 上再加任何新回调了。

这不应该是正常状态。


两种消费行为的定义方式

方式一:消费行为由消费者决定

生产者只管发消息,消息里带一个 type 字段。消费者收到后,根据 type 判断怎么处理。

typescript 复制代码
// 生产者:只发消息,type 决定"这是什么事件"
rabbitTemplate.convertAndSend("member.exchange", "register", message);
rabbitTemplate.convertAndSend("member.exchange", "upgrade", message);
rabbitTemplate.convertAndSend("member.exchange", "expire", message);

// 消费者:一个消费者处理所有 type
@RabbitListener(queues = "member.queue")
public void handle(String message) {
    Event e = JSON.parseObject(message);
    switch (e.getType()) {
        case "register": handleRegister(e); break;
        case "upgrade": handleUpgrade(e); break;
        case "expire": handleExpire(e); break;
        // ... 更多 case
    }
}

这是最常见的设计。问题也很明显:每次加一个新 type,都要改消费者的代码。

方式二:消费行为由生产者决定,通过路由分发

生产者发的每条消息,已经决定了它应该被谁处理。消费者只管自己关心的那类消息。

typescript 复制代码
// 生产者:发消息时已经决定了谁来处理
rabbitTemplate.convertAndSend("member.exchange", "register", message);
rabbitTemplate.convertAndSend("member.exchange", "upgrade", message);
rabbitTemplate.convertAndSend("member.exchange", "expire", message);

// 消费者 A:只处理 register
@RabbitListener(queues = "member.queue.register")
public void handleRegister(String message) {
    // 只处理 register 逻辑
}

// 消费者 B:只处理 upgrade
@RabbitListener(queues = "member.queue.upgrade")
public void handleUpgrade(String message) {
    // 只处理 upgrade 逻辑
}

这种方式下,新增一个 type 不需要动任何老代码,只需要新增一个消费者。


两种方式的本质区别

方式一的问题:消费者的代码会越来越臃肿。

当 type 只有两三个的时候,一个消费者写在一起还挺方便。但当 type 变成七个、八个、十来个的时候,消费者就变成了一个巨大的 switch case 集合。

每次加新 type ,开发者都要:

  1. 在消费者的 switch 里加一个 case
  2. 写新的处理方法
  3. 测试所有已有的 case 确保不回归
  4. 发版上线

更糟糕的是,不同类型的消费逻辑相互影响。一个 type 的 bug 可能影响另一个 type 的稳定性。改一个 type 的代码,要承担影响所有 type 的风险。

方式二的核心思想:不同的消息类型,由不同的消费者处理。每个消费者只关心自己该做的事。

这个原则不只适用于 MQ 。任何"一个处理者需要响应多种不同请求"的场景,都可以考虑这种方式。


什么时候选哪种方式

方式一(单一消费者 + type 路由)适合:

  • type 数量少且稳定,预计不会超过 3 个
  • 所有 type 的处理逻辑都很简单,50 行以内
  • 业务逻辑高度相似,只是参数不同
  • 你希望所有消费逻辑集中在一起,方便看全局

方式二(多消费者)适合:

  • type 数量会增长
  • 不同 type 的处理逻辑差异大
  • 稳定性要求高:一个 type 的问题不应该影响其他 type
  • 需要独立扩展:某些类型的消息消费量大,需要单独扩容

没有绝对的好坏。只有适合不适合。


总结一下

回到开头那个需求。重构之后,新增一个回调,我只是加了一个新的消费者类,配了一条路由规则,测完直接上线。

没有回归测试。没有发版风险。没有动一行老代码。

这不是 MQ 的胜利,是设计上的胜利。谁该干什么,让它自己干。

相关推荐
国科安芯20 分钟前
商业立方星平台中高集成度抗辐射MCU的功耗优化与批量化应用可行性探讨
单片机·嵌入式硬件·架构·risc-v·抗辐射·as32x601
Wang's Blog27 分钟前
Java框架 SpringCloud 快速入门: 服务拆分案例 Demo
java·开发语言·spring cloud
DongQiShanRen1 小时前
裁决台账双向互校(上):名册与实物的第一道对账
java·linux·运维·数据库·人工智能·自然语言处理·数据挖掘
cpolar技术支持2 小时前
Spring Boot 接口本地正常,异地前端却报跨域?用 cpolar 跑通 CORS 预检与白名单
java·springboot·cpolar·前后端分离·cors
高频因子挖掘机2 小时前
批量行情返回后,怎样把请求失败的股票单独挑出来?
后端·github·api
wuminyu2 小时前
Virtual Thread重投递至ForkJoinPool任务队列过程解析
java·linux·c语言·jvm·c++
小蒜学长2 小时前
在线保险服务与管理平台的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·服务平台·在线保险
掉进电商坑三年没爬出来的东叔2 小时前
青龙面板进阶:一个面板聚合多平台签到,所有积分自动领
java·开发语言
ESDWAN3 小时前
企业跨境网络合规建设指南:从线路选择到数据出境的全流程方案
网络·架构
殷色玫瑰3 小时前
C++ STL:stack、queue、priority_queue 与容器适配器详解
java·开发语言·数据结构·c++·算法·visualstudio