RabbitMQ的部分工作模式及原理和具体代码实现

本文详细介绍了RabbitMQ的四种工作模式:direct、fanout、topic和headers,包括每种模式下的交换机如何根据路由键分发消息,以及生产者和消费者的实现代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RabbitMQ的工作模式及原理

https://baijiahao.baidu.com/s?id=1732891548341088166&wfr=spider&for=pc(部分参考)

​ 在介绍这些概念之前,我们先看一张图,图中展示的是RabbitMQ的工作模型,根据这张图,下面理解起来就比较容易了:

img

​ 其中,中间的Broker表示RabbitMQ服务,每个Broker里面至少有一个Virtual host虚拟主机,每个虚拟主机中有自己的Exchange交换机、Queue队列以及Exchange交换机与Queue队列之间的绑定关系Binding。producer(生产者)和consumer(消费者)通过与Broker建立Connection来保持连接,然后在Connection的基础上建立若干Channel信道,用来发送与接收消息。

Connection(连接)

​ 每个producer(生产者)或者consumer(消费者)要通过RabbitMQ发送与消费消息,首先就要与RabbitMQ建立连接,这个连接就是Connection。Connection是一个TCP长连接。

Channel(信道)

​ Channel是在Connection的基础上建立的虚拟连接,RabbitMQ中大部分的操作都是使用Channel完成的,比如:声明Queue、声明Exchange、发布消息、消费消息等。

​ 看到此处,你是否有这样一个疑问:既然已经有了Connection,我们完全可以使用Connection完成Channel的工作,为什么还要引入Channel这样一个虚拟连接的概念呢?因为现在的程序都是支持多线程的,如果没有Channel,那么每个线程在访问RabbitMQ时都要建立一个Connection这样的TCP连接,对于操作系统来说,建立和销毁TCP连接是非常大的开销,在系统访问流量高峰时,会严重影响系统性能。

​ Channel就是为了解决这种问题,通常情况下,每个线程创建单独的Channel进行通讯,每个Channel都有自己的channel id帮助Broker和客户端识别Channel,所以Channel之间是完全隔离的。

​ Connection与Channel之间的关系可以比作光纤电缆,如果把Connection比作一条光纤电缆,那么Channel就相当于是电缆中的一束光纤。

Virtual host(虚拟主机)

​ Virtual host是一个虚拟主机的概念,一个Broker中可以有多个Virtual host,每个Virtual host都有一套自己的Exchange和Queue,同一个Virtual host中的Exchange和Queue不能重名,不同的Virtual host中的Exchange和Queue名字可以一样。这样,不同的用户在访问同一个RabbitMQ Broker时,可以创建自己单独的Virtual host,然后在自己的Virtual host中创建Exchange和Queue,很好地做到了不同用户之间相互隔离的效果。

img

Queue(队列)

​ Queue是一个用来存放消息的队列,生产者发送的消息会被放到Queue中,消费者消费消息时也是从Queue中取走消息。

Exchange(交换机)

​ Exchange是一个比较重要的概念,它是消息到达RabbitMQ的第一站,主要负责根据不同的分发规则将消息分发到不同的Queue,供订阅了相关Queue的消费者消费到指定的消息。那Exchange有哪些分发消息的规则呢?这就要说到Exchange的4种类型了:direct、fanout、topic、headers。

​ 在介绍这4种类型的Exchange之前,我们先来了解一下另外一个比较重要的概念:Routing key,翻译成中文就是路由键。当我们创建好Exchange和Queue之后,需要使用Routing key(通常叫作Binding key)将它们绑定起来,producer在向Exchange发送一条消息的时候,必须指定一个Routing key,然后Exchange接收到这条消息之后,会解析Routing key,然后根据Exchange和Queue的绑定规则,将消息分发到符合规则的Queue中。

img

​ 接下来,我们根据上面的流程再来详细介绍下4种类型的Exchange。

RabbitMQ四种工作模式

1、direct

​ direct的意思是直接的,direct类型的Exchange会将消息转发到指定Routing key的Queue上,Routing key的解析规则为精确匹配。也就是只有当producer发送的消息的Routing key与某个Binding key相等时,消息才会被分发到对应的Queue上。

img

​ (上面图片只是示例,真正的实操流程看下面)

比如我们现在有一个direct类型的Exchange,它下面绑定了两个Queue,Binding key分别是es和mongodb

在这里插入图片描述

然后我们向该Exchange中发送一条消息,消息的Routing key是mongodb:

在这里插入图片描述

按照规则分析,这条消息应该被路由到pug.mq.direct.order.mongo.queue这个Queue。消息发送成功之后,我们去Queues中查看,发现确实只有pug.mq.direct.order.mongo.queue这个QUEUE接收到了一条消息。

