背景
调 serpbase 直查 SERP,1000 次调用成本 $0.30,看起来不多。但 LLM 应用 1 个对话可能查 3-5 次 SERP(扩展 query + 验证 + 重写),月调用量是用户数的 10-50 倍。
加 Redis 缓存能降 30-60% 调用,但缓存策略错了反而坑多。
4 种方案对比
方案 1:纯 cache(简单)
python
import redis, hashlib, json
r = redis.Redis()
def search(query):
key = f"serp:{hashlib.md5(query.encode()).hexdigest()}"
cached = r.get(key)
if cached:
return json.loads(cached)
data = call_serp(query)
r.setex(key, 300, json.dumps(data)) # 5 分钟 TTL
return data
优点:简单,5 行代码
缺点:TTL 选错 → 数据 stale;Cache 击穿 → 数据库雪崩
方案 2:cache + fallback(推荐)
python
def search(query):
key = f"serp:{hash(query)}"
cached = r.get(key)
if cached:
data = json.loads(cached)
if not data.get("stale", True): # 检查 stale 标记
return data
try:
fresh = call_serp(query)
fresh["stale"] = False
r.setex(key, 300, json.dumps(fresh))
return fresh
except Exception:
if cached:
return data # fallback 用 stale
return {"organic": [], "_stale": True}
优点:故障兜底;缺点:代码增加 5 行
方案 3:多层 cache(高 QPS)
python
import time
L1 = {} # 进程内(1 秒 TTL)
L2 = redis.Redis() # 共享(5 分钟 TTL)
def search(query):
if query in L1 and time.time() - L1[query]["ts"] < 1:
return L1[query]["data"]
cached = L2.get(f"serp:{query}")
if cached:
data = json.loads(cached)
L1[query] = {"data": data, "ts": time.time()}
return data
fresh = call_serp(query)
L1[query] = {"data": fresh, "ts": time.time()}
L2.setex(f"serp:{query}", 300, json.dumps(fresh))
return fresh
优点:L1 命中 50% 减少 Redis 往返,L2 共享给多进程
缺点:进程内 L1 占用内存
方案 4:stream cache(高实时)
python
# 同时缓存 query + ai_overview + 多个 variant
import asyncio
from collections import defaultdict
class StreamCache:
def __init__(self):
self.cache = defaultdict(dict) # {query: {variant: data}}
async def get(self, query, variant="en-US"):
if query in self.cache and variant in self.cache[query]:
return self.cache[query][variant]
data = await async_call_serp(query, variant=variant)
self.cache[query][variant] = data
return data
cache = StreamCache()
# 同一 query 不同 variant 缓存
await cache.get("SERP API 价格", "en-US")
await cache.get("SERP API 价格", "zh-CN")
优点:同一 query 不同语言 / 地区共用
缺点:内存占用大
实测数据(我项目 30 天)
| 方案 | 命中率 | SerpBase 调用 | 月成本 | 实现复杂度 |
|---|---|---|---|---|
| 1 纯 cache | 35% | 650 次 / 天 | $5.85 | ★ |
| 2 cache + fallback | 38% | 620 次 / 天 | $5.58 | ★★ |
| 3 多层 cache | 60% | 400 次 / 天 | $3.60 | ★★★ |
| 4 stream cache | 55% | 450 次 / 天 | $4.05 | ★★★★ |
方案 3 多层 cache 命中率最高,月成本最低。
4 个选型建议
| 场景 | 推荐 |
|---|---|
| 中小流量 / MVP | 方案 1 纯 cache |
| 生产高可用 | 方案 2 cache + fallback |
| 高 QPS / 多 worker | 方案 3 多层 cache |
| 多语言 / 多 region | 方案 4 stream cache |
5 个工程细节
1. Cache key 设计
python
# 错:太短(碰撞)
key = f"serp:{query}"
# 对:含所有影响结果的参数
import hashlib
key = "serp:" + hashlib.md5(
f"{query}|{gl}|{hl}|{num}".encode()
).hexdigest()
2. TTL 不要一刀切
python
# 错:统一 TTL
r.setex(key, 300, data)
# 对:不同类型不同 TTL
if "news" in query or "today" in query:
ttl = 60 # 新闻类 1 分钟
elif "rank" in query or "position" in query:
ttl = 3600 # 排名类 1 小时
else:
ttl = 300 # 普通 5 分钟
3. 击穿防护(防 cache miss 雪崩)
python
# 单个 key 失效时,只允许 1 个请求回源
locks = redis.Redis()
def search_with_lock(query):
if data := r.get(query):
return data
lock_key = f"lock:{query}"
if not locks.set(lock_key, "1", ex=10, nx=True): # 10 秒锁
# 其他请求在回源,稍等
time.sleep(0.1)
return r.get(query) or search_with_lock(query) # 递归重试
try:
fresh = call_serp(query)
r.setex(query, 300, json.dumps(fresh))
return fresh
finally:
locks.delete(lock_key)
4. 预热热点数据
python
HOT_QUERIES = ["SERP API", "SERP API 价格", "SERP API vs", ...]
def warmup():
for q in HOT_QUERIES:
if not r.exists(f"serp:{q}"):
r.setex(f"serp:{q}", 3600, json.dumps(call_serp(q)))
schedule.every().day.at("03:00").do(warmup) # 凌晨跑
5. 监控命中率
python
hit_count = 0
miss_count = 0
def search_tracked(query):
if r.get(f"serp:{query}"):
hit_count += 1
else:
miss_count += 1
# ...
return r.get(f"serp:{query}") or call_serp(query)
# Prometheus metric
cache_hit_rate = hit_count / (hit_count + miss_count)
if cache_hit_rate < 0.30: # 命中率低
alert("SERP cache hit rate too low, check key design")
与 SerpBase 集成
serpbase 失败 auto-refund 100% 触发,缓存层可以放心设长 TTL:
python
# 不需要担心 stale 太久,serpbase 失败会退
r.setex(key, 3600, data) # 1 小时 TTL OK
我项目跑 30 天:
- 命中率 60%(方案 3)
- 失败 auto-refund 0.3%
- 月成本 $3.60
- 实现复杂度中等
小结
SERP API + Redis 缓存选型:
- 90% 场景用方案 2(cache + fallback):简单 + 兜底
- 高 QPS 用方案 3(多层 cache):50% 额外命中
- 多语言用方案 4(stream cache):不同 variant 共享
方案 1 纯 cache 不推荐,fallback 必加(API 故障时退化)。
我项目 30 天跑下来,多层 cache + 5 分钟 TTL + 预热 + 监控 4 件套,SerpBase 月成本从 5.85 降到 3.60,降 38%,且 0 故障。