问题描述:项目使用的架构是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 映射域名,然后注释掉其中一个后,重新启动服务器。

spring 设置每三分钟执行一次 spring定时任务执行多次_定时任务执行多次任


我以为改了这个后就好了,结果发现定时任务还是会执行两次,然后拿着这个问题去请教运维大哥,运维大哥二话不说直接拿起电脑在Linux服务上就是一顿猛操作,什么tomcat服务器关联,Nginx反向代理等等,最后查看通过查看进程日志,发现进程中只执行了一次,并没有执行多次的情况。

这其中出现了一点问题:不知道咋操作的,项目定时任务又跑了四次,后面排查出了,是因为tomcat服务器下webapps文件夹下存在ROOT和除项目war包外的文件夹。然后把里面的除项目文件夹和war包外全部删除掉,重新启动服务器后只执行2次。如图(这里为了方便截图,用的本地的一个tomcat,意思是一样的)

spring 设置每三分钟执行一次 spring定时任务执行多次_@scheduled_02


后面为了排除是否是服务器配置问题,就在本地跑了一次,发现定时任务也会执行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环境后,查看日志,定时任务也是执行一次。
这下我的问题就解决了。