rabbitTemplate rabbitmq 发布模式与消息确认

2、组成

  • 生产者
  • 消息队列
  • 消费者
  • 交换机:
  • 隔离生产者和消息队列,充当二者中间体。
  • 接受相应的消息并绑定到指定队列

3、三种发布模式

  • 根据交换机类型不同,分为3种:
  • Direct<直接>:1对1-----一个消息只能被一个消费者消费
  • Topic<主题>:1对多-----一个消息可以被多个消费者消费**(纠正:一个消息可被多个队列接收,若多个消费者监听同一个队列,会以轮询方式被多个消费者接收,本质依然是1对1,类Direct。详述见下方评论!)**
    将路由和某个模式匹配,# 匹配一个或者多个,* 匹配一个。例如 Good.insert Good.delete,则Good.#都能获得
  • Fanout<分列>:广播

4、Direct发布模式

核心依赖及application.properties(除端口外不变)

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置文件

spring.rabbitmq.host=101.132.43.162
spring.rabbitmq.port=5672
spring.rabbitmq.username=rebbitmq
spring.rabbitmq.password=3321

生产者工程

  • 4.1、创建消息队列

两种方式:代码创建

/**
     * 模式为direct,直接发送到对应的消息队列
     * @return
     */
    @Bean
    public Queue directQueue(){
        return new Queue("direct");
    }

(2)RabbitMQ管理界面手动添加队列

Spring Rabbit MQ 发送消息自动创建队列 rabbitmqtemplate发送消息_spring

  • 4.2、发送端

API:amqpTemplate.convertAndSend(“队列名”,“消息内容”)

此处队列名必须与创建的队列一致。

public void send(String str) {
    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setMessageId(UUID.randomUUID().toString());
    Message message = new Message(str.getBytes(), messageProperties);
    rabbitTemplate.convertAndSend("direct",message);
}
  • 4.3、测试类
rabbitMqService.send("direct消息");

消费者工程

  • 4.1、创建消息队列

同上:略

  • 4.2、接收端

@RabbitListener(queues = “direct”):监听器监听指定队列

@RabbitHandler
@RabbitListener(queues = "direct")
public void Handler(Message message, Channel channel) throws IOException {
    String messageId = message.getMessageProperties().getMessageId();
    System.out.println("消息编号:"+messageId);
    String s = new String(message.getBody(), StandardCharsets.UTF_8);
    System.out.println("消息消费:"+s);
    try {
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
    }catch (Exception e){
        channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
    }

}

5、Topic发布模式

生产者工程

  • 5.1、创建消息队列

API:BindingBuilder.bind(指定队列).to(交换机).with(路由键);

路由键相当于队列名

@Bean(name = "message")
public Queue queue(){
    return  new Queue("topic.message");
}
@Bean(name="messages")
public Queue queue1(){
    return new Queue("topic.messages");
}

@Bean
public TopicExchange topicExchange(){
    return new TopicExchange("exchange");
}
@Bean
public FanoutExchange fanoutExchange(){
    return new FanoutExchange("fanoutExchange");
}

@Bean
public Binding binding(@Qualifier("message") Queue message, TopicExchange topicExchange){
    return BindingBuilder.bind(message).to(topicExchange).with("topic.message");
}


@Bean
public Binding binding1(@Qualifier("messages") Queue queue,TopicExchange topicExchange){
    return BindingBuilder.bind(queue).to(topicExchange).with("topic.#");
}


  • 5.2、发送端:

API:amqpTemplate.convertAndSend(“交换机名”,“路由键”,“消息内容”)

RabbitMQ将会根据第二个参数去寻找有没有匹配此规则的队列,如果有,则把消息给它,如果有不止一个,则把消息分发给匹配的队列(每个队列都有消息!)

@Override
public void send1(String str){
    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setMessageId(UUID.randomUUID().toString());
    Message message = new Message(str.getBytes(), messageProperties);
    rabbitTemplate.convertAndSend("exchange","topic.message",message);
}
  • 5.3、测试类
rabbitMqService.send1("topic.message消息");

消费者工程

@RabbitHandler
@RabbitListener(queues = "topic.message")
public void Handler1(Message message,Channel channel) throws IOException {
    String messageId = message.getMessageProperties().getMessageId();
    System.out.println("topic.message消息编号" + messageId);
    String str = new String(message.getBody(), StandardCharsets.UTF_8);
    System.out.println("topic.message消息体" + str);
    channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}

@RabbitHandler
@RabbitListener(queues = "topic.messages")
public void Handler2(Message message,Channel channel) throws IOException {
    String messageId = message.getMessageProperties().getMessageId();
    System.out.println("topic.messages消息编号" + messageId);
    String str = new String(message.getBody(), StandardCharsets.UTF_8);
    System.out.println("topic.messages消息体" + str);
    channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}

注意:topic模式中只有消费端监听的路由键符合发送端的路由规则(路由键决定)的队列才会收到消息。

6、Fanout发布模式

  • 广播:发送到路由器的消息会使得绑定到该路由器的每一个Queue接收到消息,这个时候就算指定了Key,或者规则(即上文中convertAndSend方法的参数2),也会被忽略!
  • 交换机类型:FanoutExchange
  • API:amqpTemplate.convertAndSend(“交换机名”,“ ”,“消息内容”);//路由键被忽略
  • 消费端:只要是绑定到该交换机上的都能收到消息。
    在配置类中增加
@Bean(name = "fanoutMessage")
public Queue queueC(){
    return  new Queue("fanoutMessage");
}

   @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("fanoutExchange");
    }

  @Bean
    public Binding binding2(@Qualifier("fanoutMessage") Queue queue,FanoutExchange fanoutExchange){
        return BindingBuilder.bind(queue).to(fanoutExchange);
    }

