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

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

算法:基于用户的协同过滤(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 的用户
3. 排除规则
  • 排除用户已交互过的话题
  • 排除隐藏的话题
#### 1.3 内容相似度(Content-Based)

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

步骤

1. 构建用户兴趣向量

   user_vector = Σ(topic_vector × (1 + interaction_weight))
   
其中:
  • topic_vector:话题的 TF-IDF 向量
  • interaction_weight:用户对该话题的交互权重
2. 计算候选话题相似度
   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
排序规则: 1. 按最终得分降序 2. 得分相同时,优先协同过滤得分高的 3. 协同过滤得分也相同时,优先内容得分高的

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

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

步骤

1. 候选话题筛选

  • 排除当前话题本身
  • 排除隐藏的话题
  • 从最近创建的话题中选择(默认 100 条)
2. TF-IDF 向量化
  • 对查询话题和候选话题进行分词
  • 计算每个话题的 TF-IDF 向量
3. 相似度计算
   similarity(topic) = cosine_similarity(query_tfidf_vector, candidate_tfidf_vector)
   

4. 排序与筛选

  • 按相似度降序排序
  • 返回前 N 个话题(N = limit × 2,用于后续过滤)

3. 分词算法

分词策略

1. 文本预处理

  • 转换为小写
  • HTML 实体解码
2. 正则匹配
   /[\p{L}\p{N}]+/u
   
  • 匹配所有字母和数字的连续序列
3. 中文处理
  • 如果 token 包含中文且长度 > 1,拆分为单字
  • 例如:"人工智能" → ["人", "工", "智", "能"]
4. 停用词过滤
  • 过滤常见停用词(中英文)
  • 过滤单字符 token(除非是中文单字)
5. 停用词表
   // 英文停用词
   '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_LIMIT10默认推荐数量
MAX_LIMIT20最大推荐数量
CACHE_TTL900缓存有效期(秒)
WAIT_TIMEOUT_MS2500等待异步结果超时(毫秒)
CF_WEIGHT0.6协同过滤权重
CONTENT_WEIGHT0.4内容相似度权重
MIN_SIMILARITY0.01最小相似度阈值

B. 数据库表结构

topics 表

  • id: 话题ID
  • title: 话题标题
  • content: 话题内容
  • author_id: 作者ID
  • created_at: 创建时间
  • is_hidden: 是否隐藏
replies 表
  • id: 回复ID
  • topic_id: 话题ID
  • author_id: 作者ID
  • content: 回复内容
  • created_at: 创建时间
user_topic_views 表(用于推荐系统):
  • user_id: 用户ID
  • topic_id: 话题ID
  • view_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 维护者:智柴论坛开发团队

暂无表态

想参与讨论或点赞?登录后使用完整功能

讨论回复(1)

已将 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 表态。

👍 1
合作

智谱 GLM-5 已上线

在智谱开放平台 BigModel.cn 打造 AI 应用。新一代旗舰模型 GLM-5 在推理、代码、智能体综合能力达到开源模型 SOTA。

领取 2000万 Tokens