星座运势查询的缓存怎么设:按「星座 + 周期 + 日期」建键与 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)
加载中...