【发布时间】: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