Google API .NET 客户端:如何实现指数退避

Google API Client for .NET: How to implement Exponential Backoff

我创建了一种方法,使用 .NET 核心和 google 的 .NET 客户端库将批处理请求中的成员添加到 google 组。代码如下所示:

private void InitializeGSuiteDirectoryService()
{
    _directoryServiceCredential = GoogleCredential
        .FromJson(GlobalSettings.Instance.GSuiteSettings.Credentials)
        .CreateScoped(_scopes)
        .CreateWithUser(GlobalSettings.Instance.GSuiteSettings.User);

    _directoryService = new DirectoryService(new  BaseClientService.Initializer()
    {
        HttpClientInitializer = _directoryServiceCredential,
        ApplicationName = _applicationName
    });
}

public OperationResult<int> AddGroupMembers(Group group, IEnumerable<Member> members)
{
    var result = new OperationResult<int>();
    var memberList = members.ToList();
    var batchRequestCount = 0;

    if (memberList.Any())
    {
        var request = new BatchRequest(_directoryService);

        foreach (var member in memberList)
        {
            batchRequestCount++;
            request.Queue<Members>(_directoryService.Members.Insert(member, group.Id), (content, error, i, message) =>
            {
                if (message.IsSuccessStatusCode)
                {
                    //log OK
                }
                else
                {
                    // Implement Exponential backoff only on the request that failed.
                }
            });
            if (batchRequestCount == 30|| member.Equals(memberList.Last()))
            {
                request.ExecuteAsync().Wait();
                request = new BatchRequest(_directoryService); //Clear queue
            }
        }
    }

    return result;
}

如果成员数量少,逻辑就可以正常工作;但是,假设成员数为 100(这是我的 google 测试实例中的最大用户数),我从 Google 收到错误消息:"quotaExceeded"。根据 Google 的 documentation,他们的 Admin SDK 的批处理请求限制为 1000,我已将我的逻辑设置为在达到限制 30 时执行。

问题是:如何实现错误处理以在收到此错误时重试?他们的文档建议使用包含 'retry-able error' 的响应来实现 'Exponential Backoff'(我在检查我的响应时没有看到这个)。

因此,这就是我在将成员添加到 Gsuite 组的调用中最终实现指数退避的方法。因为我使用的是 dotnet 核心,所以我能够使用“Polly”,这是一个提供开箱即用功能的弹性和瞬态故障处理库。可能需要重构,但目前代码如下所示:

    public OperationResult<int> AddGroupMembers(Group group, IEnumerable<Member> members)
    {
        var result = new OperationResult<int>();
        var memberList = members.ToList();
        var batchRequestCount = 0;

        if (memberList.Any())
        {
            var request = new BatchRequest(_directoryService);

            foreach (var member in memberList)
            {
                retryRequest = false; // This variable needs to be declared at the class level to guarantee the value is available to the original thread running the process.
                batchRequestCount++;
                request.Queue<Members>(_directoryService.Members.Insert(member, group.Id), (content, error, i, message) =>
                {
                    // If error code is 'quotaExceeded' retry the request ( You can add as many error codes as you'd like to retry here) 
                    if (error.Code == 403)
                    {
                        retryRequest = true;
                    }

                });

                // Execute batch request to add members in batches of 30 member max
                if (batchRequestCount == 30|| member.Equals(memberList.Last()))
                {

                    // Below is what the code to retry using polly looks like
                    var response = Policy
                        .HandleResult<HttpResponseMessage>(message => message.StatusCode == HttpStatusCode.Conflict)
                        .WaitAndRetry(new[]
                        {
                            TimeSpan.FromSeconds(1),
                            TimeSpan.FromSeconds(2),
                            TimeSpan.FromSeconds(4)
                        }, (results, timeSpan, retryCount, context) =>
                        {
                            // Log Warn saying a retry was required.
                        })
                        .Execute(() =>
                        {
                            var httpResponseMsg = new HttpResponseMessage();

                            // Execute batch request Synchronously
                            request.ExecuteAsync().Wait();
                            if (retryRequest)
                            {
                                httpResponseMsg.StatusCode = HttpStatusCode.Conflict;
                                retryRequest = false;
                            }
                            else
                            {
                                httpResponseMsg.StatusCode = HttpStatusCode.OK;
                            }

                            return httpResponseMsg;
                        });
                    if (response.IsSuccessStatusCode)
                    {
                       // Log info
                    }
                    else
                    {
                        // Log warn
                    }

                    requestCount = 0;
                    request = new BatchRequest(_directoryService);
                    batchCompletedCount++;
                }
            }
        }

        return result;
    }