目录
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 维护者:智柴论坛开发团队