延迟任务解决方案(线程,队列,rabbitmq)

1.定时任务线程池(定时执行某一个任务的线程池ScheduledThreadPoolExecutor)

ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(
        scheduledThreadPoolExecutor.schedule(()->{
            System.out.println("延迟队列执行了");
        },10, TimeUnit.SECONDS);

2.延迟队列(DelayQueue)DelayQueue 是一个支持延时获取元素的无界阻塞队列,由JDK提供,队列中的元素必须实现 Delayed 接口,并重写 getDelay(TimeUnit) 和 compareTo(Delayed) 方法,DelayQueue 实现延迟队列

getDelay :获取延迟任务的延迟时间,当一个任务延迟时间到了,那么此时从延迟队列中获取延迟任务的时候就可以得到延迟任务

compareTo:当我们调用put,add方法向延迟队列中添加延迟任务的时候,此时就会调用该方法,比较延迟日内瓦的延迟时间,按照时间进行排序

@Configuration
public class DelayQueueConfiguration {
​
    @Bean
    publicDelayQueue orderDelayTask() {
        return new DelayQueue() ;
    }
​
}

public class OrderDelayTask implements Delayed {
​
    private String orderId ;
    private long delayTime ;
​
    publicStringgetOrderId() {
        returnorderId;
    }
​
    public OrderDelayTask(StringorderId , longdelayTime ) {
        this.orderId=orderId ;
        this.delayTime=System.currentTimeMillis() +delayTime ;  // 计算完整的延迟时间
    }
​
    // DelayQueue会调用该方法,每隔一秒执行一次,来获取延迟任务的剩余时间
    @Override
    public long getDelay(TimeUnitunit) {
        System.out.println("OrderDelayTask...getDelay...."+newDate());
        returndelayTime-System.currentTimeMillis();
    }
​
    /**
     * 用于比较延时任务,通过这个时间可以对队列中的元素进行排序。
     * 当生产者线程调用 put 之类的方法加入元素时,会触发 Delayed 接口中的 compareTo 方法进行排序,
     * 也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序。
     * 排在队列头部的元素是最早到期的,越往后到期时间赿晚。
     * @param o
     * @return
     */
    @Override
    public int compareTo(Delayedo) {
        return (int) (getDelay(TimeUnit.MILLISECONDS) -o.getDelay(TimeUnit.MILLISECONDS));
    }
​
}
​

// 消费者的定时任务
@Component
public class ScheduledTask {
​
    @Autowired
    private DelayQueueorderDelayTasks ;
​
    @Scheduled(cron="*/1 * * * * ?")
    public void consumerDelayQueue() {
        OrderDelayTaskdelayTask=orderDelayTasks.poll();      // 拉取一个延迟任务,如果没有没有延迟任务,那么此时返回的就是null
        if(delayTask!=null) {
            System.out.println("获取到了延迟任务"+delayTask.getOrderId());
        }
    }
​
}
​
@EnableScheduling
@SpringBootApplication
publicclassDelayApplication {
​
    public static void main(String[] args) {
        ConfigurableApplication ContextapplicationContext=SpringApplication.run(DelayApplication.class, args);
        DelayQueue delayQueue=applicationContext.getBean(DelayQueue.class);
        delayQueue.add(newOrderDelayTask("123" , 30*1000)) ;
    }
​
}
  1. Rabbitmq延迟队列

rabbitmq消息传输方式:

  1. 简单队列模式

  1. 工作队列模式:一个队列可以绑定多个消费者,多个消费者会共同消费队列中的消息

  1. 订阅模式:需要我们指定交换机

fanout:吧消息转发到与之绑定的所有队列

direct:根据消息的routingKey和队列的bindingkey进行比对,然后把消息发送给指定的队列

topic:根据消息的routingKey和队列的bindingKey进行比对,规则匹配,然后把消息发送给指定的队列!在进行bindingKey指定的时候可以使用通配符*匹配一个单词,#匹配多个单词.

rabbitmq死信队列:

作用:存储死信

什么是死信?

  1. 信息的(ttl) 信息在队列中的存活时间到了

  1. 消费者拒收消息,并且不会把这个消息重写加入到队列中

  1. 队列中的消息数量达到了队列的容器上限

默认情况下rabbitmq会直接将死信丢弃

rabbitmq延迟队列:

在rabbitmq中没有直接提供延迟队列的技术,想要实现延迟队列就需要使用死信队列+TTL时间

生产者----交换机------队列ttl(时间)----给队列绑定死信交换机----死信队列-----消费者

1.整合Rabbitmq

1、在pom.xml文件中添加如下依赖


    org.springframework.boot
    spring-boot-starter-amqp

2、在application.yml文件添加如下配置

# rabbitmq的配置
spring:
  rabbitmq:
    host: 192.168.6.103
    port: 5672
    username: admin
    password: admin
    virtual-host: /

使用RabbitTemplate发送消息测试。

消息可靠性传输

publisher-confirm-type: correlated #确认机制:可以感知消息是否投递到了交换机,如果投递成功,那么此时rabbitmq会给生产者返回ack,如果没有投递成功那么rabbitmq会返回nack

publisher-returns: true #回退机制: 生产者可以感知到消息是否正常投递给了队列,如果没有正常投递,那么此时rabbitmq就会返回一个nack

自定义RabbitTemplate 该给RabbitTemplate绑定确认机制和回退机制,把这个RabbitTemplate纳入到spring容器中

在application.yml文件中添加如下依赖:

# rabbitmq的配置
spring:
  rabbitmq:
    host: 192.168.6.103
    port: 5672
    username: admin
    password: admin
    virtual-host: /
    publisher-confirm-type: correlated #生产者到交换机确认机制
    publisher-returns: true #回退机制

定义配置类

@Configuration
public class RabbitMqConfiguration {
​
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
​
        // 创建rabbitTemplate对象
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory) ;
​
        // 设置confirmCallBack,给rabbitTemplate绑定confirmCallBack机制
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
​
            @Override
            public void confirm(CorrelationData correlationData, booleanack, Stringcause) {
                if(ack) {
                    System.out.println("消息成功发送给了交换机....");
                }else {
                    System.out.println("消息没有成功发送给交换机....");
                }
            }
​
        });
