我们有一个Spring Boot应用程序,可以在S3兼容的云存储中存储多媒体文件(最大100 MB)。应用程序通过REST调用或AMQP消息代理(RabbitMQ)接收这些文件。
通常系统负载适中,因此根本没有问题。但是,当系统负载很重时,我们会遇到访问S3的问题。目前,我们正在解决此问题,即使用随机分配给调用进程的10个AmazonS3Clients池。这实际上改善了问题,但没有解决问题。当负载太高(意味着大量的写入和读取操作)时,我们会遇到这种异常:
com.amazonaws.AmazonClientException: Unable to execute HTTP request: connect timed out at com.amazonaws.http.AmazonHttpClient.executeHelper(AmazonHttpClient.java:299) at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:170) at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:2648) at com.amazonaws.services.s3.AmazonS3Client.putObject(AmazonS3Client.java:1049) at com.amazonaws.services.s3.AmazonS3Client.putObject(AmazonS3Client.java:924)
我们使用的是aws-java-sdk的1.3.8版本,由于新版本中的区域设置,无法轻松更新到新版本。签名算法阻止我们在最新版本中正确访问我们的存储桶。
实施如下:
初始化(在构造函数级别):
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客户端的超时,数量和连接,或限制服务层的吞吐量。然而,我们正在寻找一种更复杂的方法来处理这些事情。
有没有办法判断目前是否可以使用指定的客户端或是否有太多的开放连接?有没有办法可以让客户等待下一次免费连接?
答案 0 :(得分: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!");
}
}
使用此选项可帮助调整连接池大小。基本上只是让它变得更大,直到这不再是你的瓶颈。
答案 1 :(得分:0)
如果由于大量HTTP错误(主要是超时)而执行此操作,则可能还需要关闭S3Object。如果不关闭它们,它们将成为资源消耗,并且在向S3存储桶发送请求时会导致此类错误。