前言
提示:这里可以添加本文要记录的大概内容
问题背景:本来想用XXL-JOB进行定时任务处理,但是其目前只有MySQL版本,迁移到 SQL server我又嫌麻烦,所以利用Spring中的@Scheduled进行简单的定时任务的处理。
/**
* 任务执行框架
* xxl-job没有sql-server支持 想弄的简单一点
* https://qqe2.com/cron/index
* 在线cron表达式生成
* @author lijiamin
*/
坑点:Spring中的Cron不支持年份,如果通过在线Cron表达式生成的需要把年份列删除,然而实际环境中也很少会用到年份。
代码
思路拆解后主要是四类
- 业务处理
- 线程问题处理/SpringCron缺点处理
- Feign调用
- 立即执行的控制器接口
一、业务处理
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 {
}
总结
提示:这里对文章进行总结: 例如:以上就是今天要讲的内容。