消息队列RabbitMQ-Springboot 整合

一、RMQ相关配置

1、引入依赖

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

2、启动类开启RMQ注解

/**
使用RabbitMQ
1、引入amqp场景;RabbitAutoConfiguration就会自动生效
2、给容器中自动配置了
      RabbitTemplate、AmqpAdmin、CachingConnectionFactory、RabbitMessagingTemplate
            @ConfigurationProperties(prefix="spring.rabbitmq")
            public class RabbitProperties
3、给配置文件中配置 spring.rabbitmq 信息
4、@EnableRabbit (开启RMQ注解)
**/
 @EnableRabbit   // 开启消息队列
// 添加注册发现功能
@EnableDiscoveryClient
@MapperScan("com.atguigu.gulimall.order.dao")
@SpringBootApplication
public class GulimallOrderApplication {

  public static void main(String[] args) {
    SpringApplication.run(GulimallOrderApplication.class, args);
  }

}

3、 配置文件增加RMQ属性

 # RabbitMQ配置
spring.rabbitmq.host=192.168.10.10
spring.rabbitmq.port=5672
# 虚拟主机配置
spring.rabbitmq.virtual-host=/
# 开启发送端消息抵达Broker确认
spring.rabbitmq.publisher-confirms=true
# 开启发送端消息抵达Queue确认
spring.rabbitmq.publisher-returns=true
# 只要消息抵达Queue,就会异步发送优先回调returnfirm
spring.rabbitmq.template.mandatory=true
# 手动ack消息,不使用默认的消费端确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual

4, 配置序列化

package com.kun.order.config;

import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author zhoukun 86547462@qq.com
 * @version 1.0
 * @date 2020/12/28 17:34
 */
@Configuration
public class MyRabbitConfig {
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
}

二、RMQ使用

1、单元测试

package com.kun.order;

import com.kun.order.entity.OrderReturnReasonEntity;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;
import java.util.UUID;

@SpringBootTest
@Slf4j
class GulimallOrderApplicationTests {

	@Autowired
	private AmqpAdmin amqpAdmin;   // 创建交换机名,创建队列,创建绑定

	@Autowired
	private RabbitTemplate rabbitTemplate;  // 发送消息相关
	/**
	 * 1、如何创建交换机Exchange、队列Queue、绑定关系Binding
	 *      1)、使用AmqpAdmin进行创建
	 * 2、如何收发消息
	 */

	//创建交换机Exchange
	@Test
	public void createExchange() {

		/**
		 *  DirectExchange 指定队列模式
		 *  FanoutExchange 广播模式
		 *  TopicExchange 主题模式
		 */
		Exchange directExchange = new DirectExchange("hello-java-exchange",true,false);
		amqpAdmin.declareExchange(directExchange);
		log.info("Exchange[{}]创建成功:","hello-java-exchange");
	}

	//创建队列Queue
	@Test
	public void testCreateQueue() {
		Queue queue = new Queue("hello-java-queue",true,false,false);
		amqpAdmin.declareQueue(queue);
		log.info("Queue[{}]创建成功:","hello-java-queue");
	}

	//创建绑定Binding
	@Test
	public void createBinding() {

		Binding binding = new Binding("hello-java-queue",
				Binding.DestinationType.QUEUE,
				"hello-java-exchange",
				"hello.java",
				null);
		amqpAdmin.declareBinding(binding);
		log.info("Binding[{}]创建成功:","hello-java-binding");

	}
	@Test
	public void sendMessageTest() {
		OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity();
		reasonEntity.setId(1L);
		reasonEntity.setCreateTime(new Date());
		reasonEntity.setName("reason");
		reasonEntity.setStatus(1);
		reasonEntity.setSort(2);
		String msg = "Hello World";
		//1、发送消息,如果发送的消息是个对象,会使用序列化机制,将对象写出去,对象必须实现Serializable接口

		//2、发送的对象类型的消息,可以是一个json
		rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",
				reasonEntity,new CorrelationData(UUID.randomUUID().toString()));
		log.info("消息发送完成:{}",reasonEntity);
	}
}

在这里插入图片描述

三、RMQ队列监听

监听消息
使用 @RabbitListener:必须有 @EnableRabbit

  • @RabbitListener:类+方法上(监听那些队列即可)
  • @RabbitHandler:标在方法上(重载区分不同的消息)
/**
 * 监听队列
 * 参数可以写类型
 * 1、Message message:原生消息详细信息。头+体
 * queues:声明需要监听的所有队列
 * channel:当前传输数据的通道
 *
 * Queue:可以很多人来监听。只要收到消息,队列删除消息,而且只能有一个收到消息(分布式场景)
 * 场景:
 *     1)、订单服务启动多个:同一个消息,只能有一个客户端收到
 */
@RabbitListener(queues = {"hello-java-queue"})
public void revieveMessage(Message message,
    OrderReturnReasonEntity content) {
    //拿到主体内容
    byte[] body = message.getBody();
    //拿到的消息头属性信息
    MessageProperties messageProperties = message.getMessageProperties();
    System.out.println("接受到的消息...内容" + message + "===内容:" + content);

}

消息队列-可靠投递

保证消息不丢失,可靠抵达,可以使用事务消息,性能下降250倍,为此引入确认机制。

  • publisher confirmCallback 确认模式
  • publisher returnCallback 未投递到 queue 退回模式
  • consumer ack机制
    在这里插入图片描述

一、发送端确认

修改 application.yml 文件:

