基于redisson的延迟队列

通常在一个jvm进程中,若想实现延迟逻辑,可以使用jdk自带的延迟队列DelayQueue来实现。DelayQueue中的元素PriorityQueue来实现的,DelayQueue中的元素会实现

public interface Delayed extends Comparable<Delayed> {

    /**
     * Returns the remaining delay associated with this object, in the
     * given time unit.
     *
     * @param unit the time unit
     * @return the remaining delay; zero or negative values indicate
     * that the delay has already elapsed
     */
    long getDelay(TimeUnit unit);
}

即可在DelayQueue进行poll操作时候获取最近需要的元素。但是这种延时队列是保存在内存中,所以一旦进程关闭或崩溃,队列中的数据都会丢失,所以只有配合持久化才可以保证数据不丢失。

那么如果在多进程条件下,如果要实现延迟队列,则需要一个统一的地方保存延迟元素,这个元素可以被称为任务,redis是一个不错的选择。Redisson实现了集群环境下延迟队列的实现。

引入reddison依赖

<dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson</artifactId>
     <version>3.10.3</version>
</dependency>

redis基本配置

    private Config initRedissonConfig() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://" + host + ":" + port)
                .setTimeout(timeout)
                .setConnectionPoolSize(maxIdle)
                .setConnectionMinimumIdleSize(minIdle);
        return config;
    }

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
        Config config = initRedissonConfig();
        return Redisson.create(config);
    }

定义redisson阻塞队列,注册相关bean

public class QueueConfig {

private final String queueName = "queue";

@Bean
public RBlockingQueue<String> rBlockingQueue(@Qualifier("redissonClient") RedissonClient redissonClient) {
return redissonClient.getBlockingQueue(queueName);
}

@Bean
public RDelayedQueue<String> rDelayedQueue(@Qualifier("redissonClient") RedissonClient redissonClient,
@Qualifier("rBlockingQueue") RBlockingQueue<String> blockQueue) {
return redissonClient.getDelayedQueue(blockQueue);
}
}

下面进行测试,TakeTask负责消费队列中的任务

public class TakeTask {

    @Resource(name = "rBlockingQueue")
    private RBlockingQueue<String> rBlockingQueue;

    @PostConstruct
    public void take() {

        new Thread(() -> {
            while (true) {
                try {
                    String s = rBlockingQueue.take();
                    System.out.println(s);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

在延时队列rDelayQueue中延迟添加任务,这里需要调用带参数的offerAsync的方法,延时添加。

@RestController
@RequestMapping("/")
public class TestController {

    @Resource(name = "rDelayedQueue")
    private RDelayedQueue<String> rDelayedQueue;

    @GetMapping("/offer")
    public void offer() {
        for (int i = 1; i <= 2; i++) {
            rDelayedQueue.offerAsync("task: " + i, 1, TimeUnit.SECONDS);
        }
    }
}

由于延时队列持久化在redis中,所以机器宕机数据不会异常丢失,机器重启后,会正常消费队列中积累的任务。

对于jdk中的DelayQueue延时队列是采用zset来实现,每次add,会立即将元素添加到队列中,zset会根据指定的字段进行排序,维护一个优先队列,当进行take操作时候,取到头节点的数据一定是最大或者最小的,但是此时头节点不一定能取出来,需要多一步判断,这一步其实就是  public long getDelay(TimeUnit unit);要实现的方法,只有返回值大于0才会真正被取出来。redission的延时队列是异步延时加入的,也就是说并没有立刻加入队列中,而是在指定的延时时间delay之后才会加入,所以在take的时候是一定可以直接取出来队列中的元素。


原文地址:https://www.cnblogs.com/markytsai/p/13800743.html