@scheduled 定时任务执行一段时间后莫名其妙停止 Ftp假死

之前遇到的一个问题记录一下,当时公司的项目线上运行了几个月遇到了这个问题。

1 出现问题

客户反馈说系统异常,赶紧通知运维同学发现一直获取不到数据。运维同学因为急于恢复,选择了重启服务。这也导致了我们前前后后用了一个月才解决这个问题。

我们通过日志排查问题,发现程序中几个定时任务,其中一个出现了问题,如下:

2019-10-08 21:55:06.021 |-INFO  [taskPool-7] cn.qyxing.qxt.service.RadarService [365] -| get livedata task running ……

日志只停留在这一条,从数据源获取数据,一直没有下载成功。

2 查找原因

网上查询了一下,得知:

SpringBoot使用 @scheduled 定时任务,执行的时候是在一个单线程中,如果前一个任务执行时间过长,则有可能会导致其他后续任务被阻塞,直到该任务执行完成。

此时我判断线程被阻塞,但是没有找到原因,同事说是不是有流未关闭,帮他核查了一遍代码,未发现问题。

此时突发奇想:既然是单线程的问题,换成异步的线程池是不是就能解决这个问题。

代码见文章最后 -- 异步任务的线程池和定时任务的异步线程

将代码更新以后,生产环境运行一个星期一直正常,和同事放下心来。

3 问题再现

万万没想到又过了一个星期,问题又出现了,感觉还是这个定时任务的问题,这次提醒运维同学先不要急着重启

jstack命令,导出当前环境的线程状态,结合运行日志,终于定位到问题所在

"taskPool-7" #88 prio=5 os_prio=0 tid=0x00007f60a9dca800 nid=0x9a8 runnable [0x00007f5f5e2db000]
   java.lang.Thread.State: RUNNABLE
	at java.net.PlainSocketImpl.socketAccept(Native Method)
	at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
	at java.net.ServerSocket.implAccept(ServerSocket.java:545)
	at java.net.ServerSocket.accept(ServerSocket.java:513)
	at org.apache.commons.net.ftp.FTPClient._openDataConnection_(FTPClient.java:858)
	at org.apache.commons.net.ftp.FTPClient._retrieveFileStream(FTPClient.java:1980)
	at org.apache.commons.net.ftp.FTPClient.retrieveFileStream(FTPClient.java:1967)
	at cn.qyxing.qxt.util.FtpUtil.downloadFtpFile(FtpUtil.java:204)
	at cn.qyxing.qxt.service.RadarService.getLiveData(RadarService.java:151)
	at cn.qyxing.qxt.scheduled.QxtScheduledTask.gatherRadarDataTask(QxtScheduledTask.java:62)
	at sun.reflect.GeneratedMethodAccessor135.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84)
	at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
	at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:93)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

4 解决问题

线程状态一直是 RUNNABLE,但是就是不往下执行,接着往下看发现是FTPClient的问题。

此时觉得奇怪,因为前三个月运行一直是正常的,查看版本更新记录,发现数据源发生过更换,之前提供的共享文件服务因为安全问题停用了,现在是改用ftp下载才出现的问题。

这一次给 FTPClient 设置了连接超时时间,ConnectTimeout、DataTimeout、DefaultTimeout,更新上线运行,谁知过了三个星期问题再次现。

因为已经知道是FTPClient问题,于是网上查询了一下,发现有不少人遇到这种假死问题。原来是FTP服务器采用主动模式的导致的,于是重新设置FTP服务器的传输模式,并指定端口,果然问题解决。

FTP有两种模式:主动模式(active mode)和被动模式(passive mode)。默认情况下是启动的主动模式。

在主动模式下,客户端会开启一个大于1024的端口(不固定)来接收FTP服务器通过20端口传过来的数据。这个过程FTP服务器主动向客户端发起数据连接请求,但是由于linux防火墙的屏蔽,客户端无法正常接收FTP服务器发送来的数据流,所以就会出现假死的现象

最后总结:FTP主动模式下载文件会假死,导致@scheduled定时任务阻塞


附:异步任务的线程池和定时任务的异步线程

@Configuration
@EnableAsync
@EnableScheduling
public class ExecutorConfig implements SchedulingConfigurer, AsyncConfigurer {
 
	private Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);
 
    @Autowired
    private TaskThreadPoolConfig config;
 
    @Override
    public Executor getAsyncExecutor() {
    	ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setThreadNamePrefix("taskExecutor-");
		//线程池维护线程的最少数量
		executor.setCorePoolSize(config.getCorePoolSize());
		//线程池维护线程的最大数量
		executor.setMaxPoolSize(config.getMaxPoolSize());
		//线程池所使用的缓冲队列
		executor.setQueueCapacity(config.getQueueCapacity());
		//线程池维护线程所允许的空闲时间
		executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
		//当调度器shutdown被调用时等待当前被调度的任务完成
		executor.setWaitForTasksToCompleteOnShutdown(true);
		//等待时常
		executor.setAwaitTerminationSeconds(config.getAwaitTerminationSeconds());

		executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
		executor.initialize();
        return executor;
    }

 

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable ex, Method method, Object... params) {
                logger.error("==========================" + ex.getMessage() + "=======================", ex);
                logger.error("exception method:" + method.getName());
            }
        };
    }
 
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        TaskScheduler taskScheduler = taskScheduler();
        taskRegistrar.setTaskScheduler(taskScheduler);
    }
    
    /**
     * 并行任务使用策略:多线程处理
     * 
     * @return ThreadPoolTaskScheduler 线程池
     */
     @Bean(destroyMethod = "shutdown")
     public TaskScheduler taskScheduler() {
         ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
         scheduler.setPoolSize(config.getSchedulerCorePoolSize());
         scheduler.setAwaitTerminationSeconds(60 * 6);
         scheduler.setThreadNamePrefix("taskPool-");
         scheduler.setAwaitTerminationSeconds(60);
         scheduler.setWaitForTasksToCompleteOnShutdown(true);
         return scheduler;
     }
}

 

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
@Scheduled定时任务执行两次的原因可能有多种,根据引用内容,其中可能的原因之一是服务器配置的问题。以Tomcat服务器为例,如果在配置文件server.xml中同时配置了appBase="webapps"和docBase="YourApp",相当于加载了两个应用,导致定时任务执行两次。解决方案之一是修改配置文件,确保只加载一个应用。另外,还可能涉及其他因素,比如代码中的依赖关系、任务管理器的配置等。需要进一步检查和排查相关配置和代码以确定具体原因。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [生产问题:@Scheduled Spring定时任务每次执行两次原因分析以及解决方案](https://blog.csdn.net/u010886217/article/details/107429853)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [简洁的定时任务实例](https://download.csdn.net/download/qq_37173966/9996155)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [SpringBoot @Scheduled定时任务执行两次问题](https://blog.csdn.net/ddzyx/article/details/113240322)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值