Spring简易定时任务工具

136 阅读3分钟

前言

提示:这里可以添加本文要记录的大概内容

问题背景:本来想用XXL-JOB进行定时任务处理,但是其目前只有MySQL版本,迁移到 SQL server我又嫌麻烦,所以利用Spring中的@Scheduled进行简单的定时任务的处理。

/**
 * 任务执行框架
 * xxl-job没有sql-server支持 想弄的简单一点
 * https://qqe2.com/cron/index
 * 在线cron表达式生成
 * @author lijiamin
 */

坑点:Spring中的Cron不支持年份,如果通过在线Cron表达式生成的需要把年份列删除,然而实际环境中也很少会用到年份。

代码

思路拆解后主要是四类

  1. 业务处理
  2. 线程问题处理/SpringCron缺点处理
  3. Feign调用
  4. 立即执行的控制器接口

一、业务处理

DemoBusiness示例代码

import org.springblade.aop.TaskThreadHandle;
import org.springframework.scheduling.annotation.Scheduled;

/**
 * 定时任务示例Demo
 */
@Component
@EnableScheduling
public class DemoBusiness {

	/** 上一次执行完毕时间后执行下一轮 --- 每次5秒 缺点:任务之间会发生线程阻塞 所以那个@TaskThreadHandle注解要套上 */
	@Scheduled(cron = "0/5 * * * * *")
	@TaskThreadHandle
	public void demoRunSync() throws InterruptedException {
		long startTime = System.currentTimeMillis();
		Thread.sleep(10000);
		System.out.println("=====>>>>>使用cron demoRunSync() {}");
		long lastTime = System.currentTimeMillis();
		System.out.println("demoRunSync()执行时长 = " + (lastTime - startTime) / 1000 + " 秒");
	}
}

这里有个缺点,如果当有一个线程太慢了或者阻塞了,会影响到其它进程的执行,为了解决这个问题点,我做了一个AOP切面+异步线程池,上面已经有了注释说明。

二、线程阻塞问题处理-AOP

接口

/**
 * 定时任务线程切面处理
 * @author 李家民
 */
public @interface TaskThreadHandle {
}

实现

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springblade.common.tool.ThreadPoolUtils;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;

/**
 * 定时任务线程切面处理
 * @author 李家民
 */
@Component
@Aspect
public class TaskThreadHandleImpl {

	/**
	 * 异步线程池处理 防止因为线程卡顿
	 * @param point
	 * @return
	 * @throws Throwable
	 */
	@Around("@annotation(org.springblade.aop.TaskThreadHandle)")
	public Object asyncThreadExecute(ProceedingJoinPoint point) throws Throwable {
		CompletableFuture.runAsync(() -> {
			try {
				point.proceed();
				// 这里应该套一层超时 以后再说
			} catch (Throwable e) {
				throw new RuntimeException(e);
			}
		}, ThreadPoolUtils.getThreadPool());
		// void 也不要返回值了
		return null;
	}
}

这个线程池的工具类

import java.util.concurrent.*;

/**
 * 线程池工具类
 * 后面引入框架监控
 * @author xxx
 */
public class ThreadPoolUtils {

	private static ThreadPoolExecutor pool = null;

	/**
	 * 无响应执行
	 * @param runnable
	 */
	public static void execute(Runnable runnable) {
		getThreadPool().execute(runnable);
	}

	/**
	 * 有响应执行
	 * @param callable
	 * @param <T>
	 * @return
	 */
	public static <T> Future<T> submit(Callable<T> callable) {
		return getThreadPool().submit(callable);
	}

	/**
	 * 创造线程池
	 * @return
	 */
	public static synchronized ThreadPoolExecutor getThreadPool() {
		if (pool == null) {
			// 获取处理器数量
			int cpuNum = Runtime.getRuntime().availableProcessors();
			// 根据cpu数量,计算出合理的线程并发数
			// 最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
			int maximumPoolSize = cpuNum * 2 + 1;
			// 七个参数
			// 1. 核心线程数
			// 2. 最大线程数
			// 3. 空闲线程最大存活时间
			// 4. 时间单位
			// 5. 阻塞队列
			// 6. 创建线程工厂
			// 7. 拒绝策略
			pool = new ThreadPoolExecutor(maximumPoolSize - 1,
				maximumPoolSize,
				5,
				TimeUnit.SECONDS,
				new ArrayBlockingQueue<>(50),
				Executors.defaultThreadFactory(),
				new ThreadPoolExecutor.AbortPolicy());
		}
		return pool;
	}
}

三、Feign调用

在微服务场景下,做定时任务的最终目标肯定是会调用到其它的微服务,所以我这里是这么写的。

import org.springblade.core.tool.utils.SpringUtil;

/**
 * 远程调用客户端
 * @author 李家民
 */
public class UnifyClient {

	/** 办公管理服务-客户端 */
	private static IOrderClient orderClient;

	public static IOrderClient getOrderClient() {
		if (orderClient == null) {
			orderClient = SpringUtil.getBean(IOrderClient.class);
		}
		return orderClient;
	}
}

四、立即执行的控制器接口

这里没写完,但是以后再补充吧

import org.springframework.web.bind.annotation.RestController;

/**
 * 测试用控制器
 * @author 李家民
 */
@RestController
public class TestController {
}

总结

提示:这里对文章进行总结: 例如:以上就是今天要讲的内容。