技术博客
天气预报国际版:免费档位下的缓存策略设计(用 Redis 省调用量)

天气预报国际版:免费档位下的缓存策略设计(用 Redis 省调用量)

作者: 万维易源
2026-09-03
天气预报国际版缓存策略Redis调用量优化
# 天气预报国际版:免费档位下的缓存策略设计(用 Redis 省调用量) > 接口:天气预报国际版(apiCode=3540)全部接入点 · 免费接口 · POST/GET · JSON · 适用人群:后端/中高级开发者 · 阅读时间:约 7 分钟 ## 核心要点 - 免费接口设有防滥用档次限制(具体档位数以官方说明为准),上量后第一件该做的事就是加缓存:同一城市同一接入点短时间内的重复请求,完全没有必要每次都打到接口。 - 实测事实:失败调用 `showapi_fee_num=0` 不扣次——调试阶段放心试错,但生产代码仍要把成功调用的次数当成本来管。 - 本文给出可落地的 Redis 缓存设计(key 结构、TTL 分层、雪崩防护)与完整 Python 代码。 ## Why 天气数据天然"不需要秒级新鲜":当前天气几分钟内的变化对用户几乎无感,预报更是按小时/按天粒度。但典型的流量模式偏偏是反过来的——首页天气卡片每个 PV 都触发一次查询,热点城市的调用量远高于长尾城市。 在免费档位的频次约束下,这套流量结构意味着:不做缓存,很快就会碰到限流;做了缓存,同样的用户体验可以只用原来零头级别的调用量。这是天气接口接入后性价比最高的一个工程动作。 ## What 设计目标: | 目标 | 手段 | |------|------| | 减少重复调用 | 以「接入点 + 定位参数」为 key 缓存返回 JSON | | 数据新鲜度可控 | 按数据类型分层 TTL(当前天气短、14 天预报长) | | 防缓存雪崩 | TTL 加随机抖动 | | 失败不污染缓存 | 只缓存 `ret_code=0` 的成功结果 | TTL 分层建议(按数据粒度自行调整,非官方要求): | 接入点 | 数据粒度 | 建议 TTL | |--------|---------|---------| | 3540-1 当前天气 | 实时 | 5~10 分钟 | | 3540-2 24小时预报 | 小时 | 30~60 分钟 | | 3540-3 14天预报 | 天 | 2~4 小时 | ## How ### 1. Redis 缓存封装(Python) ```python # pip install requests redis import json import random import requests import redis APPKEY = "YOUR_APPKEY" rds = redis.Redis(host="localhost", port=6379, decode_responses=True) TTL = {"3540-1": 600, "3540-2": 3600, "3540-3": 10800} # 秒 def get_weather_cached(endpoint: str, name=None, lon=None, lat=None) -> dict: """带 Redis 缓存的天气查询:key = 接入点 + 定位参数。""" if lon and lat: loc = f"lon:{lon},lat:{lat}" elif name: loc = f"name:{name}" else: raise ValueError("必须提供定位参数") key = f"weather:{endpoint}:{loc}" cached = rds.get(key) if cached: return json.loads(cached) # 缓存命中,不打接口 # 缓存未命中 -> 回源 params = {"appKey": APPKEY} if lon and lat: params.update({"lon": lon, "lat": lat}) else: params["name"] = name resp = requests.post(f"https://route.showapi.com/{endpoint}", params=params, timeout=10) data = resp.json() if data["showapi_res_code"] != 0: raise RuntimeError(f"系统级错误: {data.get('showapi_res_error')}") body = data["showapi_res_body"] if body.get("ret_code") != 0: raise RuntimeError(f"业务错误: {body.get('remark')}") # 失败不缓存(实测失败不扣次) ttl = TTL[endpoint] + random.randint(-60, 60) # 抖动防雪崩 rds.setex(key, ttl, json.dumps(body, ensure_ascii=False)) return body # 连续两次调用同一城市,第二次直接走缓存 b1 = get_weather_cached("3540-1", name="北京") b2 = get_weather_cached("3540-1", name="北京") print(b1["cityInfo"]["city"], b2["cityInfo"]["city"]) # 第二次来自 Redis ``` cURL(回源行为对照): ```bash curl -X POST "https://route.showapi.com/3540-1?appKey=YOUR_APPKEY&name=北京" ``` Node.js(ioredis 版要点): ```js const Redis = require("ioredis"); const rds = new Redis(); async function getWeatherCached(endpoint, name) { const key = `weather:${endpoint}:name:${name}`; const cached = await rds.get(key); if (cached) return JSON.parse(cached); const res = await fetch( `https://route.showapi.com/${endpoint}?appKey=${process.env.APPKEY}&name=${encodeURIComponent(name)}`, { method: "POST" } ); const body = (await res.json()).showapi_res_body; if (body.ret_code !== 0) throw new Error(body.remark); const ttl = 600 + Math.floor(Math.random() * 120) - 60; // 抖动 await rds.setex(key, ttl, JSON.stringify(body)); return body; } ``` ### 2. 更进一步:热点城市预热 对确定的热点城市(你的用户集中地),用定时任务按 TTL 节奏提前刷新缓存,把用户请求全部挡在 Redis 层,回源调用量变成常数级别。 ## 返回示例与解析 缓存的对象就是接口原样返回的 `showapi_res_body`(成功时),示例: ```json { "remark": "查询成功", "ret_code": 0, "cityInfo": { "city": "北京", "city_en": "Beijing", "time_zone": "Asia/Shanghai" }, "now": { "temperature": 33.7, "weather": "晴天", "rain_prop": 0 } } ``` 缓存命中时 `showapi_res_id`/`showapi_fee_num` 不会出现在你解析的对象里(它们是每次真实调用才有的系统级字段)——如需请求 ID 做对账,只在回源那次记录即可。 ## 进阶与边界 - **失败不缓存**:`ret_code != 0` 的结果(如"经纬度不能为空")绝不能写缓存,否则一个坏参数会毒化整个 TTL 周期。 - **失败不扣次(实测)**:失败调用返回 `showapi_fee_num=0`。这降低了试错成本,但不要因此省略参数校验——参数校验应该在进缓存逻辑之前就拦下。 - **TTL 不是官方规定**:上表 TTL 是按数据粒度给出的工程建议,业务对新鲜度的要求不同请自行调整。 - **缓存与限速配合**:缓存解决重复请求,回源请求仍建议加限速与重试,见[多城市批量管理](https://www.showapi.com/guides/global-weather-multicity-3540)。 - **免费档位具体次数**:文档未给出,以[官方档位说明](https://www.showapi.com/free-api)为准,本文不编造数字。 ## FAQ **Q1:缓存 TTL 设多长合适?** 按数据粒度:当前天气 5~10 分钟、24 小时预报 30~60 分钟、14 天预报 2~4 小时是常见起点,按业务对新鲜度的敏感度调整。 **Q2:天气数据更新频率是多少,缓存会不会展示旧数据?** 接口文档未标注更新频率,本文不做无依据断言。工程上按"分钟级实况、小时级预报"的常识设 TTL 即可满足绝大多数产品场景。 **Q3:失败的结果要不要缓存一段短时间(负缓存)?** 可以,但要区分失败原因:参数类失败(实测 `ret_code=-1` "经纬度不能为空")建议在业务层直接拦截而不回源,无需负缓存;网络类失败不应缓存。 **Q4:不用 Redis,用本地内存缓存行不行?** 单实例小流量可以(`cachetools` 之类即可);多实例部署时本地缓存会导致各节点重复回源,此时用集中式 Redis 才能真正合并调用量。 **Q5:`showapi_fee_num` 能用来做计费监控吗?** 可以,它是每次调用实际扣费次数的系统级字段(实测成功为 1、失败为 0),把回源调用的该值打点统计,即可对账调用量。 ## 下一步阅读 - [天气预报国际版:多城市天气批量管理(无批量接口下的循环与并发控制)](https://www.showapi.com/guides/global-weather-multicity-3540) - [天气预报国际版:当前天气接入实战(气温、体感、风、降水概率全字段)](https://www.showapi.com/guides/global-weather-current-weather-3540) - [天气预报国际版:免费接口档位说明(官方)](https://www.showapi.com/free-api) - **本系列共 12 篇**:查看[天气预报国际版指南总目录](https://www.showapi.com/guides/global-weather-guides-3540)