延迟队列的使用场景
1、 当分布式锁加锁失败时,将消息放入到延迟队列中处理
2、订餐通知:下单成功后60s之后给用户发送短信通知
3、在订单系统中,一个用户某个时刻下单之后通常有 30 分钟的时间进行支付,如果 30 分钟之内没有支付成功,那么这个订单需要关闭
Redis实现延迟队列的基本原理
生产者将数据放入到list队列中(调用delay函数),最关键的是利用redis的zset数据结构
jedis.zadd(queueKey,System.currentTimeMillis()+10000,s);
zadd的第二个参数是score值,这里是用当前的时间戳加上想要延迟的秒数来作为score值,第三个参数是value值
消费者多线程轮询 zset 获取到期的任务进行处理
Set values=jedis.zrangeByScore(queueKey,0,System.currentTimeMillis(),0,1);
zrangeByScore(String key, double min, double max, int offset, int count)这个函数可以看出是获取0秒到当前时间戳的数据的一条数据进行处理,这里offset是0,count 是1,表示按照分数大小从小到大进行消费
这里我突然想到数据结构中的队列,是按照入队的顺序进行消费的,如果说想要按照逆序进行消费呢?虽然不知道项目项目场景中有没有这样的情况。。。。。
获取到数据之后再调用zrem函数将消费的数据进行移除
jedis.zrem(queueKey,s)>0
代码测试
import cn.hutool.core.lang.TypeReference;
import com.alibaba.fastjson.JSON;
import redis.clients.jedis.Jedis;
import java.lang.reflect.Type;
import java.util.Set;
import java.util.UUID;
public class RedisDelayingQueue<T> {
static class TaskItem<T>{
public String id;
public T msg;
}
// fastjson 序列化对象中存在 generic 类型时,需要使用 TypeReference
private Type TaskType=new TypeReference<TaskItem<T>>() {}.getType();
private Jedis jedis;
private String queueKey;
public RedisDelayingQueue(Jedis jedis, String queueKey){
this.jedis=jedis;
this.queueKey=queueKey;
}
public void delay(T msg){
TaskItem taskItem=new TaskItem();
taskItem.id= UUID.randomUUID().toString();
taskItem.msg=msg;
System.out.println("放入的顺序"+taskItem.msg);
//将消息序列化成一个字符串作为 zset 的 value,这个消息的到期处理时间作为 score
String s= JSON.toJSONString(taskItem);
jedis.zadd(queueKey,System.currentTimeMillis()+10000,s);
}
//然后用多个线程轮询 zset 获取到期的任务进行处理,多个线程是为了保障可用性,万一挂了一个线程还有其它线程可以继续处
public void loop(){
while (!Thread.interrupted()){
Set values=jedis.zrangeByScore(queueKey,0,System.currentTimeMillis(),0,1);
if(values.isEmpty()){
try{
Thread.sleep(500);
}catch(InterruptedException e){
break;
}
continue;
}
String s= (String) values.iterator().next();
//zrem 方法是多线程多进程争抢任务的关键,通过 zrem来决定唯一的属主
if(jedis.zrem(queueKey,s)>0){
TaskItem taskItem=JSON.parseObject(s,TaskType);
System.out.println("消费的顺序"+taskItem.msg);
this.handleMsg((T) taskItem.msg);
}
}
}
//要对 handle_msg 进行异常捕获,避免因为个别任务处理问题导致循环异常退出
public void handleMsg(T msg){
System.out.println(msg);
}
public static void main(String[] args) {
System.out.println("程序开始运行");
Jedis jedis=new Jedis("127.0.0.1",6379);
RedisDelayingQueue queue=new RedisDelayingQueue<>(jedis,"q-demo");
Thread produce=new Thread(){
public void run(){
for(int i=0;i<10;i++){
queue.delay("codehole"+i);
}
}
};
Thread consumer=new Thread(){
public void run(){
for(int i=0;i<10;i++){
queue.loop();
}
}
};
produce.start();
consumer.start();
try{
produce.join();
// Thread.sleep(6000);
// consumer.interrupt();
consumer.join();
}catch (InterruptedException e){
}
}
}
复制代码