星座运势查询的缓存怎么设:按「星座 + 周期 + 日期」建键与 TTL 对齐
约 18 分钟 高级星座运势查询缓存策略RedisTTL对齐
# 星座运势查询的缓存怎么设:按「星座 + 周期 + 日期」建键与 TTL 对齐
接入点 872-1 / 872-2 · 免费服务 · 适用:中高级开发者、需要控制调用量的架构师 · 阅读时间:约 8 分钟 · 最后实测核对:2026-09-18
## 核心要点
- 星座运势查询接口(apiCode=872)的数据每天 1 点、7 点、17 点更新,窗口内内容不变,缓存键按「星座 + 周期 + 日期」建。
- 星座配对接口的结果只由两个星座决定,2026-09-18 实测性别不影响返回,键可以用无序星座对。
- 缓存键必须落到完整的星座英文码,2026-09-18 用同前缀不同后缀的对照实验确认过,前缀级别会串数据。
## 缓存要解决什么
接入点 1 每次调用计一次费,基础版档位是每日 100 次、1 QPS。社区类产品里同一个星座的今日运势会被大量用户重复请求,如果每次都回源,额度很快就到顶。
星座数据每天只变三次,命中缓存后绝大多数请求不需要回源。加一层缓存既省调用量,也把响应时间从一次网络往返压到一次本地读取。
## 键的粒度:为什么必须到完整星座码
星座英文码里有三组共享前缀:`shuiping`、`shuangyu`、`shuangzi` 同以 `shu` 开头;`tiancheng`、`tianxie` 同以 `tian` 开头;`baiyang`、`jinniu`、`juxie`、`shizi`、`chunv`、`sheshou`、`mojie` 各自独立。
2026-09-18 对同前缀的三个星座各调一次做对照:
| 传入 `star` | 返回 `grxz` | `lucky_color` | `lucky_num` | `summary_star` |
|---|---|---|---|---|
| `shuiping` | 水瓶座 | 天蓝 | `38` | `4` |
| `shuangyu` | 双鱼座 | 海蓝色 | `82` | `4` |
| `shuangzi` | 双子座 | 柠檬黄 | `11` | `4` |
三个星座的贵人星座、吉色、幸运数字都不同,说明结果与完整星座码一一对应。按 `shu` 前缀建键会把三个星座的数据混在一起,键必须写到完整码。
同一星座在同一日重复调用返回一致:2026-09-18 对 `star=shuiping` 连调两次,`day` 对象逐字节相同。星座可以作为稳定的分组键。
## 键设计
接入点 1 的键由三部分组成:
```
horoscope:{star}:{period}:{bucket}
```
`star` 用完整英文码,`period` 取 `day` / `tomorrow` / `week` / `month` / `year`,`bucket` 按周期的 `time` 格式取,不要统一用当天日期:
| 周期 | 实测 `time` 样例 | 建议的 `bucket` 取法 |
|---|---|---|
| `day` | `20260918` | 当天日期 `yyyyMMdd` |
| `tomorrow` | `20260919` | 次日日期 `yyyyMMdd` |
| `week` | `20260913-20260920` | 区间起始日 `20260913` |
| `month` | `202609` | 月份 `yyyyMM` |
| `year` | `2026` | 年份 `yyyy` |
直接用当天日期给 `week`、`month`、`year` 做 `bucket` 会让这几个周期的缓存每天都失效,跨天时又浪费一次回源。按周期各自的粒度切分更匹配数据实际变化的节奏。
接入点 2 的键可以只用两个星座,并且不必区分顺序:
```
match:{star_a}:{star_b}
```
2026-09-18 实测依据有两条。一是性别不参与结果:`star1=shizi, gender1=1` 配 `star2=jinniu`,`gender2` 分别传 `0` 和 `1`,`match`、`love`、`proportion`、`review`、`attention` 全部相同。二是顺序不影响结果:`tianxie` 配 `shuiping` 与 `shuiping` 配 `tianxie` 两次调用,`match` 都是 `50分`、`proportion` 都是 `58:42`。
写键之前把两个星座排一次序,`match:tianxie:shuiping` 与 `match:shuiping:tianxie` 会收敛到同一个键,命中率更高。
注意返回体里的 `grxz1`、`grxz2` 与 `gender1`、`gender2` 不保证按同一位置对应,落库时不要把这两组拼成同一个人的记录。
## Python 实现
```python
import json
import time
import requests
import redis
r = redis.Redis(decode_responses=True)
BASE = "https://route.showapi.com"
WINDOWS = (1, 7, 17) # 接口文档标注的更新时刻
def seconds_to_next_window(buffer=600):
"""返回到下一个更新窗口的秒数,再加缓冲,避免失效瞬间集中回源。"""
now = time.localtime()
for hour in WINDOWS:
if now.tm_hour < hour:
target = time.mktime((now.tm_year, now.tm_mon, now.tm_mday,
hour, 0, 0, 0, 0, -1))
return int(target - time.time()) + buffer
tomorrow = time.mktime((now.tm_year, now.tm_mon, now.tm_mday + 1,
0, 0, 0, 0, 0, -1)) + WINDOWS[0] * 3600
return int(tomorrow - time.time()) + buffer
def get_horoscope(star: str, period: str, appkey: str) -> dict:
"""按 星座 + 周期 + 日期 取运势;命中缓存不消耗调用额度。"""
today = time.strftime("%Y%m%d")
bucket = {"week": "week", "month": time.strftime("%Y%m"),
"year": time.strftime("%Y")}.get(period, today)
key = f"horoscope:{star}:{period}:{bucket}"
cached = r.get(key)
if cached:
return json.loads(cached)
data = requests.get(f"{BASE}/872-1", params={
"appKey": appkey, "star": star,
"needTomorrow": "1", "needWeek": "1", "needMonth": "1", "needYear": "1",
}, timeout=15).json()
if data.get("showapi_res_code") != 0:
raise RuntimeError(f"系统错误: {data.get('showapi_res_error')}")
body = data["showapi_res_body"]
if str(body.get("ret_code")) != "0":
raise RuntimeError(f"业务错误: {body.get('remark')}")
payload = body.get(period)
if payload is None:
raise RuntimeError(f"返回中缺少周期 {period}")
ttl = seconds_to_next_window()
r.setex(key, ttl, json.dumps(payload, ensure_ascii=False))
return payload
def get_match(star_a: str, star_b: str, appkey: str) -> dict:
"""配对结果只由两个星座决定,键用排序后的星座对。"""
a, b = sorted([star_a, star_b])
key = f"match:{a}:{b}"
cached = r.get(key)
if cached:
return json.loads(cached)
data = requests.get(f"{BASE}/872-2", params={
"appKey": appkey, "star1": a, "gender1": "1",
"star2": b, "gender2": "0",
}, timeout=5).json()
if data.get("showapi_res_code") != 0:
raise RuntimeError(f"系统错误: {data.get('showapi_res_error')}")
body = data["showapi_res_body"]
if str(body.get("ret_code")) != "0":
raise RuntimeError(f"业务错误: {body.get('remark')}")
# 配对结果不随日期变化,TTL 可以给到 30 天
r.setex(key, 30 * 86400, json.dumps(body, ensure_ascii=False))
return body
```
cURL 与 Node.js 的取数写法:
```bash
curl -G "https://route.showapi.com/872-1" \
--data-urlencode "appKey=YOUR_APPKEY" \
--data-urlencode "star=shizi" \
--data-urlencode "needTomorrow=1" \
--data-urlencode "needWeek=1" \
--data-urlencode "needMonth=1" \
--data-urlencode "needYear=1" \
--max-time 15
```
```javascript
const url = new URL("https://route.showapi.com/872-1");
url.searchParams.set("appKey", "YOUR_APPKEY");
url.searchParams.set("star", "shizi");
["needTomorrow", "needWeek", "needMonth", "needYear"]
.forEach(k => url.searchParams.set(k, "1"));
const data = await (await fetch(url, { signal: AbortSignal.timeout(15000) })).json();
if (String(data.showapi_res_body?.ret_code) !== "0") throw new Error("业务失败");
const body = data.showapi_res_body;
const sig = ["day", "tomorrow", "week", "month", "year"].filter(k => body[k]).join(",");
console.log(sig, body.day.time, body.week.time, body.year.time);
```
## 缓存键与 TTL 一览
| 数据 | 缓存键 | 建议 TTL | 依据 |
|---|---|---|---|
| 接入点 1 今日运势 | `horoscope:{star}:day:{yyyyMMdd}` | 到下一个更新窗口加缓冲 | 文档标注每天 3 次更新 |
| 接入点 1 单周期 | `horoscope:{star}:{period}:{bucket}` | 同上 | 周期内内容不变 |
| 接入点 2 配对 | `match:{star_a}:{star_b}` | 30 天 | 结果与日期无关,实测可复现 |
## 预热任务
按更新窗口在整点后几分钟触发一次批量回源,写入缓存:
```python
def warmup(appkey: str, stars: list):
"""在 1 点、7 点、17 点后执行;12 个星座各取一次全周期。"""
for star in stars:
try:
get_horoscope(star, "day", appkey) # 内部已带四个开关
except Exception as e:
print("预热失败", star, e) # 单个星座失败不影响其余
STARS = ["baiyang", "jinniu", "shuangzi", "juxie", "shizi", "chunv",
"tiancheng", "tianxie", "sheshou", "mojie", "shuiping", "shuangyu"]
# 由定时任务在 01:05 / 07:05 / 17:05 调用 warmup("YOUR_APPKEY", STARS)
```
十二个星座各一次全周期请求,一轮预热是 12 次调用。对比基础版每日 100 次的额度,留出了充足的余量给未命中缓存的请求。
预热任务与用户回源建议共用令牌桶或信号量,把并发控制在档位允许的 QPS 之内。
## FAQ
**Q1:缓存多久过期合适?**
对齐更新窗口即可。接口文档标注每天 1 点、7 点、17 点更新,把 TTL 设到下一个窗口再加一段缓冲,窗口内直接命中缓存。
**Q2:缓存里会不会是过期数据?**
数据的刷新节奏是每天三次,TTL 跨过窗口后就自然失效。窗口内官方数据本身不变,读到的是同一份内容。
**Q3:配对接口也要缓存吗?**
要,收益更高。同一对星座的组合会被反复查询,且结果与日期无关。2026-09-18 实测同参数连调两次返回逐字节相同,可以给更长的 TTL。
**Q4:能不能按星座前缀建键?**
不能。2026-09-18 实测 `shuiping`、`shuangyu`、`shuangzi` 三个同前缀星座的贵人星座、吉色、幸运数字各不相同,结果与完整星座码一一对应,键必须写到完整码。
**Q5:缓存集体失效时怎么办?**
给 TTL 加 5 到 10 分钟缓冲,让失效时间错开更新时刻;再给回源加单飞保护,同一个键同时只有一个请求打到接口。
**Q6:一次请求带齐所有周期,缓存键怎么拆?**
按周期拆开存。一次回源拿到五个周期,分别写入 `horoscope:{star}:{period}:{bucket}` 五个键,之后各周期独立命中。
## 下一步阅读
- [星座运势查询:一次取齐五个周期的 needX 开关规则](https://www.showapi.com/guides/horoscope-multi-period-872)
- [星座运势查询的两层状态码排查](https://www.showapi.com/guides/horoscope-error-handling-872)
- [在星座社区与社交 App 里接入星座运势查询](https://www.showapi.com/guides/horoscope-community-app-872)
- [星座运势查询:用 Python / cURL / Node.js 跑通第一次调用](https://www.showapi.com/guides/horoscope-quickstart-872)
- **本系列共 13 篇**:查看[星座运势 API 指南总目录](https://www.showapi.com/guides/horoscope-guides-872)





