4e21ca937a
Deploy Documentation / deploy (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
CI / H5 Plaza Tests and Build (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Fork goose with custom MCP widgets, platform extensions (aider, git, web, search), MindSpace H5 backend/frontend, Plaza/Ops UIs, and deploy scripts for tkmind.cn. Co-authored-by: Cursor <cursoragent@cursor.com>
209 lines
6.8 KiB
Markdown
209 lines
6.8 KiB
Markdown
---
|
||
sidebar_position: 6
|
||
title: Feed 算法
|
||
sidebar_label: Feed 算法
|
||
description: 广场热度排序公式、算法演化路径、Redis 计数策略和推荐迁移时机
|
||
---
|
||
|
||
# Feed 算法
|
||
|
||
## 设计原则
|
||
|
||
- **初期不做个性化推荐**:冷启动时用户行为数据不足,协同过滤效果差于热度排序。
|
||
- **热度排序足够用到 10 万帖子**:时间衰减 + 多维互动权重,工程实现简单,效果可预期。
|
||
- **透明可调**:热度公式的权重系数写在配置表里,运营可以实时调整,不需要发版。
|
||
- **推荐算法是独立演化的模块**:等数据量够了再接入,接口协议从第一天就预留好。
|
||
|
||
---
|
||
|
||
## 热度排序公式(初期)
|
||
|
||
```
|
||
hot_score = (V × w_v + L × w_l + C × w_c + S × w_s) / (T + decay_base) ^ decay_exp
|
||
```
|
||
|
||
| 变量 | 含义 | 默认权重 |
|
||
| --- | --- | --- |
|
||
| V | view_count 浏览量 | w_v = 0.1 |
|
||
| L | like_count 点赞数 | w_l = 3.0 |
|
||
| C | comment_count 评论数 | w_c = 5.0 |
|
||
| S | collect_count 收藏数 | w_s = 4.0 |
|
||
| T | 帖子年龄(小时),`NOW() - published_at` | - |
|
||
| decay_base | 时间衰减基数 | 默认 2 |
|
||
| decay_exp | 时间衰减指数 | 默认 1.5 |
|
||
|
||
**示例**:
|
||
|
||
一篇帖子发布 3 小时后,获得 500 次浏览、20 个点赞、3 条评论、5 个收藏:
|
||
|
||
```
|
||
分子 = 500×0.1 + 20×3 + 3×5 + 5×4 = 50 + 60 + 15 + 20 = 145
|
||
分母 = (3 + 2)^1.5 = 5^1.5 ≈ 11.18
|
||
hot_score ≈ 12.97
|
||
```
|
||
|
||
同样互动量、发布 24 小时后:
|
||
|
||
```
|
||
分母 = (24 + 2)^1.5 = 26^1.5 ≈ 132.6
|
||
hot_score ≈ 1.09
|
||
```
|
||
|
||
时间衰减效果明显,保证新内容有机会上热门。
|
||
|
||
---
|
||
|
||
## 权重配置表
|
||
|
||
```sql
|
||
CREATE TABLE plaza_algorithm_config (
|
||
key VARCHAR(100) NOT NULL,
|
||
value DOUBLE NOT NULL,
|
||
description VARCHAR(200) NOT NULL DEFAULT '',
|
||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (key)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
INSERT INTO plaza_algorithm_config VALUES
|
||
('w_view', 0.1, '浏览量权重', NOW()),
|
||
('w_like', 3.0, '点赞权重', NOW()),
|
||
('w_comment', 5.0, '评论权重', NOW()),
|
||
('w_collect', 4.0, '收藏权重', NOW()),
|
||
('decay_base', 2.0, '时间衰减基数', NOW()),
|
||
('decay_exp', 1.5, '时间衰减指数', NOW());
|
||
```
|
||
|
||
后台定时任务读取配置表后计算,运营修改配置后下次计算周期生效。
|
||
|
||
---
|
||
|
||
## 计算时机
|
||
|
||
```text
|
||
触发条件 执行动作
|
||
────────────────────────────── ──────────────────────────────
|
||
每 10 分钟定时任务 重新计算过去 48 小时内发布帖子的 hot_score
|
||
新帖发布到广场 立即计算初始 hot_score(基于 0 互动 + 年龄 0)
|
||
帖子互动量发生变化 不立即重算,等下一次定时任务
|
||
```
|
||
|
||
**为什么不实时重算**:每次点赞都重算 hot_score 并回写 MySQL,在高并发下会造成锁竞争。Redis 实时累计计数,定时批量回写 + 重算是更稳定的方案。
|
||
|
||
---
|
||
|
||
## Redis 计数策略
|
||
|
||
```text
|
||
# 每次浏览
|
||
INCR plaza:post:{id}:view_count
|
||
|
||
# 点赞
|
||
INCR plaza:post:{id}:like_count
|
||
|
||
# 取消点赞
|
||
DECR plaza:post:{id}:like_count
|
||
|
||
# 防止负数
|
||
if GET plaza:post:{id}:like_count < 0:
|
||
SET plaza:post:{id}:like_count 0
|
||
```
|
||
|
||
**定时同步任务(每 5 分钟)**:
|
||
|
||
```text
|
||
1. 维护 Redis SET plaza:sync:post_ids(每次 INCR 时将 post_id SADD 入集合)
|
||
2. SMEMBERS plaza:sync:post_ids,批量 GET 计数
|
||
3. 批量 UPDATE plaza_posts … WHERE id IN (...)
|
||
4. SREM 已同步的 post_id
|
||
```
|
||
|
||
> 避免对 `plaza:post:*` 做全量 SCAN;帖子量大时 SCAN 会阻塞 Redis。Sprint 3 前帖子少时可临时 SCAN,上线前必须改为 SET 增量同步。
|
||
|
||
**Redis 数据持久化**:开启 AOF,每秒 fsync,允许最多 1 秒的计数损失。
|
||
|
||
---
|
||
|
||
## Feed 缓存策略
|
||
|
||
```text
|
||
广场首页热门 Feed(全部分类):
|
||
缓存 Key: plaza:feed:hot:all:page:1
|
||
TTL: 5 分钟
|
||
生成时机: 定时任务重算 hot_score 后刷新
|
||
|
||
分类热门 Feed:
|
||
缓存 Key: plaza:feed:hot:{category_slug}:page:1
|
||
TTL: 5 分钟
|
||
|
||
最新 Feed:
|
||
缓存 Key: plaza:feed:new:all:cursor:{cursor}
|
||
TTL: 60 秒(最新列表变化快)
|
||
|
||
精选位(Sprint 5):
|
||
缓存 Key: plaza:featured:{position}
|
||
TTL: 5 分钟
|
||
合并规则: 见 04-backend-api「精选与 Feed 合并规则」
|
||
|
||
新帖 status 变为 published 后:
|
||
DEL plaza:feed:hot:* plaza:feed:new:*(下次请求重建)
|
||
```
|
||
|
||
---
|
||
|
||
## Feed 接口降级策略
|
||
|
||
| 场景 | 降级行为 |
|
||
| --- | --- |
|
||
| Redis 不可用 | 直接查 MySQL `ORDER BY hot_score DESC`,性能下降但不中断 |
|
||
| 热度未计算(新帖) | 按 `published_at DESC` 兜底 |
|
||
| 分类无帖子 | 返回全局热门帖子,前端提示"该分类暂无内容" |
|
||
|
||
---
|
||
|
||
## 算法演化路径
|
||
|
||
### 第一阶段:热度排序(现在)
|
||
|
||
纯公式,无需用户行为数据,工程简单。
|
||
|
||
### 第二阶段:兴趣标签匹配(月活 1 万后)
|
||
|
||
- 从用户的浏览历史和点赞行为中推断兴趣标签(与帖子的 `tags` 字段匹配)。
|
||
- 在热度排序基础上,对匹配用户兴趣的帖子 hot_score 乘以权重系数(1.2–1.5)。
|
||
- 不需要机器学习模型,纯 SQL 可实现。
|
||
|
||
### 第三阶段:协同过滤(月活 10 万后)
|
||
|
||
- 引入独立 Python 推荐服务。
|
||
- 基于用户行为矩阵(浏览、点赞、收藏)进行 item-based 协同过滤。
|
||
- 后端 `/api/plaza/v1/feed` 接口保持不变,内部调用推荐服务获取帖子 ID 列表。
|
||
- **接口协议从现在就预留**:响应中的 `posts` 数组顺序即为推荐顺序,客户端无需感知算法类型。
|
||
|
||
### 第四阶段:实时流推荐(月活 100 万后)
|
||
|
||
- Flink/Spark Streaming 实时处理行为流。
|
||
- 向量召回 + 精排模型。
|
||
- A/B 测试框架。
|
||
|
||
---
|
||
|
||
## 防刷策略
|
||
|
||
初期使用简单规则,不上机器学习:
|
||
|
||
- 同一 IP 对同一帖子,24 小时内只计 1 次有效浏览。
|
||
- 实现:Redis `SET plaza:view:{ip_hash}:{post_id} 1 EX 86400 NX`,SET 成功才 INCR 计数。
|
||
- 同一用户重复点赞(已有 UNIQUE 约束):数据库层保证幂等。
|
||
- 短时间内大量来自同一 IP 的点赞:限流中间件 + 运营人工核查。
|
||
- 评论刷量:同一用户对同一帖子每小时最多发 10 条评论。
|
||
|
||
---
|
||
|
||
## 新帖冷启动
|
||
|
||
新帖发布后 hot_score = 0,会排在热门列表末尾,导致新帖永远没有曝光机会。
|
||
|
||
解决方案:「最新」Tab 独立于「热门」存在,新帖默认进入「最新」Feed,以 `published_at DESC` 排序。
|
||
|
||
「最新」是新内容的曝光入口,「热门」是经过验证的内容的沉淀展示。这两个 Tab 服务不同的用户动机。
|