关于 recommendation-system
面向构建个性化推荐服务的开发者,提供可扩展推荐系统的设计与运维指导,涵盖特征存储、缓存、实验和质量监控。
主要功能
- 01用户与物品特征存储
- 02多层推荐缓存
- 03支持 A/B 测试的模型服务
- 04质量与延迟监控
- 05冷启动与多样性方案
适用场景
- 01构建个性化推荐 API
- 02通过实验比较推荐模型
- 03利用缓存降低响应延迟
- 04跟踪推荐质量指标
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ secondsky/ claude-skills --skill recommendation-system安装后在 Claude Code 中输入/recommendation-system确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/ab-testing-framework.mdcaching-strategies.mdmonitoring-alerting.mdproduction-architecture.md详细介绍
推荐系统
面向生产环境的可扩展推荐系统架构,包含特征存储、多层缓存、A/B 测试和全面监控。
何时使用此技能
在以下场景加载此技能:
- 构建推荐 API:大规模提供个性化推荐
- 实现缓存:通过多层缓存实现亚毫秒级延迟
- 运行 A/B 测试:试验推荐算法
- 监控质量:跟踪 CTR、转化率、多样性和覆盖率
- 优化性能:降低延迟、提高吞吐量
- 特征工程:使用特征存储管理用户和商品特征
快速开始:5 步构建推荐 API
# 1. Install dependencies
pip install "fastapi>=0.109.0" "redis>=5.0.0" "prometheus-client>=0.19.0"
# 2. Start Redis (for caching and feature store)
docker run -d -p 6379:6379 redis:alpine
# 3. Create recommendation service: app.py
cat > app.py << 'EOF'
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
import redis
import json
app = FastAPI()
cache = redis.Redis(host='localhost', port=6379, decode_responses=True)
class RecommendationResponse(BaseModel):
user_id: str
items: List[str]
cached: bool
@app.post("/recommendations", response_model=RecommendationResponse)
async def get_recommendations(user_id: str, n: int = 10):
# Check cache
cache_key = f"recs:{user_id}:{n}"
cached = cache.get(cache_key)
if cached:
return RecommendationResponse(
user_id=user_id,
items=json.loads(cached),
cached=True
)
# Generate recommendations (simplified)
items = [f"item_{i}" for i in range(n)]
# Cache for 5 minutes
cache.setex(cache_key, 300, json.dumps(items))
return RecommendationResponse(
user_id=user_id,
items=items,
cached=False
)
@app.get("/health")
async def health():
return {"status": "healthy"}
EOF
# 4. Run API
uvicorn app:app --host 0.0.0.0 --port 8000
# 5. Test
curl -X POST "http://localhost:8000/recommendations?user_id=user_123&n=10"
结果:不到 5 分钟即可获得一个带缓存的可用推荐 API。
系统架构
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ User Events │────▶│ Feature │────▶│ Model │
│ (clicks, │ │ Store │ │ Serving │
│ purchases) │ │ (Redis) │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Training │ │ API │
│ Pipeline │ │ (FastAPI) │
└─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Monitoring │
│ (Prometheus)│
└─────────────┘
核心组件
1. 特征存储
集中存储用户和商品特征:
import redis
import json
class FeatureStore:
"""Fast feature access with Redis caching."""
def __init__(self, redis_client):
self.redis = redis_client
self.ttl = 3600 # 1 hour
def get_user_features(self, user_id: str) -> dict:
cache_key = f"user_features:{user_id}"
cached = self.redis.get(cache_key)
if cached:
return json.loads(cached)
# Fetch from database
features = fetch_from_db(user_id)
# Cache
self.redis.setex(cache_key, self.ttl, json.dumps(features))
return features
2. 模型服务
为 A/B 测试提供多个模型:
class ModelServing:
"""Serve multiple recommendation models."""
def __init__(self):
self.models = {}
def register_model(self, name: str, model, is_default: bool = False):
self.models[name] = model
if is_default:
self.default_model = name
def predict(self, user_features: dict, item_features: list, model_name: str = None):
model = self.models.get(model_name or self.default_model)
return model.predict(user_features, item_features)
3. 缓存层
通过多层缓存实现低延迟:
class TieredCache:
"""L1 (memory) -> L2 (Redis) -> L3 (database)."""
def __init__(self, redis_client):
self.l1_cache = {} # In-memory
self.redis = redis_client # L2
def get(self, key: str):
# L1: In-memory (fastest)
if key in self.l1_cache:
return self.l1_cache[key]
# L2: Redis
cached = self.redis.get(key)
if cached:
value = json.loads(cached)
self.l1_cache[key] = value # Promote to L1
return value
# L3: Miss (fetch from database)
return None
关键指标
| 指标 | 描述 | 目标 |
|---|---|---|
| CTR | 点击率 | >5% |
| 转化率 | 来自推荐的购买量 | >2% |
| P95 延迟 | 第 95 百分位响应时间 | <200ms |
| 缓存命中率 | 由缓存提供的请求占比 | >80% |
| 覆盖率 | 获得推荐的商品目录占比 | >50% |
| 多样性 | 推荐内容的丰富程度 | >0.7 |
预防已知问题
1. 新用户冷启动
问题:没有历史记录的用户无法获得推荐,初次体验较差。
解决方案:使用基于热门度的备用方案:
def get_recommendations(user_id: str, n: int = 10):
user_features = feature_store.get_user_features(user_id)
# Check if new user (no purchase history)
if user_features.get('total_purchases', 0) == 0:
# Fallback to popular items
return get_popular_items(n)
# Personalized recommendations
return generate_personalized_recs(user_id, n)
2. 用户操作后缓存失效
问题:用户完成购买后,缓存中仍在推荐已购买的商品。
解决方案:在相关操作发生时使缓存失效:
INVALIDATING_ACTIONS = {'purchase', 'rating', 'add_to_cart'}
def on_user_action(user_id: str, action: str):
if action in INVALIDATING_ACTIONS:
cache_key = f"recs:{user_id}:*"
redis_client.delete(cache_key)
logger.info(f"Invalidated cache for {user_id} due to {action}")
3. 缓存过期引发惊群效应
问题:许多用户的缓存同时过期,导致数据库或模型过载。
解决方案:为 TTL 添加随机抖动:
import random
def set_cache(key: str, value: dict, base_ttl: int = 300):
# Add ±10% jitter
jitter = random.uniform(-0.1, 0.1) * base_ttl
ttl = int(base_ttl + jitter)
redis_client.setex(key, ttl, json.dumps(value))
4. 多样性不足导致信息茧房
问题:推荐内容过于相似,用户只能看到同一类别的内容。
解决方案:实施多样性约束:
def rank_with_diversity(items: list, scores: list, n: int = 10):
selected = []
category_counts = {}
for item, score in sorted(zip(items, scores), key=lambda x: -x[1]):
category = item['category']
# Limit 3 items per category
if category_counts.get(category, 0) >= 3:
continue
selected.append(item)
category_counts[category] = category_counts.get(category, 0) + 1
if len(selected) >= n:
break
return selected
5. 缺少监控导致问题悄然恶化
问题:推荐质量下降,直到用户投诉才有人发现。
解决方案:持续监控并设置告警:
from prometheus_client import Counter, Histogram
recommendation_clicks = Counter('recommendation_clicks_total')
recommendation_latency = Histogram('recommendation_latency_seconds')
@app.post("/recommendations")
async def get_recommendations(user_id: str):
start = time.time()
recs = generate_recs(user_id)
latency = time.time() - start
recommendation_latency.observe(latency)
return recs
@app.post("/track/click")
async def track_click(user_id: str, item_id: str):
recommendation_clicks.inc()
# Alert if CTR drops below 3%
6. 特征过时导致推荐过时
问题:用户偏好发生变化,但特征未更新,导致推荐不相关。
解决方案:设置适当的 TTL 和更新触发条件:
class FeatureStore:
def __init__(self, redis_client):
self.redis = redis_client
# Shorter TTL for frequently changing features
self.user_ttl = 300 # 5 minutes
self.item_ttl = 3600 # 1 hour
def update_on_event(self, user_id: str, event: str):
# Invalidate on important events
if event in ['purchase', 'rating']:
self.redis.delete(f"user_features:{user_id}")
logger.info(f"Refreshed features for {user_id}")
7. A/B 测试样本量过小
问题:过早判定胜出方案,结果不具备统计显著性。
解决方案:先计算所需样本量:
def calculate_sample_size(
baseline_rate: float,
min_detectable_effect: float,
alpha: float = 0.05,
power: float = 0.8
) -> int:
"""Calculate required sample size per variant."""
from scipy import stats
z_alpha = stats.norm.ppf(1 - alpha/2)
z_beta = stats.norm.ppf(power)
p1 = baseline_rate
p2 = baseline_rate * (1 + min_detectable_effect)
p_avg = (p1 + p2) / 2
n = (
(z_alpha + z_beta)**2 * 2 * p_avg * (1 - p_avg) /
(p2 - p1)**2
)
return int(n)
# Example: detect 10% lift with baseline CTR=5%
n_required = calculate_sample_size(
baseline_rate=0.05,
min_detectable_effect=0.10
)
print(f"Required sample size: {n_required} per variant")
# Wait until both variants reach this size before concluding
何时加载参考资料
加载参考文件,了解生产环境实现的详细信息:
-
生产架构:加载
references/production-architecture.md,查看完整的 FeatureStore、ModelServing 和 RecommendationService 实现,涵盖批量获取、缓存集成和 FastAPI 部署模式。 -
缓存策略:在实现多层缓存(L1/L2/L3)、缓存预热、失效策略、概率刷新或惊群效应防范时,加载
references/caching-strategies.md。 -
A/B 测试框架:加载
references/ab-testing-framework.md,了解确定性变体分配、Thompson 抽样(多臂老虎机)、贝叶斯和频率学派显著性检验,以及实验跟踪。 -
监控与告警:加载
references/monitoring-alerting.md,了解 Prometheus 指标集成、仪表板端点、告警规则和质量监控(多样性、覆盖率)。
最佳实践
- 特征预计算:离线计算特征,并从缓存提供
- 批量获取:使用 Redis MGET 获取多个用户/条目的数据
- 积极缓存:为用户推荐设置 5-15 分钟的 TTL
- 优雅降级:个性化推荐失败时,返回热门条目
- 全面监控:跟踪 CTR、延迟、多样性和覆盖率
- 持续进行 A/B 测试:不断尝试新算法
- 多样性约束:确保推荐内容丰富多样
- 解释推荐理由:提供原因(“评分很高”“很受欢迎”)
常见模式
推荐服务
class RecommendationService:
def __init__(self, feature_store, model_serving, cache):
self.feature_store = feature_store
self.model_serving = model_serving
self.cache = cache
def get_recommendations(self, user_id: str, n: int = 10):
# 1. Check cache
cached = self.cache.get(f"recs:{user_id}:{n}")
if cached:
return cached
# 2. Get features
user_features = self.feature_store.get_user_features(user_id)
candidates = self.get_candidates(user_id)
# 3. Score candidates
scores = self.model_serving.predict(user_features, candidates)
# 4. Rank with diversity
recommendations = self.rank_with_diversity(candidates, scores, n)
# 5. Cache
self.cache.set(f"recs:{user_id}:{n}", recommendations, ttl=300)
return recommendations
A/B 测试
def assign_variant(user_id: str, experiment_id: str) -> str:
"""Deterministic assignment - same user always gets same variant."""
import hashlib
hash_input = f"{user_id}:{experiment_id}"
hash_value = int(hashlib.md5(hash_input.encode()).hexdigest(), 16)
# 50/50 split
return 'control' if hash_value % 2 == 0 else 'treatment'
# Usage
variant = assign_variant('user_123', 'rec_algo_v2')
model_name = 'main' if variant == 'control' else 'experimental'
recs = get_recommendations(user_id, model_name=model_name)
监控
from prometheus_client import Counter, Histogram
requests_total = Counter('recommendation_requests_total', ['status'])
latency_seconds = Histogram('recommendation_latency_seconds')
@app.post("/recommendations")
async def get_recommendations(user_id: str):
with latency_seconds.time():
try:
recs = generate_recs(user_id)
requests_total.labels(status='success').inc()
return recs
except Exception as e:
requests_total.labels(status='error').inc()
raise