您正在查看静态缓存页面 · 查看完整动态版本 · 登录 参与讨论

智柴论坛推荐系统技术文档

✨步子哥 (steper) 2025年11月12日 06:06 0 次浏览

目录

  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 条(用于协同过滤)

1.2 协同过滤(Collaborative Filtering)

算法:基于用户的协同过滤(User-Based CF)

步骤

  1. 计算用户相似度
   similarity(userA, userB) = cosine_similarity(
       userA_interactions,  // 用户A与话题的交互向量
       userB_interactions    // 用户B与话题的交互向量
   )
   ```

2. **推荐得分计算**:
   ```php
   score(topic) = Σ(similarity(user, similar_user) × weight(similar_user, topic))
   ```
   其中:
   - `similar_user`:与目标用户相似的其他用户
   - `weight(similar_user, topic)`:相似用户对话题的交互权重
   - 只考虑相似度 ≥ 0.01 的用户

3. **排除规则**:
   - 排除用户已交互过的话题
   - 排除隐藏的话题

#### 1.3 内容相似度(Content-Based)

**算法**:TF-IDF + 余弦相似度

**步骤**:

1. **构建用户兴趣向量**:
   ```php
   user_vector = Σ(topic_vector × (1 + interaction_weight))
   ```
   其中:
   - `topic_vector`:话题的 TF-IDF 向量
   - `interaction_weight`:用户对该话题的交互权重

2. **计算候选话题相似度**:
   ```php
   similarity(topic) = cosine_similarity(user_vector, topic_tfidf_vector)
   ```

3. **TF-IDF 计算**:
   ```php
   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 混合得分计算

**公式**:
php finalscore = 0.6 × normalizedcfscore + 0.4 × normalizedcontentscore

**归一化**:
- 协同过滤得分和内容得分分别归一化到 [0, 1]
- 归一化方法:`normalized_score = score / max_score`

**排序规则**:
1. 按最终得分降序
2. 得分相同时,优先协同过滤得分高的
3. 协同过滤得分也相同时,优先内容得分高的

### 2. 话题推荐算法(内容相似度)

**算法**:TF-IDF + 余弦相似度

**步骤**:

1. **候选话题筛选**:
   - 排除当前话题本身
   - 排除隐藏的话题
   - 从最近创建的话题中选择(默认 100 条)

2. **TF-IDF 向量化**:
   - 对查询话题和候选话题进行分词
   - 计算每个话题的 TF-IDF 向量

3. **相似度计算**:
   ```php
   similarity(topic) = cosine_similarity(query_tfidf_vector, candidate_tfidf_vector)
   ```

4. **排序与筛选**:
   - 按相似度降序排序
   - 返回前 N 个话题(N = limit × 2,用于后续过滤)

### 3. 分词算法

**分词策略**:

1. **文本预处理**:
   - 转换为小写
   - HTML 实体解码

2. **正则匹配**:
   ```regex
   /[\p{L}\p{N}]+/u
   ```
   - 匹配所有字母和数字的连续序列

3. **中文处理**:
   - 如果 token 包含中文且长度 > 1,拆分为单字
   - 例如:"人工智能" → ["人", "工", "智", "能"]

4. **停用词过滤**:
   - 过滤常见停用词(中英文)
   - 过滤单字符 token(除非是中文单字)

5. **停用词表**:
   ```php
   // 英文停用词
   'the', 'and', 'for', 'with', 'that', 'this', ...
   
   // 中文停用词
   '的', '了', '和', '是', '我', '我们', '你', ...
   
   // 技术停用词
   'php', 'code', 'topic', 'reply', 'forum', 'http', 'https', ...
   ```

---

## 数据流与缓存机制

### 数据流图

用户请求推荐


RecommendationService.getRelatedTopics()

├─> 检查 Redis 缓存
│ ├─> 缓存命中 → 直接返回
│ └─> 缓存未命中 → 继续


DataService.get()

├─> 发送异步填充请求到 cache
fillqueue
│ └─> 等待异步结果(最多 2.5 秒)
│ ├─> 收到结果 → 返回并写入缓存
│ └─> 超时 → 返回 null


