API7 网关 3.10.6:按查询成本治理 GraphQL

更新时间 9/8/2026

核心要点

  • 请求次数限流会把简单的 GraphQL 查询与深度嵌套查询等同处理,即使二者消耗的后端资源可能相差很大。
  • API7 网关 3.10.6 发布于 2026 年 8 月 25 日,为 graphql-limit-count 新增按成本执行策略的能力。
  • 团队可以按查询 depth、解析后的 complexity 或参数驱动的 node_quantifier 衡量工作量,再通过 score_factor 缩放结果。
  • max_cost 会在单次查询到达上游前拒绝超出预算的请求;启用限流响应头时,X-Graphql-Query-Cost 会展示本次计费成本。
  • 服务级 graphql_cost_decorations 让 API 所有者可以为字段和分页参数设置权重,而无需逐条修改路由。
  • 变量解析与有界片段展开关闭了两条可能隐藏昂贵工作量或消耗网关 CPU 的路径。

传统限流关注一个简单问题:这个客户端发送了多少请求?当同一端点的不同请求成本大致相当时,这种方法十分有效。GraphQL 改变了工作量的基本单位。一个请求可能只获取一个标识符,另一个请求却可能遍历多层关系,并要求每个字段返回数十个对象。

如果计数器把两者都按一次请求收费,它能限制请求量,却无法保护后端容量。同一个 /graphql 端点可以承载截然不同的数据库、网络和解析器工作量。

API7 网关 3.10.6 将这种差异转化为可执行的网关策略。graphql-limit-count 插件可以在代理请求前计算查询成本,从时间窗口配额中扣除该成本,并阻止成本超过明确上限的单次查询。

这不能代替解析器超时、数据库保护措施或应用层授权,而是在更靠前的位置增加一层资源治理:网关先评估 GraphQL 文档并执行一致的预算,再让昂贵工作到达服务。

1flowchart LR
2    request[GraphQL 请求] --> parse[解析文档与变量]
3    parse --> cost[计算查询成本]
4    cost --> quota{配额与 max_cost 均未超限?}
5    quota -->|是| upstream[GraphQL 服务]
6    quota -->|否| reject[到达上游前拒绝]
7
8    decorations[服务成本装饰] --> cost

工作量不等时,请求次数不是正确抽象

REST API 通常通过不同路径和方法公开资源与操作,因此 API 网关可以据此分配不同策略。GraphQL 往往把许多操作集中在同一个 HTTP 端点,真正请求的字段、嵌套关系、片段和参数都位于文档内部。

因此,对只看路径的限流器而言,两次 /graphql 调用可能完全相同,实际产生的工作量却差异巨大。浅层的查看者查询可能只触发一个解析器;嵌套的商品查询则可能通过集合、关联实体和 first: 100 等分页参数成倍放大工作量。

这正是 GraphQL 治理需要两种控制的原因:

  1. 累计配额限制客户端在一个时间窗口内可以消耗的计算工作量。
  2. 单次查询上限防止一份文档耗尽过多预算,甚至阻止它抵达上游。

API7 网关在网关层同时执行这两种控制。原有固定窗口行为保持不变,但现在可以把查询工作量而不是固定的一次请求作为计数器扣费单位。

选择与风险匹配的成本模型

GraphQL Limit Count 插件 支持三种 cost_strategy,分别回答不同的运维问题。

  • depth 按选择集的最大嵌套深度计费。它仍是默认策略,因此现有配置升级后会保持原有行为。
  • complexity 根据文档中解析出的节点计分。每个节点会将子节点成本与加法和乘法权重结合起来,因此既能体现广度,也能体现嵌套关系。
  • node_quantifier 关注可以通过成本装饰从 first 等参数中解析数量的节点。当列表基数是后端工作量的主要预测指标时,这种策略更合适。

插件会把原始分数转换为计入配额的整数成本。score_factor 用于缩放该值,让团队可以把插件分数映射到实际运维预算。校准时需要注意一个细节:对于 complexitynode_quantifier,插件会先加 0.01,再应用 score_factor 并向上取整。因此,使用默认系数时,原始整数分数 3 会按 4 计费。depth 策略不会加 0.01,但仍会应用系数并向上取整。

这种行为说明预发布测量十分重要。不要直接复制理论查询模型中的阈值,并假设它适合生产流量。应先观察有代表性的文档,将报告的成本与解析器和数据库负载进行比较,再选择既能容纳合法工作负载,又能保护容量的配额和上限。

把计算出的成本变成可执行预算

只有当成本计算产生清晰决策时,它才真正有用。API7 网关 3.10.6 提供两个相互关联的执行点。

counttime_window 定义累计固定窗口预算。查询会按计算成本扣减预算,不再一律消耗一个单位。团队仍可按客户端地址、消费者身份或其他受支持变量确定计数器 key,并根据网关拓扑选择本地或 Redis 策略。

新增的 max_cost 为单份文档设置成本上限。如果计费成本高于配置值,网关会在把查询发送给上游前返回 403 Forbidden。将 max_cost 设置为 0 会关闭这项单次查询检查。

这里有一条重要的计费规则:插件会先从配额中扣除计算成本,再评估 max_cost。因此,因超过单次查询上限而被拒绝的查询仍会消耗时间窗口配额。运维人员在设置告警阈值以及向 API 消费者解释拒绝原因时,应考虑这种行为。

启用 show_limit_quota_header 后,响应会包含 X-Graphql-Query-Cost。客户端团队与平台运维人员因此可以用同一个数值理解为什么某份文档会比另一份消耗更多配额。这也形成了实用的校准闭环:在预发布环境采集成本,按操作分组,并在收紧生产限制前与上游延迟和资源使用进行对比。

