springboot单机版延迟任务

在实际开发中经常能遇到一些补偿措施使用延迟任务来做,有些可以使用定时任务来实现,但是有些需要有固定的周期规律,所以需要延迟指定时间后搞些事情。

但是呢,本来开发的一个单机很low的一个项目,没有必要引用一些其他的中间件,所以就做了个简单的延迟任务小样,这个是延迟给第三方系统发送通知的业务。

使用的是jdk里面的Delayed作为任务队列,使用过期时间做排序,这里要说一下这并不是简单的链表,实际是带排序的链表。

package xxx;

import lombok.Data;

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * 任务信息对象
 */
@Data
public class TaskDelayed implements Delayed {
    Integer level; // 等级
    long expire; // 过期时间
    String data;
    String notifyUrl;

    /**
     * 任务信息构造
     * @param level
     * @param msg
     * @param url
     */
    public TaskDelayed(Integer level, String msg,String url) {
        this.level = level;
        this.expire = System.currentTimeMillis() + TaskUtil.levelMap.get(level);
        data = msg;
        notifyUrl = url;
    }

    /**
     * 任务升级
     * @return
     */
    public boolean upgrade(){
        this.level++;
        if(TaskUtil.levelMap.containsKey(level)){
            this.expire = System.currentTimeMillis() + TaskUtil.levelMap.get(level);
            return true;
        }else{
            return false;
        }
    }


    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        long f = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
        return (int) f;
    }
}

工具类,在这里面定义了队列,和任务等级,以及任务自动升级的方法

package xxx;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.DelayQueue;

/**
 * 单例的任务队列
 */
@Slf4j
@Component
public class TaskUtil {
    private DelayQueue<TaskDelayed> delayQueue = new DelayQueue<TaskDelayed>();

    /**
     * 等级对应延迟时间
     */
    public static Map<Integer,Long> levelMap = new HashMap<>();

    /**
     * 自定义等级对应延迟持剑
     */
    static {
        levelMap.put(1,1000*0l);
        levelMap.put(2,1000*3l);
        levelMap.put(3,1000*30l);
        levelMap.put(4,1000*300l);
        levelMap.put(5,1000*3000l);
    }

    /**
     * 添加任务
     * @param task
     */
    public void addTask(TaskDelayed task) {
        delayQueue.add(task);
    }

    /**
     * 判断是否有需要执行的任务
     * @return
     */
    public boolean isEmpty() {
        return delayQueue.isEmpty();
    }

    /**
     * 获取接下来需要执行的任务
     * @return
     */
    public TaskDelayed take(){
        try {
            return delayQueue.take();
        } catch (InterruptedException e) {
            log.error("delayQueue.take失败",e);
        }
        return null;
    }

    /**
     * 任务升级到下一个等级,并且仍会丢列中
     * @param task
     */
    public void upAndAdd(TaskDelayed task) {
        if (task.upgrade()) {
            this.addTask(task);
        }
    }
}

下面这些就是启动时间轮用的监听器和时间轮实现类,轮巡获取队列面的任务

package xxx;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

/**
 * 延迟任务启动监听器
 * 打开@Component生效
 */
//@Component
@Slf4j
public class TaskApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private TaskUtil taskUtil;

    @Autowired
    private TaskTimeWheel taskTimeWheel;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        //开启定时任务监听
        taskTimeWheel.startWheel();

        //添加测试任务
        //需要发送通知的时候引入TaskUtil就调用这个方法
//        taskUtil.addTask(new TaskDelayed(1, "msg", "url"));
//        taskUtil.addTask(new TaskDelayed(2, "msg2", "url2"));
    }
}
package xxx;

public interface TaskTimeWheel {
    void startWheel();
}
package xxx;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class TaskTimeWheelImpl implements TaskTimeWheel{
    @Autowired
    private TaskUtil taskUtil;

    @Autowired
    private TaskNatifyService taskNatifyService;

    @Async
    @Override
    public void startWheel(){
        log.info("延迟任务时间轮线程启动");
        while (true) {
            taskNatifyService.send(taskUtil.take());
        }
    }
}

下面这个就是具体执行任务的方法了,我这个是发送http请求

package xxx;

public interface TaskNatifyService {

    void send(TaskDelayed task);
}
package xxx;

import xxx.utils.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class TaskNatifyServiceImpl implements TaskNatifyService {
    @Autowired
    private TaskUtil taskUtil;

    /**
     * 异步执行延迟任务,执行失败升级任务并扔会队列中
     * @param task
     */
    @Async
    @Override
    public void send(TaskDelayed task) {
        log.info("执行延时任务:{}", task);
        if(task == null){
            return;
        }
        String result = null;
        try {
            result = HttpUtil.sendHttpRequest(task.getNotifyUrl(), task.getData(), "UTF-8");
            if (result!=null && !result.equals("success")) {
                log.info("延迟任务发送失败:{}",result);
                taskUtil.upAndAdd(task);
            }
        } catch (Exception e) {
            log.error("延时任务执行失败", e);
            taskUtil.upAndAdd(task);
        }

    }
}

这个没有做任务的落库,实际使用最好给log落库,然后项目重启的时候,从库里面把未执行完的任务补偿到队列里面。总之还是比较简陋的,提供个思路罢了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在写bug的路上越走越远

谢谢老板,老板发大财

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值