问题描述:项目使用的架构是ssm+maven,war包部署到Linux环境tomcat服务器上,在一次通过日志排查问题的过程中,发现定时任务会执行4次,也就是4个线程同时执行了。
先说解决服务器执行4次的过程:
刚开始猜测会不会是Linux启动了多个tomcat服务器进程,导致执行多次的。然后通过命令
ps -ef|grep tomcat
查看tomcat进程,发现确实有多个,然后通过 kill - 9 进程号 杀掉全部的tomcat进程,重新启动tomcat试试,发现定时任务还是会执行多次。
然后在网上找了一大堆,大部分说是要修改tomcat服务器中的conf文件下的server.xml 中的host name。
如下图。我自己tomcat服务器配置中配了两个host name 映射域名,然后注释掉其中一个后,重新启动服务器。
我以为改了这个后就好了,结果发现定时任务还是会执行两次,然后拿着这个问题去请教运维大哥,运维大哥二话不说直接拿起电脑在Linux服务上就是一顿猛操作,什么tomcat服务器关联,Nginx反向代理等等,最后查看通过查看进程日志,发现进程中只执行了一次,并没有执行多次的情况。
这其中出现了一点问题:不知道咋操作的,项目定时任务又跑了四次,后面排查出了,是因为tomcat服务器下webapps文件夹下存在ROOT和除项目war包外的文件夹。然后把里面的除项目文件夹和war包外全部删除掉,重新启动服务器后只执行2次。如图(这里为了方便截图,用的本地的一个tomcat,意思是一样的)
后面为了排除是否是服务器配置问题,就在本地跑了一次,发现定时任务也会执行2次。
项目定时任务使用的是任务调度的一个工具类,如下
package com.manage.spring.task;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
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.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import com.manage.spring.framework.AppConstants;
import com.manage.spring.service.ITaskService;
import com.manage.spring.util.TimeUtil;
@Lazy(false)
@Component
@EnableScheduling
public abstract class DynamicCornTask implements SchedulingConfigurer {
// 查询数据库中定时任务数据
@Autowired
protected ITaskService taskServiceImpl;
protected static ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
protected Map<String,Object> task = new HashMap<String,Object>();
static Logger logger = Logger.getLogger(DynamicCornTask.class);
static {
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("自定义线程名称前缀");
scheduler.setAwaitTerminationSeconds(60);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.initialize();
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
if(taskRegistrar.getScheduler()==null) {
taskRegistrar.setScheduler(scheduler);
}
taskRegistrar.addTriggerTask(new Runnable() {
@Override
public void run() {
// 配置文件中定义的是否开启定时任务
if(AppConstants.getValue("is_task_open").equals("true")) {
if(!checkAddress()) {
return;
}
// 任务逻辑
if(checkOpen()){
doTask();
}
}
}
}, new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
return getDate( triggerContext);
}
});
}
// 获取时间
protected Date getDate(TriggerContext triggerContext) {
if(!AppConstants.getValue("is_task_open").equals("true")) {
return null;
}
// 查询定时任务中的corn表达式
String corn = findCorn();
if (corn == null) {
return null;
}
// 任务触发,可修改任务的执行周期
CronTrigger trigger = new CronTrigger(corn);
Date nextExec = trigger.nextExecutionTime(triggerContext);
return nextExec;
}
public abstract String getCode();
// 查询定时任务中的corn表达式
protected String findCorn() {
Map<String, Object> param = new HashMap<String, Object>();
param.put("code", getCode());
return taskServiceImpl.findCorn(param);
}
// 判断定时任务状态是否可用
protected boolean checkOpen() {
if(task.isEmpty()) {
return false;
}
return task.get("state").toString().equals("1");
}
// 通过定义好的定时任务编码查询表中的信息
protected Map<String, Object> findCornMap() {
Map<String, Object> param = new HashMap<String, Object>();
param.put("code", getCode());
return taskServiceImpl.findCornMap(param);
}
private boolean checkAddress() {
task = findCornMap();
try {
InetAddress address = InetAddress.getLocalHost();
//比较IP地址,如果一致,则当前执行
if(task.get("ip").equals(address.getHostAddress())) {
updateIPAndExcuteTime(address.getHostAddress());
return true;
}
long lastExcuteTime = TimeUtil.getTimestamp(task.get("last_excute_time").toString());
Integer interval =(int) task.get("interval");
//IP地址不一致,比较距离上次执行是否间隔超过执行周期
if((new Date().getTime() - lastExcuteTime)>interval) {
updateIPAndExcuteTime(address.getHostAddress());
return true;
}
return false;
} catch (UnknownHostException e) {
logger.error(e.getMessage());
return false;
}
}
private void updateIPAndExcuteTime(String ip) {
Map<String,Object> map = new HashMap<String,Object>();
map.put("ip", ip);
map.put("code", getCode());
taskServiceImpl.updateLastExcuteTime(map);
}
public abstract void doTask();
}
定时任务类继承该任务调度工具类(部分代码内容脱敏)
package com.manage.spring.task;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Component;
/**
* @Description //TODO 每天定时更新
**/
@Component
public class SaveOrUpdateNodeInspectionTask extends DynamicCornTask {
private static String CODE = "自定义的定时任务编码";
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
super.configureTasks(taskRegistrar);
}
@Override
public String getCode() {
return CODE;
}
@Override
public void doTask() {
try {
// 执行的业务逻辑方法
} catch (Exception e) {
e.printStackTrace();
}
}
public String toChain(String hash) {
return hash;
}
}
自己在网上又找了一大堆,改web.xml配置,删除掉@Component注解,定义一个专门扫定时任务的xml等,都没有效果。
最后参考了文章,找了找自己spring-mvc.xml
<!-- 注解扫描包 -->
<context:component-scan base-package="com.manage.*">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Service" />
</context:component-scan>
配置中的扫描注解包com.manage.* 后面有个* 号
然后把 * 号去掉,换成Controller
<!-- 注解扫描包 -->
<context:component-scan base-package="com.manage.spring.controller">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Service" />
</context:component-scan>
然后本地运行,定时任务只执行了一次。
发布到Linux环境后,查看日志,定时任务也是执行一次。
这下我的问题就解决了。