Implementation of delay queue in java

Hello everyone, I am a CRUD engineer . Recently, my friend suddenly asked me how to implement a delayed queue. I blurted out MQ. But it suddenly occurred to me that the company's project seemed to use a native class of Java. So I thought about taking advantage of the weekend to explore the advantages and disadvantages of various methods to implement delay queues.

delayed message

Delayed message literally means that after the system receives the message, it needs to be processed after a period of time, whether it is a few seconds, minutes or hours. The occurrence of the message here is called a delayed message.

After my continuous research, I found that there are 5 common methods to achieve it (welcome additions are welcome)

DelayQueue

As a native Java class, DelayQueuewe can implement delayed sending.
DelayQueueIt is an unbounded BlockingQueueobject used to place objects that implement Delayedthe interface. The objects in it can only be removed from the queue when they expire. This kind of queue is ordered, that is, the head object has the longest delayed expiration time.

When using it, the elements of the queue we add need to implement the Delayed interface (at the same time, this interface inherits the Comparable interface, so our DelayQueue is ordered). However, this will cause a problem,
because DelayQueueit is a class in Java itself. , it is not persistent and will cause data loss once the server is restarted. And if multi-machine deployment is carried out, distributed locks need to be added, so in my opinion, using this method in production is not a good choice. If the importance of delayed messages is not very high, the impact will not be big. Of course, being a native class of Java also has advantages, that is, the system does not need to communicate with other services for data. All requests are made within the project content, thus avoiding data loss caused by channel instability between the two services. .

time wheel algorithm

For a detailed introduction, you can refer to Baidu
Netty. The Netty package provides an implementation of a time wheel - HashedWheelTimer. Its bottom layer uses the data structure of array + linked list:


//1996 年 George Varghese 和 Tony Lauck 的论文《Hashed and Hierarchical Timing Wheels: 
//Data Structures for the Efficient Implementation of a Timer Facility》中提出了一种时间轮管理 Timeout 事件的方式。其设计非常巧妙,并且类似时钟的运行,
//原始时间轮有 8 个格子,假定指针经过每个格子花费时间是 1 个时间单位,当前指针指向 0,一个 17 个时间单位后超时的任务则需要运转 2 圈再通过一个格子后被执行,放在相同格子的任务会形成一个链表。
public class Test{
    
    
 
    public static void main(String[] args) {
    
    
 
        //设置每个格子是 100ms, 总共 256 个格子
        HashedWheelTimer hashedWheelTimer = new HashedWheelTimer(100, TimeUnit.MILLISECONDS, 256);
 
        //加入三个任务,依次设置超时时间是 10s 5s 20s

        System.out.println("加入第一个任务, time= " + LocalDateTime.now());
        hashedWheelTimer.newTimeout(timeout -> {
    
    
            System.out.println("执行第一个任务, time= " + LocalDateTime.now());
        }, 10, TimeUnit.SECONDS);
 
        System.out.println("加入第二个任务, time= " + LocalDateTime.now());
        hashedWheelTimer.newTimeout(timeout -> {
    
    
            System.out.println("执行第二个任务, time= " + LocalDateTime.now());
        }, 5, TimeUnit.SECONDS);
 
        System.out.println("加入第三个任务, time= " + LocalDateTime.now());
        hashedWheelTimer.newTimeout(timeout -> {
    
    
            System.out.println("执行第三个任务, time= " + LocalDateTime.now());
        }, 20, TimeUnit.SECONDS);
 
        System.out.println("等待任务执行===========");
    }
}

However, this will also lead to data loss and can be used in some less important situations.

Redis

With storage capabilities, fast reading and writing, and persistence operations, I immediately thought of Redis .

Regarding Redis to implement delay queue, I thought of two methods:

1. Redis provides a data structure called a zsetsortable collection and Redis supports data persistence. Youzan's delay queue is zsetdesigned and stored based on passing.
Overview: Using time as the score of zset, zrangewithscores can obtain the element with the smallest score value of zset (that is, the task that is about to expire, to determine the system time and score, and if they are equal, execute and delete the task. (If you want to To be asynchronous, you can use Timer to open a thread to monitor Redis's zset)
2. Use Redis to store the expiration time of the data, and the server opens an expiration callback. (It is relatively simple, but if the Key value cannot be obtained in the expiration callback of Redis, you need to use Value again. Store another Key in it)

Message queue (RabbitMQ delay queue)

RabbitMQI believe everyone has heard of this name. In my impression, RabbitMQI don’t seem to support delayed sending. To achieve this function, I mainly rely on it TTL(Time To Live message survival time).
Brief description: The producer passes the Key Put the message into the corresponding queue, but do not consume the queue. When the element in the queue triggers expiration (the expiration time is the time that needs to be delayed), the message will enter the dead letter queue. At this time, we can The message is forwarded again to the normal queue for consumption, or directly consumed in the dead letter queue, thereby achieving the effect of delayed queue.

Because RabbitMQit specializes in message queues, its message reliability will be higher than Redis (message delivery reliability, at least once processing consumption semantics, repeated delivery, manual ACK, message callback for failed delivery, etc.)

Message queue (RocketMQ latency level)

RocketMQYou can also set the delay level when delivering messages

Message message = new Message("test", ("Hello world").getBytes());
//设置延时等级
message.setDelayTimeLevel(3);
producer.send(message);

Supports 18 latency levels by default

messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

After we set the delay level of the message, RocketMQthe message will not be delivered directly to the corresponding topic, but will be forwarded to the queue of the corresponding delay level. Internally Broker, each delay is TimerTaskqueued to determine whether a message has arrived. If it expires, the message is stored again in the CommitLog and forwarded to the real target topic.

in conclusion

This time I mainly introduce the line of sight method of various delay queue methods in Java, and just put some code (detailed code will be released in the next few weeks). In fact, in the company's
projects, in many cases, the more complicated the better, the better. The better the technology, we need to make a choice based on different business scenarios and resource conditions.

To quote the Nuggets:
Many times, the systems we see are terrible, the technology stack is terrible, and we feel annoyed when we find that best practices are not used in many scenarios. When we were young, we all wanted to refactor. But in fact, every introduction of middleware requires a cost, and roughness also has advantages.
As long as the business can support it perfectly, it is a good solution.

Guess you like

Origin blog.csdn.net/qq_43649799/article/details/129336220