process
sqlitequeue.php (后台进程)

├─> 从 cache
fillqueue 读取请求
├─> 调用 AsyncSQLiteWriter.executeCacheFillTask()
│ └─> handleTopicRecommendations()
│ ├─> 从 SQLite 查询话题数据
│ ├─> 调用 RecommendationEngine.rankRelatedTopics()
│ └─> 构建推荐结果


写入 Redis 缓存

└─> 缓存键: topic
recommendations:{topicId}:{limit}
TTL: 900 秒(15分钟)


### 缓存策略

**缓存键格式**:
- 用户推荐:`user_recommendations:{userId}:{limit}`
- 话题推荐:`topic_recommendations:{topicId}:{limit}`

**缓存结构**:

json
{
"id": 123,
"title": "话题标题",
"snippet": "内容摘要...",
"score": 0.8523,
"cfscore": 0.6234,
"content
score": 0.4567,
"createdat": "2025-01-15 18:30:00",
"author
id": 456
}


**缓存 TTL**:
- 推荐结果:900 秒(15分钟)
- NULL 结果:2 秒(防止频繁查询不存在的数据)

### 异步填充机制

**队列名称**:`zhichai:cache_fill_queue`

**消息格式**:

json
{
"type": "topicrecommendations",
"payload": {
"topic
id": 123,
"limit": 5
},
"responsekey": "cacheresponse:req67890abcdef"
}


**响应机制**:
1. 后台进程计算完成后,将结果写入 `cache_response:{requestId}`
2. 主进程通过 `BRPOP` 等待响应(最多 2.5 秒)
3. 收到响应后,将结果写入主缓存键并返回

---

## 降级策略

### 降级场景

1. **用户无交互数据**:回退到热门话题
2. **协同过滤无结果**:仅使用内容推荐
3. **内容推荐无结果**:回退到热门话题
4. **话题不存在或隐藏**:回退到热门话题
5. **缓存填充超时**:返回空数组(前端不显示推荐)

### 热门话题算法

**排序规则**:

sql
ORDER BY (
COALESCE(rc.reply
count, 0) 2 +
(julianday('now') - julianday(t.createdat)) -0.1
) DESC


**得分公式**:

score = replycount × 2 - dayssincecreated × 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. 在话题详情页展示推荐

php
// 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. 获取用户推荐

php
// 获取用户推荐(最多10个)
$recommendationService = new \Services\RecommendationService();
$recommendations = $recommendationService->getRecommendationsForUser($userId, 10);

foreach ($recommendations as $rec) {
echo "推荐话题: {$rec['title']} (得分: {$rec['score']})\n";
echo " - 协同过滤得分: {$rec['cfscore']}\n";
echo " - 内容得分: {$rec['content
score']}\n";
}


### 3. 前端展示

html







相关推荐




$data['RelatedTopics'] as $relatedTopic): ?>










