1. 延时队列
  1. 通过redis中的zset我们可以实现一个简单的延时队列。通过将score设置为时间,然后多线程轮训的去查询是否有到期的可以执行的任务。
  2. 考虑到多线程并发,使用zrem去保证每一个任务只会被执行1次,这里也可以使用lua脚本来优化,减少空取的情况。
  1. 代码实现
package com.xliu.chapter1;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import redis.clients.jedis.Jedis;

import java.lang.reflect.Type;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author liuxin
 * @version 1.0
 * @date 2020/4/21 20:06
 */
public class RedisDelayingQueue<T> {
    private Jedis jedis;
    private String queueKey;

    static class TaskItem<T> {
        public String id;
        public T msg;
    }

    private Type TaskType = new TypeReference<TaskItem<T>>(){}.getType();

    public RedisDelayingQueue(Jedis jedis, String queueKey) {
        this.jedis = jedis;
        this.queueKey = queueKey;
    }

    public void delay(T msg){
        TaskItem<T> task = new TaskItem<>();
        task.id = UUID.randomUUID().toString();
        task.msg = msg;
        String s = JSON.toJSONString(task);
        long score = System.currentTimeMillis() + 5000;
        jedis.zadd(queueKey,score,s);
    }

    public void loop(){
        while(!Thread.interrupted()){
            Set<String> values = jedis.zrangeByScore(queueKey, 0, System.currentTimeMillis(), 0, 1);
            if(values.isEmpty()){
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    break;
                }
                continue;
            }
            String s = values.iterator().next();
            if(jedis.zrem(queueKey,s) > 0){
                TaskItem<T> task = JSON.parseObject(s,TaskType);
                this.handleMsg((T) (Thread.currentThread().getName() + " " +task.msg));
            }
        }
        System.out.println("结束");
    }

    private void handleMsg(T msg) {
        System.out.println(msg);
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.198.128");
        Jedis jedis1 = new Jedis("192.168.198.128");
        Jedis jedis2 = new Jedis("192.168.198.128");
        RedisDelayingQueue<String>queue = new RedisDelayingQueue<>(jedis,"q-demo");
        RedisDelayingQueue<String>queue1 = new RedisDelayingQueue<>(jedis1,"q-demo");
        RedisDelayingQueue<String>queue2 = new RedisDelayingQueue<>(jedis2,"q-demo");
        Thread producer = new Thread(){
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    queue.delay("codehole" + i);
                    try {
                        TimeUnit.MILLISECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread consumer1 = new Thread(){
            @Override
            public void run() {
                queue1.loop();
            }
        };
        Thread consumer2 = new Thread(){
            @Override
            public void run() {
                queue2.loop();
            }
        };
        producer.start();
        consumer1.start();
        consumer2.start();
        try {
            producer.join();
            TimeUnit.SECONDS.sleep(10);
            consumer1.interrupt();
            consumer1.join();
            consumer2.interrupt();
            consumer2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }


}
  1. 运行结果 可以看到每个任务只被消费了一次。
Thread-1 codehole0
Thread-2 codehole1
Thread-1 codehole2
Thread-2 codehole3
Thread-2 codehole4
Thread-2 codehole5
Thread-1 codehole6
Thread-1 codehole7
Thread-1 codehole8
Thread-2 codehole9
结束
结束