RabbitMq学习——Springboot整合rabbitmq之配置延迟消息

拜拜、爱过 提交于 2020-02-06 03:03:43

一、前言

昨晚上,一个大佬说了有关他做一个业务功能,如何将一个商品进行延迟上架,大佬说的方式听着觉得很是新奇,今天特意按照大佬的思维,重新自己搭建实现测试了一下简单的操作。

二、配置项

明人不说暗话,不喜欢大篇幅的阐述相关,只想将我实践的时候以及碰见的问题说明下,废话不多说,直接上配置。
针对rabbitmq这个消息队列的使用,我的专栏中有大篇幅的文章,进行了简单的描述,我们接下来以最简单的direct类转发器为例。

2.1、插件的安装(重点)

这个东西为什么拿在最开始的时候说呢,原因在于我最开始配置文件编写好了之后,出了一大堆的错误信息,还将我的springboot-demo给宕了,找了很久的问题,才发现是我的rabbitmq的配置中,缺少一个文件。


安装插件的步骤:
1、下载指定的插件
由于我使用的是 3.8.1 ,这里以windows环境下的rabbitmq插件安装作为案例。

http://www.rabbitmq.com/community-plugins.html

这个网址中,进入后搜索 rabbitmq_delayed_message_exchange。如下图所示
在这里插入图片描述
点击进去,下载一个后缀为 .ez 的文件,并将其复制在你安装的rabbitmq的plugins文件中,如下图所示
在这里插入图片描述
2、启用插件
在复制进指定的plugins文件中后,cmd 进入指定的rabbitmq的sbin目录下,输入以下指令,启用延迟插件:

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

出现如下的显示信息,表示启用成功
在这里插入图片描述
3、重启rabbitmq
这里很重要的一点,一定要重启!

2.2、配置文件的编写

上面讲述的插件的安装,一定要事先安装好指定的插件,并启用成功。

2.2.1、pom依赖文件的编写

在创建好的springboot项目中的pom文件内,加入以下依赖

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

2.2.2、配置文件的编写

添加好依赖文件后,我们需要配置一个rabbitmq的连接操作,在springboot中的配置相比原有的Spring的配置容易很多,我们只需要在 src/main/resources 文件中,创建一个 application.yml 文件编写相关的配置信息即可,配置信息如下所示:

server:
  port: 80

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: xiangjiao
    password: bunana
    virtual-host: /xiangjiao
    publisher-confirms: true   #开启发送确认
    publisher-returns: true  #开启发送失败回退(开启return 确认机制)
    template:
      mandatory: true #设置为true后,消费者在消息没有被路由到合适队列的情况下会被return监听,而不会自动删除
    #开启ack
    listener:
      direct:
        acknowledge-mode: manual
      simple:
        acknowledge-mode: manual #采取手动应答   #none 不确认,auto 自动确认 manual 手动确认
        #concurrency: 1 # 指定最小的消费者数量
        #max-concurrency: 1 #指定最大的消费者数量
        retry:
          enabled: true # 是否支持重试

我们操作一个rabbitmq进行消息的生产和消费时,通常是采取以下的流程方式:
在这里插入图片描述

消息生产者生产消息,并将消息推送至消息转发器中;
消息转发器将消息发送给指定的消息队列;
消费者监听指定的消息队列,获取相关的消息信息。

我们在编写配置文件时,头脑中一定要对这个顺序很清晰,接下来我们来编写配置文件。

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

/**
 * <p>消息队列和消息路由器的配置类</p><br >
 * 此处的配置逻辑分为以下几点:<br>
 * 1、消息生产者生产的消息是发送至指定的消息路由器(转发器)中;<br>
 * 2、消息消费者是从队列中进行消息的获取监听操作;<br>
 * 3、消息转发器和消息队列之间的绑定采取的最简单的direct类型,其他类型可以自行配置;<br>
 * @author 765199214
 *
 */
@Configuration
public class MQConfig {
	
	//配置消息转发器,接收生产者提供的消息
	@Bean(name="getDelayExchange")
	public DirectExchange getDelayExchange(){
		//DirectExchange(String name, boolean durable, boolean autoDelete)
		DirectExchange directExchange = new DirectExchange("delayExchange", true, false);
		//开启转发器推送消息至消息队列的延迟属性
		directExchange.setDelayed(true);
		return directExchange;
	}
	
	//设置消息队列的属性
	@Bean(name="getDelayQueue")
	public Queue getDelayQueue(){
		//Queue(String name, boolean durable, boolean exclusive, boolean autoDelete)
		return new Queue("delayQueue",true,false,false);
	}
	
	//将指定的消息队列和消息转发器进行绑定操作
	@Bean
	public Binding bindDelayExchangeAndQueue(
			@Qualifier(value="getDelayExchange") DirectExchange getDelayExchange,
			@Qualifier(value="getDelayQueue") Queue getDelayQueue){
		return BindingBuilder.bind(getDelayQueue).to(getDelayExchange).with("delay_key");
	}
}

[注意:]
此时的消息转发器和消息队列必须在原有的rabbitmq中不存在!也就是说此时的转发器和队列名是新的,不然会出现属性不一致的错误问题,这里不做过多的阐述。

2.2.3、编写消息发送业务类