生产者服务

@Override
public void send2(String str){
    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setMessageId(UUID.randomUUID().toString());
    Message message = new Message(str.getBytes(), messageProperties);
    rabbitTemplate.convertAndSend("fanoutExchange","",message);
}

消费者服务

@RabbitHandler
@RabbitListener(queues = "fanoutMessage")
public void Handler3(Message message,Channel channel) throws IOException {
    String messageId = message.getMessageProperties().getMessageId();
    System.out.println("fanoutMessage消息编号" + messageId);
    String str = new String(message.getBody(), StandardCharsets.UTF_8);
    System.out.println("fanoutMessage消息体" + str);
    channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}

人工确认消息发送和消费成功

在application.properties中增加配置

#设置消息确认成功确认后进行回调
#NONE值是禁用发布确认模式,是默认值
#CORRELATED值是发布消息成功到交换器后会触发回调方法,如1示例
#SIMPLE值经测试有两种效果,其一效果和CORRELATED值一样会触发回调方法,其二在发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待broker节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDie方法如果返回false则会关闭channel,则接下来无法发送消息到broker;
#调用confirmCallback接口
spring.rabbitmq.publisher-confirm-type=correlated
#设置成功到达路由器后不会调用returnsCallback 方法,否则则会调用returnsCallback
spring.rabbitmq.publisher-returns=true

消息队列配置

package cn.atiaozao.springbootmqprovider.config;

import com.rabbitmq.client.AMQP;
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.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MqConfig {

    /**
     * 模式为direct,直接发送到对应的消息队列
     * @return
     */
    @Bean
    public Queue directQueue(){
        return new Queue("direct");
    }
    
    @Bean(name = "message")
    public Queue queue(){
        return  new Queue("topic.message");
    }
      @Bean(name="messages")
    public Queue queue1(){
        return new Queue("topic.messages");
    }
    
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange("exchange");
    }
 
  //消息绑定
    @Bean
    public Binding binding(@Qualifier("message") Queue message, TopicExchange topicExchange){
        return BindingBuilder.bind(message).to(topicExchange).with("topic.message");
    }
		
	//表达式绑定
    @Bean
    public Binding binding1(@Qualifier("messages") Queue queue,TopicExchange topicExchange){
        return BindingBuilder.bind(queue).to(topicExchange).with("topic.#");
    }
}

消息服务方法

package cn.atiaozao.springbootmqprovider.service.impl;

import cn.atiaozao.springbootmqprovider.service.RabbitMqService;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.UUID;

@Component
public class RabiitmqServiceImpl implements RabbitMqService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private ConfirmMessageImpl confirmMessageImpl;

    /**
     * 使用@PostConstruct 在注入完成之后进行设置消息push 确认的回调方法
     *
     */
    @PostConstruct
    public void init(){
        //消息成功到达指定队列的回调方法
        rabbitTemplate.setConfirmCallback(confirmMessageImpl);
        //消息如果未成功到达队列的
        rabbitTemplate.setReturnCallback(confirmMessageImpl);
    }
    @Override

    public void send(String str) {
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setMessageId(UUID.randomUUID().toString());
        Message message = new Message(str.getBytes(), messageProperties);
        rabbitTemplate.convertAndSend("direct",message);
    }
    @Override
    public void send1(String str){
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setMessageId(UUID.randomUUID().toString());
        Message message = new Message(str.getBytes(), messageProperties);
        rabbitTemplate.convertAndSend("exchange","topic.message",message);
    }

    @Override
    public void send2(String str){
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setMessageId(UUID.randomUUID().toString());
        Message message = new Message(str.getBytes(), messageProperties);
        rabbitTemplate.convertAndSend("exchange","",message);
    }
}

消息成功到达路由器后的确认回调方法

package cn.atiaozao.springbootmqprovider.service.impl;

import lombok.extern.java.Log;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Log
@Component
public class ConfirmMessageImpl implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        System.out.println("消息确认成功"+b+"|返回错误消息"+s+"|附带数据"+correlationData);
    }


    @Override
    public void returnedMessage(Message message, int i, String s, String s1, String s2) {
        System.out.println("消息发送失败|"+message.getBody());
    }
}

消息消费服务

#设置消息队列使用手工确认的方式
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual

消息消费监听

package cn.atiaozao.springbootmqconsumer.consumer;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.logging.Handler;


@Component
public class DirectConsumerHander {

    @RabbitHandler
    @RabbitListener(queues = "direct")
    public void Handler(Message message, Channel channel) throws IOException {
        String messageId = message.getMessageProperties().getMessageId();
        System.out.println("消息编号:"+messageId);
        String s = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println("消息消费:"+s);
        try {
            //消息人工确认
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
        }catch (Exception e){
            channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
        }

    }
    @RabbitHandler
    @RabbitListener(queues = "topic.message")
    public void Handler1(Message message,Channel channel) throws IOException {
        String messageId = message.getMessageProperties().getMessageId();
        System.out.println("topic.message消息编号" + messageId);
        String str = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println("topic.message消息体" + str);
        //消息人工确认
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
    }

    @RabbitHandler
    @RabbitListener(queues = "topic.messages")
    public void Handler2(Message message,Channel channel) throws IOException {
        String messageId = message.getMessageProperties().getMessageId();
        System.out.println("topic.messages消息编号" + messageId);
        String str = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println("topic.messages消息体" + str);
        //消息人工确认
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
    }

}