AmazonS3:收到警告:S3AbortableInputStream:未从S3ObjectInputStream读取所有字节,中止HTTP连接。

39

以下是我收到的警告信息:

S3AbortableInputStream:未能从S3ObjectInputStream读取所有字节,中止HTTP连接。这可能是一个错误,并可能导致次优行为。通过分段GET请求仅请求您需要的字节或在使用后排除输入流,可以避免这种情况。

我尝试使用try-with-resources,但似乎无法通过此方法关闭S3ObjectInputStream。

 try (S3Object s3object = s3Client.getObject(new GetObjectRequest(bucket, key));
      S3ObjectInputStream s3ObjectInputStream = s3object.getObjectContent();
      BufferedReader reader = new BufferedReader(new InputStreamReader(s3ObjectInputStream, StandardCharsets.UTF_8));
    ){
  //some code here blah blah blah
 }

我还尝试了下面的代码并明确关闭,但也没有用:

S3Object s3object = s3Client.getObject(new GetObjectRequest(bucket, key));
S3ObjectInputStream s3ObjectInputStream = s3object.getObjectContent();

try (BufferedReader reader = new BufferedReader(new InputStreamReader(s3ObjectInputStream, StandardCharsets.UTF_8));
){
     //some code here blah blah
     s3ObjectInputStream.close();
     s3object.close();
}

任何帮助都将不胜感激。

附:我只从S3读取文件的两行数据,而该文件还有更多数据。

6个回答

34

通过其他渠道得到了答案,现在分享给大家:

警告提示表示您调用了 close(),但并未将整个文件读取完毕。这是有问题的,因为 S3 仍在尝试发送数据,而您正在使连接处于不稳定状态。

这里有两个选项:

  1. 从输入流中读取剩余的数据,以便可以重用连接。
  2. 调用 s3ObjectInputStream.abort() 关闭连接而不读取数据。连接将不会被重用,因此下一个请求会受到性能影响,需要重新创建连接。如果读取剩余文件需要很长时间,则可能值得付出这个代价。

2
“连接可以被重复使用”是什么意思?我认为每个S3 GET请求都会创建一个HTTP连接。 - ares
嗨,Chirag,你能详细解释一下第1点吗(从输入流中读取剩余的数据,以便可以重用连接)?我的代码是这样的:S3ObjectInputStream targetStream = confige.getObjectContent(); XSSFWorkbook excelf = new XSSFWorkbook(targetStream); 不确定如何消除此警告。 - Cocu_1012
2
@ares SDK在后台维护一个连接池,可以重复使用连接以提高性能。 - Sherms

6

按照Chirag Sejpal的第一种选项,我使用以下语句来清除S3AbortableInputStream以确保连接可以被重用:

com.amazonaws.util.IOUtils.drainInputStream(s3ObjectInputStream);
 

4

我遇到了同样的问题,下面这个类帮助了我

@Data
@AllArgsConstructor
public class S3ObjectClosable implements Closeable {
    private final S3Object s3Object;

    @Override
    public void close() throws IOException {
        s3Object.getObjectContent().abort();
        s3Object.close();
    }
}

现在你可以无需警告地使用它

try (final var s3ObjectClosable = new S3ObjectClosable(s3Client.getObject(bucket, key))) {

//same code

}


等等,为什么要使用这个,S3Object已经是可关闭的了。https://aws.amazon.com/blogs/developer/closeable-s3objects/ - Suketu Bhuta

