java使用Redis实现延时队列

java使用Redis实现延时队列需求

前言:有收到一个需求需要对上报上来的告警通过配置的规则(收到消息后x秒后发送、发送x次、每次间隔x秒)发送对应的短信提醒用户,当时想到用mq的延时队列来实现,但是只有这一个地方需要使用到队列,引入mq有点杀鸡用牛刀了,后续了解到Redis也可以实现类型延时队列的功能,就使用Redis实现了,记录一下

1.引入Redis

 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <version>2.3.6.RELEASE</version>
 </dependency>

2.定义任务抽象接口

public interface RedisDelayTask {

    /**
     *  任务ID
     * @return
     */
    String getId();

    /**
     *  队列中的值
     * @return
     */
    String getValue();

    /**
     *  延迟时间(单位:s)
     * @return
     */
    long getDelayTime();

    /**
     *  任务执行
     */
    void execute();

    /**
     * 任务类型(具体业务类型)
     */
     String getTopic();
}

3.实现任务抽象接口


import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.platform.maintain.queue.RedisDelayTask;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
public class AbstractRedisDelayTask implements RedisDelayTask {

    /**
     * 延迟任务的唯一标识,用于检索任务
     */
    @JsonSerialize(using = ToStringSerializer.class)
    protected String id;


    protected String value;

    @ApiModelProperty(value = "过期时间(秒)")
    private long delayTime;

    protected String topic;

    public AbstractRedisDelayTask(String id, String value, long delayTime) {
        this.id = id;
        this.value = value;
        this.delayTime = delayTime;
    }

    public AbstractRedisDelayTask(String id, String value, long delayTime,String topic) {
        this.id = id;
        this.value = value;
        this.delayTime = delayTime;
        this.topic=topic;
    }

    public AbstractRedisDelayTask(String value) {
        this.value=value;
    }

    @Override
    public void execute() {
        //业务
    }
}

4.定义任务管理器