消息生产者生产消息后,需要将消息发送至指定的消息转发器上,我们此时需要编写一个能够实现消息发送的业务类。

public interface IMessageServcie {
	public void sendDelayMessage(String exchange,String routingKey,Object msg,Integer delayTimes);
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback;
import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import cn.linkpower.service.IMessageServcie;

/**
 * 消息发送操作的实现类
 * @author 765199214
 *
 */
@Component
public class MessageServiceImpl implements IMessageServcie,ConfirmCallback,ReturnCallback {
	
	private static Logger log = LoggerFactory.getLogger(MessageServiceImpl.class);
	
	@Autowired
	private RabbitTemplate rabbitTemplate;
	
	@Override
	public void sendDelayMessage(String exchange,String routingKey,Object msg,Integer delayTimes) {
		//消息发送失败返回到队列中, yml需要配置 publisher-returns: true
		rabbitTemplate.setMandatory(true);
		//消息消费者确认收到消息后,手动ack回执
		rabbitTemplate.setConfirmCallback(this);
		rabbitTemplate.setReturnCallback(this);
		
		//发送消息
		rabbitTemplate.convertAndSend(exchange,routingKey,msg,new MessagePostProcessor() {
			@Override
			public Message postProcessMessage(Message message) throws AmqpException {
				//设置消息的持久化
				message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
				//设置延迟的时间
				message.getMessageProperties().setDelay(delayTimes);
				return message;
			}
		});
	}

	@Override
	public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
		log.info("---- returnedMessage ----replyCode="+replyCode+" replyText="+replyText+" ");
	}
	
	@Override
	public void confirm(CorrelationData correlationData, boolean ack, String cause) {
		log.info("---- confirm ----ack="+ack+"  cause="+String.valueOf(cause));
		//log.info("correlationData -->"+correlationData.toString());
		if(ack){
			log.info("---- confirm ----ack==true  cause="+cause);
		}else{
			log.info("---- confirm ----ack==false  cause="+cause);
		}
	}

}

2.2.4、自定义消息消费者

当消息队列中有了消息后,需要让消费者去消费,原则上消费者可以是其他的项目,这里只是为了做简单的配置说明讲解,将生产者和消费者放置于一个项目中,正常的业务流程这种做法是不合理的。

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 com.rabbitmq.client.Channel;

@Component
public class DirectQueueConsumer {
	
	private static Logger log = LoggerFactory.getLogger(DirectQueueConsumer.class);
	
	@RabbitListener(queues="delayQueue")
	@RabbitHandler
	public void delayMessage(String msg,Channel channel, Message message) throws IOException{
		
		try {
			/**
			 * 确认一条消息:<br>
			 * channel.basicAck(deliveryTag, false); <br>
			 * deliveryTag:该消息的index <br>
			 * multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息 <br>
			 */
			channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
			//打印获取到的消息的时间
			log.info("成功收到消息--{}", String.valueOf(msg));
		} catch (Exception e) {
			/**
			 * 拒绝确认消息:<br>
			 * channel.basicNack(long deliveryTag, boolean multiple, boolean requeue) ; <br>
			 * deliveryTag:该消息的index<br>
			 * multiple:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。<br>
			 * requeue:被拒绝的是否重新入队列 <br>
			 */
			channel.basicNack(message.getMessageProperties().getDeliveryTag(),
					false, true);
			log.error("收到消息异常--{}",String.valueOf(e));
		}
	}
}

我们在消息的消费者中加入了成功收到消息后,告诉消息队列可以删除消息,消息消费失败则将消息重新纳入队列中,进行继续消费的操作,保证了一定的消息事务性。
当然在配置文件中我们也加入了手动消息确认的配置。

spring.rabbitmq.listener.simple.acknowledge-mode=manual

2.2.5、消息的产生

有了消息生产业务类,也配置了消息消费者,以及消息转发器和消息队列在项目启动过程中会进行创建操作后,我们接下来就需要对其进行简单的测试了。
所以我们编写一个测试控制器,实现一种获取消息后,延迟6秒让消费者收到消息的操作。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import cn.linkpower.service.IMessageServcie;

@RestController
public class TestController {
	private static Logger log = LoggerFactory.getLogger(TestController.class);
	
	@Autowired
	private IMessageServcie messageServiceImpl;
	
	@RequestMapping("/test1")
	public String testDelay1(String msg){
		messageServiceImpl.sendDelayMessage("delayExchange", "delay_key", msg, 6000);
		log.info("成功发送消息");
		return "成功发送消息  ";
	}
}

启动项目,当我们请求指定的地址,我们需要看到的效果是:

请求后,6秒多消费者能够获取到消息。

三、测试

那我们就启动项目,实际测试下,探究是否可以实现延迟消息的操作。

http://localhost/test1?msg=777

在这里插入图片描述
通过控制台日志信息发现:
当我进行消息发送操作后,过了6秒多后,消息消费者获取到了指定的消息。

四、案例代码

github案例地址

五、参照文章

Spring Boot RabbitMQ 延迟消息实现完整版–Sam哥哥》的插件安装和配置

springboot+RabbitMQ做延迟消息详解(一)死信延迟,已运用到公司项目中》大佬的思路逻辑

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!