可续租任务执行器 #
作用:用于执行某个耗时任务,同时定时向 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;
}
}
}