延时队列的使用及应用场景

一、 应用场景

常见的打车软件都会有匹配司机,这个可以用延迟队列来实现;处理已提交订单超过30分钟未付款失效的订单,延迟队列可以很好的解决;又或者注册了超过30天的用户,发短信撩动等。

二、技术选型(如何实现延时队列)

1.通过实现Delayed接口
2.redis,用数据结构为zset的来实现。给每个键都添加了一个score的元素,就是分数。我们可以拿时间戳当作score给这个key,然后通过zrevrange获取key的时候指定score范围即可。最简单的延迟队列,而且,吞吐量也不算特别小
3.rabbitmq添加插件x-delayed-message来实现,极限时间为49天,选用了rabbitmq则会增加系统的复杂度

具体选型看具体业务场景和业务规模

三、不同选型的具体实现

1.通过实现Delay接口

package com.hsxymini.app.common.queue.delayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * 延时队列中的消息体 将任务封装为消息体
 */
public class DelayTask<T extends Runnable> implements Delayed {

    private final long time;

    private final T task; // 任务类,也就是之前定义的任务类

    public DelayTask(long timeout, T task) {
        this.time = System.nanoTime() + timeout;
        this.task = task;
    }

    @Override
    public int compareTo(Delayed o) {
        // TODO Auto-generated method stub
        DelayTask other = (DelayTask) o;
        long diff = time - other.time;
        if (diff > 0) {
            return 1;
        } else if (diff < 0) {
            return -1;
        } else {
            return 0;
        }
    }

    @Override
    public long getDelay(TimeUnit unit) {
        // TODO Auto-generated method stub
        return unit.convert(this.time - System.nanoTime(), TimeUnit.NANOSECONDS);
    }

    @Override
    public int hashCode() {
        return task.hashCode();
    }

    public T getTask() {
        return task;
    }


}

package com.hsxymini.app.common.queue.delayQueue;
import com.hsxymini.app.common.queue.DelayOrderTask;
import java.util.Map;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 延时队列管理类,用来添加任务、执行任务
 */
public class DelayQueueService {
    private final static int DEFAULT_THREAD_NUM = 5;
    private static int thread_num = DEFAULT_THREAD_NUM;
    // 固定大小线程池
    private ExecutorService executor;
    // 守护线程
    private Thread daemonThread;
    // 延时队列
    private DelayQueue<DelayOrderTask<?>> delayQueue;
    private static final AtomicLong atomic = new AtomicLong(0);
    private final long n = 1;
    private static DelayQueueService instance = new DelayQueueService();

    private DelayQueueService() {
        executor = Executors.newFixedThreadPool(thread_num);
        delayQueue = new DelayQueue<>();
        init();
    }

    //实例化自己
    public static DelayQueueService getInstance() {
        return instance;
    }
    /**
     * 初始化
     */
    public void init() {
        daemonThread = new Thread(() -> {
            execute();
        });
        daemonThread.setName("DelayQueueMonitor");
        daemonThread.start();
    }

    private void execute() {
        while (true) {
            //收集所有存活线程
            Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
            System.out.println("当前存活线程数量:" + map.size());
            int taskNum = delayQueue.size();
            System.out.println("当前延时任务数量:" + taskNum);
            try {
                // 从延时队列中获取任务
                DelayOrderTask<?> delayOrderTask = delayQueue.take();
                if (delayOrderTask != null) {
                    Runnable task = delayOrderTask.getTask();
                    if (null == task) {
                        continue;
                    }
                    // 提交到线程池执行task
                    executor.execute(task);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 添加任务
     *
     * @param task
     * @param time 延时时间
     * @param unit 时间单位
     */
    public void put(Runnable task, long time, TimeUnit unit) {
        // 获取延时时间
        long timeout = TimeUnit.NANOSECONDS.convert(time, unit);
        // 将任务封装成实现Delayed接口的消息体
        DelayOrderTask<?> delayOrder = new DelayOrderTask<>(timeout, task);
        // 将消息体放到延时队列中
        delayQueue.put(delayOrder);
    }

    /**
     * 删除任务
     *
     * @param task
     * @return
     */
    public boolean removeTask(DelayOrderTask task) {

        return delayQueue.remove(task);
    }
}
package com.hsxymini.app.common.queue.delayQueue;

/**
 * 被执行的业务
 */
public class DelayWorker implements Runnable{


    @Override
    public void run() {

    }
}
package com.hsxymini.app.common.queue.delayQueue;

import com.hsxymini.app.common.annotation.AuthIgnore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/delay")
public class DelayQueueController {

    DelayQueueService manager = DelayQueueService.getInstance();

    @GetMapping("/add")
    @AuthIgnore
    public void doAdd(){
        DelayWorker work1 = new DelayWorker();
        manager.put(work1, 3000, TimeUnit.MILLISECONDS);
    }


}

小结:上面只是一个大致示例,需要根据具体业务场景具体设计。

2.通过rabbitmq延时消息队列

1.安装插件rabbitmq_delayed_message_exchange
将插件放到安装rabbitmq的plugins目录
执行下面代码激活插件

> rabbitmq-plugins enable rabbitmq_delayed_message_exchange

然后重启rabbitmq即可

下面是配置config


import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class RabbitMQDelayedMessageConfig {

    /**
     * 延迟消息交换机
     */
    public final static String DELAY_EXCHANGE = "jaymin.delay.exchange";
    /**
     * 队列
     */
    public final static String DELAY_QUEUE = "jaymin.delay.queue";
    /**
     * 路由Key
     */
    public final static String DELAY_ROUTING_KEY = "jaymin.delay.routingKey";

    @Bean
    public CustomExchange delayMessageExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        //自定义交换机
        return new CustomExchange(DELAY_EXCHANGE, "x-delayed-message", false, false, args);
    }

    @Bean
    public Queue delayMessageQueue() {
        return new Queue(DELAY_QUEUE, false, false, false);
    }

    @Bean
    public Binding bindingDelayExchangeAndQueue() {
        return BindingBuilder.bind(delayMessageQueue()).to(delayMessageExchange()).with(DELAY_ROUTING_KEY).noargs();
    }
}

具体使用:

        Integer ttl = 10000;
        rabbitTemplate.convertAndSend(RabbitMQDelayedMessageConfig.DELAY_EXCHANGE,
        RabbitMQDelayedMessageConfig.DELAY_ROUTING_KEY, letter, message -> {
//             设置过期时间
            message.getMessageProperties().setDelay(ttl);
            return message;
        });

3.redis的zset实现暂先搁置,比较容易

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值