智柴论坛推荐系统技术文档
1. 系统概述 2. 架构设计 3. 核心组件 4. 推荐算法 5. 数据流与缓存机制 6. 降级策略 7. 性能优化 8. 使用示例 9. 未来改进方向
目录
1. 系统概述 2. 架构设计 3. 核心组件 4. 推荐算法 5. 数据流与缓存机制 6. 降级策略 7. 性能优化 8. 使用示例 9. 未来改进方向
系统概述
智柴论坛推荐系统是一个混合推荐系统,结合了 协同过滤(Collaborative Filtering) 和 基于内容的推荐(Content-Based) 两种算法,为用户提供个性化的话题推荐。
核心特性
- 混合推荐算法:协同过滤(60%)+ 内容相似度(40%)
- 异步缓存架构:通过 Redis 缓存 + SQLite 持久化,确保高性能
- 智能降级策略:推荐失败时自动回退到热门话题
- 实时计算:支持用户推荐和话题相似度推荐两种场景
- 中文分词支持:针对中英文混合内容优化的分词算法
推荐场景
1. 用户推荐:基于用户历史行为(发布话题、回复)推荐感兴趣的话题 2. 话题推荐:基于话题内容相似度推荐相关话题(用于话题详情页)
架构设计
整体架构图
┌─────────────────────────────────────────────────────────────┐
│ 前端请求层 │
│ TopicController.showTopic() │
│ └─> RecommendationService.getRelatedTopics() │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 服务层 (RecommendationService) │
│ - 缓存键管理 │
│ - 参数验证与限制 │
│ - 调用 DataService 获取数据 │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 缓存层 (DataService) │
│ - Redis 缓存查询 │
│ - 缓存未命中时发送异步填充请求 │
│ - 等待异步结果(最多 2.5 秒) │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 异步队列 (cache_fill_queue) │
│ Redis List: zhichai:cache_fill_queue │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 后台处理进程 (process_sqlite_queue.php) │
│ AsyncSQLiteWriter.executeCacheFillTask() │
│ ├─> handleUserRecommendations() │
│ └─> handleTopicRecommendations() │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 推荐引擎 (RecommendationEngine) │
│ ├─> buildHybridScores() (用户推荐) │
│ │ ├─> computeCollaborativeScores() │
│ │ └─> computeContentScores() │
│ └─> rankRelatedTopics() (话题推荐) │
│ └─> computeTfIdfSimilarities() │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 数据存储层 │
│ ├─> SQLite: topics, replies, user_topic_views │
│ └─> Redis: 推荐结果缓存 (TTL: 15分钟) │
└─────────────────────────────────────────────────────────────┘
关键设计原则
1. 异步优先:推荐计算在后台进程异步执行,不阻塞主请求 2. 缓存优先:所有推荐结果优先从 Redis 缓存读取 3. 降级保障:推荐失败时自动回退到热门话题,确保用户体验 4. 数据分离:推荐引擎是纯计算逻辑,不依赖外部资源,便于测试
核心组件
1. RecommendationService
职责:推荐服务的入口,负责缓存管理和参数验证
关键方法:
// 获取用户推荐
getRecommendationsForUser(int $userId, int $limit = 10): array
// 获取话题相关推荐
getRelatedTopics(int $topicId, int $limit = 10): array
配置参数:
DEFAULT_LIMIT = 10:默认推荐数量MAX_LIMIT = 20:最大推荐数量CACHE_TTL = 900:缓存有效期(15分钟)WAIT_TIMEOUT_MS = 2500:等待异步结果超时(2.5秒)
2. RecommendationEngine
职责:纯计算引擎,实现推荐算法核心逻辑
关键方法:
// 混合推荐得分计算(用户推荐)
buildHybridScores(
int $targetUserId,
array $targetInteractions, // 目标用户与话题的交互权重
array $allUserInteractions, // 所有用户与话题的交互权重
array $topicContents, // 话题内容数据
array $excludeTopicIds = [], // 排除的话题ID
int $limit = 10
): array
// 话题相似度排序(话题推荐)
rankRelatedTopics(
array $queryTopic, // 查询话题
array $candidateDocs, // 候选话题
int $limit = 10
): array
算法权重:
CF_WEIGHT = 0.6:协同过滤权重CONTENT_WEIGHT = 0.4:内容相似度权重MIN_SIMILARITY = 0.01:最小相似度阈值
3. AsyncSQLiteWriter
职责:异步处理推荐计算,负责数据收集和结果构建
关键方法:
// 处理用户推荐请求
handleUserRecommendations(\PDO $pdo, array $payload): array
// 处理话题推荐请求
handleTopicRecommendations(\PDO $pdo, array $payload): array
// 收集用户交互数据
collectUserInteractionsForRecommendations(\PDO $pdo, int $userId): array
// 收集全局交互数据
collectGlobalInteractionsForRecommendations(\PDO $pdo, array &$topicDetails, int $excludeUserId): array
推荐算法
1. 用户推荐算法(混合推荐)
#### 1.1 数据收集
用户交互权重:
- 发布话题:权重
3.0 - 回复话题:权重
1.0
- 用户发布的话题:最近 200 条
- 用户回复的话题:最近 400 条
- 全局话题数据:最近 1000 条(用于协同过滤)
- 全局回复数据:最近 2000 条(用于协同过滤)
算法:基于用户的协同过滤(User-Based CF)
步骤:
1. 计算用户相似度:
similarity(userA, userB) = cosine_similarity(
userA_interactions, // 用户A与话题的交互向量
userB_interactions // 用户B与话题的交互向量
)
2. 推荐得分计算:
score(topic) = Σ(similarity(user, similar_user) × weight(similar_user, topic))
其中:
similar_user:与目标用户相似的其他用户weight(similar_user, topic):相似用户对话题的交互权重- 只考虑相似度 ≥ 0.01 的用户
- 排除用户已交互过的话题
- 排除隐藏的话题
算法:TF-IDF + 余弦相似度
步骤:
1. 构建用户兴趣向量:
user_vector = Σ(topic_vector × (1 + interaction_weight))
其中:
topic_vector:话题的 TF-IDF 向量interaction_weight:用户对该话题的交互权重
similarity(topic) = cosine_similarity(user_vector, topic_tfidf_vector)
3. TF-IDF 计算:
TF(term, doc) = count(term in doc)
IDF(term) = log((total_docs + 1) / (docs_containing_term + 1)) + 1
TF-IDF(term, doc) = TF(term, doc) × IDF(term)
#### 1.4 混合得分计算
公式:
final_score = 0.6 × normalized_cf_score + 0.4 × normalized_content_score
归一化:
- 协同过滤得分和内容得分分别归一化到 [0, 1]
- 归一化方法:
normalized_score = score / max_score
2. 话题推荐算法(内容相似度)
算法:TF-IDF + 余弦相似度
步骤:
1. 候选话题筛选:
- 排除当前话题本身
- 排除隐藏的话题
- 从最近创建的话题中选择(默认 100 条)
- 对查询话题和候选话题进行分词
- 计算每个话题的 TF-IDF 向量
similarity(topic) = cosine_similarity(query_tfidf_vector, candidate_tfidf_vector)
4. 排序与筛选:
- 按相似度降序排序
- 返回前 N 个话题(N = limit × 2,用于后续过滤)
3. 分词算法
分词策略:
1. 文本预处理:
- 转换为小写
- HTML 实体解码
/[\p{L}\p{N}]+/u
- 匹配所有字母和数字的连续序列
- 如果 token 包含中文且长度 > 1,拆分为单字
- 例如:"人工智能" → ["人", "工", "智", "能"]
- 过滤常见停用词(中英文)
- 过滤单字符 token(除非是中文单字)
// 英文停用词
'the', 'and', 'for', 'with', 'that', 'this', ...
// 中文停用词
'的', '了', '和', '是', '我', '我们', '你', ...
// 技术停用词
'php', 'code', 'topic', 'reply', 'forum', 'http', 'https', ...
数据流与缓存机制
数据流图
用户请求推荐
│
▼
RecommendationService.getRelatedTopics()
│
├─> 检查 Redis 缓存
│ ├─> 缓存命中 → 直接返回
│ └─> 缓存未命中 → 继续
│
▼
DataService.get()
│
├─> 发送异步填充请求到 cache_fill_queue
│ └─> 等待异步结果(最多 2.5 秒)
│ ├─> 收到结果 → 返回并写入缓存
│ └─> 超时 → 返回 null
│
▼
process_sqlite_queue.php (后台进程)
│
├─> 从 cache_fill_queue 读取请求
├─> 调用 AsyncSQLiteWriter.executeCacheFillTask()
│ └─> handleTopicRecommendations()
│ ├─> 从 SQLite 查询话题数据
│ ├─> 调用 RecommendationEngine.rankRelatedTopics()
│ └─> 构建推荐结果
│
▼
写入 Redis 缓存
│
└─> 缓存键: topic_recommendations:{topicId}:{limit}
TTL: 900 秒(15分钟)
缓存策略
缓存键格式:
- 用户推荐:
user_recommendations:{userId}:{limit} - 话题推荐:
topic_recommendations:{topicId}:{limit}
{
"id": 123,
"title": "话题标题",
"snippet": "内容摘要...",
"score": 0.8523,
"cf_score": 0.6234,
"content_score": 0.4567,
"created_at": "2025-01-15 18:30:00",
"author_id": 456
}
缓存 TTL:
- 推荐结果:900 秒(15分钟)
- NULL 结果:2 秒(防止频繁查询不存在的数据)
异步填充机制
队列名称:zhichai:cache_fill_queue
消息格式:
{
"type": "topic_recommendations",
"payload": {
"topic_id": 123,
"limit": 5
},
"response_key": "cache_response:req_67890abcdef"
}
响应机制:
1. 后台进程计算完成后,将结果写入 cache_response:{requestId}
2. 主进程通过 BRPOP 等待响应(最多 2.5 秒)
3. 收到响应后,将结果写入主缓存键并返回
降级策略
降级场景
1. 用户无交互数据:回退到热门话题 2. 协同过滤无结果:仅使用内容推荐 3. 内容推荐无结果:回退到热门话题 4. 话题不存在或隐藏:回退到热门话题 5. 缓存填充超时:返回空数组(前端不显示推荐)
热门话题算法
排序规则:
ORDER BY (
COALESCE(rc.reply_count, 0) * 2 +
(julianday('now') - julianday(t.created_at)) * -0.1
) DESC
得分公式:
score = reply_count × 2 - days_since_created × 0.1
特点:
- 回复数越多,得分越高
- 时间越新,得分越高
- 平衡了热度和新鲜度
性能优化
1. 数据量限制
用户推荐:
- 用户发布话题:最多 200 条
- 用户回复话题:最多 400 条
- 全局话题:最多 1000 条
- 全局回复:最多 2000 条
- 候选话题:最多 100 条(或
limit × 10)
2. 计算优化
预计算候选集:
- 话题推荐时,先筛选候选话题(排除隐藏、排除自身)
- 减少需要计算相似度的话题数量
- 最小相似度:0.01
- 低于阈值的结果直接丢弃
- 用户推荐:计算
limit × 3个结果,最终返回limit个 - 话题推荐:计算
limit × 2个结果,最终返回limit个
3. 缓存优化
缓存预热:
- 热门话题可以定期预热
- 用户推荐可以在用户活跃时预热
- 主缓存:完整的推荐结果(TTL: 15分钟)
- NULL 缓存:防止频繁查询不存在的数据(TTL: 2秒)
4. 异步处理
非阻塞设计:
- 推荐计算在后台进程异步执行
- 主请求最多等待 2.5 秒,超时后返回空结果
- 使用 Redis List 作为消息队列
- 支持多个后台进程并发处理
使用示例
1. 在话题详情页展示推荐
// TopicController.php
$recommendationService = new \Services\RecommendationService();
$relatedTopicsRaw = $recommendationService->getRelatedTopics($topicId, 5);
// 转换为 Topic 对象
foreach ($relatedTopicsRaw as $relatedTopicData) {
if (isset($relatedTopicData['id'])) {
$relatedTopic = $this->topicService->getTopicByID((int)$relatedTopicData['id']);
// ... 添加到推荐列表
}
}
2. 获取用户推荐
// 获取用户推荐(最多10个)
$recommendationService = new \Services\RecommendationService();
$recommendations = $recommendationService->getRecommendationsForUser($userId, 10);
foreach ($recommendations as $rec) {
echo "推荐话题: {$rec['title']} (得分: {$rec['score']})\n";
echo " - 协同过滤得分: {$rec['cf_score']}\n";
echo " - 内容得分: {$rec['content_score']}\n";
}
3. 前端展示
<!-- views/topic_content.html -->
<?php if (!empty($data['RelatedTopics'])): ?>
<div class="row mt-5 mb-4">
<div class="col-12">
<div class="card shadow-sm">
<div class="card-header bg-light">
<h5 class="mb-0">
<i class="bi bi-lightbulb"></i> 相关推荐
</h5>
</div>
<div class="card-body">
<div class="row g-3">
<?php foreach ($data['RelatedTopics'] as $relatedTopic): ?>
<div class="col-12 col-sm-6 col-md-4 col-lg">
<!-- 推荐卡片 -->
</div>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
未来改进方向
1. 算法优化
深度学习推荐:
- 引入神经网络模型(如 Wide & Deep)
- 使用 Embedding 技术学习用户和话题的向量表示
- 基于用户实时行为(点击、浏览时长)更新推荐
- 使用在线学习算法(如 FTRL)
- 避免推荐结果过于相似
- 引入多样性指标(如 Intra-List Diversity)
2. 数据增强
行为数据:
- 记录用户浏览时长
- 记录用户点赞、收藏行为
- 记录用户搜索关键词
- 提取话题标签
- 分析话题情感倾向
- 识别话题类别(技术、生活、讨论等)
3. 性能提升
分布式计算:
- 使用 Spark 或 Flink 进行大规模推荐计算
- 支持实时推荐和离线推荐两种模式
- 使用 Redis Cluster 支持更大规模
- 引入多级缓存(L1: 本地缓存,L2: Redis)
- 定期批量计算热门推荐
- 使用增量更新机制
4. 评估体系
离线评估:
- A/B 测试框架
- 评估指标:准确率、召回率、覆盖率、多样性
- 点击率(CTR)
- 转化率(用户从推荐进入话题详情)
- 用户满意度反馈
5. 冷启动优化
新用户:
- 基于注册信息(兴趣标签)推荐
- 基于地理位置推荐
- 推荐热门话题
- 基于话题标签匹配
- 基于作者历史话题推荐
- 基于话题类别推荐
附录
A. 配置参数
| 参数 | 默认值 | 说明 |
|---|---|---|
DEFAULT_LIMIT | 10 | 默认推荐数量 |
MAX_LIMIT | 20 | 最大推荐数量 |
CACHE_TTL | 900 | 缓存有效期(秒) |
WAIT_TIMEOUT_MS | 2500 | 等待异步结果超时(毫秒) |
CF_WEIGHT | 0.6 | 协同过滤权重 |
CONTENT_WEIGHT | 0.4 | 内容相似度权重 |
MIN_SIMILARITY | 0.01 | 最小相似度阈值 |
B. 数据库表结构
topics 表:
id: 话题IDtitle: 话题标题content: 话题内容author_id: 作者IDcreated_at: 创建时间is_hidden: 是否隐藏
id: 回复IDtopic_id: 话题IDauthor_id: 作者IDcontent: 回复内容created_at: 创建时间
user_id: 用户IDtopic_id: 话题IDview_count: 浏览次数last_viewed_at: 最后浏览时间
C. 相关文件
src/Services/RecommendationService.php:推荐服务入口src/Services/RecommendationEngine.php:推荐算法引擎src/Services/AsyncSQLiteWriter.php:异步数据处理src/Services/DataService.php:缓存服务src/Controllers/TopicController.php:话题控制器(使用推荐服务)views/topic_content.html:话题详情页模板(展示推荐)
文档版本:v1.0 最后更新:2025-01-15 维护者:智柴论坛开发团队