6、六大工作模式-2

1、路由模式

官网的路由(Routing)模式流程示意图:

该模式也需要加入交换机,指定其类型为direct 。队列在绑定交换机时要指定routing key (路由键),消息会转发到符合routing key的队列。交换机根据routingKey进行完全匹配,如果匹配失败则丢弃消息。

例子:

队列 A 绑定 error

队列 B 绑定 infoerror

  • 发送 routing‑key=error:队列 A、队列 B 同时收到
  • 发送 routing‑key=info:只有队列 B 收到

可以实现选择性投递,而不是无脑广播。

场景:日志系统,error 日志发给告警队列。

xml 复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.15</version>
    <relativePath/>
  </parent>

  <groupId>com.demo</groupId>
  <artifactId>rabbitmq_direct</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>rabbitmq_direct</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!-- rabbitmq -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <!-- web 测试接口 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  </dependencies>
</project>
java 复制代码
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual
server:
  port: 8080`在这里插入代码片
java 复制代码
package com.demo.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 关键点:同一个队列可以绑定多个不同的 binding‑key
 */
@Configuration
public class DirectConfig {

    //交换机名称
    public static final String DIRECT_EXCHANGE = "direct_exchange_log";
    //队列
    public static final String QUEUE_LOG = "direct_queue_log";
    public static final String QUEUE_ALERT = "direct_queue_alert";

    //1.声明Direct交换机
    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange(DIRECT_EXCHANGE,true,false);
    }

    //2.声明日志队列
    @Bean
    public Queue logQueue(){
        return new Queue(QUEUE_LOG,true);
    }

    //3.声明告警队列
    @Bean
    public Queue alertQueue(){
        return new Queue(QUEUE_ALERT,true);
    }

    /**
     * 日志队列绑定交换机
     * binding‑key: info,warning,error 三种级别日志都接收
     */
    @Bean
    public Binding bindingLogInfo(DirectExchange directExchange,Queue logQueue){
        return BindingBuilder.bind(logQueue).to(directExchange).with("info");
    }
    @Bean
    public Binding bindingLogWarning(DirectExchange directExchange,Queue logQueue){
        return BindingBuilder.bind(logQueue).to(directExchange).with("warning");
    }
    @Bean
    public Binding bindingLogError(DirectExchange directExchange,Queue logQueue){
        return BindingBuilder.bind(logQueue).to(directExchange).with("error");
    }

    /**
     * 告警队列只绑定 error,只接收错误日志
     */
    @Bean
    public Binding bindingAlertError(DirectExchange directExchange,Queue alertQueue){
        return BindingBuilder.bind(alertQueue).to(directExchange).with("error");
    }

}
java 复制代码
package com.demo.producer;

import com.demo.config.DirectConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DirectProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * level:info / warning / error
     * http://127.0.0.1:8080/sendLog/error
     */
    @GetMapping("/sendLog/{level}")
    public String sendLog(@PathVariable String level){
        String msg = "日志内容,日志级别:" + level;
        rabbitTemplate.convertAndSend(DirectConfig.DIRECT_EXCHANGE,level,msg);
        System.out.println("生产者发送消息,routing‑key:" + level);
        return "消息发送成功,routing‑key=" + level;
    }
}
java 复制代码
package com.demo.consumer;

import com.demo.config.DirectConfig;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class AlertConsumer {

    @RabbitListener(queues = DirectConfig.QUEUE_ALERT)
    public void consume(String msg, Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            System.out.println("【告警队列消费者】收到消息:" + msg);
            Thread.sleep(300);
            channel.basicAck(deliveryTag,false);
            System.out.println("【告警队列消费者】消费完成ACK");
        }catch (Exception e){
            channel.basicNack(deliveryTag,false,true);
            System.out.println("【告警队列消费者】消费失败");
        }
    }
}
java 复制代码
package com.demo.consumer;

import com.demo.config.DirectConfig;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class LogConsumer {

    @RabbitListener(queues = DirectConfig.QUEUE_LOG)
    public void consume(String msg, Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            System.out.println("【日志队列消费者】收到消息:" + msg);
            Thread.sleep(300);
            channel.basicAck(deliveryTag,false);
            System.out.println("【日志队列消费者】消费完成ACK");
        }catch (Exception e){
            channel.basicNack(deliveryTag,false,true);
            System.out.println("【日志队列消费者】消费失败");
        }
    }
}
java 复制代码
package com.demo;

/**
 * Hello world!
 *
 */
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DirectApplication {

    public static void main(String[] args) {
        SpringApplication.run(DirectApplication.class,args);
    }
}
  1. 发送 error 日志:http://127.0.0.1:8080/sendLog/error

结果:日志队列 + 告警队列两个消费者都会收到消息

java 复制代码
2026-08-17 20:24:35.174  INFO 5008 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2026-08-17 20:24:35.174  INFO 5008 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2026-08-17 20:24:35.174  INFO 5008 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
生产者发送消息,routing‑key:error
【告警队列消费者】收到消息:日志内容,日志级别:error
【日志队列消费者】收到消息:日志内容,日志级别:error
【日志队列消费者】消费完成ACK
【告警队列消费者】消费完成ACK
java 复制代码
2026-08-17 20:24:35.174  INFO 5008 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2026-08-17 20:24:35.174  INFO 5008 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2026-08-17 20:24:35.174  INFO 5008 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
生产者发送消息,routing‑key:info
【日志队列消费者】收到消息:日志内容,日志级别:info
【日志队列消费者】消费完成ACK

2、主题模式

架构

生产者Topic 交换机 → 队列使用带通配符的binding‑key订阅消息

  • * 匹配一个单词
  • # 匹配 0 个或多个单词
    routing‑key 使用.分割单词,例如 order.create.pay

示例:

  • 队列 1 绑定 order.# → 接收所有订单相关消息
  • 队列 2 绑定 *.pay → 接收所有支付事件

Topic = Direct + Fanout 的综合体,灵活最强,微服务事件总线首选。

业务示例:订单事件消息

  • order.create 订单创建
  • order.pay 订单支付
  • order.cancel 订单取消
  • goods.stock.reduce 库存扣减
xml 复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.15</version>
    <relativePath/>
  </parent>
  <groupId>com.demo</groupId>
  <artifactId>rabbitmq_topic</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>rabbitmq_topic</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!-- rabbitmq -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <!-- web 测试接口 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  </dependencies>
</project>
java 复制代码
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual
server:
  port: 8080
java 复制代码
package com.demo.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TopicConfig {

    //主题交换机名称
    public static final String TOPIC_EXCHANGE = "topic_exchange_event";

    //队列1:订单队列,订阅所有订单事件 order.#
    public static final String QUEUE_ORDER = "topic_queue_order";
    //队列2:支付队列,只订阅一级后缀为pay的事件 *.pay
    public static final String QUEUE_PAY = "topic_queue_pay";

    //1.声明Topic交换机
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange(TOPIC_EXCHANGE,true,false);
    }

    //2.声明订单队列
    @Bean
    public Queue orderQueue(){
        return new Queue(QUEUE_ORDER,true);
    }

    //3.声明支付队列
    @Bean
    public Queue payQueue(){
        return new Queue(QUEUE_PAY,true);
    }

    /**
     * 订单队列绑定,binding‑key = order.#
     * 匹配所有以 order. 开头的 routing‑key
     */
    @Bean
    public Binding bindingOrder(TopicExchange topicExchange,Queue orderQueue){
        return BindingBuilder.bind(orderQueue).to(topicExchange).with("order.#");
    }

    /**
     * 支付队列绑定,binding‑key = *.pay
     * 只能匹配两段,第二段是pay,例如 order.pay、goods.pay
     */
    @Bean
    public Binding bindingPay(TopicExchange topicExchange,Queue payQueue){
        return BindingBuilder.bind(payQueue).to(topicExchange).with("*.pay");
    }

}
java 复制代码
package com.demo.producer;

import com.demo.config.TopicConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TopicProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 测试地址示例:
     * http://127.0.0.1:8080/sendTopic/order.create
     * http://127.0.0.1:8080/sendTopic/order.pay
     * http://127.0.0.1:8080/sendTopic/order.cancel
     * http://127.0.0.1:8080/sendTopic/goods.pay
     * http://127.0.0.1:8080/sendTopic/order.create.pay.success
     */
    @GetMapping("/sendTopic/{routingKey}")
    public String sendTopicMsg(@PathVariable String routingKey){
        String msg = "事件消息,routing‑key = " + routingKey;
        rabbitTemplate.convertAndSend(TopicConfig.TOPIC_EXCHANGE,routingKey,msg);
        System.out.println("生产者发送消息:" + msg);
        return "发送成功,routing‑key:"+routingKey;
    }
}
java 复制代码
package com.demo.consumer;

import com.demo.config.TopicConfig;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class OrderConsumer {

    @RabbitListener(queues = TopicConfig.QUEUE_ORDER)
    public void consume(String msg, Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try{
            System.out.println("【订单队列消费者】收到消息:"+msg);
            Thread.sleep(300);
            channel.basicAck(deliveryTag,false);
            System.out.println("【订单队列消费者】消费完成ACK");
        }catch (Exception e){
            channel.basicNack(deliveryTag,false,true);
            System.out.println("【订单队列消费者】消费失败");
        }
    }
}
java 复制代码
package com.demo.consumer;

import com.demo.config.TopicConfig;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class PayConsumer {

    @RabbitListener(queues = TopicConfig.QUEUE_PAY)
    public void consume(String msg, Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try{
            System.out.println("【支付队列消费者】收到消息:"+msg);
            Thread.sleep(300);
            channel.basicAck(deliveryTag,false);
            System.out.println("【支付队列消费者】消费完成ACK");
        }catch (Exception e){
            channel.basicNack(deliveryTag,false,true);
            System.out.println("【支付队列消费者】消费失败");
        }
    }
}
java 复制代码
package com.demo;

/**
 * Hello world!
 *
 */
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TopicApplication {

    public static void main(String[] args) {
        SpringApplication.run(TopicApplication.class,args);
    }
}
java 复制代码
2026-08-17 20:39:11.869  INFO 7336 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2026-08-17 20:39:11.869  INFO 7336 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2026-08-17 20:39:11.869  INFO 7336 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
生产者发送消息:事件消息,routing‑key = order.create
【订单队列消费者】收到消息:事件消息,routing‑key = order.create
【订单队列消费者】消费完成ACK
java 复制代码
生产者发送消息:事件消息,routing‑key = order.pay
【订单队列消费者】收到消息:事件消息,routing‑key = order.pay
【支付队列消费者】收到消息:事件消息,routing‑key = order.pay
【支付队列消费者】消费完成ACK
【订单队列消费者】消费完成ACK
java 复制代码
生产者发送消息:事件消息,routing‑key = order.cancel
【订单队列消费者】收到消息:事件消息,routing‑key = order.cancel
【订单队列消费者】消费完成ACK
相关推荐
2601_963870211 小时前
【计算机毕业设计】基于Spring Boot的非遗文创产品交易平台的设计与实现
java·spring boot·后端
lv__pf1 小时前
Spring之AOP底层源码解析(下)【TL spring14】
java·后端·spring
水无痕simon2 小时前
5、六大工作模式-1
java
KhalilRuan2 小时前
UnityCsReference——笔记
java·开发语言·笔记
何以解忧,唯有..3 小时前
SpringBoot 中 @Transactional 注解失效的 8 种常见场景与解决方案
java
lazy H3 小时前
不同的消息队列有什么区别?Kafka、RabbitMQ、RocketMQ、Pulsar、ActiveMQ 选型对比
后端·中间件·kafka·rabbitmq·rocketmq
SL-staff3 小时前
AB测试数据失真根因与变量热替换实战:JVS-Rules函数计算器架构解析
java·运维·微服务·函数式编程·规则引擎·变量管理·营销技术
Irene19913 小时前
Java 3 天入门:“最小必要知识”的功利性学法
java
长谷深风1113 小时前
Agent 何时该 Replan:五个关键判断
java·大数据·开发语言·ai agent·ai智能体·agent设计·clarify机制