rabbitMQ 延时队列

来记录一下笔记吧

rabbitmq要实现延时队列,主要利用消息的过期时间TTL,和死信机制来做,简单来说,我们可以将需要延时发送的消息,设置过期时间,然后把消息发送到某个队列,并且在这个队列上绑定一个死信交换机,这个死信交换机和另一个队列建立绑定关系,这样只需要等待第一个队列的消息过期,然后mq会自动将过期的消息通过绑定的死信交换机,路由到死信交换机绑定的队列,最后监听队列即可

springboot环境

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

mq相关配置

spring.rabbitmq.addresses=
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
spring.rabbitmq.connection-timeout=15000
spring.rabbitmq.publisher-confirms=true
spring.rabbitmq.publisher-returns=true
#必须为true,否则失败,发送者,接受不到回调消息,false 服务器丢失请求

spring.rabbitmq.template.mandatory=true
#消费端手工ack
spring.rabbitmq.listener.simple.acknowledge-mode=manual
#消费者最小数量
spring.rabbitmq.listener.simple.concurrency=1
#消费者最大数量,控制并发,若需要消息有消费,则可配置1个消费端
spring.rabbitmq.listener.simple.max-concurrency=1
#每次请求处理的消息数
spring.rabbitmq.listener.simple.prefetch=1

配置:RabbitAdmin ,rabbitAdmin用来操作队列,交换机,
比如说创建队列,创建交换机,建立绑定关系

package com.fnm.feynman.hospital.common.config;

import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;

/**
 * @author yanjun.liu
 * @version 1.0
 * @date 2020/9/2--14:38
 */
@SpringBootConfiguration
public class RabbitMqConfig {

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        rabbitAdmin.setAutoStartup(true);
        return rabbitAdmin;
    }
}

消息的发送者

解释一下以下代码,最上方,是两个回调,在消息发送成功或者失败,将保存数据库做留存,以备后期对发送失败的消息进行补偿,
下面贴上代码
先发送一条及时消息,然后在发送一条延时消息

package com.fnm.feynman.hospital.business.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fnm.feynman.common.utils.UUIDUtils;
import com.fnm.feynman.hospital.business.entity.ZcMqMessageEntity;
import com.fnm.feynman.hospital.business.resp.OrderMessageResp;
import com.fnm.feynman.hospital.business.service.SendMessageService;
import com.fnm.feynman.hospital.business.service.ZcMqMessageService;
import com.fnm.feynman.hospital.constant.FinalConst;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.sql.Time;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author yanjun.liu
 * @version 1.0
 * @date 2020/9/2--12:00
 */
@Service
@Slf4j
public class SendMessageServiceImpl implements SendMessageService {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private RabbitAdmin rabbitAdmin;

    @Resource
    private ZcMqMessageService zcMqMessageService;

    private static final String ROUTE_KING="#";

    private static final String DELAYED="_delayed";

    private static final String TTL="_ttl";