在这里插入图片描述

进入这个队列,通过getMessage取出消息查看,确实是我们刚才手动发送的那条消息。

在这里插入图片描述

所以,direct类型的Exchange在分发消息时,必须保证producer发送消息的Routing key与Exchange和Queue绑定的Binding key相等才可以。

生产者代码示例:

1、创建direct模型配置文件

@Configuration
public class DirectOrderRabbitMQConfiguration {

    /*direct模型*/
    // 定义一个交换机
    public static final String DIRECT_ORDER_EXCHANGE = "pug.mq.direct.order.ex";
    // 定义一个路由key -- mongodb
    public static final String DIRECT_ORDER_MONGODB_ROUTEING_KEY = "mongodb";
    // 定义一个路由key --- es
    public static final String DIRECT_ORDER_ES_ROUTEING_KEY = "es";
    // 定义一个队列
    public static final String DIRECT_ORDER_MONGO_QUEUE = "pug.mq.direct.order.mongo.queue";
    // 定义一个队列
    public static final String DIRECT_ORDER_ES_QUEUE = "pug.mq.direct.order.es.queue";

    /**
     * 创建交换机
     * @return
     */
    @Bean
    public Exchange orderDirectExchange() {
        return ExchangeBuilder.directExchange(DIRECT_ORDER_EXCHANGE).durable(true).build();
    }
    
    /**
     * 创建mongodb队列queue
     * @return
     */
    @Bean
    public Queue orderDirectMongoQueue() {
        return QueueBuilder.durable(DIRECT_ORDER_MONGO_QUEUE).build();
    }

    /**
     * 创建ES队列queue
     * @return
     */
    @Bean
    public Queue orderDirectESQueue() {
        return QueueBuilder.durable(DIRECT_ORDER_ES_QUEUE).build();
    }

    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Binding orderDirectMongoBinding() {
        return BindingBuilder
                .bind(orderDirectMongoQueue())  // 绑定队列
                .to(orderDirectExchange())	// 到交换机
                .with(DIRECT_ORDER_MONGODB_ROUTEING_KEY)  // 使用mongodb路由键
            	.noargs();
    }

    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Binding orderDirectESBinding() {
        return BindingBuilder
                .bind(orderDirectESQueue())	// 绑定队列
                .to(orderDirectExchange())	// 到交换机
                .with(DIRECT_ORDER_ES_ROUTEING_KEY)	// 使用ES路由键
            	.noargs();
    }

}

2、发送消息时指定对应路由key

@Component
@Slf4j
public class ResponsitoryMQService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 发送消息到MongoDb队列
     * @param pugMessage
     */
    public void publishMongoDb(PugMessage pugMessage) {
        // 1: 指定交换机
        rabbitTemplate.setExchange(DirectOrderRabbitMQConfiguration.DIRECT_ORDER_EXCHANGE);
        // 2: 指定路由key
        rabbitTemplate.setRoutingKey(DirectOrderRabbitMQConfiguration.DIRECT_ORDER_MONGODB_ROUTEING_KEY);
        // 3: 发送消息指定参数
        rabbitTemplate.convertAndSend(pugMessage, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                // 设置消息的附属信息,来达到延时或者限制的目的
                MessageProperties messageProperties = message.getMessageProperties();
                messageProperties.setHeader("msgtype","1");
                //生产者设置消息过期时间,避免消费者处理消息出现异常导致死循环
                //messageProperties.setExpiration("10000");
                return message;
            }
        });
    }

	/**
     * 发送消息到ES队列
     * @param pugMessage
     */
    public void publishESDb(PugMessage pugMessage) {
        // 1: 指定交换机
        rabbitTemplate.setExchange(DirectOrderRabbitMQConfiguration.DIRECT_ORDER_EXCHANGE);
        // 2: 指定路由key
        rabbitTemplate.setRoutingKey(DirectOrderRabbitMQConfiguration.DIRECT_ORDER_ES_ROUTEING_KEY);
        // 3: 发送消息指定参数
        rabbitTemplate.convertAndSend(pugMessage, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                // 设置消息的附属信息,来达到延时或者限制的目的
                MessageProperties messageProperties = message.getMessageProperties();
                messageProperties.setHeader("msgtype","1");
                //messageProperties.setExpiration("10000");
                return message;
            }
        });


    }

}