import cn.hutool.core.util.ObjectUtil;
import com.platform.maintain.queue.RedisDelayTask;
import com.platform.maintain.queue.imple.SecureTask;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public class RedisSecureQueueManager implements InitializingBean {
    
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 添加延迟任务到队列
     *
     * @param task
     */
    public void addTask(RedisDelayTask task) {
        Long rank = redisTemplate.opsForZSet().rank(task.getId(), task.getValue());
        if(ObjectUtil.isNull(rank)){
            long delayedTime = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(task.getDelayTime(), TimeUnit.SECONDS);
            boolean r = redisTemplate.opsForZSet().add(task.getId(), task.getValue(), delayedTime);
        }
    }

    public void deleteTask(RedisDelayTask task){
        redisTemplate.opsForZSet().remove("SecureTask", task.getValue());
    }
    /**
     * 检查并执行任务
     */
    private void checkAndExecuteTask() {
        while (true) {
            // score就是任务要执行的时间点,如果<=当前时间,说明任务该执行了
            Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet().rangeByScoreWithScores("SecureTask", 0, System.currentTimeMillis());
            if (!CollectionUtils.isEmpty(tuples)) {
                for (ZSetOperations.TypedTuple<String> tuple : tuples) {
                    // 移除并执行任务
                    RedisDelayTask task = new SecureTask(tuple.getValue());
                    if (task != null) {
                        // 从队列中删除
                        redisTemplate.opsForZSet().remove("SecureTask", tuple.getValue());
                        task.execute();
                    }
                }
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // 新起一个线程执行任务
        new Thread(() -> {
            System.out.println("已开启延时任务!!");
            checkAndExecuteTask();
        }, "redis-SecureTask").start();
    }
}

5.继承任务实现类并重写业务接口


import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.platform.common.api.RemoteUserYhService;
import com.platform.common.api.cache.IUserYhCache;
import com.platform.common.api.domain.SysUser;
import com.platform.common.api.model.LoginUser;
import com.platform.common.core.domain.R;
import com.platform.common.core.utils.SpringUtils;
import com.platform.maintain.queue.manager.RedisMessageTaskQueueManager;
import com.platform.maintain.queue.vo.WaterLevelDto;
import com.platform.maintain.util.SmsSendSimple;

import java.util.Date;
import java.util.List;

public class SecureTask extends AbstractRedisDelayTask {

    private RedisMessageTaskQueueManager redisMessageTaskQueueManager = SpringUtils.getBean(RedisMessageTaskQueueManager.class);

    public SecureTask(String id, String value, long delayTime) {
        super(id, value, delayTime);
    }

    public SecureTask(String id, String value, long delayTime, String topic) {
        super(id, value, delayTime, topic);
    }

    public SecureTask(String value) {
        super(value);
    }

    @Override
    public void execute() {
    	//实现具体的业务流程
        WaterLevelDto waterLevelDto = JSONObject.parseObject(this.value, WaterLevelDto.class);
        //如果执行失败可以重新将任务放回队列
        RedisDelayTask task = new NoticeTask("MessageTask",JSONObject.toJSONString(waterLevel), smsTemplate.getSendInterval(),"失败重试");

    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 延时队列是指队列中的任务需要在一定延时后才能被执行,可以有效地解决某些任务需要延时处理的问题。Java Redis是一个基于RedisJava客户端,它可以实现Java语言Redis数据结构的操作,因此可以很方便地实现延时队列的功能。 Java Redis实现延时队列的主要思路是利用Redis的Sorted Set数据结构,将任务按照延时时间作为Score,任务内容作为Value,插入Sorted Set中,并设置过期时间,过期后将任务弹出并执行。具体实现步骤如下: 1. 创建一个Sorted Set,将任务插入其中,Score为任务的延时时间,Value为任务的内容。 2. 使用Redis的zremrangebyscore命令扫描Sorted Set中Score小于等于当前时间的任务并弹出,并将任务内容推送到执行队列中。 3. 设置延时任务的过期时间,过期时间为延时时间加当前时间,可以使用Redis的zadd命令添加任务时同时设置Score和过期时间。 4. 执行队列按顺序执行任务,任务执行完成后从执行队列中删除。 通过以上步骤,可以实现一个高效可靠的延时队列,可以优化系统任务调度、异步处理、消息通知等场景下的问题。 ### 回答2: Java Redis延时队列是一种常用的消息队列模式,在很多应用场景中都有应用。Java Redis延时队列通过将消息发送到Redis进行存储,在指定的时间后再将消息取出来进行处理。这个过程中,通过Redis的Sorted Set类型进行排序来保证队列中的消息有序。下面来详细介绍Java Redis延时队列实现方式。 一、Redis数据结构 Java Redis延时队列的关键在于Redis数据结构的设计。在Redis中,Sorted Set就是用来解决排序问题的。所以我们需要借助Sorted Set实现延时队列。具体来说,可以使用Redis中的zadd命令将消息发送到Sorted Set中,并按照时间顺序进行排序。Sorted Set中的元素包含值和权重,我们可以根据权重(即时间戳)来实现有序存储。 二、消息入队 消息的入队过程如下: 1. 获取消息的过期时间TTL。 2. 计算出当前的时间戳now。 3. 将消息写入到Sorted Set中,权重为now+TTL。 ```redis-cli> ZADD delay-queue (now + TTL) message``` 三、消息出队 消息出队过程如下: 1. 获取当前时间戳now。 2. 使用zrangebyscore命令从Sorted Set中获取所有权重小于等于now的元素,即过期的元素。 3. 遍历查询结果,对每个元素执行出队操作(移除元素)。 ```redis-cli> ZRANGEBYSCORE delay-queue -inf now``` 四、多线程处理 为避免在出队过程中同时处理多个过期元素时出现问题,可以使用多线程处理消息。Java的并发包中提供了Executor框架,这里可以使用ThreadPoolExecutor线程池。 五、消息重试 有时候由于网络波动等原因,在执行消息处理时可能会失败,所以需要将失败的消息重新入队。此时,可以加入重试机制,重新入队时TTL加上重试时间,即可实现延时次数的控制。 六、总结 Java Redis延时队列利用Redis的Sorted Set实现有序存储,使用多线程和重试机制解决了消息处理时的并发和失败问题,保障了消息的可靠性。在实际应用中,可以根据业务需求进行调优和扩展,如设置合理的时间间隔、增加监控和报警等。 ### 回答3: Java Redis实现延时队列可以分为以下几步: 1.将任务加入到延时队列中:首先需要将任务和对应的过期时间放入Redis的有序集合中,以过期时间为score值,任务为value。这样可以保证按照过期时间顺序进行排序,具有先进先出的特点。代码实现如下: ```java //添加任务到延时队列 public void addToDelayQueue(String taskId, long delayTime) { //计算过期时间 long expireTime = System.currentTimeMillis() + delayTime; //添加到有序集合中,score为过期时间 jedis.zadd(DELAY_QUEUE_KEY, expireTime, taskId); } ``` 2.从延时队列中取出任务:需要循环从有序集合中取出第一个score小于等于当前时间的任务,并将其从有序集合中删除。代码实现如下: ```java //获取延时队列中的任务 public void getFromDelayQueue() { while (true) { //获取第一个score小于等于当前时间的任务 Set<String> set = jedis.zrangeByScore(DELAY_QUEUE_KEY, 0, System.currentTimeMillis(), 0, 1); if (set == null || set.isEmpty()) { try { Thread.sleep(1000);//如果没有取到任务,则等待1秒 } catch (InterruptedException e) { e.printStackTrace(); } continue; } String taskId = set.iterator().next(); //删除任务 jedis.zrem(DELAY_QUEUE_KEY, taskId); //处理任务 handleTask(taskId); } } //处理任务 public void handleTask(String taskId) { //TODO: 根据taskId执行对应的任务 } ``` 3.使用Redis发布订阅机制以及线程池来处理任务:在处理任务时,可以使用Redis发布订阅机制将任务相关的信息发布到指定的频道,由相应的消费者线程池来进行任务处理,可以降低单线程处理任务的压力。代码实现如下: ```java //处理任务 public void handleTask(String taskId) { //通过发布订阅机制将任务信息发布到指定频道 jedis.publish(TASK_CHANNEL, taskId); } //消费者线程池处理任务 public void consumeTask() { JedisPubSub jedisPubSub = new JedisPubSub() { public void onMessage(String channel, String message) { //TODO: 根据message信息执行对应的任务 } }; jedis.subscribe(jedisPubSub, TASK_CHANNEL); executorService.execute(jedisPubSub::quit); } ``` 通过以上步骤,我们就可以实现Java Redis延时队列的功能,实现了任务的延迟执行。延时队列实现可以实现具有任务按照时间顺序执行,且任务可取消、可重试等特点,应用场景广泛。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值