一、前言
昨晚上,一个大佬说了有关他做一个业务功能,如何将一个商品进行延迟上架,大佬说的方式听着觉得很是新奇,今天特意按照大佬的思维,重新自己搭建实现测试了一下简单的操作。
二、配置项
明人不说暗话,不喜欢大篇幅的阐述相关,只想将我实践的时候以及碰见的问题说明下,废话不多说,直接上配置。
针对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秒多后,消息消费者获取到了指定的消息。
四、案例代码
五、参照文章
《Spring Boot RabbitMQ 延迟消息实现完整版–Sam哥哥》的插件安装和配置
来源:CSDN
作者:一个只些技术总结的猿
链接:https://blog.csdn.net/qq_38322527/article/details/104188372