3、下单选择那个队列发送消息

    @GetMapping("/makeorder")
    public void makeorder(Long orderId) {
        // 写下单的业务
        // 1保存订单

        // 发送消息到mongodb
        PugMessage message = new PugMessage();
        message.setOpid(orderId+"");
        message.setMsgtype("1");
        // 选择mongodb队列发送消息
        responsitoryMQService.publishMongoDb(message);
        log.info("pug direct order save mongodb, id is :{}", orderId);

        // 发送消息到es
//        PugMessage message1 = new PugMessage();
//        message1.setOpid(orderId+"");
//        message1.setMsgtype("1");
//        responsitoryMQService.publishESDb(message1);
//        log.info("pug direct order save es, id is :{}", orderId);
    }

}
消费者代码示例:

1、编写消费者对应的配置文件(和消息生产者一致,但是多了消费者的消费模式)

@Configuration
public class DirectOrderRabbitMQConfiguration {

    /*direct模型*/
    // 定义一个交换机
    public static final String DIRECT_ORDER_EXCHANGE = "pug.mq.direct.order.ex";
    // 定义一个路由key -- mongodb
    public static final String DIRECT_ORDER_MONGODB_ROUTEING_KEY = "mongodb";
    // 定义一个路由key --- es
    public static final String DIRECT_ORDER_ES_ROUTEING_KEY = "es";
    // 定义一个队列
    public static final String DIRECT_ORDER_MONGO_QUEUE = "pug.mq.direct.order.mongo.queue";
    // 定义一个队列
    public static final String DIRECT_ORDER_ES_QUEUE = "pug.mq.direct.order.es.queue";
    // 设置RabbitMQ的链接工厂实例
    @Autowired
    private CachingConnectionFactory connectionFactory;


    /**
     * 创建交换机
     * @return
     */
    @Bean
    public Exchange orderDirectExchange() {
        return ExchangeBuilder.directExchange(DIRECT_ORDER_EXCHANGE).durable(true).build();
    }


    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Queue orderDirectMongoQueue() {
        return QueueBuilder.durable(DIRECT_ORDER_MONGO_QUEUE).build();
    }

    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Queue orderDirectESQueue() {
        return QueueBuilder.durable(DIRECT_ORDER_ES_QUEUE).build();
    }


    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Binding orderDirectMongoBinding() {
        return BindingBuilder
                .bind(orderDirectMongoQueue())
                .to(orderDirectExchange())
                .with(DIRECT_ORDER_MONGODB_ROUTEING_KEY).noargs();
    }

    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Binding orderDirectESBinding() {
        return BindingBuilder
                .bind(orderDirectESQueue())
                .to(orderDirectExchange())
                .with(DIRECT_ORDER_ES_ROUTEING_KEY).noargs();
    }

    /**
     * 单一消费者,确认模式是:ACK
     */
    @Bean("simpleListenerContainer")
    public SimpleRabbitListenerContainerFactory listenerContainer(){
        // 创建消息监听器所在的容器工厂实例
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        // 为容器工厂实例设置链接工厂
        factory.setConnectionFactory(connectionFactory);
        // 设置消息在传输过程中的格式为JSON
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        // 设置消息并发实例,这里采用单一模式
        factory.setConcurrentConsumers(1);
        // 设置消费者并发最大数量实例
        factory.setMaxConcurrentConsumers(1);
        // 设置消费每个并发实例预拉取的消息数据量
        factory.setPrefetchCount(1);
        // 返回监听器容器工厂实例
        return factory;
    }


}

2、消费者消费消息

@Component
@Slf4j
public class MessageDirectConsumer {
    
    @Autowired
    private PugMessageService PugMessageService;

    // 消费MongoDb队列
    @RabbitListener(queues = {DirectOrderRabbitMQConfiguration.DIRECT_ORDER_MONGO_QUEUE}
            ,containerFactory = "simpleListenerContainer")
    public void ordermonodbmessage(@Payload PugMessageDto pugMessage, Message message, Channel channel) {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String receivedExchange = message.getMessageProperties().getReceivedExchange();
        String receivedRoutingKey = message.getMessageProperties().getReceivedRoutingKey();
        String headerMsgType = message.getMessageProperties().getHeader("msgtype");
        log.info("***************pug fanout order mongodb message is :{},key:{}", pugMessage,receivedRoutingKey);
        // 保存消息(对应的逻辑)
        pugMessageService.saveMessage(pugMessage);
   
    }

    // 消费ES队列
    @RabbitListener(queues = {DirectOrderRabbitMQConfiguration.DIRECT_ORDER_ES_QUEUE}
            ,containerFactory = "simpleListenerContainer")
    public void orderesmessage(@Payload PugMessageDto pugMessage, Message message, Channel channel) {
        String receivedExchange = message.getMessageProperties().getReceivedExchange();
        String receivedRoutingKey = message.getMessageProperties().getReceivedRoutingKey();
        String headerMsgType = message.getMessageProperties().getHeader("msgtype");
        log.info("================pug direct save es  message is :{},key:{}", pugMessage,receivedRoutingKey);
        // 保存消息(对应的逻辑)
        pugMessageService.saveMessage(pugMessage);
       
    }

}