`` --- ## 未来改进方向 ### 1. 算法优化 **深度学习推荐**: - 引入神经网络模型(如 Wide & Deep) - 使用 Embedding 技术学习用户和话题的向量表示 **实时学习**: - 基于用户实时行为(点击、浏览时长)更新推荐 - 使用在线学习算法(如 FTRL) **多样性优化**: - 避免推荐结果过于相似 - 引入多样性指标(如 Intra-List Diversity) ### 2. 数据增强 **行为数据**: - 记录用户浏览时长 - 记录用户点赞、收藏行为 - 记录用户搜索关键词 **内容特征**: - 提取话题标签 - 分析话题情感倾向 - 识别话题类别(技术、生活、讨论等) ### 3. 性能提升 **分布式计算**: - 使用 Spark 或 Flink 进行大规模推荐计算 - 支持实时推荐和离线推荐两种模式 **缓存优化**: - 使用 Redis Cluster 支持更大规模 - 引入多级缓存(L1: 本地缓存,L2: Redis) **预计算**: - 定期批量计算热门推荐 - 使用增量更新机制 ### 4. 评估体系 **离线评估**: - A/B 测试框架 - 评估指标:准确率、召回率、覆盖率、多样性 **在线评估**: - 点击率(CTR) - 转化率(用户从推荐进入话题详情) - 用户满意度反馈 ### 5. 冷启动优化 **新用户**: - 基于注册信息(兴趣标签)推荐 - 基于地理位置推荐 - 推荐热门话题 **新话题**: - 基于话题标签匹配 - 基于作者历史话题推荐 - 基于话题类别推荐 --- ## 附录 ### A. 配置参数 | 参数 | 默认值 | 说明 | |------|--------|------| | DEFAULTLIMIT | 10 | 默认推荐数量 | | MAXLIMIT | 20 | 最大推荐数量 | | CACHETTL | 900 | 缓存有效期(秒) | | WAITTIMEOUTMS | 2500 | 等待异步结果超时(毫秒) | | CFWEIGHT | 0.6 | 协同过滤权重 | | CONTENTWEIGHT | 0.4 | 内容相似度权重 | | MINSIMILARITY | 0.01 | 最小相似度阈值 | ### B. 数据库表结构 **topics 表**: - id: 话题ID - title: 话题标题 - content: 话题内容 - authorid: 作者ID - createdat: 创建时间 - ishidden: 是否隐藏 **replies 表**: - id: 回复ID - topicid: 话题ID - authorid: 作者ID - content: 回复内容 - createdat: 创建时间 **user_topic_views 表**(用于推荐系统): - userid: 用户ID - topicid: 话题ID - viewcount: 浏览次数 - lastviewedat: 最后浏览时间 ### 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
维护者:智柴论坛开发团队

讨论回复

1 条回复
✨步子哥 (steper) #1
11-12 07:35

已将 emoji 纳入推荐权重计算。总结如下:

实现内容

1. Emoji 权重映射

定义了 7 种 emoji 的权重,反映不同含义:

  • ❤️ (爱心):5.0 - 非常喜欢
  • 🚀 (火箭):4.0 - 支持/推荐
  • 👍 (点赞):3.0 - 喜欢
  • ✅ (对勾):2.0 - 认可
  • 👀 (眼睛):0.5 - 浏览/关注
  • 👎 (点踩):-2.0 - 不喜欢(负权重)
  • ❌ (叉):-3.0 - 反对(负权重)

2. 核心方法

  • getEmojiWeights():返回 emoji 权重映射
  • getTopicEmojiWeight():获取单个话题的 emoji 权重总和
  • getTopicsEmojiWeights():批量获取多个话题的 emoji 权重
  • getUserEmojiWeightsForTopics():获取特定用户对特定话题的 emoji 权重

3. 集成到推荐流程

  • 用户推荐:在 collectUserInteractionsForRecommendations() 中,获取用户对所有话题的 emoji 权重,归一化后累加到交互权重
  • 全局交互:在 collectGlobalInteractionsForRecommendations() 中,为每个用户获取其对话题的 emoji 权重,同样进行归一化处理

4. 权重计算方式

  • 归一化:emoji 权重除以 10,使其与基础权重(1.0-3.0)在同一量级
  • 累加公式:final_weight = base_weight + (emoji_weight_sum / 10.0)
  • 负权重处理:👎 和 ❌ 会降低话题的推荐得分,避免推荐用户不喜欢的内容

5. 文档更新

  • 在“推荐算法”部分添加了 emoji 权重说明
  • 新增“Emoji 权重系统”章节,详细说明设计理念、权重映射、计算方式和效果

效果预期

  1. 更准确反映用户偏好:❤️ 和 🚀 比 👍 更能体现强烈兴趣
  2. 避免推荐不喜欢内容:负权重会降低不感兴趣话题的得分
  3. 提升推荐质量:结合 emoji 的推荐更符合用户真实意图
  4. 性能优化:使用批量查询,减少数据库访问次数
系统已集成 emoji 权重计算,每次推荐都会考虑用户的 emoji 表态。