Springboot中使用RabbitMQ的延时队列的实例

Springboot中使用RabbitMQ的延时队列的实例

一、Springboot中RabbitMQ的配置文件

PS:说明
  • 设计思路(发布订阅模式为例):

    • 定义一个交换机:my-dlx-exchange
    • 定义一个队列:my-dlx-Queue(死信队列)
    • 把这个队列和交换机绑定到一起,并定义好路由key,此时该队列能够接收交换机的消息
    • 定义一个队列:delayQueue(延时队列),并指定他关联的交换机路由key
    • ps:延时队列的消息过期后,会由关联的交换机,根据路由key,将消息发送到绑定的私信队列中。
  • 1、定义一个队列作为延时队列,

	//定义延时队列
    @Bean("delayQueue")
    public Queue delayQueue(){
    	//使用QueueBuilder构建完整队列,durable:持久化方法,参数:队列名称
        return QueueBuilder.durable("delayQueue")
                //1、设置死信交换机:如果消息过时,消息会被投递到当前对应的my-dlx-exchange
                .withArgument("x-dead-letter-exchange","my-dlx-exchange")
                
                //2、设置交换机的路由key:交换机(my-dlx-exchange)会根据路由key(routing-key-delay)投递消息到对应私信队列
                .withArgument("x-dead-letter-routing-key","routing-key-delay").build();
    }
  • 2、定义一个队列作为死信队列,用于存放过期的消息
	//定义死信队列
    @Bean("dlxQueue")
    public Queue dlxQueue(){
        return QueueBuilder.durable("my-dlx-Queue").build();
    }
  • 3、定义交换机、绑定私信队列、设置好路由key
    • 普通的发布订阅模式可以不设置路由key
    • 路由模式需要设置路由key,实现根据key订阅指定队列
    • 通配符模式交换机设置为topicExchange即可
      • DIRECT:定向(使用路由key指定私信队列)
      • FANOUT:扇形,发送消息到每一个私信队列
      • TOPIC: 通配符方式
      • HEADERS:参数匹配方

	//定义死信交换机
    @Bean("dlxExchange")
    public Exchange dlxExchange(){
        /**
         * DIRECT:定向(使用路由key指定私信队列)
         * FANOUT:扇形,发送消息到每一个私信队列
         * TOPIC: 通配符方式
         * HEADERS:参数匹配方式
         * */
        return ExchangeBuilder.directExchange("my-dlx-exchange").build();
    }

	//路由:绑定死信交换机与死信队列,并设置路由key
    @Bean("dlxBinding")
    public Binding dlxBinding(@Qualifier("dlxExchange") Exchange exchange, @Qualifier("dlxQueue") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("routing-key-delay")
                .noargs();
        	//  noargs();是不需要指定参数
    }

  • 5、完整的配置文件
package com.zydl.yjalarmapi.common.RabbitMQ;


import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//@Configuration
public class RabbitMqConfig {
	
	//定义延时队列
    @Bean("delayQueue")
    public Queue delayQueue(){
        //设置死信交换机及路由key
        return QueueBuilder.durable("delayQueue")
                //如果消息过时,则会被投递到当前对应的my-dlx-exchange
                .withArgument("x-dead-letter-exchange","my-dlx-exchange")
                //如果消息过时,my-dlx-exchange会根据routing-key-delay投递消息到对应队列
                .withArgument("x-dead-letter-routing-key","routing-key-delay").build();
    }

	//定义死信队列
    @Bean("dlxQueue")
    public Queue dlxQueue(){
        return QueueBuilder.durable("my-dlx-Queue").build();
    }

	//定义死信交换机
    @Bean("dlxExchange")
    public Exchange dlxExchange(){
        /**
         * DIRECT:定向(使用路由key指定私信队列)
         * FANOUT:扇形,发送消息到每一个私信队列
         * TOPIC: 通配符方式
         * HEADERS:参数匹配方式
         * */
        return ExchangeBuilder.directExchange("my-dlx-exchange").build();
    }

	//路由:绑定死信交换机与死信队列
    @Bean("dlxBinding")
    public Binding dlxBinding(@Qualifier("dlxExchange") Exchange exchange, @Qualifier("dlxQueue") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("routing-key-delay")
                .noargs();
        /*
        * noargs();是不需要指定参数
        * */
    }
}

二、定义生产者

  • 配置文件中添加配置
spring:
  rabbitmq:
    host: 192.168.150.101 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itcast # 用户名
    password: 123321 # 密码
  • 生产者
package com.zydl.yjalarmapi.common.RabbitMQ;