2、fanout

fanout是扇形的意思,该类型通常叫作广播类型。fanout类型的Exchange不处理Routing key,而是会将发送给它的消息路由到所有与它绑定的Queue上。

img

​ (上面图片只是示例,真正的实操流程看下面)

比如我们现在有一个fanout类型的Exchange,它下面绑定了两个Queue,Binding key分别是es和mongodb:

在这里插入图片描述

然后我们向该Exchange中发送一条消息,消息的Routing key随便填一个值123:

在这里插入图片描述

按照规则分析,这条消息应该被路由到所有与该Exchange绑定的Queue,即两个Queue都应该会受到消息。消息发送成功之后,我们去Queues中查看,发现确实每个QUEUE都接收到了一条消息。

进入这三个QUEUE,通过getMessage取出消息查看,确实是我们刚才手动发送的那条消息。

在这里插入图片描述

所以,fanout类型的Exchange不管Routing key是什么,它都会将接收到的消息分发给所有与自己绑定了的Queue上。

生产者代码示例:

1、创建fanout模型配置文件

@Configuration
public class FanoutOrderRabbitMQConfiguration {
    /*
    rabbitmq遵循的:amqp协议:


    - producer 生产者
    - connection 连接(spring框架已经处理好了)
    - channel 通道(spring框架已经处理好了)
        - spring容器会利用starter机制会把rabbitmq进行服务的连接和初始化,会产生一个模板对象RabbitTemplate
        - RabbitTemplate可以取完成消息的投递的工作

    - exchange 交换机(如果代码中没有指定交换机:就会走到默认的交换机进行投递消息)
    - routingkey 路由key(条件过滤的规则,可以为空,为空就是:Publish/Subscribe,direct,topic)
    - bingding 绑定 (负责:把交换机和路由和queue进行捆绑)
    - queue 队列 最终数据存储的地方(持久化问题)

    - consumer 消费者
     */

    /*fanout模型*/
    // 定义一个交换机
    public static final String FANOUT_ORDER_EXCHANGE = "pug.mq.fanout.order.ex";
    // 定义一个路由key
    public static final String FANOUT_ORDER_ROUTEING_KEY = "";
    // 定义一个队列
    public static final String FANOUT_ORDER_MONGO_QUEUE = "pug.mq.fanout.order.mongo.queue";
    // 定义一个队列
    public static final String FANOUT_ORDER_ES_QUEUE = "pug.mq.fanout.order.es.queue";

    /**
     * 创建交换机
     * @return
     */
    @Bean
    public Exchange orderFanoutExchange() {
        return ExchangeBuilder.fanoutExchange(FANOUT_ORDER_EXCHANGE).durable(true).build();
    }


    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Queue orderFanoutMongoQueue() {
        return QueueBuilder.durable(FANOUT_ORDER_MONGO_QUEUE).build();
    }

    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Queue orderFanoutESQueue() {
        return QueueBuilder.durable(FANOUT_ORDER_ES_QUEUE).build();
    }


    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Binding orderFanoutMongoBinding() {
        return BindingBuilder
                .bind(orderFanoutMongoQueue())
                .to(orderFanoutExchange())
                .with(FANOUT_ORDER_ROUTEING_KEY).noargs();
    }

    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Binding orderFanoutESBinding() {
        return BindingBuilder
                .bind(orderFanoutESQueue())
                .to(orderFanoutExchange())
                .with(FANOUT_ORDER_ROUTEING_KEY).noargs();
    }

}
消费者代码示例:

1、编写消费者对应的配置文件(和消息生产者一致,但是多了消费者的消费模式)

@Configuration
public class FanoutOrderRabbitMQConfiguration {
    /*
    rabbitmq遵循的:amqp协议:
    - producer 生产者
    - connection 连接(spring框架已经处理好了)
    - channel 通道(spring框架已经处理好了)
        - spring容器会利用starter机制会把rabbitmq进行服务的连接和初始化,会产生一个模板对象RabbitTemplate
        - RabbitTemplate可以取完成消息的投递的工作

    - exchange 交换机(如果代码中没有指定交换机:就会走到默认的交换机进行投递消息)
    - routingkey 路由key(条件过滤的规则,可以为空,为空就是:Publish/Subscribe,direct,topic)
    - bingding 绑定 (负责:把交换机和路由和queue进行捆绑)
    - queue 队列 最终数据存储的地方(持久化问题)

    - consumer 消费者
     */