2
为了给Chirag Sejpal的回答(关于选项#1的详细说明)增加一个示例,可以使用以下内容来读取输入流中剩余的数据,然后再关闭它:最初的回答
S3Object s3object = s3Client.getObject(new GetObjectRequest(bucket, key));

try (S3ObjectInputStream s3ObjectInputStream = s3object.getObjectContent()) {
  try {
    // Read from stream as necessary
  } catch (Exception e) {
    // Handle exceptions as necessary
  } finally {
    while (s3ObjectInputStream != null && s3ObjectInputStream.read() != -1) {
      // Read the rest of the stream
    }
  }

  // The stream will be closed automatically by the try-with-resources statement
}

如果在“//根据需要从流中读取”部分抛出异常,我认为这不会起作用。 - sworisbreathing
1
@sworisbreathing 我已更新示例,即使在“//根据需要从流中读取”部分发生异常,也会读取其余的流。 - DPG
我尝试了你的方法,但它显示了 java.io.IOException: Attempted read on closed stream - Dylan
1
这种_try-with-resources_代码风格不起作用,因为资源将在执行catchfinally块之前关闭。因此,要使该代码正常工作,您必须切换回旧的样式try/catch/finally块,并在finally块中手动关闭。 - Jacek Prucia

0

我遇到了同样的错误。

正如其他人指出的那样,lambda中的/tmp空间仅限于512 MB。 如果lambda上下文被重用以进行新的调用,则/tmp空间已经占用一半。

因此,当读取S3对象并将所有文件写入/tmp目录(就像我所做的那样)时, 我在中途就耗尽了磁盘空间。 Lambda退出并显示错误,但未读取S3ObjectInputStream中的所有字节

因此,需要记住两件事:

1)如果第一次执行引起问题,请节约使用/tmp空间。 我们只有512 MB

2)如果第二次执行引起问题,则可以通过解决根本问题来解决此问题。 不可能删除/tmp文件夹。 因此,在执行完成后,请删除/tmp文件夹中的所有文件。

在Java中,以下是我所做的成功解决问题的方法。

public String handleRequest(Map < String, String > keyValuePairs, Context lambdaContext) {
  try {
    // All work here
  } catch (Exception e) {
    logger.error("Error {}", e.toString());
    return "Error";
  } finally {
    deleteAllFilesInTmpDir();
  }
}
private void deleteAllFilesInTmpDir() {
  Path path = java.nio.file.Paths.get(File.separator, "tmp", File.separator);
  try {
    if (Files.exists(path)) {
      deleteDir(path.toFile());
      logger.info("Successfully cleaned up the tmp directory");
    }
  } catch (Exception ex) {
    logger.error("Unable to clean up the tmp directory");
  }
}
public void deleteDir(File dir) {
  File[] files = dir.listFiles();
  if (files != null) {
    for (final File file: files) {
      deleteDir(file);
    }
  }
  dir.delete();
}


0

这是我的解决方案。我正在使用Spring Boot 2.4.3。

创建一个Amazon S3客户端

AmazonS3 amazonS3Client = AmazonS3ClientBuilder
                .standard()
                .withRegion("your-region")
                .withCredentials(
                        new AWSStaticCredentialsProvider(
                            new BasicAWSCredentials("your-access-key", "your-secret-access-key")))
                .build();

创建一个亚马逊传输客户端
TransferManager transferManagerClient = TransferManagerBuilder.standard()
                .withS3Client(amazonS3Client)
                .build();

/tmp/{your-s3-key}中创建一个临时文件,以便我们可以将下载的文件放入其中。
File file = new File(System.getProperty("java.io.tmpdir"), "your-s3-key"); 

try {
    file.createNewFile(); // Create temporary file
} catch (IOException e) {
    e.printStackTrace();
}

file.mkdirs();  // Create the directory of the temporary file

然后,我们使用传输管理器客户端从S3下载文件。

// Note that in this line the s3 file downloaded has been transferred in to the temporary file that we created
Download download = transferManagerClient.download(
               new GetObjectRequest("your-s3-bucket-name", "your-s3-key"), file); 

// This line blocks the thread until the download is finished
download.waitForCompletion();  

现在,s3文件已成功传输到我们创建的临时文件中。我们可以获取临时文件的InputStream。

InputStream input = new DataInputStream(new FileInputStream(file));

由于不再需要这个临时文件,我们只需将其删除。

file.delete();

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