S3 Java客户端经常出现“Content-Length delimited message body异常结束”或“java.net.SocketException套接字关闭”错误。

19

我有一个应用程序在S3上执行许多工作,主要是从中下载文件。我看到很多这样的错误,并且想知道这是我的代码的问题还是服务真的像这样不可靠。

我用来读取S3对象流的代码如下:

public static final void write(InputStream stream, OutputStream output) {

  byte[] buffer = new byte[1024];

  int read = -1;

  try {

    while ((read = stream.read(buffer)) != -1) {
      output.write(buffer, 0, read);
    }

    stream.close();
    output.flush();
    output.close();
  } catch (IOException e) {
    throw new RuntimeException(e);
  }

}

这个OutputStream是一个新的BufferedOutputStream( new FileOutputStream( file ) )。我正在使用最新版本的Amazon S3 Java客户端,并且在放弃之前会尝试四次重试此调用。因此,在再试了4次后它仍然失败。

如果有任何提示或建议,欢迎分享。


这是否发生在所有(或大多数)文件中,随机的文件中,还是仅在一组有限且可重现的文件中?您是否在第一次上传之前设置任何元数据?我曾看到过某些文件的元数据(或缺少元数据)可能导致某些奇怪的问题。如果您还没有尝试过,值得一试。 - Rafael Steil
大多数是随机文件,我们不使用任何元数据 :( - Maurício Linhares
只是猜测。你确定那些随机文件已经正确上传到S3了吗?尝试通过GET请求或其他工具下载这些文件。 - shashankaholic
最终它会起作用,这是我的主要问题,我必须重试很多次才能做到。 - Maurício Linhares
6个回答

28

我刚刚成功地解决了一个非常类似的问题。在我的情况下,我得到的异常是相同的;它发生在较大的文件上,但对于小文件却没有发生,并且在通过调试器单步执行代码时根本没有发生。

问题的根源是AmazonS3Client对象在下载中间被垃圾回收了,这导致网络连接断开。这是因为我每次调用加载文件时都会构造一个新的AmazonS3Client对象,而首选的用法是创建一个持久的客户端对象,该对象在跨多个调用过程中保持存活状态,或者至少在整个下载期间保证存在。所以,简单的解决方法是确保保留对AmazonS3Client的引用,以免被垃圾回收掉。

帮助我解决问题的AWS论坛链接在这里:https://forums.aws.amazon.com/thread.jspa?threadID=83326


将客户端对象保留在方法内部就解决了问题,哎呀。谢谢Steve! - Maurício Linhares
将S3Client保存在Spring Singleton Bean的字段中并不能解决问题。仍然存在此错误。 - bvn13

4
网络在某种情况下关闭了连接,导致客户端没有获取全部数据。这是发生的情况。HTTP请求中的一部分是内容长度。您的代码正在获取头文件,告诉客户端,这里有数据,并且数据量为多少...然后连接在客户端读取所有数据之前断开了。因此,代码会出现异常。建议检查操作系统/网络/JVM连接超时设置(虚拟机通常从操作系统继承此情况)。关键是找出网络问题的原因。是您的计算机级别设置说不要再等待包了吗?还是您正在使用非阻塞读取,在代码中有个超时设置,它会说,“嘿,自从我等待的时间以来,没有从服务器获得任何数据,所以我会断开连接并抛出异常”等等。最好的办法是低级别地窥探数据包流量并向后跟踪,以查看连接断开发生在哪里,或者尝试提高您可以控制的软件、操作系统和JVM中的超时时间。

太好了,我们遇到这个问题已经有一段时间了,而且我们的负载均衡器也存在超时问题。出于某种原因,我没有想到这两个问题可能是相同的。 - Roee Gavirel

1

首先,如果您自己与亚马逊S3之间存在连接问题,则您的代码将完全正常运行。正如Michael Slade 指出的那样,应用标准连接级调试建议。

至于您的实际源代码,我注意到了一些应该注意的代码问题。直接在源代码中进行注释:

public static final void write(InputStream stream, OutputStream output) {

  byte[] buffer = new byte[1024]; // !! Abstract 1024 into a constant to make 
                                  //  this easier to configure and understand.

  int read = -1;

  try {

    while ((read = stream.read(buffer)) != -1) {
      output.write(buffer, 0, read);
    }

    stream.close(); // !! Unexpected side effects: closing of your passed in 
                    //  InputStream. This may have unexpected results if your
                    //  stream type supports reset, and currently carries no 
                    //  visible documentation.

    output.flush(); // !! Violation of RAII. Refactor this into a finally block, 
    output.close(); //  a la Reference 1 (below).

  } catch (IOException e) {
    throw new RuntimeException(e); // !! Possibly indicative of an outer 
                                   //   try-catch block for RuntimeException. 
                                   //   Consider keeping this as IOException.
  }
}

参考文献1

除此之外,代码本身看起来很好。在连接到不稳定的远程主机的情况下,IO异常应该是预期发生的情况,您最好的做法是制定一个合理的策略来缓存和重新连接这些情况。


0
  1. 尝试使用wireshark查看发生此问题时电线上发生了什么。

  2. 尝试暂时将S3替换为您自己的Web服务器,看看问题是否仍然存在。如果是,则是您的代码而不是S3。

随机性表明您的主机与某些S3主机之间存在网络问题。


0
也根据我的经验,S3可以关闭缓慢的连接。

如果可能的话,尝试将输入流存储到文件中,并通过浏览器使用POST上传到S3(将您的Java服务器代码替换为同一台机器上的浏览器,以查找问题是否在您的代码中)。 - Askar Kalykov

0

我建议你仔细检查最靠近客户端应用程序的网络设备。这个问题似乎是在你和服务之间有一些网络设备丢失了数据包。请查看问题首次出现的起点。是否有任何更改,例如路由器固件更新或交换机更换?

验证你的带宽使用量是否与从ISP购买的数量相符。在一天中是否有接近限制的时间?你能否获得带宽使用图表?看看过早终止是否与高带宽使用相关,特别是如果它接近某个已知限制。如果问题似乎只针对较小的文件,并且仅在大文件快要下载完成时才出现,请考虑从ISP购买更多带宽以解决问题。


这些是连接到S3机器的EC2机器,没有任何限制。但还是谢谢 :) - Maurício Linhares

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接