一 前言
最近看到项目中的定时任务感觉还是很有意思的,就简单的记录一下,我们开发中会遇到多模块的定时任务,但是这些定时任务都有着相似的逻辑,大量的定时任务都要去写这些相似的逻辑,就降低了代码的解耦性,那我们可以把这些相似的逻辑提取出来,抽成一个抽象类,其他的类只需要去继承这个抽象类,无需再去写那些重复的逻辑,只需关注具体的业务实现,当然这些事Java, Spring ,Quartz,XXL-Job等都帮我们设计好了,我们也可以自己搞来玩玩!
1.新建定时任务接口
public interface Task {
/**
* @Description
* @Param [request, response]
* @return void
**/
void execute();
/**
* @Description
* @Param []
* @return java.lang.String
**/
String getTaskName();
}
2.抽象定时工厂类
@Slf4j
public abstract class AbstractTask implements Task {
@Resource
private RedisLockRegistry redisLockRegistry;
private static final String TASK_LOCK_KEY_PREFIX = "task-lock:";
private static final String TASK_LOCK_KEY_SEPARATOR = "^_^";
@Override
public void execute() {
//获取定时任务名
String taskName = getTaskName();
//获取定时任务锁
String lockKey = fetchTaskLockKey(taskName);
Lock lock = redisLockRegistry.obtain(lockKey);
if (lock.tryLock()){
Stopwatch stopwatch = Stopwatch.createStarted();
try {
log.info("Starting execute task {}", taskName);
run();
} catch (Exception e) {
log.error("Task {} execute error with exception", taskName, e);
throw e;
} finally {
stopwatch.stop();
log.info("Task {} execute completed and use time {} ms", taskName, stopwatch.elapsed(TimeUnit.MILLISECONDS));
lock.unlock();
}
}
}
protected abstract void run();
/***
* @Description 获取定时任务锁 key
* @Date 2023/3/24
* @Param [taskName]
* @return java.lang.String
**/
protected String fetchTaskLockKey(String taskName) {
if (StringUtils.isBlank(taskName)) {
throw new BusinessException("TaskName must not be empty", SystemErrorCode.ERROR);
}
return TASK_LOCK_KEY_PREFIX + TASK_LOCK_KEY_SEPARATOR + taskName;
}
}
3.具体定时业务实现类
@Slf4j
@AllArgsConstructor
@Component
public class OrderOverTimeTask extends AbstractTask{
@Override
public String getTaskName() {
return TaskFactoryConstant.TASK_NAME_ORDER_OVERTIME;
}
@Override
protected void run() {
//逻辑业务
}
}
4.定时工厂
@Component
public class TaskFactory {
@Resource
private OrderOverTimeTask orderOverTimeTask;
public Task getTask(String taskName) {
if (AxinStringUtils.isNotBlank(taskName)) {
switch (taskName) {
case "order_overtime":
return orderOverTimeTask;
default:
break;
// 其他定时任务
}
}
throw new RuntimeException("获取定时任务失败");
}
}
5.公共业务逻辑
@Slf4j
@AllArgsConstructor
@Service
public class TaskServiceImpl implements TaskService {
final private TaskFactory taskFactory;
final private JsonMapper jsonMapper;
@Override
public void execute(HttpServletRequest request, HttpServletResponse response) {
try {
//签名验证
final String json = SignatureUtils.getJson(request);
String signature = SignatureUtils.getSignature(request);
if (AxinStringUtils.isBlank(signature)) {
signature = SignatureUtils.getSign(request);
}
final String innerKey = "hb_task";
final boolean verify = SignatureUtils.verify(json, signature, innerKey);
if (!verify) {
throw new BusinessException(RequestErrorCode.INVALID_SIGNATURE);
}
final String taskName = jsonMapper.getNode(json, "qname", String.class);
//获取对应定时任务
final Task task = taskFactory.getTask(taskName);
//响应前端
writeJobServer(response);
try {
//执行定任务
task.execute();
} catch (Exception e) {
}
} catch (Exception e) {
log.error("Task service execute error with exception", e);
}
}
public void writeJobServer(HttpServletResponse resp) {
try {
final JobServerResultDTO jobServerResult = JobServerResultDTO.builder()
.respCode("0000")
.respDesc("接收JobServer任务成功")
.build();
final String json = JsonMapper.create().toJson(jobServerResult);
resp.getOutputStream().write(json.getBytes(StandardCharsets.UTF_8));
resp.getOutputStream().close();
} catch (Exception e) {
log.error("Quartz rcv Response to client error", e);
}
}
}
6.单次处理多个定时任务,通过异步线程池处理,需在具体的定时业务任务类中调用处理;
@Slf4j
@Service
public class AsyncTaskServiceImpl implements AsyncTaskService {
private final LazyTraceAsyncTaskExecutor lazyTraceAsyncTaskExecutor;
public AsyncTaskServiceImpl(BeanFactory beanFactory, AsyncTaskExecutor asyncTaskExecutor) {
this.lazyTraceAsyncTaskExecutor = new LazyTraceAsyncTaskExecutor(beanFactory, asyncTaskExecutor);
}
@Override
public <T, R> List<R> execute(List<T> tasks, Function<T, R> executor) {
List<R> result = Lists.newArrayList();
List<CompletableFuture<R>> completableFutures = Lists.newArrayList();
if (AxinCollectionUtils.isNotEmpty(tasks)) {
//遍历任务集合
tasks.forEach(task -> {
if (AxinObjectUtils.isNotEmpty(task)) {
//异步执行
CompletableFuture<R> completeAsync = CompletableFuture.supplyAsync(() ->
executor.apply(task), lazyTraceAsyncTaskExecutor
).exceptionally(throwable -> {
log.info("异步任务执行失败:{}", throwable.getMessage());
return null;
}).whenCompleteAsync((r, e) -> Optional.ofNullable(r).ifPresent(result::add));
//将线程添加集合
completableFutures.add(completeAsync);
}
});
}
if (AxinCollectionUtils.isNotEmpty(completableFutures)) {
//获取所有线程执行结果
CompletableFuture<R>[] futures = (CompletableFuture<R>[]) completableFutures.toArray(new CompletableFuture[tasks.size()]);
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(futures);
try {
voidCompletableFuture.get(60 * 60, TimeUnit.SECONDS);
} catch (Exception e) {
log.error("异步任务结果获取失败:{}", e);
}
}
return result;
}
}
7.多个定时任务处理
@Slf4j
@AllArgsConstructor
@Component
public class OrderQueryTask extends AbstractTask {
private final PayConfig payConfig;
private final OrderQueryPay orderQueryPay;
private final OrderQueryRefund orderQueryRefund;
private final PayOrderMapper payOrderMapper;
private final RefundOrderMapper refundOrderMapper;
private final AsyncTaskService asyncTaskService;
@Override
protected void run() {
final String orderQueryBackOff = payConfig.getPay().getOrderQueryBackOff();
final List<Pair<String, String>> segments = SegmentFrequency.create(orderQueryBackOff).segments();
asyncTaskService.execute(segments, item -> {
executeTask1(item);
executeTask2(item);
return true;
});
}
private void executeTask1(Pair<String, String> timeParam) {
final String start = timeParam.getLeft();
final String end = timeParam.getRight();
final List<Integer> status = Lists.newArrayList(OrderStatusEnum.ORDER_STATUS_UN_PAY.getStatus(), OrderStatusEnum.ORDER_STATUS_ERROR.getStatus());
final List<String> busChannels = payOrderMapper.findOrderQueryPayBusChannel(start, end, status);
final List<Pair<String, Integer>> executeResult = asyncTaskService.execute(busChannels, busChannel -> {
final AtomicInteger sum = new AtomicInteger();
ShardingExecutors.apply(LIMIT).execute((index, limit, context) -> {
index = index * limit;
final List<PayOrder> payOrders = payOrderMapper.findBusChannelOrderQueryPayLimit(busChannel, start, end, status, index, limit);
orderQueryPay.query(payOrders);
final int size = payOrders.size();
sum.addAndGet(size);
return size;
});
return Pair.of(busChannel, sum.get());
});
log.info("Execute pay order query completed <timeRange={}, result={}>", timeParam, executeResult);
}
private void executeTask2(Pair<String, String> timeParam) {
final String start = timeParam.getLeft();
final String end = timeParam.getRight();
final List<Integer> status = Lists.newArrayList(OrderStatusEnum.ORDER_STATUS_REFUNDING.getStatus(), OrderStatusEnum.ORDER_STATUS_REFUND_ERROR.getStatus());
final List<String> busChannels = refundOrderMapper.findOrderQueryRefundBusChannel(start, end, status);
final List<Pair<String, Integer>> executeResult = asyncTaskService.execute(busChannels, busChannel -> {
final AtomicInteger sum = new AtomicInteger();
ShardingExecutors.apply(LIMIT).execute((index, limit, context) -> {
index = index * limit;
final List<RefundOrder> refundOrders = refundOrderMapper.findBusChannelOrderQueryRefundLimit(busChannel, start, end, status, index, limit);
orderQueryRefund.query(refundOrders);
final int size = refundOrders.size();
sum.addAndGet(size);
return size;
});
return Pair.of(busChannel, sum.get());
});
log.info("Execute refund order query completed <timeRange={}, result={}>", timeParam, executeResult);
}
@Override
public String taskName() {
return TaskFactory.TASK_TEST;
}
}