摘要:定时任务,线程池,ftp文件传输协议
背景:最近在公司做一个项目需要给政府传输数据,数据是写入文件的,然后定时任务进行将文件发送给政府。
起因:线上发布之后发现定时任务执行了几次之后不在执行了,执行的几次都是正常的没有报错。
跟踪:查询线上日志,定时任务不在执行之后并无异常抛出,初步定位定时任务线程阻塞了。
既然猜测是线程阻塞导致的那么就说干就干
1.使用jps命令查询一下线上机器的java任务
jps是jdk提供的一个查看当前java进程的小工具可以看做是JavaVirtual Machine Process Status Tool的缩写
得到应用的pid:5034
2.使用jstack命令将当前应用的线程信息打印到/tmp/log.txt文件中
jstack -l5034>> /tmp/log.txtjstack 是JVM自带的Java堆栈跟踪工具用于打印出给定的java进程ID、core file、远程调试服务的Java堆栈信息.
3.得到线程堆栈信息后,使用grep命令定位具体线程
"pool-10-thread-1"#593 prio=5 os_prio=0 tid=0x00007f0895b29800 nid=0x77d runnable [0x00007f084023c000]java.lang.Thread.State:RUNNABLEatjava.net.SocketInputStream.socketRead0(Native Method)atjava.net.SocketInputStream.socketRead(SocketInputStream.java:116)atjava.net.SocketInputStream.read(SocketInputStream.java:171)atjava.net.SocketInputStream.read(SocketInputStream.java:141)atsun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)atsun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)atsun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)-locked <0x0000000643f03330> (a java.io.InputStreamReader)atjava.io.InputStreamReader.read(InputStreamReader.java:184)atjava.io.BufferedReader.fill(BufferedReader.java:161)atjava.io.BufferedReader.read(BufferedReader.java:182)-locked <0x0000000643f03330> (a java.io.InputStreamReader)atorg.apache.commons.net.io.CRLFLineReader.readLine(CRLFLineReader.java:58)-locked <0x0000000643f03330> (a java.io.InputStreamReader)atorg.apache.commons.net.ftp.FTP.__getReply(FTP.java:310)atorg.apache.commons.net.ftp.FTP.__getReply(FTP.java:290)atorg.apache.commons.net.ftp.FTP.sendCommand(FTP.java:479)atorg.apache.commons.net.ftp.FTP.sendCommand(FTP.java:552)atorg.apache.commons.net.ftp.FTP.sendCommand(FTP.java:601)atorg.apache.commons.net.ftp.FTP.pasv(FTP.java:952)atorg.apache.commons.net.ftp.FTPClient._openDataConnection_(FTPClient.java:755)atorg.apache.commons.net.ftp.FTPClient._storeFile(FTPClient.java:565)atorg.apache.commons.net.ftp.FTPClient.__storeFile(FTPClient.java:557)atorg.apache.commons.net.ftp.FTPClient.storeFile(FTPClient.java:1795)atcom.our.my.dep.schedule.job.tianjin.TianJinFilePushSchedule.send(TianJinFilePushSchedule.java:161)atsun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)atjava.lang.reflect.Method.invoke(Method.java:498)atorg.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)atorg.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)atorg.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)atjava.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)atjava.util.concurrent.FutureTask.run(FutureTask.java:266)atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)atjava.lang.Thread.run(Thread.java:748)
第27行显示此时线程处在发送文件过程,下面是具体的传输代码
161行正是ftp数据请求中的代码。
4.为了进一步验证线程是否是堵塞 我在2分钟后又导出了一份线程堆栈信息
"pool-10-thread-1"#593 prio=5 os_prio=0 tid=0x00007f0895b29800 nid=0x77d runnable [0x00007f084023c000]java.lang.Thread.State:RUNNABLEatjava.net.SocketInputStream.socketRead0(Native Method)atjava.net.SocketInputStream.socketRead(SocketInputStream.java:116)atjava.net.SocketInputStream.read(SocketInputStream.java:171)atjava.net.SocketInputStream.read(SocketInputStream.java:141)atsun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)atsun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)atsun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)-locked <0x0000000643f03330> (a java.io.InputStreamReader)atjava.io.InputStreamReader.read(InputStreamReader.java:184)atjava.io.BufferedReader.fill(BufferedReader.java:161)atjava.io.BufferedReader.read(BufferedReader.java:182)-locked <0x0000000643f03330> (a java.io.InputStreamReader)atorg.apache.commons.net.io.CRLFLineReader.readLine(CRLFLineReader.java:58)-locked <0x0000000643f03330> (a java.io.InputStreamReader)atorg.apache.commons.net.ftp.FTP.__getReply(FTP.java:310)atorg.apache.commons.net.ftp.FTP.__getReply(FTP.java:290)atorg.apache.commons.net.ftp.FTP.sendCommand(FTP.java:479)atorg.apache.commons.net.ftp.FTP.sendCommand(FTP.java:552)atorg.apache.commons.net.ftp.FTP.sendCommand(FTP.java:601)atorg.apache.commons.net.ftp.FTP.pasv(FTP.java:952)atorg.apache.commons.net.ftp.FTPClient._openDataConnection_(FTPClient.java:755)atorg.apache.commons.net.ftp.FTPClient._storeFile(FTPClient.java:565)atorg.apache.commons.net.ftp.FTPClient.__storeFile(FTPClient.java:557)atorg.apache.commons.net.ftp.FTPClient.storeFile(FTPClient.java:1795)atcom.our.my.dep.schedule.job.tianjin.TianJinFilePushSchedule.send(TianJinFilePushSchedule.java:161)atsun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)atjava.lang.reflect.Method.invoke(Method.java:498)atorg.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)atorg.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)atorg.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)atjava.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)atjava.util.concurrent.FutureTask.run(FutureTask.java:266)atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)atjava.lang.Thread.run(Thread.java:748)
同样的方法定位得到对应的线程,发现线程信息一模一样,由此可见线程的确是卡死在发送文件的过程了。
思考:既然问题已经定位到了,下面就是思考如何解决问题了
下面来看一下ftpClient初始化代码
privateFTPClientinitFtp(){try{FTPClient ftp =newFTPClient();// 设置连接超时(以毫秒为单位),该超时将传递给Socket对象的connect()方法。 ftp.setConnectTimeout(FTPConstants.FTP_CONNECT_TIMEOUT);// 设置从数据连接读取时使用的超时(以毫秒为单位)。 打开数据连接后,将立即设置此超时。 ftp.setDataTimeout(FTPConstants.FTP_DATA_TIMEOUT); ftp.setControlEncoding(FTPConstants.CHARSET_UTF_8); ftp.connect(host, port); ftp.login(username, password);intreplyCode = ftp.getReplyCode();// 确定答复代码是否为肯定的完成答复if(!FTPReply.isPositiveCompletion(replyCode)) { ftp.disconnect();log.error("登录失败:{}", replyCode);returnnull; }// 文件类型为二进制 ftp.setFileType(FTPClient.BINARY_FILE_TYPE);// 被动模式 每次数据连接之前,ftp client告诉ftp server开通一个端口来传输数据 防止假卡死 ftp.enterLocalPassiveMode();// 创建目录切换目录(文件接收方)ftp.makeDirectory("/test");ftp.changeWorkingDirectory("/test");returnftp;}catch(Exception e) {log.error("ftp链接登录失败:{}", e.getMessage(), e);returnnull; } }
通过代码我们发现连接超时是设置了的啊,不应该在这里卡死的呀。
这里看一下官方解释:
FtpClient API
这里有个很重要的参数:
setSoTimeout()读取数据时阻塞链路的超时时间。
注意这里所说的内容:
如果超时到期,则尽管Socket仍然有效,但将引发java.net.SocketTimeoutException 。必须先启用该选项,然后才能执行阻止操作。超时时间必须> 0 。零超时被解释为无限超时。
也就是说如果不设置,可能会在网络波动时阻塞,至此无限时阻塞在此。。。
也就是连接成功之后需要设置soTimeOut
于是在此基础中我进行了改造:
文件传输时:
提取一下几个比较重要的参数
// 设置连接超时(以毫秒为单位),该超时将传递给Socket对象的connect()方法ftp.setConnectTimeout(FTPConstants.FTP_CONNECT_TIMEOUT)// 设置从数据连接读取时使用的超时(以毫秒为单位)。打开数据连接后,将立即设置此超时ftp.setDataTimeout(FTPConstants.FTP_DATA_TIMEOUT);// 连接ftp服务器ftp.connect(host, port);// 登陆ftp服务器ftp.login(username, password);// 设置字符集UTF-8ftp.setControlEncoding(FTPConstants.CHARSET_UTF_8);// 文件类型为二进制ftp.setFileType(FTPClient.BINARY_FILE_TYPE);// 被动模式 每次数据连接之前,ftp client告诉ftp server开通一个端口来传输数据 防止假卡死ftp.setControlKeepAliveTimeout(FTPConstants.CONTROL_KEEP_ALIVE_TIMEOUT);// 以毫秒为单位设置当前打开的连接的超时时间。仅在connect()打开connect()后才调用此方法。ftp.setSoTimeout(10000);
至此上线后不再出现阻塞现象。