#rabbitmq配置
  rabbitmq:
    host: 106.55.168.234
    port: 5672
    virtual-host: /  # 虚拟主机配置
    publisher-returns: true  # 开启发送端消息抵达Broker确认
    publisher-confirm-type: correlated #必须配置这个才会确认回调

创建配置文件:MyRabbitConfig.java

package com.kun.order.config;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

/**
 * @author zhoukun 86547462@qq.com
 * @version 1.0
 * @date 2020/12/28 17:34
 */
@Configuration
public class MyRabbitConfig {
    @Autowired
    RabbitTemplate rabbitTemplate;

/**
序列化
**/
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
    /**
     * 定制RabbitTemplate
     * 1、服务收到消息就会回调
     * 1、spring.rabbitmq.publisher-confirms: true
     * 2、设置确认回调
     * 2、消息正确抵达队列就会进行回调
     * 1、spring.rabbitmq.publisher-returns: true
     * spring.rabbitmq.template.mandatory: true
     * 2、设置确认回调ReturnCallback
     * <p>
     * 3、消费端确认(保证每个消息都被正确消费,此时才可以broker删除这个消息)
     */
    @PostConstruct  //MyRabbitConfig对象创建完成以后,执行这个方法
    public void initRabbitTemplate() {

        /**
         * 1、只要消息抵达Broker就ack=true
         * correlationData:当前消息的唯一关联数据(这个是消息的唯一id)
         * ack:消息是否成功收到
         * cause:失败的原因
         */
        //设置确认回调
        rabbitTemplate.setConfirmCallback((correlationData,ack,cause) -> {
            System.out.println("confirm...correlationData["+correlationData+"]==>ack:["+ack+"]==>cause:["+cause+"]");
        });

        /**
         * 只要消息没有投递给指定的队列,就触发这个失败回调
         * message:投递失败的消息详细信息
         * replyCode:回复的状态码
         * replyText:回复的文本内容
         * exchange:当时这个消息发给哪个交换机
         * routingKey:当时这个消息用哪个路邮键
         */
        rabbitTemplate.setReturnCallback((message,replyCode,replyText,exchange,routingKey) -> {
            System.out.println("Fail Message["+message+"]==>replyCode["+replyCode+"]" +
                    "==>replyText["+replyText+"]==>exchange["+exchange+"]==>routingKey["+routingKey+"]");
        });
    }
}

二、消费端确认

可靠抵达-Ack消息确认机制说明:

1、消费者获取到消息,成功处理,可以回复Ack给Broker

  • basic.ack 用于肯定确认;broker将移除此消息
  • basic.nack 用于否定确认;可以指定broker是否丢弃此消息,可以批量
  • basic.reject 用于否定确认;同上,但不能批量

2、默认,消息被消费者收到,就会从broker的queue中移除
3、queue无消费者,消息依然会被存储,直到消费者消费
4、消费者收到消息,默认会自动ack。但是如果无法确定此消息是否被处理完成,或者成功处理。我们可以开启手动ack模式。

  • 消息处理成功,ack(),接受下一个消息,此消息broker就会移除。
  • 消息处理失败,nack()/reject(), 重新发送给其他人处理,或者容错处理后ack。
  • 消息一直没有调用ack/nack方法,broker认为此消息正在被处理,不会投递给别人,此时* 客户端断开,消息不会被broker移除,会投递给别人。

配置修改

#rabbitmq配置
  rabbitmq:
    host: 106.55.168.234
    port: 5672
    virtual-host: /  # 虚拟主机配置
    publisher-returns: true
    publisher-confirm-type: correlated #必须配置这个才会确认回调
    listener:
      direct:
        acknowledge-mode: manual # # 手动ack消息,不使用默认的消费端确认

测试

package com.kun.order.service.impl;

import com.kun.order.entity.OrderReturnReasonEntity;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Map;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kun.common.utils.PageUtils;
import com.kun.common.utils.Query;

import com.kun.order.dao.OrderDao;
import com.kun.order.entity.OrderEntity;
import com.kun.order.service.OrderService;

@RabbitListener(queues = {"hello-java-queue"})
@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );

        return new PageUtils(page);
    }
    @RabbitHandler
    public void revieveMessage(Message message,
                               OrderReturnReasonEntity content, Channel channel) throws IOException {

        System.out.println("接收到消息..." + content);

        //拿到主体内容
        byte[] body = message.getBody();
        //拿到的消息头属性信息
        MessageProperties messageProperties = message.getMessageProperties();
        System.out.println("接受到的消息...内容" + message + "===内容:" + content);
   //消息是否是第二次派发过来的
   Boolean redelivered = message.getMessageProperties().getRedelivered();
  
        // Thread.sleep(3000);
        // Channel内按顺序自增
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("deliveryTag===>" + deliveryTag);

        try {
            // 签收货物, v2非批量模式
            channel.basicAck(deliveryTag, false);
            //退货  v2非批量模式,v3 true:重新加入队列,false:丢弃
            channel.basicNack(deliveryTag,false,true);
        } catch (Exception e) {
            // 网络中断(突然)
        }
    }

    @RabbitHandler
    public void revieveMessage2(Message message,
                               OrderEntity entity, Channel channel) throws IOException {

        System.out.println("2接收到消息..." + entity);

        //拿到主体内容
        byte[] body = message.getBody();
        //拿到的消息头属性信息
        MessageProperties messageProperties = message.getMessageProperties();
        System.out.println("2接受到的消息...内容" + message + "===内容:" + entity);

        // Thread.sleep(3000);
        // Channel内按顺序自增
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("2deliveryTag===>" + deliveryTag);

        try {
            // 签收货物,非批量模式
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            // 网络中断(突然)
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值