版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/eumenides_/article/details/86027185
上篇讲的是springboot整合rabbitmq实现延时队列之TTL方式实现rabbitmq的延时队列功能,在消息死亡时间比较灵活复杂的时候我们不可能声明很多死信队列去管理,而且声明一个就要6个bean,很蛋疼,所以希望能够有种方式使其消息死亡异步化,到期即死即消费,不会被阻塞,这里介绍使用插件的方式,不过需要rabbitmq要是3.6版本以上,也就是说,加入你的rabbitmq版本太老只能用TTL。
基于插件方式实现流程:
这里和TTL方式有个很大的不同就是TTL存放消息在死信队列(delayqueue)里,二基于插件存放消息在延时交换机里(x-delayed-message exchange)。
①:生产者将消息(msg)和路由键(routekey)发送指定的延时交换机(exchange)上
②:延时交换机(exchange)存储消息等待消息到期根据路由键(routekey)找到绑定自己的队列(queue)并把消息给它
③:队列(queue)再把消息发送给监听它的消费者(customer)
先去官网下载插件
下载的插件放到rabbitmq的plugins里,执行命令安装插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
流程介绍完了,看下具体代码吧!
- 首先pom因为依赖,和上一个一样:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- 配置文件配置rabbitmq的信息
# rabbitmq
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
# 手动ACK 不开启自动ACK模式,目的是防止报错后未正确处理消息丢失 默认 为 none
spring.rabbitmq.listener.simple.acknowledge-mode=manual
- 编写rabbitmq配置类,声明几个bean,我这里的名字和上篇一样,注意修改或者删掉原来的
/**
* rabbitmq配置类
* 员工系统配置延时队列
* @author zhanghang
* @date 2019/1/7
*/
@Configuration
public class RabbitUserConfig {
/**
* 延时队列交换机
* 注意这里的交换机类型:CustomExchange
* @return
*/
@Bean
public CustomExchange delayExchange(){
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange("delay_exchange","x-delayed-message",true, false,args);
}
/**
* 延时队列
* @return
*/
@Bean
public Queue delayQueue(){
return new Queue("delay_queue",true);
}
/**
* 给延时队列绑定交换机
* @return
*/
@Bean
public Binding cfgDelayBinding(Queue cfgDelayQueue,CustomExchange cfgUserDelayExchange){
return BindingBuilder.bind(cfgDelayQueue).to(cfgUserDelayExchange).with("delay_key").noargs();
}
}
- 编写rabbitmq生产者:
/**
* rabbitMq生产者类
* @author zhanghang
* @date 2018/12/13
*/
@Component
@Slf4j
public class RabbitProduct{
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendDelayMessage(List<Integer> list) {
//这里的消息可以是任意对象,无需额外配置,直接传即可
log.info("===============延时队列生产消息====================");
log.info("发送时间:{},发送内容:{}", LocalDateTime.now(), list.toString());
this.rabbitTemplate.convertAndSend(
"delay_exchange",
"delay_key",
list,
message -> {
//注意这里时间可以使long,而且是设置header
message.getMessageProperties().setHeader("x-delay",60000);
return message;
}
);
log.info("{}ms后执行", 60000);
}
- 编写rabbitmq消费者:
/**
* activeMq消费者类
* @author zhanghang
* @date 2017/12/19
*/
@Component
@Slf4j
public class RabbitConsumer {
@Autowired
private CcqCustomerCfgService ccqCustomerCfgService;
/**
* 默认情况下,如果没有配置手动ACK, 那么Spring Data AMQP 会在消息消费完毕后自动帮我们去ACK
* 存在问题:如果报错了,消息不会丢失,但是会无限循环消费,一直报错,如果开启了错误日志很容易就吧磁盘空间耗完
* 解决方案:手动ACK,或者try-catch 然后在 catch 里面将错误的消息转移到其它的系列中去
* spring.rabbitmq.listener.simple.acknowledge-mode = manual
* @param list 监听的内容
*/
@RabbitListener(queues = "delay_queue")
public void cfgUserReceiveDealy(List<Integer> list, Message message, Channel channel) throws IOException {
log.info("===============接收队列接收消息====================");
log.info("接收时间:{},接受内容:{}", LocalDateTime.now(), list.toString());
//通知 MQ 消息已被接收,可以ACK(从队列中删除)了
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
try {
dosomething.....
} catch (Exception e) {
log.error("============消费失败,尝试消息补发再次消费!==============");
log.error(e.getMessage());
/**
* basicRecover方法是进行补发操作,
* 其中的参数如果为true是把消息退回到queue但是有可能被其它的consumer(集群)接收到,
* 设置为false是只补发给当前的consumer
*/
channel.basicRecover(false);
}
}
}
- 编写测试类:
/**
* @author zhanghang
* @date 2019/1/3 17:57
*/
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private RabbitProduct rabbitProduct;
@GetMapping("/sendMessage")
public void sendMessage(){
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
rabbitProduct.sendDelayMessage(list);
}
}