    /**
     * 回调函数: confirm确认
     * correlationData 消息唯一id
     * ack 是否到达Broker
     * cause nack出现异常,返回的异常消息
     */
    final RabbitTemplate.ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() {
        @Override
        public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            if (!ack) {
                log.error("消息发送到Broker失败,消息id为:{}",correlationData);
                updateMessageStatus(FinalConst.ERROR,correlationData,FinalConst.CONFIRM);
            } else {
                updateMessageStatus(FinalConst.SUCCESS,correlationData,FinalConst.CONFIRM);
                log.info("消息发送到Broker成功,消息id为:{}",correlationData);
            }
        }
    };
    private void updateMessageStatus(String status,CorrelationData correlationData,String callbackType){
        ZcMqMessageEntity zcMqMessageEntity=zcMqMessageService.getOne(new QueryWrapper<ZcMqMessageEntity>().lambda().eq(ZcMqMessageEntity::getMqMessageId,correlationData.getId()));
        if(zcMqMessageEntity != null){
            zcMqMessageEntity.setIsSend(FinalConst.SUCCESS);
            zcMqMessageEntity.setCallbackType(callbackType);
            zcMqMessageService.updateById(zcMqMessageEntity);
        }
    }
    /**
     * return返回回调
     * 若routingKey不可达。会回调此方法
     * message     消息
     * replyCode   不可达错误码
     * replyText   错误内容信息
     * exchange    交换机
     * routingKey  路由key
     */
    final RabbitTemplate.ReturnCallback returnCallback = new RabbitTemplate.ReturnCallback() {
        @Override
        public void returnedMessage(org.springframework.amqp.core.Message message, int replyCode, String replyText,
                                    String exchange, String routingKey) {

            String messageId = message.getMessageProperties().getMessageId();
            ZcMqMessageEntity zcMqMessageEntity=zcMqMessageService.getOne(new QueryWrapper<ZcMqMessageEntity>().lambda().eq(ZcMqMessageEntity::getMqMessageId,messageId));
            if(zcMqMessageEntity != null){
                zcMqMessageEntity.setIsSend(FinalConst.ERROR);
                zcMqMessageEntity.setCallbackType(FinalConst.RETURN);
                zcMqMessageService.updateById(zcMqMessageEntity);
            }
            log.error("消息发送失败:routingKey不可达,消息为:{}",message.getMessageProperties().getMessageId());
            log.error("消息发送失败:routingKey不可达,消息为:{}",message);
            log.error("消息发送失败:routingKey不可达,错误码:{}",replyCode);
            log.error("消息发送失败:routingKey不可达,错误内容信息:{}",replyText);
            log.error("消息发送失败:routingKey不可达,交换机:{}",exchange);
            log.error("消息发送失败:routingKey不可达,路由key:{}",routingKey);
        }
    };

    /**
     * 发送预约消息
     * 若预约时间减去一小时小于,<当前时间,那么消息会丢失,延时队列不会收到该消息
     * @param queue
     * @param orderMessageResp
     */
    @Override
    public void sendOrderMessage(String exchange, String queue, OrderMessageResp orderMessageResp, Date date, Time time,String orderNo){
        String json = JSON.toJSONString(orderMessageResp);
        rabbitAdmin.declareExchange(new TopicExchange(exchange, true, false));
        rabbitAdmin.declareQueue(new Queue(queue, true));
        rabbitAdmin.declareBinding(
            BindingBuilder
                .bind(new Queue(queue))
                .to(new TopicExchange(exchange))
                .with(ROUTE_KING));
        rabbitTemplate.setConfirmCallback(confirmCallback);
        rabbitTemplate.setReturnCallback(returnCallback);
        //消息id = 时间戳 + 全局唯一id
        CorrelationData correlationData = new CorrelationData(UUIDUtils.generate32BitId() + Long.toString(System.currentTimeMillis()));
        orderMessageResp.setTitle("预约成功");
        //入库
        saveMessage(json,exchange,queue,orderNo,correlationData.getId(),ROUTE_KING,FinalConst.NOT_DELAY);
        //发送
        rabbitTemplate.convertAndSend(exchange, ROUTE_KING, json, correlationData);
        log.info("消息发送成功,消息内容为:{}",json);
		//开始发送延时消息,上面一部分是发送的即时消息
        //声明死信交换机,绑定到队列
        Map<String, Object> agruments = new HashMap<>(2);
        agruments.put("x-dead-letter-exchange", exchange+TTL);
        agruments.put("x-dead-letter-routing-key", ROUTE_KING);

        rabbitAdmin.declareQueue(new Queue(queue+DELAYED, true,false, false,agruments));
        rabbitAdmin.declareExchange(new TopicExchange(exchange+DELAYED, true, false));
        rabbitAdmin.declareBinding(
            BindingBuilder
                .bind(new Queue(queue+DELAYED))
                .to(new TopicExchange(exchange+DELAYED))
                .with(ROUTE_KING));
        orderMessageResp.setMessage(orderMessageResp.getMessage()+",距离预约时间还有一小时,请您及时前往");

        LocalDateTime localDateTime = time.toLocalTime().atDate(LocalDate.from(date.toInstant().atZone(ZoneId.systemDefault()))).minusHours(1);
        //Long duration = null;
        long  duration = java.time.Duration.between(LocalDateTime.now(), localDateTime).toMillis();
        if(duration<0){
            duration= 30000L;
            orderMessageResp.setMessage(orderMessageResp.getMessage()+",您的预约时间不足一小时,请您及时前往");
        }
        orderMessageResp.setTitle("预约提醒");
        String delayedMessage = JSON.toJSONString(orderMessageResp);
        log.info("{}:毫秒之后过期,到死信队列",duration);
        //发送消息到延时队列,等待过期
        long finalDuration = duration;
        //延时队列消息id
        CorrelationData delayedCorrelationData = new CorrelationData(UUIDUtils.generate32BitId() + Long.toString(System.currentTimeMillis()));
        //入库
        saveMessage(delayedMessage,exchange+DELAYED,queue+DELAYED,orderNo,delayedCorrelationData.getId(),ROUTE_KING,FinalConst.YES_DELAY);
        rabbitTemplate.convertAndSend(exchange+DELAYED, ROUTE_KING, delayedMessage, message -> {
            MessageProperties messageProperties = message.getMessageProperties();
            // 设置这条消息的过期时间
            messageProperties.setExpiration(Long.valueOf(finalDuration).toString());
            return message;
        }, delayedCorrelationData);
        log.info("发送消息到延迟队列成功,消息为{},消息id为:{}",delayedMessage,correlationData);

        //死信交换机绑定死信队列
        rabbitAdmin.declareQueue(new Queue(queue+TTL, true));
        rabbitAdmin.declareExchange(new TopicExchange(exchange+TTL, true, false));
        rabbitAdmin.declareBinding(
            BindingBuilder
                .bind(new Queue(queue+TTL))
                .to(new TopicExchange(exchange+TTL))
                .with(ROUTE_KING));
    }

    private void saveMessage(String json,String exchange,String queue,String orderNo,String correlationData,String routeKey,String delay){
        ZcMqMessageEntity zcMqMessageEntity = new ZcMqMessageEntity();
        zcMqMessageEntity.setMessage(json);
        zcMqMessageEntity.setExchange(exchange);
        zcMqMessageEntity.setQueue(queue);
        zcMqMessageEntity.setOrderNumber(orderNo);
        zcMqMessageEntity.setMqMessageId(correlationData);
        zcMqMessageEntity.setRouteKey(routeKey);
        zcMqMessageEntity.setSendTime(new Date());
        zcMqMessageEntity.setIsDelay(delay);
        zcMqMessageService.save(zcMqMessageEntity);
    }
}

以上是消息的发送者,消费者只要监听队列即可,然后做好手动ack确认消息
,因为消费者由前端监听队列消费,所以消费者的部分就省了。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值