【发布时间】:2021-09-25 02:40:19
【问题描述】:
我们有一个 java 7 代码库,我们在其中使用 Apache commons vfs2 v2.2,它使用 JSch-0.1.54 作为 sftp 提供程序。
现在,用例是通过 sftp 将文件传输到远程主机。但是,文件上传过程时不时会卡住。在获取应用程序的线程转储后,我们发现两个线程(t1,将数据发送到远程 sftp 和 t2,从 sftp 接收数据)都处于等待状态永远的状态。下面是线程转储快照。
JSch 会话线程:
"Connect thread remote.ftp.com session" daemon prio=10 tid=0x00007f99cc243000 nid=0x144 in Object.wait() [0x00007f9985606000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.io.PipedInputStream.awaitSpace(PipedInputStream.java:273)
at java.io.PipedInputStream.receive(PipedInputStream.java:231)
- locked <0x000000043eda02d8> (a com.jcraft.jsch.Channel$MyPipedInputStream)
at java.io.PipedOutputStream.write(PipedOutputStream.java:149)
at com.jcraft.jsch.IO.put(IO.java:64)
at com.jcraft.jsch.Channel.write(Channel.java:438)
at com.jcraft.jsch.Session.run(Session.java:1459)
at java.lang.Thread.run(Thread.java:748)
用于上传文件数据的应用线程。
"akka.actor.default-dispatcher-19" prio=10 tid=0x00007f99d4012000 nid=0xea in Object.wait() [0x00007f9988785000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at com.jcraft.jsch.Session.write(Session.java:1269)
- locked <0x0000000440a61b48> (a com.jcraft.jsch.ChannelSftp)
at com.jcraft.jsch.ChannelSftp.sendWRITE(ChannelSftp.java:2646)
at com.jcraft.jsch.ChannelSftp.access$100(ChannelSftp.java:36)
at com.jcraft.jsch.ChannelSftp$1.write(ChannelSftp.java:806)
at java.io.BufferedOutputStream.write(BufferedOutputStream.java:122)
- locked <0x0000000440aab240> (a org.apache.commons.vfs2.provider.sftp.SftpFileObject$SftpOutputStream)
at org.apache.commons.vfs2.util.MonitorOutputStream.write(MonitorOutputStream.java:104)
- locked <0x0000000440aab240> (a org.apache.commons.vfs2.provider.sftp.SftpFileObject$SftpOutputStream)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.write(BufferedOutputStream.java:126)
- locked <0x0000000440aac278> (a org.apache.commons.vfs2.provider.DefaultFileContent$FileContentOutputStream)
at org.apache.commons.vfs2.util.MonitorOutputStream.write(MonitorOutputStream.java:104)
- locked <0x0000000440aac278> (a org.apache.commons.vfs2.provider.DefaultFileContent$FileContentOutputStream)
at org.apache.commons.vfs2.provider.DefaultFileContent.write(DefaultFileContent.java:741)
at org.apache.commons.vfs2.provider.DefaultFileContent.write(DefaultFileContent.java:720)
at org.apache.commons.vfs2.provider.DefaultFileContent.write(DefaultFileContent.java:691)
at org.apache.commons.vfs2.provider.DefaultFileContent.write(DefaultFileContent.java:707)
at org.apache.commons.vfs2.FileUtil.copyContent(FileUtil.java:78)
at org.apache.commons.vfs2.provider.AbstractFileObject.copyFrom(AbstractFileObject.java:289)
在查看了 Jsch 库的codebase 之后,这就是我觉得正在发生的事情。
- 应用程序线程正在以 4KB 的块上传文件数据。
- 每次块写入后,应用程序都会读取输入套接字以获取任何确认,直到输入套接字缓冲区为空。
- 在块写入期间,它会检查 ssh 窗口大小。如果它小于有效负载大小,我们会等到远程服务器调整它的大小。(这是我的应用程序线程永远等待的地方)这个调整大小的消息由 ssh 会话线程监听,同样是在通道对象处更新,之后应用程序线程继续写入。
- 在单独的线程中,会话正在侦听来自远程服务器的传入数据。根据接收到的消息,它会采取相关操作,例如调整频道窗口大小,将确认消息传递给频道(读取应用程序)以供使用。
- 现在,当一条消息到达供通道消费时,它被写入链接到 PipedInputStream 的 PipedOutStream。此输入流由应用程序线程读取以获取确认消息。如果应用程序线程未能读取任何消息,则 PipedOutputstream 的缓冲区已满,因此它进入等待状态,直到应用程序读取一些数据。 (这是会话线程永远等待的地方)
现在,两个线程相互依赖。因此,这是一种僵局。
此外,我检查了运行此应用程序的 linux 机器,套接字的 RecQ 一直在建立。这意味着,socket 还活着,远程服务器不时地发送 32KB 的数据包。
sudo netstat -anpt | grep 19321
tcp6 0 0 10.14.233.97:59594 64.233.167.130:19321 TIME_WAIT -
tcp6 58256 0 10.14.233.97:58214 64.233.167.130:19321 ESTABLISHED 460144/java
tcp6 499888 0 10.14.233.97:58422 64.233.167.130:19321 ESTABLISHED 460144/java
tcp6 0 0 10.14.233.97:59622 64.233.167.130:19321 ESTABLISHED 460144/java
tcp6 0 0 10.14.233.97:59608 64.233.167.130:19321 TIME_WAIT -
tcp6 74672 0 10.14.233.97:56656 64.233.167.130:19321 ESTABLISHED 460144/java
tcp6 92688 0 10.14.233.97:56842 64.233.167.130:19321 ESTABLISHED 460144/java
现在,我有 2 个问题。
- 为什么会这样?这种情况很少发生,但一旦发生,就会经常发生。
- 如何解决此问题?
P.S. 我知道 Apache commons vfs 库的多线程问题,因此,所有 ssh 会话都在单独的线程中运行。因此,它看起来不像是图书馆的问题。
【问题讨论】:
-
那里正在进行大量分析(其中一些是矛盾的,IMO,围绕线程问题)并且几乎没有数据。请生成runnable demo 并发布代码
-
@g00se 不幸的是,这个错误非常随机,因此我们无法重现它。它是一个生产错误。即使在极端负载测试之后,我们也没有在较低的环境中遇到这个问题。这可能是因为,在较低的环境中,我们使用内部远程服务器。在生产中,我们有 3rd 方远程服务器。无论如何,将分享示例客户端代码。
-
您是否消除了网络本身作为问题的根源?
-
Ys 网络似乎很好,因为还有其他线程正在成功上传文件。有一个批处理作业正在单个线程中上传文件。另外,我可以在 netstat 中看到,recq 正在不断构建。
标签: java multithreading ssh jsch apache-commons-vfs