生肖运势查询:每 2 小时更新,如何设计缓存避免重复调用
# 生肖运势查询:每 2 小时更新,如何设计缓存避免重复调用
> 元信息:生肖运势查询(接入点 1) · 免费 · POST / GET · JSON · 中高级开发者、架构师 · 阅读约 8 分钟
## 核心要点
- 官方每 **2 小时**更新一次 12 生肖运势,同一生肖短窗口内结果恒定,天然适合缓存。
- 缓存维度:`sx + 日期`(或 `sx + 日期 + 周期标记`),TTL≈2h;命中即返回,不回源。
- 免费接口设使用档位限制,缓存=省额度+降延迟+抗峰值,是生产必做项。
## Why:为什么一定要缓存
生肖运势不是实时数据,2 小时内对全站所有用户返回的是同一份。如果不缓存,每来一个用户请求就打一次易源接口——用户量一大,免费额度很快被刷完,接口变慢甚至被限流。缓存后:首访回源、之后 2 小时内全站命中,额度消耗降到"每生肖每 2 小时 1 次"。
## What:缓存设计要点
| 项 | 建议 |
|----|------|
| 缓存键 | `fortune:{sx}:{YYYYMMDD}`(今日);含周期用 `fortune:{sx}:{YYYYMM}`(本月) |
| TTL | 约 7200 秒(2 小时),略大于更新周期防抖动 |
| 存储 | Redis(分布式)或进程内存(单机)均可 |
| 回源条件 | 缓存未命中 / 已过期 |
| 防穿透 | 回源失败保留旧值或短空值,避免雪崩打满上游 |
## How:Redis 缓存封装(Python)
```python
import redis, requests, time
r = redis.Redis(host="localhost", port=6379, db=0)
APPKEY = "YOUR_APPKEY"
CACHE_TTL = 7200 # 2 小时
def get_fortune(sx: str) -> dict:
day_key = time.strftime("%Y%m%d")
cache_key = f"fortune:{sx}:{day_key}"
cached = r.get(cache_key)
if cached:
return __import__("json").loads(cached) # 命中,不回源
# 未命中,回源
resp = requests.get(
"https://route.showapi.com/2219-1",
params={"appKey": APPKEY, "sx": sx},
timeout=15,
)
body = resp.json().get("showapi_res_body", {})
if body.get("ret_code") != 0:
raise RuntimeError(body.get("remark"))
r.set(cache_key, __import__("json").dumps(body), ex=CACHE_TTL) # 写回,TTL=2h
return body
```
### Node.js(内存版,单机适用)
```javascript
const cache = new Map(); // key -> { exp, data }
const APPKEY = process.env.SHOWAPI_APPKEY;
async function getFortune(sx) {
const day = new Date().toISOString().slice(0, 10).replace(/-/g, "");
const key = `fortune:${sx}:${day}`;
const hit = cache.get(key);
if (hit && hit.exp > Date.now()) return hit.data; // 命中
const url = `https://route.showapi.com/2219-1?appKey=${APPKEY}&sx=${sx}`;
const body = (await (await fetch(url, { signal: AbortSignal.timeout(15000) })).json()).showapi_res_body;
if (body.ret_code !== 0) throw new Error(body.remark);
cache.set(key, { exp: Date.now() + 7200_000, data: body }); // TTL 2h
return body;
}
```
## 返回示例与解析
缓存命中时直接返回已存的 `showapi_res_body`(结构同《[生肖运势查询:返回字段全解](https://www.showapi.com/guides/shengxiao-fortune-response-fields-2219)》),对调用方透明;只有缓存未命中才真正请求易源。
## 进阶 / 边界
- **本月缓存键不同**:若开启 `needMonth=1`,本月数据用 `fortune:{sx}:{YYYYMM}` 维度缓存(按月失效),不要和今日混用同一 key。
- **TTL 取 2h+ 缓冲**:官方"每两小时更新",TTL 设 7200s 即可;若担心边界抖动,可设 7500s 留余量。
- **防穿透/雪崩**:大量生肖同时过期时,用单飞(singleflight)/ 互斥锁让同一 key 只回源一次;回源失败保留上一周期旧值,避免上游被打挂后全站无数据。
- **额度监控**:在代理层打点"缓存命中率",命中率长期偏低说明 TTL 或 key 设计有问题。
## FAQ
**Q1:缓存 2 小时,用户看到的是旧数据吗?**
A:官方数据本身每 2 小时才更新一次,所以 2 小时内的"旧"就是最新值,不存在时效问题。TTL 对齐更新周期正是为了既新鲜又省额度。
**Q2:12 生肖要不要分 12 个 key?**
A:要。按 `sx` 分别缓存,命中更精准——用户只查猴就只回源猴,不浪费其他生肖额度。
**Q3:开启 needMonth 后缓存怎么处理?**
A:本月用 `YYYYMM` 维度(按月失效),今日用 `YYYYMMDD`(按日失效),两者 key 不同、互不干扰。
**Q4:免费额度还会被刷完吗?**
A:缓存后每生肖每 2 小时仅 1 次回源,正常情况下极省;但仍建议代理层加限流,防止异常循环或攻击打满。具体档位以官方档位说明为准。
**Q5:用内存 Map 还是 Redis?**
A:单机/低并发用内存 Map 足够;多实例/分布式部署必须用 Redis 等共享存储,否则各实例各自回源、缓存失效。
## 相关能力 / 下一步阅读
- [生肖运势查询:集成到网站 / 小程序 / 公众号的实战方案](https://www.showapi.com/guides/shengxiao-fortune-app-integration-2219)
- [生肖运势查询:如何同时拿到今日 / 明日 / 本月运势(needTomorrow / needMonth 开关)](https://www.showapi.com/guides/shengxiao-fortune-daily-monthly-2219)
- [生肖运势查询:sx 参数(12 生肖拼音编码)正确用法与对照表](https://www.showapi.com/guides/shengxiao-fortune-sx-param-2219)
- **本系列共 9 篇**:查看[生肖运势查询指南总目录](https://www.showapi.com/guides/shengxiao-fortune-guides-2219)