有关 Azure Resource Graph 中的受限制请求的指南

创建编程代码和频繁使用 Azure Resource Graph 数据时,应考虑到限制对查询结果的影响。 更改请求数据的方式可帮助你和你的组织避免受到限制并维护有关 Azure 资源的及时数据流。

本文涵盖与在 Azure Resource Graph 中创建查询相关的四个领域和模式:

  • 了解限制标头。
  • 分组查询。
  • 错开查询。
  • 分页的效果。

了解限制标头

Azure Resource Graph 基于时段为每个用户分配配额数量。 例如,用户可以在每 5 秒的时段内最多发送 15 个查询,而不会受到限制。 配额值由许多因素确定,可能会发生更改。

在每个查询响应中,Azure Resource Graph 会添加两个限制标头:

  • x-ms-user-quota-remaining (int):用户的剩余资源配额。 此值映射到查询计数。
  • x-ms-user-quota-resets-after (hh:mm:ss):在用户的配额消耗量重置之前的持续时间。

当安全主体有权访问租户或管理组查询范围中 10,000 个以上的订阅时,响应仅限于前 10,000 个订阅,x-ms-tenant-subscription-limit-hit 标头将返回为 true

为了说明标头的工作方式,我们来看看具有标头并且值为 x-ms-user-quota-remaining: 10x-ms-user-quota-resets-after: 00:00:03 查询响应。

  • 在接下来的 3 秒内,最多可以提交 10 个查询,这不会受到限制。
  • 3 秒后,x-ms-user-quota-remainingx-ms-user-quota-resets-after 的值分别重置为 1500:00:05

有关演示如何使用标头回退查询请求的示例,请参阅并行查询中的示例。

对查询分组

按订阅、资源组或单个资源对查询分组比并行查询更加高效。 较大查询的配额成本通常低于许多小型定向查询的配额成本。 组大小建议小于 300。

  • 优化不当的方法示例。

    // NOT RECOMMENDED
    var header = /* your request header */
    var subscriptionIds = /* A big list of subscriptionIds */
    
    foreach (var subscriptionId in subscriptionIds)
    {
        var userQueryRequest = new QueryRequest(
            subscriptions: new[] { subscriptionId },
            query: "Resoures | project name, type");
    
        var azureOperationResponse = await this.resourceGraphClient
            .ResourcesWithHttpMessagesAsync(userQueryRequest, header)
            .ConfigureAwait(false);
    
    // ...
    }
    
  • 优化的分组方法示例。

    // RECOMMENDED
    var header = /* your request header */
    var subscriptionIds = /* A big list of subscriptionIds */
    
    const int groupSize = 100;
    for (var i = 0; i <= subscriptionIds.Count / groupSize; ++i)
    {
        var currSubscriptionGroup = subscriptionIds.Skip(i * groupSize).Take(groupSize).ToList();
        var userQueryRequest = new QueryRequest(
            subscriptions: currSubscriptionGroup,
            query: "Resources | project name, type");
    
        var azureOperationResponse = await this.resourceGraphClient
            .ResourcesWithHttpMessagesAsync(userQueryRequest, header)
            .ConfigureAwait(false);
    
      // ...
    }
    
  • 优化的分组方法示例,用于在一个查询中获取多个资源。

    Resources | where id in~ ({resourceIdGroup}) | project name, type
    
    // RECOMMENDED
    var header = /* your request header */
    var resourceIds = /* A big list of resourceIds */
    
    const int groupSize = 100;
    for (var i = 0; i <= resourceIds.Count / groupSize; ++i)
    {
        var resourceIdGroup = string.Join(",",
            resourceIds.Skip(i * groupSize).Take(groupSize).Select(id => string.Format("'{0}'", id)));
        var userQueryRequest = new QueryRequest(
            subscriptions: subscriptionList,
            query: $"Resources | where id in~ ({resourceIdGroup}) | project name, type");
    
        var azureOperationResponse = await this.resourceGraphClient
            .ResourcesWithHttpMessagesAsync(userQueryRequest, header)
            .ConfigureAwait(false);
    
      // ...
    }
    

错开查询

由于强制实施限制的方式,建议错开查询。 例如,不同时发送 60 个查询,而是在四个 5 秒时限内将查询错开。

  • 未错开的查询计划。

    查询计数 60 0 0 0
    时间间隔(秒) 0-5 5-10 10-15 15-20
  • 错开的查询计划。

    查询计数 15 15 15 15
    时间间隔(秒) 0-5 5-10 10-15 15-20

