RabbitMQ实现延时队列的两种方法

方法1

采用ttl+dlx
原理:创建一个有统一过期时间的的队列使消息到时过期,或者给消息本身设置过期时间,消息过期之后会进入死信队列,等待死信队列收到过期消息之后做相应的处理
具体参考:死信队列
本次介绍设置统一过期的队列

创建队列与交换机

	/**
     * 设置统一过期时间
     * @return
     */
    @Bean
    public Queue delayQueue(){
        //maxPriority(15):设置该队列中的消息的优先级最大值.发布消息的时候,可以指定消息的优先级,
        // 优先级高的先被消费.如果没有设置该参数,那么该队列不支持消息优先级功能.也就是说,就算发布消息的时候传入了优先级的值,也不会起什么作用.
        return QueueBuilder
                .durable("delay-queue")
                .ttl(20000)
                .maxPriority(15)
                //绑定死信交换机
                .deadLetterExchange("dlx-exchange1")
                //设置路由键
                .deadLetterRoutingKey("error")
                .build();
    }
    
 	/**
     * 死信队列
     * @return
     */
    @Bean
    public Queue dlxQueue1(){
        return QueueBuilder.durable("dlx-queue1").build();
    }

    /**
     * 死信交换机
     * @return
     */
    @Bean
    public DirectExchange dlxExchange1(){
        return ExchangeBuilder.directExchange("dlx-exchange1").build();
    }
	
	/**
     * 延迟队列与交换机绑定
     * @return
     */
    @Bean
    public Binding delayBind(){
        return BindingBuilder.bind(delayQueue()).to(delayExchange()).with("delay");
    }
	
	 /**
     * 死信队列与死信交换机绑定
     * @return
     */
    @Bean
    public Binding dlx1Bind(){
        return BindingBuilder.bind(dlxQueue1()).to(dlxExchange1()).with("error");
    }
	
	@Bean
    public RabbitAdmin delayRabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin=new RabbitAdmin(connectionFactory);
        rabbitAdmin.declareQueue(delayQueue());
        rabbitAdmin.declareQueue(dlxQueue1());
        rabbitAdmin.declareExchange(delayExchange());
        rabbitAdmin.declareExchange(dlxExchange1());
        return rabbitAdmin;
    }

生产者发送消息

	private static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Resource(name = "rabbitTemplate")
    private RabbitTemplate rabbitTemplate;
	
	/**
     * 组装消息
     * @param msg
     * @return
     */
    private static Map<String, Object> createMsg1(Object msg,Integer delayMills) {
        String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
        Map<String,Object> message= Maps.newHashMap();
        message.put("sendTime",sdf.format(new Date()));
        message.put("msg", msg);
        message.put("delay",delayMills/1000+"s");
        message.put("msgId",msgId);
        return message;
    }
	
	@ApiOperation("ttl+dlx")
    @GetMapping("ttl")
    public String ttl(@RequestParam String msg){
        Map<String, Object> map = createMsg1(msg,20000);
        rabbitTemplate.convertAndSend("delay-exchange", "delay",map, message -> {
            MessageProperties messageProperties = message.getMessageProperties();
            //设置消息优先级
            messageProperties.setPriority(Integer.MAX_VALUE);
            return message;
        });
        return "ok";
    }

消费者接收消息

如何保证消息按时过期,主要和两个参数prefetch(一次抓取消息的数量,可以减少网络传输时间,默认250)、concurrency(消费者(线程)数量,默认为1,concurrency在整个项目中是有限制的,加起来的数量不超过2047(默认),修改数量可参考增加消费者数量)有关
具体代码如下:

	 /**
     * concurrency默认为100,一个RabbitListener对应的消费者数量
     * @param message 消息
     * @param c 通道
     * @param msg 消息内容
     * @throws IOException
     */
    //使用queuesToDeclare属性,如果不存在则会创建队列,注:此处声明的队列要和生产者属性保持一致
    @RabbitListener(queuesToDeclare = @Queue(value = "dlx-queue1",declare = "false"),concurrency ="100")
    public void delay(Message message,Channel c,Map msg) throws IOException, ParseException {
        String name = Thread.currentThread().getName();
        MessageProperties properties = message.getMessageProperties();
        long sendTime = sdf.parse(msg.get("sendTime").toString()).getTime();
        long now = System.currentTimeMillis();
        String s=(now-sendTime)/1000+"s";
        String delay = msg.get("delay").toString();
        log.info("当前处理线程名:{},要求延迟:{},实际延迟:{}",name,delay,s);
        //手动回执,不批量签收,回执后才能处理下一批消息
        long tag = properties.getDeliveryTag();
        //模拟业务处理时长-比如查询订单有没有被处理,没有处理的话可以设置超时
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        c.basicAck(tag,false);
    }

