从Azure表存储服务重试408超时

Iva*_*van 5 c# azure azure-storage azure-table-storage retrypolicy

我们正在使用Azure表存储,并且在执行InsertOrMerge操作时偶尔会遇到408超时.在这种情况下,我们想重试,但似乎没有遵循这些错误的重试策略.

这是我们用来处理表交互的类.GetFooEntityAsync方法尝试从表存储中检索实体.如果不能,则创建一个新的FooEntity并将其添加到表中(映射到FooTableEntity).

public class FooTableStorageBase
{
    private readonly string tableName;

    protected readonly CloudStorageAccount storageAccount;

    protected TableRequestOptions DefaultTableRequestOptions { get; }

    protected OperationContext DefaultOperationContext { get; }

    public CloudTable Table
    {
        get
        {
            return storageAccount.CreateCloudTableClient().GetTableReference(tableName);
        }
    }

    public FooTableStorage(string tableName)
    {
        if (String.IsNullOrWhiteSpace(tableName))
        {
            throw new ArgumentNullException(nameof(tableName));
        }

        this.tableName = tableName;

        storageAccount = CloudStorageAccount.Parse(ConnectionString);

        ServicePoint tableServicePoint = ServicePointManager.FindServicePoint(storageAccount.TableEndpoint);
        tableServicePoint.UseNagleAlgorithm = false;
        tableServicePoint.ConnectionLimit = 100; // Increasing connection limit from default of 2.

        DefaultTableRequestOptions = new TableRequestOptions()
        {
            PayloadFormat = TablePayloadFormat.JsonNoMetadata,
            MaximumExecutionTime = TimeSpan.FromSeconds(1),
            RetryPolicy = new OnTimeoutRetry(TimeSpan.FromMilliseconds(250), 3),
            LocationMode = LocationMode.PrimaryOnly 
        };

        DefaultOperationContext = new OperationContext();

        DefaultOperationContext.Retrying += (sender, args) =>
        {
            // This is never executed.
            Debug.WriteLine($"Retry policy activated in {this.GetType().Name} due to HTTP code {args.RequestInformation.HttpStatusCode} with exception {args.RequestInformation.Exception.ToString()}");
        };

        DefaultOperationContext.RequestCompleted += (sender, args) =>
        {
            if (args.Response == null)
            {
                // This is occasionally executed - we want to retry in this case.
                Debug.WriteLine($"Request failed in {this.GetType().Name} due to HTTP code {args.RequestInformation.HttpStatusCode} with exception {args.RequestInformation.Exception.ToString()}");
            }
            else
            {
                Debug.WriteLine($"{this.GetType().Name} operation complete: Status code {args.Response.StatusCode} at {args.Response.ResponseUri}");
            }
        };

        Table.CreateIfNotExists(DefaultTableRequestOptions, DefaultOperationContext);
    }

    public async Task<FooEntity> GetFooEntityAsync()
    {
        var retrieveOperation = TableOperation.Retrieve<FooTableEntity>(FooTableEntity.GenerateKey());

        var tableEntity = (await Table.ExecuteAsync(retrieveOperation, DefaultTableRequestOptions, DefaultOperationContext)).Result as FooTableEntity;

        if (tableEntity != null)
        {
            return tableEntity.ToFooEntity();
        }

        var fooEntity = CalculateFooEntity();

        var insertOperation = TableOperation.InsertOrMerge(new FooTableEntity(fooEntity));
        var executeResult = await Table.ExecuteAsync(insertOperation);

        if (executeResult.HttpStatusCode == 408)
        {
            // This is never executed.
            Debug.WriteLine("Got a 408");
        }

        return fooEntity;
    }

    public class OnTimeoutRetry : IRetryPolicy
    {
        int maxRetryAttempts = 3;

        TimeSpan defaultRetryInterval = TimeSpan.FromMilliseconds(250);

        public OnTimeoutRetry(TimeSpan deltaBackoff, int retryAttempts)
        {
            maxRetryAttempts = retryAttempts;
            defaultRetryInterval = deltaBackoff;
        }

        public IRetryPolicy CreateInstance()
        {
            return new OnTimeoutRetry(TimeSpan.FromMilliseconds(250), 3);
        }

        public bool ShouldRetry(int currentRetryCount, int statusCode, Exception lastException, out TimeSpan retryInterval, OperationContext operationContext)
        {
            retryInterval = defaultRetryInterval;
            if (currentRetryCount >= maxRetryAttempts)
            {
                return false;
            }

            // Non-retryable exceptions are all 400 ( >=400 and <500) class exceptions (Bad gateway, Not Found, etc.) as well as 501 and 505. 
            // This custom retry policy also retries on a 408 timeout.
            if ((statusCode >= 400 && statusCode <= 500 && statusCode != 408) || statusCode == 501 || statusCode == 505)
            {
                return false;
            }

            return true;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

调用GetFooEntityAsync()时,有时会执行"请求失败"行.检查值args.RequestInformation.HttpStatusCode= 408时.但是:

  • Debug.WriteLine("Got a 408"); 在GetFooEntity方法中永远不会执行.

  • Debug.WriteLine($"Retry policy activated...DefaultOperationContext.Retrying委托中永远不会执行(我希望这会被执行两次 - 这不是重试吗?).

  • DefaultOperationContext.RequestResults 包含一长串结果(主要是状态代码404,大约204s).

根据这篇(相当古老的)博客文章,代码在400到500之间以及501和505的异常是不可重试的.然而,超时(408)正是我们想要重试的情况.也许我需要为此案例编写自定义重试策略.

我不完全理解408的来源,因为除了调用RequestCompleted委托之外,我在代码中找不到它.我一直在为我的重试政策尝试不同的设置而没有运气.我在这里错过了什么?如何从表存储中重新启动408的操作?

编辑:我已更新代码以显示我实现的自定义重试策略,以重试408错误.但是,似乎我的重试断点仍未被击中,因此看起来重试没有被触发.我的重试政策没有被激活的原因是什么?