Ответ 1
ОБНОВЛЕНИЕ. В ответ на проблемы, которые я создал на GitHub, были обновлены SDK AWS. Я не уверен, как изменилась ситуация. Вторая часть этого ответа (критика getObject
) скорее всего (надеюсь)?) Неправильно.
S3 предназначен для отказа, и он часто не работает.
К счастью, AWS SDK для Java имеет встроенные средства для повторных запросов. К сожалению, они не охватывают случай SocketExceptions при загрузке объектов S3 (они делают работают при загрузке и выполнении других операций). Таким образом, необходим код, аналогичный этому в вопросе (см. Ниже).
Когда механизм работает по своему желанию, вы все равно увидите сообщения в своем журнале. Вы можете скрыть их путем фильтрации событий INFO
журнала событий с com.amazonaws.http.AmazonHttpClient
. (AWS SDK использует журнал регистрации Apache.)
В зависимости от вашего сетевого подключения и работоспособности серверов Amazon механизм повтора может завершиться неудачей. Как указано lvlv, способ настройки соответствующих параметров осуществляется через ClientConfiguration. Параметр, который я предлагаю изменить, - это количество повторений, которое по умолчанию 3
. Другие вещи, которые вы можете попробовать, - это увеличение или уменьшение времени ожидания подключения и сокета (по умолчанию 50 секунд, что не слишком долго, вероятно, слишком долго, учитывая тот факт, что вы часто будете таймаутом независимо от того, что) и используя TCP KeepAlive (по умолчанию выкл).
ClientConfiguration cc = new ClientConfiguration()
.withMaxErrorRetry (10)
.withConnectionTimeout (10_000)
.withSocketTimeout (10_000)
.withTcpKeepAlive (true);
AmazonS3 s3Client = new AmazonS3Client (credentials, cc);
Механизм повтора может быть даже превышен, установив a RetryPolicy
(опять же, в ClientConfiguration
). Его наиболее интересным элементом является RetryCondition
, который по умолчанию:
проверяет различные условия в следующем порядке:
- Повторить попытки исключения из AmazonClientException, вызванные IOException;
- Повторить на исключениях AmazonServiceException, которые являются либо 500 внутренними серверами ошибки, 503 ошибки обслуживания недоступны, ошибки управления сервисом или ошибки перекоса часов.
См. SDKDefaultRetryCondition javadoc и источник
Половинные попытки повторной попытки скрыты в SDK
Что встроенный механизм (который используется во всем AWS SDK) не обрабатывает, это чтение данных объекта S3.
AmazonS3Client использует свой собственный механизм повтора, если вы вызываете AmazonS3.getObject (GetObjectRequest getObjectRequest, File destinationFile)
. Механизм находится внутри ServiceUtils.retryableDownloadS3ObjectToFile
(source), который использует неоптимальное поведение проводного повторного использования (он будет только повторять один раз и никогда на SocketException!). Весь код в ServiceUtils
кажется плохо спроектированным (issue).
Я использую код, похожий на:
public void
read(String key, Path path) throws StorageException
{
GetObjectRequest request = new GetObjectRequest (bucket, key);
for (int retries = 5; retries > 0; retries--)
try (S3Object s3Object = s3.getObject (request))
{
if (s3Object == null)
return; // occurs if we set GetObjectRequest constraints that aren't satisfied
try (OutputStream outputStream = Files.newOutputStream (path, WRITE, CREATE, TRUNCATE_EXISTING))
{
byte[] buffer = new byte [16_384];
int bytesRead;
while ((bytesRead = s3Object.getObjectContent().read (buffer)) > -1) {
outputStream.write (buffer, 0, bytesRead);
}
}
catch (SocketException | SocketTimeoutException e)
{
// We retry exceptions that happen during the actual download
// Errors that happen earlier are retried by AmazonHttpClient
try { Thread.sleep (1000); } catch (InterruptedException i) { throw new StorageException (i); }
log.log (Level.INFO, "Retrying...", e);
continue;
}
catch (IOException e)
{
// There must have been a filesystem problem
// We call `abort` to save bandwidth
s3Object.getObjectContent().abort();
throw new StorageException (e);
}
return; // Success
}
catch (AmazonClientException | IOException e)
{
// Either we couldn't connect to S3
// or AmazonHttpClient ran out of retries
// or s3Object.close() threw an exception
throw new StorageException (e);
}
throw new StorageException ("Ran out of retries.");
}