    /*fanout模型*/
    // 定义一个交换机
    public static final String FANOUT_ORDER_EXCHANGE = "pug.mq.fanout.order.ex";
    // 定义一个路由key
    public static final String FANOUT_ORDER_ROUTEING_KEY = "";
    // 定义一个队列
    public static final String FANOUT_ORDER_MONGO_QUEUE = "pug.mq.fanout.order.mongo.queue";
    // 定义一个队列
    public static final String FANOUT_ORDER_ES_QUEUE = "pug.mq.fanout.order.es.queue";
    // 设置RabbitMQ的链接工厂实例
    @Autowired
    private CachingConnectionFactory connectionFactory;

    /**
     * 创建交换机
     * @return
     */
    @Bean
    public Exchange orderFanoutExchange() {
        return ExchangeBuilder.fanoutExchange(FANOUT_ORDER_EXCHANGE).durable(true).build();
    }


    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Queue orderFanoutMongoQueue() {
        return QueueBuilder.durable(FANOUT_ORDER_MONGO_QUEUE).build();
    }

    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Queue orderFanoutESQueue() {
        return QueueBuilder.durable(FANOUT_ORDER_ES_QUEUE).build();
    }


    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Binding orderFanoutMongoBinding() {
        return BindingBuilder
                .bind(orderFanoutMongoQueue())
                .to(orderFanoutExchange())
                .with(FANOUT_ORDER_ROUTEING_KEY).noargs();
    }

    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Binding orderFanoutESBinding() {
        return BindingBuilder
                .bind(orderFanoutESQueue())
                .to(orderFanoutExchange())
                .with(FANOUT_ORDER_ROUTEING_KEY).noargs();
    }

    /**
     * 单一消费者,确认模式是:ACK
     */
    @Bean("simpleListenerContainer")
    public SimpleRabbitListenerContainerFactory listenerContainer(){
        // 创建消息监听器所在的容器工厂实例
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        // 为容器工厂实例设置链接工厂
        factory.setConnectionFactory(connectionFactory);
        // 设置消息在传输过程中的格式为JSON
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        // 设置消息并发实例,这里采用单一模式
        factory.setConcurrentConsumers(1);
        // 设置消费者并发最大数量实例
        factory.setMaxConcurrentConsumers(1);
        // 设置消费每个并发实例预拉取的消息数据量
        factory.setPrefetchCount(1);
        // 返回监听器容器工厂实例
        return factory;
    }


}

2、消费者消费消息

@Component
@Slf4j
public class MessageDirectConsumer {
    @Autowired
    private PugMessageService PugMessageService;

    // 消费MongoDb队列
    @RabbitListener(queues = {FanoutOrderRabbitMQConfiguration.FANOUT_ORDER_MONGO_QUEUE}
                    ,containerFactory = "simpleListenerContainer")
    public void ordermonodbmessage(@Payload PugMessageDto pugMessage, Message message, Channel channel) {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String receivedExchange = message.getMessageProperties().getReceivedExchange();
        String receivedRoutingKey = message.getMessageProperties().getReceivedRoutingKey();
        String headerMsgType = message.getMessageProperties().getHeader("msgtype");
        log.info("***************pug fanout order mongodb message is :{},key:{}", pugMessage,receivedRoutingKey);
        // 保存消息(对应的逻辑)
        pugMessageService.saveMessage(pugMessage);

    }

    // 消费ES队列
    @RabbitListener(queues = {FanoutOrderRabbitMQConfiguration.FANOUT_ORDER_ES_QUEUE}
                    ,containerFactory = "simpleListenerContainer")
    public void orderesmessage(@Payload PugMessageDto pugMessage, Message message, Channel channel) {
        String receivedExchange = message.getMessageProperties().getReceivedExchange();
        String receivedRoutingKey = message.getMessageProperties().getReceivedRoutingKey();
        String headerMsgType = message.getMessageProperties().getHeader("msgtype");
        log.info("================pug direct save es  message is :{},key:{}", pugMessage,receivedRoutingKey);
        // 保存消息(对应的逻辑)
        pugMessageService.saveMessage(pugMessage);
   
	}
}

3、topic

​ topic的意思是主题,topic类型的Exchange会根据通配符对Routing key进行匹配,只要Routing key满足某个通配符的条件,就会被路由到对应的Queue上。通配符的匹配规则如下:

img

​ ● Routing key必须是一串字符串,每个单词用“.”分隔;

​ ● 符号“#”表示匹配一个或多个单词;

​ ● 符号“*”表示匹配一个单词。

