C# 在Azure表存储服务408超时时重试

C# 在Azure表存储服务408超时时重试,c#,azure,azure-storage,azure-table-storage,retrypolicy,C#,Azure,Azure Storage,Azure Table Storage,Retrypolicy,我们正在使用Azure表存储,在执行InsertOrMerge操作时,偶尔会出现408次超时。在这种情况下,我们希望重试,但这些错误似乎没有遵循重试策略 这是一个用于处理表交互的类。GetFooEntityAsync方法尝试从表存储中检索实体。如果不能,它将创建一个新的FooEntity并将其添加到表中(映射到FooTableEntity) 公共类FooTableStorageBase { 私有只读字符串表名; 受保护的只读CloudStorageAccount-storageAccount;

我们正在使用Azure表存储,在执行InsertOrMerge操作时,偶尔会出现408次超时。在这种情况下,我们希望重试,但这些错误似乎没有遵循重试策略

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

公共类FooTableStorageBase
{
私有只读字符串表名;
受保护的只读CloudStorageAccount-storageAccount;
受保护的TableRequestOptions DefaultTableRequestOptions{get;}
受保护的OperationContext DefaultOperationContext{get;}
公共云表
{
得到
{
返回storageAccount.CreateCloudTableClient().GetTableReference(tableName);
}
}
公共FooTableStorage(字符串表名)
{
if(String.IsNullOrWhiteSpace(tableName))
{
抛出新ArgumentNullException(nameof(tableName));
}
this.tableName=tableName;
storageAccount=CloudStorageAccount.Parse(ConnectionString);
ServicePoint tableServicePoint=ServicePointManager.FindServicePoint(storageAccount.TableEndpoint);
tableServicePoint.UseNagleAlgorithm=false;
tableServicePoint.ConnectionLimit=100;//从默认值2增加连接限制。
DefaultTableRequestOptions=新建TableRequestOptions()
{
PayloadFormat=TablePayloadFormat.JsonNoMetadata,
MaximumExecutionTime=TimeSpan.FromSeconds(1),
RetryPolicy=new-OnTimeoutRetry(TimeSpan.From毫秒(250),3),
LocationMode=LocationMode.PrimaryOnly
};
DefaultOperationContext=新的OperationContext();
DefaultOperationContext.Retrying+=(发件人,参数)=>
{
//这是永远不会执行的。
Debug.WriteLine($“由于HTTP代码{args.RequestInformation.HttpStatusCode}而在{this.GetType().Name}中激活的重试策略,异常为{args.RequestInformation.exception.ToString()}”);
};
DefaultOperationContext.RequestCompleted+=(发送方,参数)=>
{
如果(args.Response==null)
{
//这是偶尔执行的-在这种情况下,我们希望重试。
Debug.WriteLine($”由于HTTP代码{args.RequestInformation.HttpStatusCode},请求在{this.GetType().Name}中失败,异常为{args.RequestInformation.exception.ToString()}”);
}
其他的
{
Debug.WriteLine($“{this.GetType().Name}操作完成:状态代码{args.Response.StatusCode}位于{args.Response.ResponseUri}”);
}
};
CreateIfNotExists(DefaultTableRequestOptions,DefaultOperationContext);
}
公共异步任务GetFooEntityAsync()
{
var retrieveOperation=TableOperation.Retrieve(FooTableEntity.GenerateKey());
var tableEntity=(wait Table.ExecuteAsync(retrieveOperation、DefaultTableRequestOptions、DefaultOperationContext))。结果为FooTableEntity;
if(tableEntity!=null)
{
返回tableEntity.tofootentity();
}
var fooEntity=CalculateFoEntity();
var insertOperation=TableOperation.InsertOrMerge(新的FooTableEntity(FootEntity));
var executesult=wait Table.ExecuteAsync(插入操作);
if(executeResult.HttpStatusCode==408)
{
//这是永远不会执行的。
Debug.WriteLine(“得到一个408”);
}
返回实体;
}
公共类OnTimeoutRetry:IRetryPolicy
{
int maxRetryAttempts=3;
TimeSpan defaultRetryInterval=TimeSpan.From毫秒(250);
公共OnTimeoutRetry(TimeSpan deltaBackoff,int retryAttempts)
{
maxRetryAttempts=retryAttempts;
defaultRetryInterval=deltaBackoff;
}
公共IRetryPolicy CreateInstance()
{
返回新的OnTimeoutRetry(TimeSpan.FromMillistics(250),3);
}
public bool ShouldRetry(int currentrycount、int statusCode、Exception lastException、out TimeSpan retryInterval、OperationContext OperationContext)
{
retryInterval=默认retryInterval;
如果(currentRetryCount>=maxRetryAttempts)
{
返回false;
}

//不可重试的异常都是400(>=400和=400&&statusCode什么是foo实体的分区/行键?您是正确的。您需要编写自定义重试策略并将408错误代码标记为可重试。它是点(“纬度,经度”)的字符串表示形式从FooEntity中的数据生成。它几乎在所有情况下都有效。我认为这与超时无关。我只是想知道是否因为您使用insertOrMerge而发生了冲突。感谢Guarav,我将尝试一下。感谢RabtFt,可能会发生冲突,但这不应该由insertOrMerge处理吗?
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;
        }
    }
}