que estoy tratando de hacer uso de S3 tan grande capa de almacenamiento en caché de un 'infinito' para algunos 'bastante' documentos XML estáticos. Quiero asegurarme de que la aplicación cliente (que se ejecutará en miles de máquinas al mismo tiempo y solicitando los documentos XML muchas veces por hora) solo descargue estos documentos XML si su contenido ha cambiado desde la última vez que la aplicación cliente los descargó.
Enfoque
en Amazon S3, podemos utilizar ETAG HTTP para esto. Por defecto, los objetos de Amazon S3 tienen su ETAG configurado en el hash MD5 del objeto.
A continuación, podemos especificar el hash MD5 del documento XML dentro de la propiedad GetObjectRequest.ETagToNotMatch
. Esto garantiza que cuando realizamos la llamada AmazonS3.GetObject
(o en mi caso la versión asincrónica AmazonS3.BeginGetObject
y AmazonS3.EndGetObject
), si el documento solicitado tiene el mismo hash MD5 que el GetObjectRequest.ETagToNotMatch
, S3 devuelve automáticamente el código de estado HTTP 304 (NotModified)) y el contenido real del documento XML es no descargado.
Problema
El problema sin embargo es que al llamar AmazonS3.GetObject
(o es asíncrono equivalente) de la API de Amazon .Net realmente ve el código de estado HTTP 304 (NotModified) como un error y se vuelve a intentar la petición de obtención de tres veces y luego arroja un Amazon.S3.AmazonS3Exception: Maximum number of retry attempts reached : 3
.
Obviamente podría cambiar esta implementación para usar AmazonS3.GetObjectMetaData
y luego comparar el ETAG y usar AmazonS3.GetObject
si no coinciden, pero luego hay dos solicitudes a S3 en vez de una cuando el archivo está desactualizado. Preferiría tener una solicitud independientemente de si el documento XML debe descargarse o no.
¿Alguna idea? ¿Es esto un error o me estoy perdiendo algo? ¿Hay alguna forma en que pueda reducir el número de intentos a uno y 'procesar' la excepción (aunque me siento 'asco' por esta ruta).
Implementación
estoy usando el AWS SDK para .NET (versión 1.3.14).
Aquí es mi aplicación (reducido ligeramente para mantenerlo corto):
public Task<GetObjectResponse> DownloadString(string key, string etag = null) {
var request = new GetObjectRequest { Key = key, BucketName = Bucket };
if (etag != null) {
request.ETagToNotMatch = etag;
}
var task = Task<GetObjectResponse>.Factory.FromAsync(_s3Client.BeginGetObject, _s3Client.EndGetObject, request, null);
return task;
}
entonces yo llamo a esto como:
var dlTask = s3Manager.DownloadString("new one", "d7db7bc318d6eb9222d728747879b52e");
var responseTasks = new[]
{
dlTask.ContinueWith(x => _log.Error("Error downloading string.", x.Exception), TaskContinuationOptions.OnlyOnFaulted),
dlTask.ContinueWith(x => _log.Warn("Downloading string was cancelled."), TaskContinuationOptions.OnlyOnCanceled),
dlTask.ContinueWith(x => _log.Info(string.Format("Done with download: {0}", x.Result.ETag)), TaskContinuationOptions.OnlyOnRanToCompletion)
};
try {
Task.WaitAny(responseTasks);
} catch (AggregateException aex) {
_log.Error("Error while processing download string.", aex);
}
_log.Info("Exiting...");
Esto produce entonces esta salida del archivo de registro:
2011-10-11 13:21:20,376 [11] INFO Amazon.S3.AmazonS3Client - Received response for GetObject (id 2ee99002-d148-4572-b19b-29259534f48f) with status code NotModified in 00:00:01.6140812.
2011-10-11 13:21:20,385 [11] INFO Amazon.S3.AmazonS3Client - Request for GetObject is being redirect to https://s3.amazonaws.com/x/new%20one.
2011-10-11 13:21:20,789 [11] INFO Amazon.S3.AmazonS3Client - Retry number 1 for request GetObject.
2011-10-11 13:21:22,329 [11] INFO Amazon.S3.AmazonS3Client - Received response for GetObject (id 2ee99002-d148-4572-b19b-29259534f48f) with status code NotModified in 00:00:01.1400356.
2011-10-11 13:21:22,329 [11] INFO Amazon.S3.AmazonS3Client - Request for GetObject is being redirect to https://s3.amazonaws.com/x/new%20one.
2011-10-11 13:21:23,929 [11] INFO Amazon.S3.AmazonS3Client - Retry number 2 for request GetObject.
2011-10-11 13:21:26,508 [11] INFO Amazon.S3.AmazonS3Client - Received response for GetObject (id 2ee99002-d148-4572-b19b-29259534f48f) with status code NotModified in 00:00:00.9790314.
2011-10-11 13:21:26,508 [11] INFO Amazon.S3.AmazonS3Client - Request for GetObject is being redirect to https://s3.amazonaws.com/x/new%20one.
2011-10-11 13:21:32,908 [11] INFO Amazon.S3.AmazonS3Client - Retry number 3 for request GetObject.
2011-10-11 13:21:40,604 [11] INFO Amazon.S3.AmazonS3Client - Received response for GetObject (id 2ee99002-d148-4572-b19b-29259534f48f) with status code NotModified in 00:00:01.2950718.
2011-10-11 13:21:40,605 [11] INFO Amazon.S3.AmazonS3Client - Request for GetObject is being redirect to https://s3.amazonaws.com/x/new%20one.
2011-10-11 13:21:40,621 [11] ERROR Amazon.S3.AmazonS3Client - Error for GetResponse
Amazon.S3.AmazonS3Exception: Maximum number of retry attempts reached : 3
at Amazon.S3.AmazonS3Client.pauseOnRetry(Int32 retries, Int32 maxRetries, HttpStatusCode status, String requestAddr, WebHeaderCollection headers, Exception cause)
at Amazon.S3.AmazonS3Client.handleHttpResponse[T](S3Request userRequest, HttpWebRequest request, HttpWebResponse httpResponse, Int32 retries, TimeSpan lengthOfRequest, T& response, Exception& cause, HttpStatusCode& statusCode)
at Amazon.S3.AmazonS3Client.getResponseCallback[T](IAsyncResult result)
2011-10-11 13:21:40,635 [10] INFO Example.Program - Exiting...
2011-10-11 13:21:40,638 [19] ERROR Example.Program - Error downloading string.
System.AggregateException: One or more errors occurred. ---> Amazon.S3.AmazonS3Exception: Maximum number of retry attempts reached : 3
at Amazon.S3.AmazonS3Client.pauseOnRetry(Int32 retries, Int32 maxRetries, HttpStatusCode status, String requestAddr, WebHeaderCollection headers, Exception cause)
at Amazon.S3.AmazonS3Client.handleHttpResponse[T](S3Request userRequest, HttpWebRequest request, HttpWebResponse httpResponse, Int32 retries, TimeSpan lengthOfRequest, T& response, Exception& cause, HttpStatusCode& statusCode)
at Amazon.S3.AmazonS3Client.getResponseCallback[T](IAsyncResult result)
at Amazon.S3.AmazonS3Client.endOperation[T](IAsyncResult result)
at Amazon.S3.AmazonS3Client.EndGetObject(IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endMethod, TaskCompletionSource`1 tcs)
--- End of inner exception stack trace ---
---> (Inner Exception #0) Amazon.S3.AmazonS3Exception: Maximum number of retry attempts reached : 3
at Amazon.S3.AmazonS3Client.pauseOnRetry(Int32 retries, Int32 maxRetries, HttpStatusCode status, String requestAddr, WebHeaderCollection headers, Exception cause)
at Amazon.S3.AmazonS3Client.handleHttpResponse[T](S3Request userRequest, HttpWebRequest request, HttpWebResponse httpResponse, Int32 retries, TimeSpan lengthOfRequest, T& response, Exception& cause, HttpStatusCode& statusCode)
at Amazon.S3.AmazonS3Client.getResponseCallback[T](IAsyncResult result)
at Amazon.S3.AmazonS3Client.endOperation[T](IAsyncResult result)
at Amazon.S3.AmazonS3Client.EndGetObject(IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endMethod, TaskCompletionSource`1 tcs)<---
Nos encontramos con esto recientemente, y decidí que un parche rápido para AWS SDK sería la mejor respuesta ya que Amazon obviamente no solucionará esto pronto. https://github.com/skilitix/aws-sdk-net es nuestro fork que arregla el reintento en 304 y agrega un booleano 'NotModified' a' GetObjectResponse' - si dedico tiempo a descubrir una API más compatible con la parte posterior Le enviaré un parche a Amazon (mientras tanto, aceptamos los PRs: P). –
@SimonBuchan ¡Impresionante! Lo revisaré. – InvertedAcceleration
Ten en cuenta que no mantenemos el ritmo de subida, ya tenemos ~ 3 puntos de retraso, pero parecen ser nuevas API y deberían fusionarse automáticamente. –