结果:在这里插入图片描述

方法2

使用延时队列插件,具体安装参考延迟队列安装
注:使用延迟队列插件时需要关闭消息确认模式中的回退模式,不然会误报,因为消息会在延迟时间过后才会到达指定队列

创建延迟交换机以及队列

	public static final String DELAY_EXCHANGE = "message-exchange";

    public static final String DELAY_ROUTING_KEY="delay1";
 
 	/**
     * 定义延迟交换机
     * @return
     */
    @Bean
    public CustomExchange delayMessageExchange(){
        Map<String, Object> arguments= Maps.newHashMap();
        //指定交换机类型
        arguments.put("x-delayed-type", "direct");
        return new CustomExchange(DELAY_EXCHANGE,"x-delayed-message",true,false,arguments);
    }
    
 	 /**
     * 延迟队列
     * @return
     */
    @Bean
    public Queue delayMessageQueue(){
        return QueueBuilder.durable("delay-message-queue").build();
    }
	
	 /**
     * 队列与延迟交换机绑定
     * @return
     */
    @Bean Binding delayMessageBind(){
        return BindingBuilder.bind(delayMessageQueue()).to(delayMessageExchange()).with(DELAY_ROUTING_KEY).noargs();
    }
	
	@Bean
    public RabbitAdmin delayRabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin=new RabbitAdmin(connectionFactory);
        rabbitAdmin.declareExchange(delayMessageExchange());
        rabbitAdmin.declareBinding(delayMessageBind());
        return rabbitAdmin;
    }

生产者发送消息

	private static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Resource(name = "rabbitTemplate1")
    private RabbitTemplate rabbitTemplate;
	
	/**
     * 组装消息
     * @param msg
     * @return
     */
    private static Map<String, Object> createMsg1(Object msg,Integer delayMills) {
        String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
        Map<String,Object> message= Maps.newHashMap();
        message.put("sendTime",sdf.format(new Date()));
        message.put("msg", msg);
        message.put("delay",delayMills/1000+"s");
        message.put("msgId",msgId);
        return message;
    }

 	@ApiOperation("使用延迟队列插件")
    @GetMapping("delay")
    public String delay(@RequestParam String msg,@RequestParam Integer delayMills){
        Map<String, Object> map = createMsg1(msg,delayMills);
        rabbitTemplate1.convertAndSend(DelayConfiguration.DELAY_EXCHANGE, DelayConfiguration.DELAY_ROUTING_KEY,map, message->{
            MessageProperties properties = message.getMessageProperties();
            //设置消息多少ms之后到达队列
            properties.setDelay(delayMills);
            return message;
        });
        return "ok";
    }

消费者接收消息

如何保证消息按时过期,主要和两个参数prefetch(一次抓取消息的数量,可以减少网络传输时间,默认250)、concurrency(消费者(线程)数量,默认为1,concurrency在整个项目中是有限制的,加起来的数量不超过2047(默认),修改数量可参考增加消费者数量)有关
代码实现

	@RabbitListener(queuesToDeclare = @Queue(value = "delay-message-queue",declare = "false"),concurrency ="100")
    public void receiveD(Map msg, Channel channel,Message message) throws IOException, ParseException {
        String name = Thread.currentThread().getName();
        MessageProperties properties = message.getMessageProperties();
        long sendTime = sdf.parse(msg.get("sendTime").toString()).getTime();
        long now = System.currentTimeMillis();
        String s=(now-sendTime)/1000+"s";
        String delay = msg.get("delay").toString();
        log.info("当前处理线程名:{},要求延迟:{},实际延迟:{}",name,delay,s);
        //手动回执,不批量签收,回执后才能处理下一批消息
        long tag = properties.getDeliveryTag();
        //模拟业务处理时长-比如查询订单有没有被处理,没有处理的话可以设置超时
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        channel.basicAck(tag, false);
    }

结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值