import com.zydl.yjalarmapi.entity.Approvalprocess;
import com.zydl.yjalarmapi.entity.Eventreport;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class RabbitProducter {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /*
     * 1、这个方法的作用就是将消息添加到延时队列中
     * 2、等消息过期后会由交换机:将消息推送到死信队列中
     * 3、监听器会监听死信队列中的消息并进行处理
     * */
    public void Producter(Map<String,Object> map) {
        /**
         * 参数1:延时队列名称
         * 参数2:发送的消息(map)
         * 参数3:消息处理器(MessagePostProcessor):如果不需要设置延时时间,第三个参数省略即可。
         * */
        rabbitTemplate.convertAndSend("delayQueue", map, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //消息后处理对象MessagePostProcessor 来设置过期时间
                //设置消息过期时间
                message.getMessageProperties().setExpiration("10000");
                return message;
            }
        });
    }
}

三、定义消费者监听器

PS:说明
  • 1、普通模式

    • 生产者生产消息:rabbitTemplate.convertAndSend(“my_queue”, message),
    • 消费者直接绑定到生产者发消息的队列中即可:@RabbitListener(queues = “my_queue”)
  • 2、延时队列

    • 生产者生成消息,设定好过期时间,消息过期后会被交换机发送到死信队列
  • 消费者监听死信队列即可。

  • 配置文件中添加配置

spring:
  rabbitmq:
    host: 192.168.150.101 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itcast # 用户名
    password: 123321 # 密码
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
  • 普通消费者
@Component
//绑定私信队列
@RabbitListener(queues = "my-dlx-Queue")
public class Messgelistener {
    
    @RabbitHandler
    public void recevice(Map<String,Object> map){
        // 业务逻辑
    }

}

  • Work模型:多个消费者

    • 多个消费者绑定到一个队列,同一条消息只会被一个消费者处理
    • 通过设置prefetch来控制消费者预取的消息数量
@Component
public class Messgelistener {
    	//绑定死信队列
    	@RabbitListener(queues = "simple.queue")
	    public void listenWorkQueue1(String msg) throws InterruptedException {
	      System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
	      Thread.sleep(20);
	    }
      	//绑定死信队列
	    @RabbitListener(queues = "simple.queue")
	    public void listenWorkQueue2(String msg) throws InterruptedException {
	        System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());
	        Thread.sleep(200);
	    }
  }

MQ的可靠性机制:confirm、return、ACK

  • 消息投递可靠性:confirm、return模式:
    • confirm模式:生产者发送消息到交换机的时机上使用
    • return模式:交换机转发给queue的时机上使用
一、confirm示例:
  • 起步依赖
 <!--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>
  • 配置文件
  spring:
  rabbitmq:
    port: 5672
    host: localhost
    username: guest
    password: guest
    virtual-host: /
    #开启confirms这个模式
    #springboot2.2.0.RELEASE支持这个
    #publisher-confirm-type: correlated
    publisher-confirms: true
  • 实现ConfirmCallback接口,回调函数
package com.example.confirm;
 
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
 
