- 延时队列
- 通过redis中的zset我们可以实现一个简单的延时队列。通过将score设置为时间,然后多线程轮训的去查询是否有到期的可以执行的任务。
- 考虑到多线程并发,使用zrem去保证每一个任务只会被执行1次,这里也可以使用lua脚本来优化,减少空取的情况。
- 代码实现
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();
}
}
}
- 运行结果 可以看到每个任务只被消费了一次。
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
结束
结束