​
        // 设置returnCallBack
        rabbitTemplate.setMandatory(true);  // 将发送失败的消息再次发给生产者
        rabbitTemplate.setReturnCallback(newRabbitTemplate.ReturnCallback() {
​
            @Override
            public void returnedMessage(Messagemessage, intreplyCode, String replyText, String exchange, String routingKey) {
                System.out.println("消息正常发送给交换机"+exchange+",但是没有发送给队列 ---->routingKey:"+routingKey);
            }
​
        });
​
        // 返回
        return rabbitTemplate ;
​
    }
​
}

扩展:提取为公共模块后,自定义注解,哪里需要在使用

@Target(value = ElementType.TYPE)           // 当前这个自定义注解的使用位置为启动类
@Retention(value = RetentionPolicy.RUNTIME)  // 生效时期
@Import(value = RabbitMQConfiguration.class)
public @interface EnableRabbit {

}

延迟队列设计


设计1:两个交换机两个队列

设计2:一个交换机(死信交换机和消息交换机使用同一个),两个队列

相关交换机和队列声明


通过java代码声明对应的队列和交换机,以及队列和交换机的绑定信息

配置类定义如下所示:

/**
 * 当使用RabbitTempalte发送消息的时候此时配置才会生效
 * 关于如下信息的声明可以使用@RabbitListener注解完成,并且使用@RabbitListener声明完毕以后,声明信息会立即生效
 */
@Configuration
public class MqConfiguration {