@Component
public class MyConfirmCallBack implements RabbitTemplate.ConfirmCallback {
    /**
     * 回调方法,发送消息后都会调用该方法
     * @param correlationData  数据
     * @param ack true发送成功 ,false发送失败
     * @param cause 如果是失败,那么返回这个失败原因的字符串。如果成功 原因就是null
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if(ack){
            //模拟在这里减100
            System.out.println("发送成功,减100成功");
        }else{
            System.out.println("发送失败,失败原因是:"+cause);
        }
    }
}
  • 创建队列、交换机、绑定队列与交换机
  //省略
  • 创建生产者
package com.example.controller;
 
import com.example.confirm.MyConfirmCallBack;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/testsend")
public class TestSendController {
 
    @Autowired
    private RabbitTemplate rabbitTemplate;
 
    @Autowired
    //或者用这个
    //private MyConfirmCallBack myConfirmCallBack;
    private RabbitTemplate.ConfirmCallback confirmCallback;
 
    @GetMapping ("/send1")
    public String send1(){
        //设置回调函数-------绑定确认回调方法
        rabbitTemplate.setConfirmCallback(confirmCallback);
        //发送消息
        rabbitTemplate.convertAndSend("exchange_test6","test6.insert","消息本身");
        return "ok";
    }
 
}
二、return模式
  • 起步依赖
  • 配置文件
spring:
  rabbitmq:
    port: 5672
    host: localhost
    username: guest
    password: guest
    virtual-host: /
    #开启confirms这个模式
    #springboot2.2.0.RELEASE支持这个
    #publisher-confirm-type: correlated
    publisher-confirms: true
    #开启return模式
    publisher-returns: true
  • 实现ReturnCallback,回调方法
package com.example.confirm;
 
import org.springframework.amqp.core.Message;
 
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
 
@Component
public class MyReturnCallBack implements RabbitTemplate.ReturnCallback {
    /**
     *
     * 交换机转发消息到队列时调用 , 如果成功的时候就不调用该方法了,只有转发失败的时候才调用
     * @param message     消息内容本身
     * @param replyCode   响应状态码
     * @param replyText   响应内容
     * @param exchange    交换机
     * @param routingKey   routingKey
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        //获取到消息本身
        System.out.println(new String(message.getBody()));
        //message.getMessageProperties()可以获取到其他的别的信息
 
        //状态码
        System.out.println(replyCode);
 
        System.out.println(replyText);
        //exchange
        System.out.println(exchange);
        //routingKey
        System.out.println(routingKey);
 
    }
 
 
}
  • 定义队列、交换机、绑定队列与交换机
//省略
  • 实现生产者测试
package com.example.controller;
 
import com.example.confirm.MyConfirmCallBack;
import com.example.confirm.MyReturnCallBack;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/testsend")
public class TestSendController {
 
    @Autowired
    private RabbitTemplate rabbitTemplate;
 
    @Autowired
    //或者用这个:confirm回调函数
    //private MyConfirmCallBack myConfirmCallBack;
    private RabbitTemplate.ConfirmCallback confirmCallback;

    @Autowired
    //private MyReturnCallBack myReturnCallBack;
    //或者用这个:return回调函数
    private RabbitTemplate.ReturnCallback returnsCallback;
 
    @GetMapping ("/send1")
    public String send1(){
        //设置confirm回调函数
        rabbitTemplate.setConfirmCallback(confirmCallback);
        //设置return回调函数
        rabbitTemplate.setReturnCallback(returnsCallback);
        //发送消息
        rabbitTemplate.convertAndSend("exchange_test6","test6.insert","消息本身");
 
        return "ok";
    }
}
  • 消息接收可靠性:ACK有三种方式

    • 自动确认:acknowledge=“none”
    • 手动确认 acknowledge=“manual”
    • 根据异常情况来确认(暂时不怎么用) acknowledge=“auto”
三、ACK手动确认示例
  • 配置文件
spring:
  rabbitmq:
    port: 5672
    host: localhost
    username: guest
    password: guest
    virtual-host: /
    #开启confirms这个模式
    #springboot2.2.0.RELEASE支持这个
    #publisher-confirm-type: correlated
    publisher-confirms: true
    #开启return模式
    publisher-returns: true
    listener:
      direct:
        #开启手动签收
        acknowledge-mode: manual
  • 测试
package com.example.listener;
 
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.Component;
 
import java.io.IOException;
import java.util.Map;
 
@Component
@RabbitListener(queues = "queue_test6")
public class MyRabbitListener {
    /**
     *
     * @param message 消息封装的对象,(包括了消息的序号,消息本身,消费者名称等)
     * @param channel  链接的通道
     * @param msg  消息本身
     */
    @RabbitHandler //用于处理具体类型的消息,会自动把消息转换成对应的对象
    public void receiveMessage(Message message, Channel channel,String msg){
        //接收消息
        System.out.println(msg);
        MessageProperties messageProperties = message.getMessageProperties();
        try {
 
            //模拟业务-100
            System.out.println("消费后减100元");
            //模拟出问题
            int i = 10/0;
            //如果正常就签收消息
            //参数1,消息的序号
            //参数二,是否批量签收 true是批量签收
            channel.basicAck(messageProperties.getDeliveryTag(),true);
        } catch (Exception e) {
            e.printStackTrace();
            //不正常就拒收消息(丢弃了)
 
            try {
                //如果该消息重回过队列就不投递了,避免死循环
                if(messageProperties.getRedelivered()){
                    System.out.println("已经重新投递过一次了");
                }else{
                    //****三种方式,用一种即可************
                   
                    //同时支持拒绝多个消息,可以拒绝该消费者先前接收未ack的所有消息。拒绝后的消息也会被自己消费到。
                    //参数1 消息序号
                    //参数2 是否批量拒绝消息
                    //参数3 是否把消息重新回到队列中:requeue
                    channel.basicNack(messageProperties.getDeliveryTag(),true,true);
                    
                    //basicReject处理拒绝消息。(不支持批量拒绝)
                    //参数1 消息序号
                    //参数2 true:重新放回队列,false:否则丢弃或者进入死信队列。
                    //该方法reject后,该消费者还是会消费到该条被reject的消息。
                	channel.basicReject(deliveryTag, true);
                    
                    //basicRecover是否恢复消息到队列,
                    //参数1  true则重新入队列,并且尽可能的将之前recover的消息投递给其他消费者消费,而不是自己再次消费。
                    //		false则消息会重新被投递给自己
                    channel.basicRecover(true);
                    
                }
                
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
 
        }
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值