线上环境有一个定时任务,每秒钟扫描ftp,然后取文件,结果总是莫名其妙的卡住,后来改成线程池异步执行,但是没多久就发生线程池被打满的情况。重启后没多久又又又出现这个现象🤪🤪🤪,下决心要给它解决掉!😎
问题排查:
首先使用jps命令查看服务的pid,再使用 jstack pid号 > info.txt 查看堆栈信息。
最后排查到是因为线程执行到 FTPClien.listfiles() 就会出现卡住的现象,且线程处于RUNNABLE状态。
现象总结:
ftp的连接阻塞,导致线程一直处于RUNNING状态,线程资源无法被回收,进而导致线程池需要一直创建新线程处理新的任务,直到线程池爆掉。
解决办法(2种方法):
那么问题就一目了然了,是FTPClien出现了问题,经过各方查找,由于设置了不合理的配置,会出现ftp假死的现象。下面给出以下解决办法
方法1(推荐).设置ftpClient为被动模式,并设置超时时间:
ftpClient.enterLocalPassiveMode(); // 使用被动模式
ftpClient.setConnectTimeout() 用于设置终端 Socket 与 FTP 服务器建立连接这个过程的超时时间。
ftpClient.setDefaultTimeout() 用于设置终端的传输控制命令的 Socket 的 SoTimeout,即针对传输控制命令的 Socket 的输入流做读取操作时每次陷入阻塞的超时时间。
ftpClient.setSoTimeout() 作用跟上个方法一样,区别仅在于该方法设置的超时会覆盖掉上个方法设置的值。
ftpClient.setDataTimeout() 用于设置终端的传输数据的 Socket 的 Sotimeout,即针对传输文件数据的 Socket 的输入流做读取操作时每次陷入阻塞的超时时间。
ftpClient.setControlKeepAliveTimeout() 用于设置当处于传输数据过程中,按指定的时间阈值定期让传输控制命令的 Socket 发送一个无操作命令 NOOP 给服务器,让它 keep alive。
ftpClient.setControlKeepAliveReplyTimeout():只有调用上个方法后,该方法才能生效,用于设置在传输数据这个过程中,暂时替换掉传输控制命令的 Socket 的 SoTimeout,传输过程结束恢复这个 Socket 原本的 SoTimeout。
方法2.或者自定义程序超时机制。实现方式有很多,比如时间轮算法、手动计算时长。下面给出使用Future的超时机制的参考代码。
try {
Future < String > future = exec.submit(call);
result = future.get(1000 * 3, TimeUnit.MILLISECONDS); //任务处理超时时间设为 3 秒
} catch(TimeoutException ex) {
System.out.println("调用接口,处理超时......");
ex.printStackTrace();
} catch(Exception e) {
System.out.println("调用接口,处理失败......");
e.printStackTrace();
}