【问题标题】:Amazon AWS Client Timeout with lots of requests有大量请求的 Amazon AWS 客户端超时
【发布时间】:2019-06-13 02:36:12
【问题描述】:

我们有一个 Spring Boot 应用程序,可将多媒体文件(最大 100 MB)存储在与 S3 兼容的云存储中。应用程序通过 REST 调用或 AMQP 消息代理 (RabbitMQ) 接收这些文件。

通常系统上的负载是适中的,因此完全没有问题。但是,当系统负载很重时,我们会遇到访问 S3 的问题。目前,我们正在使用随机分配给调用进程的 10 个 AmazonS3Client 池来解决此问题。这实际上改善了问题,但并没有解决问题。当负载太高(意味着大量的写入和读取操作)时,我们会遇到这种异常:

 com.amazonaws.AmazonClientException:无法执行 HTTP 请求:连接超时
    在 com.amazonaws.http.AmazonHttpClient.executeHelper(AmazonHttpClient.java:299)
    在 com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:170)
    在 com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:2648)
    在 com.amazonaws.services.s3.AmazonS3Client.putObject(AmazonS3Client.java:1049)
    在 com.amazonaws.services.s3.AmazonS3Client.putObject(AmazonS3Client.java:924)

我们使用的是 1.3.8 版本的 aws-java-sdk,由于较新版本中的区域设置,无法轻松更新到较新版本。签名算法会阻止我们在最新版本中正确访问我们的存储桶。

实现如下:

初始化(在构造函数级别):

ClientConfiguration clientConfiguration = new ClientConfiguration();
clientConfiguration.setConnectionTimeout(AWS_CONNECTION_TIMEOUT);
clientConfiguration.setMaxConnections(AWS_MAX_CONNECTIONS);
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);

for (int i = 0; i < AWS_MAX_CLIENTS; i++) {
     s3[i] = new AmazonS3Client(credentials, clientConfiguration);
     s3[i].setEndpoint(endpoint);       
}

放:

int i = getRandomClient();
s3[i].putObject(bucketName, key, file);

获取:

ReadableByteChannel channel;
try {
    int i = getRandomClient();
    S3Object object = s3[i].getObject(bucketName, addPrefix(fileId, prefix));
    S3ObjectInputStream stream = object.getObjectContent();
    channel = Channels.newChannel(stream);

    File file = File.createTempFile(fileId, "");
    try (WritableByteChannel outChannel = Channels.newChannel(new FileOutputStream(file))) {
        ByteBuffer buffer = ByteBuffer.allocate(8192);
        int read;
        while ((read = channel.read(buffer)) > 0) {
            buffer.rewind();
            buffer.limit(read);
            while (read > 0) {
                read -= outChannel.write(buffer);
            }
            buffer.clear();
        }
        IOUtils.closeQuietly(stream);
        return file;
    }        
}
catch (AmazonClientException e) {
    if (!isMissingKey(e)) {
        throw new IOException(e);
    }
}
finally {
    if (channel != null) {
        channel.close();
    }
}

很明显,有限数量的连接和客户端是瓶颈。有很多方法可以调整实现以使其正常工作。我们当然可以限制收听消息代理的消费者数量。我们还可以增加 aws 客户端的超时、数量和连接,或者限制服务层的吞吐量。但是,我们正在寻找一种更复杂的方法来处理这里的事情。

有什么方法可以判断指定的客户端当前是否可以使用或是否有太多打开的连接?有什么办法可以让客户端等待下一个空闲连接?

【问题讨论】:

  • 您创建客户端数组是否有原因? AWS 客户端各自管理自己的连接池。简单地增加AWS_MAX_CONNECTIONS 并使用单个客户端似乎是更简单的解决方案。然后,您可以在 CloudWatch 上启用客户端资源指标来监控您的连接池使用情况并微调所需的数量。
  • 这基本上是我偶然发现的最后手段:stackoverflow.com/questions/16354966/… 问题是我们使用的是 ceph 存储并且没有可用的 CloudWatch,所以我们很难真正看到连接池在做什么。此外,如果我们只是调整池,我们仍然无法完全解决问题。如果负载增加,我们将再次面临问题。

标签: java spring-boot amazon-s3 aws-sdk


【解决方案1】:

增加客户端数量与增加单个客户端的连接池大小没有什么不同,除了现在您必须担心使用getRandomClient() 对您的客户端数组进行伪“负载平衡”。此外,创建多个客户端和维护不必要数量的连接池会产生大量开销。您正在尝试重新发明轮子。

您可以做的一件事是捕获超时期间抛出的异常,如下所示:

try {
    ... do s3 read/write ...
} catch (AmazonClientException ace) {
    if (ace.getCause() instanceof org.apache.http.conn.ConnectionPoolTimeoutException) {
        log.error("S3 connection pool timeout!");
    }
}

使用它来帮助调整您的连接池大小。基本上只是继续扩大它,直到这不再是你的瓶颈。

【讨论】:

    【解决方案2】:

    如果由于大量 HTTP 错误(主要是超时)而执行此操作,也可能是您需要关闭 S3Objects。如果您不关闭它们,它们就会成为资源消耗,并且在向 S3 存储桶发送请求时会导致此类错误。

    https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/model/S3Object.html#close--

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-01
      • 2017-04-09
      • 2019-03-21
      • 2016-07-21
      相关资源
      最近更新 更多