​ 例如:“*.123” 能够匹配到 “abc.123”,但匹配不到 “abc.def.123”;“#.123” 既能够匹配到 “abc.123”,也能匹配到 “abc.def.123”。

​ 比如我们现在有一个topic类型的Exchange,它下面绑定了2个Queue,Binding key分别是 *.mongodb.# 和 #.es

在这里插入图片描述

然后我们向该Exchange中发送一条消息,消息的Routing key为:user.mongodb。

按照规则分析,user.mongodb这个Routing key只可以匹配到 “*.mongodb.#” ,所以,这条消息应该被路由到pug.mq.topic.order.mongo.queue这个Queue中。消息发送成功之后,我们去Queues中查看,发现结果符合我们的预期。

在这里插入图片描述

进入这个QUEUE,通过getMessage取出消息查看,确实是我们刚才手动发送的那条消息。

在这里插入图片描述

生产者代码示例:

1、创建topic模型配置文件

@Configuration
public class TopicOrderRabbitMQConfiguration {

    /*topic模型*/
    // 定义一个交换机
    public static final String TOPIC_ORDER_EXCHANGE = "pug.mq.topic.order.ex";
    // 定义一个路由key -- mongodb #0个或者多个  *至少要有一个
    // routingkey = #.mongodb.es.# --- a.b.c.mongodb.es / mongodb.es /mongodb.es.a.b/
    // routingkey = *.mongodb.es.*
    // ------------ a.mongodb.es.b
    public static final String TOPIC_ORDER_MONGODB_ROUTEING_KEY = "*.mongodb.#";
    // 定义一个路由key --- es
    public static final String TOPIC_ORDER_ES_ROUTEING_KEY = "#.es";
    // 定义一个队列
    public static final String TOPIC_ORDER_MONGO_QUEUE = "pug.mq.topic.order.mongo.queue";
    // 定义一个队列
    public static final String TOPIC_ORDER_ES_QUEUE = "pug.mq.topic.order.es.queue";

    // 设置RabbitMQ的链接工厂实例
    @Autowired
    private CachingConnectionFactory connectionFactory;

    /**
     * 创建交换机
     * @return
     */
    @Bean
    public Exchange orderTopicExchange() {
        return ExchangeBuilder.topicExchange(TOPIC_ORDER_EXCHANGE).durable(true).build();
    }


    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Queue orderTopicMongoQueue() {
        return QueueBuilder.durable(TOPIC_ORDER_MONGO_QUEUE).build();
    }

    /**
     * 创建队列queue
     * @return
     */
    @Bean
    public Queue orderTopicESQueue() {
        return QueueBuilder.durable(TOPIC_ORDER_ES_QUEUE).build();
    }

    /**
     * 绑定队列到到交换机
     * @return
     */
    @Bean
    public Binding orderTopicMongoBinding() {
        return BindingBuilder
                .bind(orderTopicMongoQueue())
                .to(orderTopicExchange())
                .with(TOPIC_ORDER_MONGODB_ROUTEING_KEY).noargs();
    }

    /**
     * 绑定队列到到交换机
     * @return
     */
    @Bean
    public Binding orderTopicESBinding() {
        return BindingBuilder
                .bind(orderTopicESQueue())
                .to(orderTopicExchange())
                .with(TOPIC_ORDER_ES_ROUTEING_KEY).noargs();
    }

}

2、生产者发送消息的实现类

@Component
@Slf4j
public class ResponsitoryTopicMQService {

    @Autowired
    private RabbitTemplate rabbitTemplate;


