启用 server API key 和 billing 后仍出现 OVER_QUERY_LIMIT
结论
请求虽然已经创建 server API key 并启用 billing,但示例 URL 中没有 key 参数。这类请求可能会被视为未认证请求,无法使用对应项目的计费配额。控制台显示的 10% 用量也不一定包含这些请求。
OVER_QUERY_LIMIT 也不只是指每日配额已经用完。短时间内请求过于密集,超过每秒或每分钟限制时,同样可能返回这个状态。
正确携带 API key
请使用 HTTPS,并在请求中明确传入 API key:
https://maps.googleapis.com/maps/api/geocode/xml?address=Switzerland%20Bern&components=country%3ACH&key=YOUR_API_KEY
使用 JSON 时,请求可以写成:
https://maps.googleapis.com/maps/api/geocode/json?address=Switzerland%20Bern&components=country%3ACH&key=YOUR_API_KEY
这里必须包含 key=YOUR_API_KEY。只在项目中创建 key、启用 Geocoding API 或开启 billing,不会让匿名请求自动关联到该项目。
sensor=false 是旧版接口曾要求提供的参数,现在通常不再需要。是否仍受支持,应以当前使用的 Geocoding API 版本文档为准。
为什么控制台配额与实际结果不一致
可能的原因有:
- 请求没有携带 API key,因此没有计入预期的项目或凭据。
- 查看了错误的项目或 API,或者控制台筛选的是其他凭据。
- 每日配额还有剩余,但瞬时请求频率已经超限。
- 多台服务器或多个进程共用一个 key,实际总请求量超过了单机统计。
- key 设置了 IP 地址限制,但请求的实际出口 IP 与配置不符。
- billing 已经开启,但没有绑定到发送请求的项目。
- 对应项目没有启用 API,或配额被手动调低。
- 重试逻辑没有退避,收到限流响应后立刻连续重试,进一步增加了请求量。
不同年代、账户类型和 Google Maps Platform 配置对应的配额规则可能不同,不能只根据旧的“每天 2500 次”来判断。请以项目控制台当前显示的 Geocoding API 配额为准。
建议的排查步骤
1. 确认请求确实携带 key
记录程序最终发出的 URL 或请求参数,同时在日志中隐藏完整 key:
key=AIza...REDACTED
如果请求经过代理、网关或第三方 HTTP 客户端,还要检查 key 参数是否在转发过程中被删除。
2. 检查完整错误响应
不要只读取 <status>。某些错误响应还会包含 error_message:
<GeocodeResponse>
<status>OVER_QUERY_LIMIT</status>
<error_message>...</error_message>
</GeocodeResponse>
error_message 不一定每次都会出现,但在有值时,通常有助于判断问题出在凭据、计费还是配额。HTTP 状态码、响应头和响应时间也应该一起记录。
3. 核对项目配置
在 Google Cloud Console 中逐项确认:
- 当前 API key 属于哪个项目。
- billing 是否绑定到同一个项目。
- 该项目是否启用了 Geocoding API。
- 配额页面显示的是否确实是 Geocoding API。
- 是否设有每分钟、每用户或其他细分配额。
- API key 的 application restrictions 是否与服务器出口 IP 一致。
- API restrictions 是否允许调用 Geocoding API。
修改 key 限制后,配置可能要过一小段时间才会生效。
4. 统计实际请求频率
除了每日总量,还要按秒和按分钟统计以下信息:
timestamp
server_instance
outbound_ip
api_key_id
http_status
geocoding_status
retry_count
重点检查定时任务是否集中启动、多个并发 worker 是否同时发送请求,以及失败后是否立即重试。即使每天只有几百次调用,只要集中在几秒内发出,也可能触发限流。
5. 使用指数退避
遇到 OVER_QUERY_LIMIT 后,不要立即无限重试。可以使用带随机抖动的指数退避:
async function geocodeWithRetry(request, maxRetries = 5) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await request();
if (response.status !== "OVER_QUERY_LIMIT") {
return response;
}
if (attempt === maxRetries) {
throw new Error("Geocoding request remained over quota");
}
const baseDelay = 500 * (2 ** attempt);
const jitter = Math.floor(Math.random() * 250);
await new Promise(resolve => setTimeout(resolve, baseDelay + jitter));
}
}
指数退避只能缓解临时的频率限制。如果每日配额已经用完、billing 配置有误或凭据无效,持续重试并不能解决问题。
代码示例
服务器端可以显式构造查询参数,减少参数遗漏和编码错误:
const params = new URLSearchParams({
address: "Switzerland Bern",
components: "country:CH",
key: process.env.GOOGLE_MAPS_API_KEY
});
const url =
`https://maps.googleapis.com/maps/api/geocode/json?${params.toString()}`;
const response = await fetch(url);
const result = await response.json();
if (result.status !== "OK") {
console.error({
status: result.status,
errorMessage: result.error_message,
httpStatus: response.status
});
}
不要把 API key 硬编码进源码,也不要将其提交到版本库。服务器 key 应限制可调用的 API,并根据实际部署环境设置出口 IP 限制。
最需要先修正的地方
当前示例请求是:
http://maps.googleapis.com/maps/api/geocode/xml?address=Switzerland+Bern+&components=country:CH&sensor=false
至少需要改成:
https://maps.googleapis.com/maps/api/geocode/xml?address=Switzerland%20Bern&components=country%3ACH&key=YOUR_API_KEY
如果加入 key 后仍然出现 OVER_QUERY_LIMIT,请优先检查短时间内的请求频率、共用该 key 的其他调用方、服务器的实际出口 IP,以及控制台中的细分配额。
备注:内容仅供参考。