以下代码是查询 Azure Resource Graph 时遵从限制标头的示例。

while (/* Need to query more? */)
{
    var userQueryRequest = /* ... */
    // Send post request to Azure Resource Graph
    var azureOperationResponse = await this.resourceGraphClient
        .ResourcesWithHttpMessagesAsync(userQueryRequest, header)
        .ConfigureAwait(false);

    var responseHeaders = azureOperationResponse.response.Headers;
    int remainingQuota = /* read and parse x-ms-user-quota-remaining from responseHeaders */
    TimeSpan resetAfter = /* read and parse x-ms-user-quota-resets-after from responseHeaders */
    if (remainingQuota == 0)
    {
        // Need to wait until new quota is allocated
        await Task.Delay(resetAfter).ConfigureAwait(false);
    }
}

并行查询

虽然建议进行分组而不是采用并行,不过有时候无法轻松地对查询分组。 在这些情况下,可能需要通过并行发送多个查询来查询 Azure Resource Graph。 以下示例演示如何基于限制标头进行回退

IEnumerable<IEnumerable<string>> queryGroup = /* Groups of queries  */
// Run groups in parallel.
await Task.WhenAll(queryGroup.Select(ExecuteQueries)).ConfigureAwait(false);

async Task ExecuteQueries(IEnumerable<string> queries)
{
    foreach (var query in queries)
    {
        var userQueryRequest = new QueryRequest(
            subscriptions: subscriptionList,
            query: query);
        // Send post request to Azure Resource Graph.
        var azureOperationResponse = await this.resourceGraphClient
            .ResourcesWithHttpMessagesAsync(userQueryRequest, header)
            .ConfigureAwait(false);

        var responseHeaders = azureOperationResponse.response.Headers;
        int remainingQuota = /* read and parse x-ms-user-quota-remaining from responseHeaders */
        TimeSpan resetAfter = /* read and parse x-ms-user-quota-resets-after from responseHeaders */
        if (remainingQuota == 0)
        {
            // Delay by a random period to avoid bursting when the quota is reset.
            var delay = (new Random()).Next(1, 5) * resetAfter;
            await Task.Delay(delay).ConfigureAwait(false);
        }
    }
}

分页

由于 Azure Resource Graph 在单个查询响应中最多返回 1,000 个条目,因此你可能需要将查询分页才能获取所需的完整数据集。 但某些 Azure Resource Graph 客户端处理分页的方式与其他客户端不同。

使用 ResourceGraph SDK 时,需要通过将从上一个查询响应返回的跳过标记传递到下一个分页查询来处理分页。 这种设计意味着需要从所有分页调用收集结果,最后将它们合并在一起。 在这种情况下,发送的每个分页查询都会占用一个查询配额。

var results = new List<object>();
var queryRequest = new QueryRequest(
  subscriptions: new[] { mySubscriptionId },
  query: "Resources | project id, name, type");
var azureOperationResponse = await this.resourceGraphClient
  .ResourcesWithHttpMessagesAsync(queryRequest, header)
  .ConfigureAwait(false);
while (!string.IsNullOrEmpty(azureOperationResponse.Body.SkipToken))
{
  queryRequest.Options ??= new QueryRequestOptions();
  queryRequest.Options.SkipToken = azureOperationResponse.Body.SkipToken;
  var azureOperationResponse = await this.resourceGraphClient
      .ResourcesWithHttpMessagesAsync(queryRequest, header)
      .ConfigureAwait(false);
  results.Add(azureOperationResponse.Body.Data.Rows);

// Inspect throttling headers in query response and delay the next call if needed.
}

仍受限?

如果按本文建议操作后 Azure Resource Graph 查询仍受限,请联系 Azure Resource Graph 团队。 该团队提供 Azure Resource Graph 支持但不提供 Microsoft Graph 限制支持。

联系 Azure Resource Graph 团队时请提供以下详细信息:

  • 对更高限制的特定用例和业务驱动因素需求。
  • 你可以访问多少资源? 从单个查询返回多少资源?
  • 你对哪些类型的资源感兴趣?
  • 你的查询模式是什么? 每 Y 秒 X 个查询,等等。

后续步骤