    public void publishMongoDb(PugMessage pugMessage, String routeKey) {
        rabbitTemplate.setMandatory(true);
        // 设置消息发送确认机制,即发送成功时输出日志
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            // 这里如果能收到回执,说明rabbitmq一定收到了消息。
            log.info("消息发送成功!!!!,correlationData:{},ack:{},casuse:{}", correlationData, ack, cause);
        });

        // 设置消息发送确认机制,即发送完消息后输出反馈信息,如消息是否丢失等。
        rabbitTemplate.setReturnsCallback((resultMessage) -> {
            log.info("消息确认了:,message:{},replycode:{},replytext:{},exchange:{},routingKey:{}",
                    resultMessage.getMessage().getBody(), resultMessage.getReplyCode(), resultMessage.getReplyText(), resultMessage.getExchange(), resultMessage.getRoutingKey());
        });
        // 1: 指定交换机
        rabbitTemplate.setExchange(TopicOrderRabbitMQConfiguration.TOPIC_ORDER_EXCHANGE);
        // 2: 指定路由key
        rabbitTemplate.setRoutingKey(routeKey);
        // 3: 发送消息指定参数
        rabbitTemplate.convertAndSend(pugMessage, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                // 设置消息的附属信息,来达到延时或者限制的目的
                MessageProperties messageProperties = message.getMessageProperties();
                messageProperties.setHeader("msgtype", "1");
                //messageProperties.setExpiration("10000");
                return message;
            }
        });
    }


    public void publishESDb(PugMessage pugMessage, String routeKey) {
        rabbitTemplate.setMandatory(true);
        // 设置消息发送确认机制,即发送成功时输出日志 只要你的消息投递到了交换机,就代表以及收到了消息,rabbitserver就会给你一个确认操作.
        // 哪怕投递到了一个不存在的路由key或者交换机中都会进入setConfirmCallback
        // 但是在开发的时候,我们最好还是要判断一个下ack是否收到回执,如果收到rabbItmqserver服务器给与回执ack=true,
        // 代表生产者已经把消息已经到了交换机中
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if(!ack){
                // 这里什么时候进入:就是交换机没有收到生产者提供的消息就进入到setConfirmCallback,ack就是false
                System.out.println("没有收到rabbitmq server 确认.....");
                return;
            }
            log.info("1--------------------消息发送成功!!!!,correlationData:{},ack:{},casuse:{}", correlationData, ack, cause);
        });

        // 设置消息发送确认机制,即发送完消息后输出反馈信息,如消息是否丢失等。
        //  ReturnCallback消息没有正确到达队列时触发回调,如果正确到达队列不执行
        //
        rabbitTemplate.setReturnsCallback((resultMessage) -> {
            System.out.println("2=================returnCallback触发。消息路由到queue失败===========");
            System.out.println("msg=" + new String(resultMessage.getMessage().getBody()));
            System.out.println("replyCode=" + resultMessage.getReplyCode());
            System.out.println("replyText=" + resultMessage.getReplyText());
            System.out.println("exchange=" + resultMessage.getExchange());
        });

        // 1: 指定交换机
        rabbitTemplate.setExchange(TopicOrderRabbitMQConfiguration.TOPIC_ORDER_EXCHANGE);
        // 2: 指定路由key
        rabbitTemplate.setRoutingKey(routeKey);
        // 3: 发送消息指定参数
        rabbitTemplate.convertAndSend(pugMessage, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                // 设置消息的附属信息,来达到延时或者限制的目的
                MessageProperties messageProperties = message.getMessageProperties();
                // 附属消息
                messageProperties.setHeader("msgtype", "1");
                // 附属消息中,添加默认重试次数为0
                messageProperties.setHeader("retry-count", "0");
                //messageProperties.setExpiration("10000");
                return message;
            }
        });
    }

}

3、生产者下单

@Service
@Slf4j
public class OrderTopicServiceProducer {

    @Autowired
    private ResponsitoryTopicMQService responsitoryTopicMQService;


    /**
     * 模拟下单
     *
     * @param orderId
     */
    public void makeorder(Long orderId) {
        // 写下单的业务
        // 1保存订单

        // 发送消息到mongodb es
        PugMessage message = new PugMessage();
        message.setOpid(orderId+"");
        message.setMsgtype("1");
        //   order.mongodb.es // mongodb es
        //   order.mongodb.a //mongodb
        //   order.es//es
        responsitoryTopicMQService.publishESDb(pugMessage,"user.mongodb");  // 第二个参数是路由键
        log.info("pug topic order save mongodb, id is :{}", orderId);
    }

}
消费者代码示例:

1、创建topic模型配置文件

@Component
@Slf4j
public class MessageTopicConsumer {


