使用@Scheduled出现的问题
公司线上的一个项目,用户偶尔反馈App上部分功能没有数据,运营也是紧急联系我们技术,我经过排查发现app没有数据的功能,都是通过@Scheduled任务执行放入缓存的,但是又排查下来发现也并不是所有的定时任务没有执行,只是部分定时任务没有执行,由于情况紧急只能叫运维重启线上定时任务服务
由于问题的断断续续,且一般偶尔周末发生,断断续续的花了几个月时间才完全解决
问题排查
@Scheduled 单线程堵塞问题
首先查看定时任务服务打印的日志,发现无任何问题,在去下级服务查看日志发现也无报错,心想是不是Scheduled单线程执行问题,导致任务线程堵塞了
如果一个定时任务A中执行中堵塞了,另一个定时任务B执行时间到了,定时任务B也不会执行,只有任务A执行完,任务B才执行。
但是查看项目代码发现配置了Scheduled的异步执行工具类,让所有的定时任务都有一个独立线程执行
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.util.ErrorHandler;
import java.util.concurrent.Executor;
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer, AsyncConfigurer {
/**
* 异步处理
*/
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
TaskScheduler taskScheduler = taskScheduler();
taskRegistrar.setTaskScheduler(taskScheduler);
}
/**
* 定时任务多线程处理
*/
@Bean(destroyMethod = "shutdown")
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(50);
scheduler.setThreadNamePrefix("task-");
scheduler.setAwaitTerminationSeconds(60);
scheduler.setErrorHandler(new ScheduleErrorHandler());
scheduler.setWaitForTasksToCompleteOnShutdown(true);
return scheduler;
}
/**
* 异步处理
*/
@Override
public Executor getAsyncExecutor() {
return taskScheduler();
}
/**
* 异步处理 异常
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
/**
* 异常捕捉处理
*/
private static class ScheduleErrorHandler implements ErrorHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void handleError(Throwable t) {
StackTraceElement stackTraceElement = t.getStackTrace()[0];
String className = stackTraceElement.getClassName();
String methodName = stackTraceElement.getMethodName();
int lineNumber = stackTraceElement.getLineNumber();
logger.error("{} in {}:{}:{}", t.toString(), className, methodName, lineNumber);
}
}
}
每个定时器都是单线程问题
排除了定时任务单线程执行问题,我怀疑没执行的定时任务执行时间太快了导致线程堵塞了,可能是代码写的有问题,或者处理的数据量太大,任务没执行完导致后续请求进不来导致任务假死
虽然配置了异步处理定时任务,但是一个定时器还是一个线程执行,一旦定时任务执行时候过长就会导致下一个周期的定时任务无法执行
这种情况我通过优化代码和让定时器任务以从线程池中拿线程的方式去执行任务,避免执行时间过长,下个周期不执行问题
@Scheduled(cron = "0/15 * * * * ? ")
public void todaySchedule() {
log.info("===========今日赛事定时任务启动============");
ThreadPoolUtil.getsInstance().execute(() -> {
List<TodayScheduleOutVO> data = footScheduleResultApi.todaySchedule(10).getData();
redisService.del(MatchConstant.TODAY_EVENT_KEY);
redisService.set(MatchConstant.TODAY_EVENT_KEY, JSON.toJSONString(data), TimeConstant.SMS_OVERDUE_TIME);
});
}
经过优化代码和配置线程池执行,让项目的定时任务达到了稳定
服务器超时问题
最近公司又上线一个新项目,项目是从之前老项目复制过来在上面二次开发的的项目,在项目刚上线的时候,每到晚上运营技术对接群异常热闹,原因是项目每晚都炸,究其原因还是定时任务问题,让app没有数据
我通过查看日志发现公司封装的HttpClientUtils发送请求的时候,一直在超时,这就导致定时任务本该解析存储数据库和缓存的数据,无法入库
我去查看了请求工具类的超时时间发现只有5s,我怀疑是超时时间太短了,请求某个网站的时候可能晚上请求量多,导致处理时间过长,这样我把超时改了30s,并在晚上更新了
reqConfig = RequestConfig.custom()
.setConnectionRequestTimeout(5000)
.setConnectTimeout(5000)
.setSocketTimeout(5000)
.setExpectContinueEnabled(false)
.setProxy(new HttpHost(proxyHost, proxyPort))
.build();
第二天晚上8点左右,定时任务又出现异常,还是老问题请求超时,这个时候也无法去动线上代码,只有通过手动往数据库和redis加数据,保证app正常运行
我又一直在想项目代码也没啥问题,为什么会一直超时呢,超时时间我已经改成了30s还是超时,项目的内存和cpu都是没有什么异常,而且另一个同样代码的项目在线上跑的好好的,我就怀疑是服务器有问题,
当晚我技术老大还是决定换一个更大的服务器,看是不是这个服务器网络波动导致请求的超时
第三天新服务换好,晚上老问题重现任务超时
我通过liunx的curl命令去两个服务器中去访问了超时的网址发现新服务器返回的时间也很慢
这样就应该不是网络波动导致的超时,而应该是服务器地址的问题了
因为公司新项目的服务器地址是在在香港,所以问题是在香港发送请求到内地超时,定位到最终的问题,我们处理方式是吧,定时任务的代码抽成了一个新项目,放到国内服务器去执行,避免因为服务器地址调用部分网站很慢的情况
有兴趣的小伙伴可以加微信技术群里,遇到问题可以一起讨论