可续租任务工具类

可续租任务执行器 #

作用:用于执行某个耗时任务,同时定时向 redis 续租某个 key,防止该任务被重复执行。

java
import cn.hutool.extra.spring.SpringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author jiahui156
 */
public class RenewTaskUtils {
    private final static Logger log = LoggerFactory.getLogger(RenewTaskUtils.class);
    private static final String RENEW_TASK_KEY = "renew_task:";

    private static class Holder {
        private static final RedisTemplate<String, Object> redisTemplate = SpringUtil.getBean(RedisTemplate.class);
        private static final ScheduledExecutorService scheduledExecutorService = SpringUtil.getBean("scheduledExecutorService");
    }

    private static <T> Callable<T> newCallable(Runnable runnable) {
        return () -> {
            runnable.run();
            return null;
        };
    }

    public static Runnable wrap(String taskId, Runnable runnable) {
        return () -> run(taskId, runnable);
    }

    public static void run(String taskId, Runnable runnable) {
        run(taskId, newCallable(runnable));
    }

    public static <T> T run(String taskId, Callable<T> callable) {
        String redisKey = RENEW_TASK_KEY + taskId;

        if (!tryAcquire(redisKey)) {
            throw new IllegalStateException("任务已存在!");
        }

        // 续租任务
        RenewTask renewTask = new RenewTask(redisKey);
        renewTask.schedule();

        try {
            return callable.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            log.error("run error", e);
            throw new RuntimeException("内部异常:" + e.getMessage());
        } finally {
            // 终止续租任务
            renewTask.cancel();
            Holder.redisTemplate.delete(redisKey);
        }
    }

    public static boolean isRunning(String taskId) {
        String redisKey = RENEW_TASK_KEY + taskId;
        return Boolean.TRUE.equals(Holder.redisTemplate.hasKey(redisKey));
    }

    public static boolean tryAcquire(String taskId) {
        String redisKey = RENEW_TASK_KEY + taskId;
        return Boolean.TRUE.equals(Holder.redisTemplate.opsForValue().setIfAbsent(redisKey, Boolean.TRUE, 30, TimeUnit.SECONDS));
    }

    static class RenewTask implements Runnable {
        private final String key;
        private volatile boolean isCancelled = false;

        public RenewTask(String key) {
            this.key = key;
        }

        @Override
        public void run() {
            if (isCancelled) {
                return;
            }
            Holder.redisTemplate.opsForValue().set(key, Boolean.TRUE, 30, TimeUnit.SECONDS);
            schedule();
        }

        public void schedule() {
            Holder.scheduledExecutorService.schedule(this, 20, TimeUnit.SECONDS);
        }

        public void cancel() {
            this.isCancelled = true;
        }
    }
}
2024年10月28日