    @RabbitListener(queues = {TopicOrderRabbitMQConfiguration.TOPIC_ORDER_MONGO_QUEUE},
            containerFactory = "simpleTopicListenerContainerMANUALAck")
    public void ordermonodbmessage(@Payload PugMessageDto messageDto, Message message, Channel channel) throws IOException, InterruptedException {

        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            // 故意报错
            System.out.println(1 / 0);
            channel.basicAck(deliveryTag, false);
            log.info("================pug topic order mongodb message is :{}", messageDto);
        } catch (Exception ex) {
            channel.basicAck(deliveryTag, false);
            //当消息回滚到消息队列时,这条消息不会回到队列尾部,而是仍是在队列头部。
            //这时消费者会立马又接收到这条消息进行处理,接着抛出异常,进行 回滚,如此反复进行
            //而比较理想的方式是,出现异常时,消息到达消息队列尾部,这样既保证消息不回丢失,又保证了正常业务的进行。
            //因此我们采取的解决方案是,将消息进行应答。
            //这时消息队列会删除该消息,同时我们再次发送该消息 到消息队列,这时就实现了错误消息进行消息队列尾部的方案
            //1.应答
            //2.重新发送到MQ中
            AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
                    .contentType("application/json").build();
            // 重新把消息发动自己的交换机和队列中继续消费
            channel.basicPublish(message.getMessageProperties().getReceivedExchange(),
                    message.getMessageProperties().getReceivedRoutingKey(), basicProperties,
                    message.getBody());
        }
    }


    @RabbitListener(queues = {TopicOrderRabbitMQConfiguration.TOPIC_ORDER_ES_QUEUE},
            containerFactory = "simpleTopicListenerContainerAutoACK")
    public void orderesmessage(@Payload PugMessageDto messageDto, Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            // basicAck自动应答
            //System.out.println(1 / 0);
            // 处理消息的逻辑

            // 参数1:消息的id
            // 参数2:消息是否进行批量确认,如果你默认配置,就写false,代表就把当前的消息进行确认.
            channel.basicAck(deliveryTag, false);
            log.info("================pug topic order es message is :{}", messageDto);
        } catch (Exception ex) {
            // 发送消息时将retry-count的值放进Header中
            Map<String, Object> headers = message.getMessageProperties().getHeaders();
            //重试次数
            Integer retryCount;
            String mapKey = "retry-count";
            if (!headers.containsKey(mapKey)) {
                retryCount = 0;
            } else {
                retryCount = (Integer) headers.get(mapKey);
            }

            if (retryCount++ < 3) {
                log.info("已经重试 " + retryCount + " 次");
                headers.put("retry-count", retryCount);
                //当消息回滚到消息队列时,这条消息不会回到队列尾部,而是仍是在队列头部。
                //这时消费者会立马又接收到这条消息进行处理,接着抛出异常,进行 回滚,如此反复进行
                //而比较理想的方式是,出现异常时,消息到达消息队列尾部,这样既保证消息不回丢失,又保证了正常业务的进行。
                //因此我们采取的解决方案是,将消息进行应答。
                //这时消息队列会删除该消息,同时我们再次发送该消息 到消息队列,这时就实现了错误消息进行消息队列尾部的方案
                //1.应答
                channel.basicAck(deliveryTag, false);
                //2.重新发送到MQ中
                AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
                        .contentType("application/json").headers(headers).build();
                // 重新把消息发动自己的交换机和队列中继续消费
                channel.basicPublish(message.getMessageProperties().getReceivedExchange(),
                        message.getMessageProperties().getReceivedRoutingKey(), basicProperties,
                        message.getBody());
            } else {
                log.info("现在重试次数为:" + retryCount);
                /**
                 * 重要的操作 存盘
                 * 手动ack
                 * channel.basicAck(deliveryTag,false);
                 * 通知人工处理
                 * log.error("重试三次异常,快来人工处理");
                 */
                //消息存盘
//                MsgLog msgLog = new MsgLog();
//                msgLog.setMsgId(msg.getMsgId());
//                msgLog.setMsg(new String(message.getBody(),"utf-8"));
//                msgLog.setExchange(message.getMessageProperties().getReceivedExchange());
//                msgLog.setRoutingKey(message.getMessageProperties().getReceivedRoutingKey());
//                msgLog.setTryCount(retryCount);
//                msgLog.setStatus(MsgLogStatusEnum.FAIL.getStatus());
//                msgLogService.save(msgLog);

                /**
                 * 不重要的操作放入 死信队列
                 * 消息异常处理:消费出现异常后,延时几秒,然后从新入队列消费,直到达到ttl超时时间,再转到死信,证明这个信息有问题需要人工干预
                 */
                //休眠2s 延迟写入队列,触发转入死信队列(需要添加死信队列,然后将正常队列绑定到死信交换机上)
                //Thread.sleep(2000);
                //channel.basicNack(deliveryTag, false, true);
            }
        }
    }

}
辅助理解三种确认方式的区别
channel.basicReject(deliveryTag, true);

basic.reject方法拒绝deliveryTag对应的消息,第二个参数是否requeue,true则重新入队列,否则丢弃或者进入死信队列。

该方法reject后,该消费者还是会消费到该条被reject的消息。

channel.basicNack(deliveryTag, false, true);

​ basic.nack方法为不确认deliveryTag对应的消息,第二个参数是否应用于多消息,第三个参数是否requeue,与basic.reject区别就是同时支持多个消息,可以nack该消费者先前接收未ack的所有消息。nack后的消息也会被自己消费到。

channel.basicRecover(true);

​ basic.recover是否恢复消息到队列,参数是是否requeue,true则重新入队列,并且尽可能的将之前recover的消息投递给其他消费者消费,而不是自己再次消费。false则消息会重新被投递给自己。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值