Files
john 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
Add TKMind platform extensions, H5/MindSpace stack, and deployment tooling.
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>
2026-06-14 21:30:20 +08:00

209 lines
6.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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.21.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 服务不同的用户动机。