在服务边界建模业务成本

仅靠 GraphQL 语法无法识别每个昂贵字段。执行简单内存查询的解析器,与向远程系统扇出的解析器,在文档中看起来可能十分相似。API 所有者需要把服务知识补充到通用成本模型中。

API7 网关 3.10.6 新增服务级子资源 graphql_cost_decorations。一条装饰通过 field_path 标识 GraphQL 位置,例如 Query.productsProduct 或更长的字段链,并调整匹配节点对成本的贡献。

  • add_value 为节点自身增加固定成本。
  • mul_value 乘以该节点的子节点成本。
  • add_arguments 把指定参数值加到节点自身成本中。
  • mul_arguments 使用指定参数值乘以后代成本。

例如,针对 Query.products 的装饰可以把 first: 10 视为乘数,而不是让它与 first: 1 产生相同成本。同一服务中,每个 field_path 只能配置一条装饰。

把装饰保存在服务上,可以让其在服务的多条路由之间复用。API 所有者无需改写承载 graphql-limit-count 的每条路由就能更新成本模型,网关运维人员则可以继续在流量策略层管理执行规则与计数器配置。

将 Schema 内省视为受控依赖

要把成本装饰与查询匹配,插件需要获得上游 GraphQL schema。对于配置了装饰的服务,每个网关 Worker 会在处理第一个适用请求时内省 schema,并缓存结果直至插件重新加载。没有成本装饰的路由不会触发内省。

默认情况下,插件会从上游推导内省目标。introspection_endpoint 可以指向另一个 HTTP 或 HTTPS 端点,introspection_headers 则可提供该端点需要的凭据。凭据来自配置而不是客户端请求,因为缓存的 schema 会被该 Worker 处理的多个调用方复用。

这项依赖必须纳入发布计划。需要验证每个网关实例都能访问内省端点,配置的凭据只拥有所需权限,并在 schema 变更后执行刷新缓存所需的重新加载。启用数据面加密时,introspection_headers 会加密落盘。

如果没有成本装饰,complexity 可以使用默认节点权重,无需内省 schema。node_quantifier 则需要匹配装饰和可用的数量参数;如果二者均不存在,其原始分数为 0,使用默认缩放行为后计费成本为 1

在变量隐藏数量之前完成解析

GraphQL 客户端经常把参数值放入变量。只检查文档文本的策略可能只能看到 first: $pageSize,却无法知道调用方请求的是 10 个还是 10,000 个对象。

API7 网关 3.10.6 默认启用 resolve_variables。插件会在计算成本前解析调用方提供的变量、GraphQL 操作声明的默认值,以及上游 schema 中的参数默认值。这样,参数驱动策略评估的就是服务预计会使用的实际值。

关闭变量解析可能会低估通过变量传入数量参数的查询。除非经过测试的兼容性限制要求关闭,否则应保持启用,并把大量使用变量的操作纳入用于设置阈值的预发布查询样本。

限制片段展开,避免消耗网关自身资源

成本感知治理也必须保护执行计算的网关。在 3.10.6 之前,GraphQL 片段相互展开时,每次引用都会再次展开。因此,即使请求体很小,也可能在检查期间触发不受限的 CPU 工作;更新日志记录了一份 1.4 KB、包含 34 层片段链的文档,它能让一个 Worker 满载运行超过 45 秒。

API7 网关 3.10.6 现在只计算每个片段一次。片段展开形成环的文档会以 HTTP 400 拒绝。这项修复与选择哪种成本策略无关:如果策略输入本身的解析就能独占网关 Worker,那么策略引擎也无法保护上游。

现在的边界更加明确:有效片段会参与成本计算;重复引用不会触发无上限的展开工作;循环片段图会在代理前失败。

启用成本感知限流前应验证什么

请结合 GraphQL 限流指南、3.10.6 更新日志和插件文档,使用真实操作验证策略:

  1. 盘点持久化操作与有代表性的临时查询,包括嵌套选择、片段、别名和通过变量提供的分页参数。
  2. 根据需要的工作负载信号选择 depthcomplexitynode_quantifier;如果现有策略已经有效,则继续使用 depth
  3. 在预发布环境记录 X-Graphql-Query-Cost,并将其与解析器延迟、数据库查询、下游调用和响应大小进行关联。
  4. 校准 score_factor、时间窗口 countmax_cost 时,考虑插件的取整方式和“先扣费、后拒绝”行为。
  5. 与 GraphQL 服务所有者共同定义 graphql_cost_decorations,并确保服务内每个 field_path 唯一。
  6. 在每个网关实例上测试 schema 内省的可达性、凭据、缓存刷新行为和失败处理。
  7. 验证本地或 Redis 计数器在所有网关节点和需要共享预算的消费者身份之间是否具有正确范围。
  8. 测试重复与循环片段,确认无效文档返回 HTTP 400,且不会降低无关路由的可用性。

治理实际工作量,而不只是请求次数

GraphQL 的灵活性把真正的资源消耗单位放进了请求体。如果把每次调用都视为相同成本,API 平台就无法区分简单查询与跨字段和集合成倍放大工作量的查询。

API7 网关 3.10.6 为团队提供了更实用的控制点:在代理前计算成本,把累计配额与单次查询上限结合起来,让服务所有者描述昂贵的 schema 路径,解析由变量驱动的数量,并在网关层限制片段处理。

最终得到的不是适用于所有场景的 GraphQL 成本公式,而是一套可以根据自身解析器和容量进行校准的策略框架。请阅读完整的 API7 网关 3.10.6 更新日志,查阅 GraphQL Limit Count 插件,再把有代表性的查询成本转化为经过测试的生产预算。

获取方案