    // 声明交换机
    @Bean
    public Exchange Exchange() {
        return ExchangeBuilder.directExchange(RabbitConstant.ORDER_EXCHANGE).durable(true).build() ;
    }

    // 声明订单队列
    @Bean
    public Queue Queue() {
        return QueueBuilder.durable(RabbitConstant.ORDER_QUEUE).deadLetterExchange(RabbitConstant.ORDER_EXCHANGE)
                .deadLetterRoutingKey(RabbitConstant.ORDER_DELAY_ROUTING_KEY)
                .ttl(RabbitConstant.ORDER_MSG_TTL).build() ;
    }

    // 完成订单队列和订单交换机的绑定
    @Bean
    public Binding QueueBinding() {
        return BindingBuilder.bind(orderQueue()).to(orderExchange()).with(RabbitConstant.ORDER_ROUTING_KEY).noargs() ;
    }

    // 声明关闭订单队列
    @Bean
    public Queue closeQueue() {
        return QueueBuilder.durable(RabbitConstant.CLOSE_QUEUE).build() ;
    }

    // 完成关单队列和交换机的绑定
    @Bean
    public Binding CloseQueueBinding() {
        return BindingBuilder.bind(closeQueue()).to(orderExchange()).with(RabbitConstant.ORDER_DELAY_ROUTING_KEY).noargs() ;
    }

常量:

public interface MqConstant {

    /**
     * 定义订单超时未支付的交换机
     */
    public static final String ORDER_TIMEOUT_EXCHANGE = "order.exchange" ;

    /**
     * 定义订单超时未支付的队列
     */
    public static final String ORDER_TIMEOUT_QUEUE = "order.queue" ;

    /**
     * 定义死信队列的routingKey
     */
    public static final String ORDER_TIMEOUT_CLOSE_ROUTINGKEY = "close" ;

    /**
     * 定时的超时时间
     */
    public static final Integer ORDER_TIMEOUT = 1000 * 30 ;

    /**
     * 定义订单队列的bindKey
     */
    public static final String ORDER_ROUTINGKEY = "order" ;

    /**
     * 定义关闭订单的队列名称
     */
    public static final String ORDER_CLOSE_QUEUE = "close.queue" ;

    /**
     * 定义消息重试次数
     */
    public static final String REDIS_MSG_COUNT = "msg:count:" ;

}

为了保证消费消息的时候消息不丢失,那么此时就需要开启消费者手动确认机制:

# rabbitmq的配置
spring:
  rabbitmq:
    host: 192.168.6.103
    port: 5672
    username: admin
    password: admin
    virtual-host: /
    publisher-confirm-type: correlated #交换机确认机制
    publisher-returns: true #回退机制
    listener:
      simple:
        acknowledge-mode: manual    # 手动确认
        prefetch: 1 #进行消费者限流

消费者监听器代码实现:

@Component
public class OrderDelayListener {

    @Autowired
    private OrderBizService orderBizService ;

    @RabbitListener(queues = RabbitConstant.CLOSE_QUEUE)
    public void closeQueueListener(Message message, Channel channel) {

        // 获取消费者标签
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {

            // 对业务数据进行处理,如果处理成功返回ack标记,如果处理失败返回nack标记
            String closeOrderJsonInfo = new String(message.getBody()) ;
            Map map = JSON.parseObject(closeOrderJsonInfo, Map.class);

            // 获取订单的id和用户的id
            Long orderId = Long.parseLong(map.get("orderId").toString());
            Long userId = Long.parseLong(map.get("userId").toString());

            // 调用关单方法
            orderBizService.closeOrder(orderId , userId);

            // 返回ack
            channel.basicAck(deliveryTag , true);

        }catch (Exception e) {

            e.printStackTrace();
            // 产生异常,处理失败返回nack标记

            try {
                channel.basicNack(deliveryTag , true , true);
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }

        }

    }

}

你可能感兴趣的:(java-rabbitmq,rabbitmq,java)