跳转到内容

News Feed — 信息流

V1:做个团队内部的动态墙,大家发发日报

Section titled “V1:做个团队内部的动态墙,大家发发日报”

你们团队有 8 个人,每天站会大家口头同步进展,经常有人忘了昨天做了什么。 组长说:“搞一个简单的动态墙吧,每个人把日报发上去,大家随时看。” 你不想折腾服务器和数据库,先做个纯前端版本,大家各自在自己浏览器里发日报看日报。 反正团队就几个人,先用起来再说——数据不互通的问题以后再解决。

用纯前端实现一个信息流页面,支持发布动态和按时间排序展示,数据存在 localStorage。

帮我创建一个纯前端的团队动态墙,要求如下:
1. 单个 index.html 文件,内联 CSS 和 JS,不用任何构建工具或外部依赖
2. 功能:
- 顶部有一个发布区域:输入昵称(记住上次输入的)、标题、内容(多行文本框)
- 点击"发布"后,动态出现在下方列表中
- 动态列表按发布时间倒序排列,显示:昵称、标题、内容、发布时间(相对时间如"5分钟前")
- 每条动态有删除按钮
- 简单的统计:今日发布数、总发布数
3. 数据存储:
- 所有动态存在 localStorage,格式为 JSON 数组
- 每条动态结构:{id, nickname, title, content, created_at}
- 昵称存在 localStorage 里,下次打开自动填充
4. 界面风格:
- 类似微信朋友圈的单列信息流布局
- 每条动态用卡片样式展示,圆角 + 阴影
- 响应式布局,手机上也能用
  • 打开页面,输入昵称和内容,发布成功,动态出现在列表中
  • 刷新页面,之前发布的动态还在,昵称自动填充
  • 发布多条动态,按时间倒序排列(最新的在最上面)
  • 删除某条动态,列表即时更新
  • 在手机浏览器打开,布局正常可用
  • 前端数据建模:用 JSON 数组存储列表数据,每条记录有唯一 id → M2 数据模型
  • 时间处理:Date 对象、相对时间计算(几分钟前/几小时前)→ M9 数据处理
  • localStorage 容量限制:约 5MB,超了怎么办?自然引出后端需求 → M2 数据模型

V2:团队用起来了,但大家的数据不互通

Section titled “V2:团队用起来了,但大家的数据不互通”

团队真的用起了你的动态墙。但问题来了:每个人只能看到自己发的,因为数据存在各自的浏览器里。 小张发了条日报,小李看不到——这就失去了”团队动态墙”的意义。 你需要加一个后端,让大家的数据存在同一个地方。用户量就 8 个人,用 SQLite 足够了,不需要装数据库服务器。 顺便加个最简单的身份识别——输入用户名就行,先不搞密码和登录。

搭建 Go 后端 + SQLite 存储,让团队成员可以共享同一份动态数据。

帮我创建一个团队动态墙的后端服务,要求如下:
1. 技术栈:Go + Gin + SQLite(用 go-sqlite3 驱动)
2. 数据库表设计:
- posts 表:id INTEGER PRIMARY KEY, username TEXT, title TEXT, content TEXT, created_at DATETIME
- 启动时自动建表(CREATE TABLE IF NOT EXISTS)
3. API 接口:
- POST /api/posts:创建动态,body 为 {"username": "...", "title": "...", "content": "..."}
- GET /api/posts:获取所有动态,按 created_at 倒序,支持 ?limit=20&offset=0 分页
- DELETE /api/posts/:id:删除指定动态
- GET /api/stats:返回 {"total": 总数, "today": 今日数}
4. 简单身份识别:
- 请求头 X-Username 传用户名
- 删除操作只能删自己的动态(通过 X-Username 匹配)
- 不需要密码、不需要 JWT,就是最简单的用户名标识
5. 前端:
- 提供一个 React 前端(Vite 创建),展示动态列表
- 首次访问弹窗输入用户名,存在 localStorage
- 发布动态、查看列表、删除自己的动态、分页加载
- 用 Tailwind CSS 做样式
6. SQLite 数据库文件保存为 data.db,放在项目根目录
  • 启动后端,data.db 文件自动创建,posts 表存在
  • 用户 A 发布动态,用户 B 的页面能看到
  • 分页功能正常:第一页 20 条,点”加载更多”显示下一页
  • 用户 A 只能删自己的动态,删别人的返回 403
  • 统计接口返回正确的总数和今日数量
  • 关闭服务器再重启,数据不丢失
  • SQLite 的适用场景:嵌入式数据库,无需安装服务,适合小规模应用 → M2 数据模型
  • 分页查询:LIMIT + OFFSET 的基本分页模式及其缺点(深分页问题)→ M2 数据模型
  • 简单身份识别 vs 真正认证:X-Username 的漏洞在哪?为什么需要真正的认证?→ M12 认证
  • 前后端分离:API 约定、CORS 处理、前端状态管理基础 → M1 API 设计

V3:公司用上了,关注了 100 人但刷新很慢,大V发帖粉丝等半天

Section titled “V3:公司用上了,关注了 100 人但刷新很慢,大V发帖粉丝等半天”

动态墙在公司内部火了,从 8 个人扩展到全公司 2000 人使用。产品经理加了”关注”功能——每个人只看关注的人的动态。 问题随之而来:用户 A 关注了 100 个人,刷新首页时后端要查 100 个人的动态然后合并排序(Pull 模型),接口响应 3 秒。 更糟的是公司 CEO 发了条动态,他有 1500 个关注者,1500 个人的信息流都要更新——但不能让 CEO 发帖时卡住。 你需要重新设计信息流架构:改用推模型(Fan-out),用 Redis 缓存每个用户的 feed,大V用混合策略。

重构信息流为推模型架构,用 Redis 缓存用户 feed,消息队列做异步推送,大V用混合模式。

帮我重构信息流系统为推模型架构,要求如下:
1. 技术栈:Go + Gin + PostgreSQL + Redis
2. 数据库设计:
- users 表:id, username, display_name, is_celebrity (粉丝超过 500 自动标记)
- posts 表:id, user_id, title, content, created_at
- follows 表:follower_id, followee_id, created_at(联合唯一索引)
3. Feed 缓存(Redis):
- 每个用户有一个 Redis List:feed:{user_id},存储最新 200 条帖子 ID
- 查看信息流时:LRANGE feed:{user_id} 0 19 取最新 20 条,再批量查 posts 表获取详情
- 帖子详情也缓存到 Redis:post:{post_id},TTL 30 分钟
4. Fan-out 策略:
- 普通用户发帖:同步将帖子 ID LPUSH 到所有粉丝的 feed:{user_id},LTRIM 保留 200 条
- 大V(is_celebrity=true)发帖:不做 fan-out,粉丝读取 feed 时额外查大V的最新帖子合并排序
- Fan-out 操作通过 goroutine worker pool 异步执行,发帖接口立即返回
5. 异步 Worker:
- 发帖时将任务写入 Go channel(模拟消息队列)
- Worker 从 channel 读取,批量 LPUSH 到粉丝 feed 列表
- 每批处理 100 个粉丝,避免一次性操作太多 Redis key
6. API 接口:
- POST /api/posts:发帖(触发异步 fan-out)
- GET /api/feed:获取当前用户的信息流(合并 Redis feed + 大V最新帖子)
- POST /api/follow/:user_id:关注用户
- DELETE /api/follow/:user_id:取消关注
7. 提供 docker-compose.yml 包含 PostgreSQL 和 Redis
  • 用户 A 关注用户 B,B 发帖后 A 的 feed 中出现该帖子
  • 普通用户发帖后,redis-cli LRANGE feed:{follower_id} 0 -1 显示新帖子 ID
  • 大V发帖后,redis-cli 中粉丝的 feed 列表没有变化(不做预推送)
  • 读取大V粉丝的信息流时,大V的帖子被实时合并进来
  • feed 列表长度不超过 200(LTRIM 生效)
  • 同时模拟 50 个粉丝查看 feed,响应时间在 200ms 以内
  • 关注/取消关注后,feed 内容正确更新
  • 推模型 vs 拉模型:Fan-out on Write vs Fan-out on Read 的权衡 → M2 数据模型
  • Redis List 作为 Feed 缓存:LPUSH + LTRIM + LRANGE 的经典模式 → M4 缓存
  • 大V问题与混合策略:粉丝太多时推模型的代价,混合方案的设计思路 → M5 消息队列
  • 异步任务处理:channel 作为内存消息队列,worker pool 消费任务 → M9 数据处理

V4:通知太多了,用户想要个性化过滤

Section titled “V4:通知太多了,用户想要个性化过滤”

信息流系统用户增长到 1000 人,新问题出现了:活跃用户关注了几十个人,每天收到上百条动态,信息过载严重。 用户反馈:“能不能让我只看重要的?""能不能别老推重复类型的内容?” 另外,一些用户几天不登录,错过了重要动态。产品经理想加个邮件摘要功能,把精华内容推送给不活跃的用户。 还有个性能问题:每次打开 Feed 都显示未读消息数量,但计算未读数需要遍历整个 feed 列表,随着用户增多越来越慢。

增加通知偏好设置、简单内容评分排序、Redis 计数器优化未读数、邮件摘要功能。

帮我为信息流系统增加个性化过滤和通知能力,要求如下:
1. 技术栈:Go + Gin + PostgreSQL + Redis + SMTP
2. 通知偏好设置:
- 新增 user_preferences 表:user_id, muted_users (JSON数组), muted_keywords (JSON数组),
notification_enabled BOOL, email_digest_enabled BOOL, digest_frequency (daily/weekly)
- API 接口:GET/PUT /api/preferences 查看和更新偏好
- Feed 生成时过滤掉 muted_users 的帖子和包含 muted_keywords 的内容
- 前端设置页面:屏蔽用户、屏蔽关键词、通知开关
3. 简单内容评分:
- 每条帖子计算一个 score 用于排序(不改变推模型架构)
- score = recency_score * 0.5 + interaction_score * 0.3 + author_score * 0.2
- recency_score:发布时间越近分数越高(24小时内线性衰减)
- interaction_score:帖子的点赞数 + 评论数(归一化到 0-1)
- author_score:当前用户与该作者的历史互动频率(点赞/评论次数)
- Feed 返回时按 score 降序排列,而非纯时间排序
- 用户可切换"推荐排序"和"时间排序"
4. 未读计数优化(Redis Counter):
- 每个用户维护 Redis key:unread:{user_id},值为未读帖子数量
- 新帖子 fan-out 时,同时 INCR unread:{follower_id}
- 用户查看 Feed 时,GETSET unread:{user_id} 0(原子读取并清零)
- 前端轮询 GET /api/unread 显示未读数红点(每 30 秒一次)
5. 邮件摘要(Email Digest):
- 定时任务:每天 8:00 / 每周一 8:00 检查需要发送摘要的用户
- 筛选该用户最近一段时间内 score 最高的 10 条帖子
- 生成 HTML 邮件模板,包含帖子标题、摘要(前 100 字)、跳转链接
- 通过 SMTP 发送(开发环境用 MailHog 模拟)
- 新增 digest_logs 表记录发送历史,避免重复发送
6. docker-compose.yml 新增 MailHog 服务
  • 设置屏蔽用户后,该用户的帖子不再出现在 Feed 中
  • 设置屏蔽关键词后,包含该关键词的帖子被过滤
  • 切换到”推荐排序”,互动多的帖子排在前面(而非纯时间排序)
  • 新帖子推送后,redis-cli GET unread:{user_id} 值正确递增
  • 查看 Feed 后,未读计数清零
  • 邮件摘要定时发送,MailHog 中能看到收到的摘要邮件
  • 摘要内容包含 score 最高的帖子,格式正确
  • 个性化过滤:用户偏好驱动的内容过滤策略,屏蔽与推荐的平衡 → M9 数据处理
  • 简单评分排序:多因子加权评分模型,从纯时间排序到相关性排序的演进 → M2 数据模型
  • Redis 原子计数器:INCR/GETSET 实现高性能计数,避免数据库查询 → M4 缓存
  • 邮件摘要系统:定时任务 + 模板渲染 + SMTP 发送的完整链路 → M5 消息队列

V5:Feed生成变慢,推模式的Redis内存暴涨

Section titled “V5:Feed生成变慢,推模式的Redis内存暴涨”

用户增长到 1 万人,推模型的问题集中爆发了: 每个用户的 feed 列表占 Redis 内存,1 万用户 × 200 条 × 平均 50 字节 = 100MB,还在快速增长。 很多用户注册后再也没登录过,但他们的 feed 列表一直在被更新——纯浪费。 活跃用户抱怨翻到第 5 页以后加载很慢——LRANGE 的偏移量越大性能越差。 而且 feed 列表里堆积了大量过期内容,3 个月前的帖子还躺在那里占着内存。

实现混合推拉模型,优化缓存策略,引入游标分页,后台清理过期 feed 数据。

帮我优化信息流系统的推模型架构,要求如下:
1. 技术栈:Go + Gin + PostgreSQL + Redis
2. 混合推拉模型:
- 活跃用户(7 天内登录过):保持推模型,维护 Redis feed 列表
- 不活跃用户:不再推送到 Redis,登录时实时拉取(Pull 模式)拼装 feed
- 用户活跃状态判断:登录时更新 Redis key last_active:{user_id}(TTL 7 天)
- Fan-out 前检查 last_active,不活跃用户跳过推送
- 用户重新变活跃时,首次拉取 feed 后重建 Redis 缓存
3. Feed 缓存优化:
- 每个用户只缓存最近 200 条帖子 ID(已有)
- Redis feed 列表的 TTL 设为 7 天,不活跃用户的 feed 自动过期
- 帖子详情缓存使用 Redis Hash(post:{id} → {title, content, author, created_at})
- 批量获取帖子详情:HMGET 替代多次 GET,减少 Redis 往返
4. 游标分页(Cursor-based Pagination):
- 废弃 LRANGE offset 分页,改用游标分页
- 游标 = 上一页最后一条帖子的 ID(或时间戳)
- GET /api/feed?cursor=post_12345&limit=20
- 混合排序时,cursor 基于 score 而非纯 ID
- 首页请求不传 cursor,返回最新的 20 条 + next_cursor
5. 后台清理任务:
- 每天凌晨 3:00 执行清理任务
- 删除超过 90 天的帖子详情缓存
- 清理已删除帖子在 feed 列表中的残留 ID(lazy cleanup)
- 统计并记录清理结果:清理了多少 key、释放了多少内存
6. 内存监控与告警:
- Redis 内存使用率监控,超过 80% 触发告警
- Feed 列表数量统计:总数、活跃用户数、不活跃用户数
- 提供 /api/admin/feed-stats 接口查看 feed 系统健康状态
  • 活跃用户发帖后,只有活跃粉丝的 Redis feed 被更新(不活跃粉丝跳过)
  • 不活跃用户登录后,通过拉取模式正确生成 feed 并重建 Redis 缓存
  • 游标分页正常:传入 cursor 后返回下一页内容,不重复不遗漏
  • 翻到第 10 页的响应速度与第 1 页相近(游标分页无深分页问题)
  • 7 天未登录用户的 Redis feed 列表自动过期(TTL 验证)
  • 凌晨清理任务执行后,过期缓存被删除,Redis 内存下降
  • /api/admin/feed-stats 返回正确的活跃/不活跃用户 feed 统计
  • 混合推拉模型:根据用户活跃度动态切换策略,平衡性能和资源消耗 → M4 缓存
  • 游标分页:Cursor-based 分页解决深分页性能问题 → M2 数据模型
  • 缓存生命周期管理:TTL、定时清理、lazy cleanup 的组合策略 → M4 缓存
  • 资源优化思维:不为不活跃用户浪费资源,按需加载的设计哲学 → M8 扩展性

V6:要做智能推荐,不只是时间排序

Section titled “V6:要做智能推荐,不只是时间排序”

用户规模突破 10 万,产品经理提出了新目标:“提升用户留存率和互动率。” 目前的简单评分排序效果有限——所有用户看到的排序逻辑是一样的,没有真正的个性化。 产品想做 A/B 测试来验证不同的排序策略效果,但现在没有实验框架。 更大的挑战是:10 万用户的 feed 实时计算评分太慢了,需要预计算和离线处理。

引入基于机器学习特征的排序模型、A/B 测试框架和 feed 预计算流水线。

帮我为信息流系统增加智能推荐和实验框架,要求如下:
1. 技术栈:Go + Gin + PostgreSQL + Redis + Python(排序模型)+ Kafka
2. 排序特征工程:
- 帖子特征:发布时间衰减、点赞数、评论数、分享数、内容长度、是否含图片
- 作者特征:粉丝数、近 7 天发帖频率、历史互动率
- 用户-作者交互特征:历史点赞次数、评论次数、最近一次互动时间
- 内容相似度特征:用户近期互动内容的关键词与候选帖子的关键词重合度(TF-IDF)
- 所有特征实时计算太慢,改为定期预计算存入 Redis Hash:
user_features:{user_id}、post_features:{post_id}、interaction:{user_id}:{author_id}
3. 轻量排序模型:
- Python 服务(Flask)训练和推理简单的逻辑回归模型
- 训练数据:用户历史互动(点赞=正样本,曝光未互动=负样本)
- 模型输出:每条候选帖子的预测互动概率(0-1)
- Go 服务调用 Python 排序服务:POST /rank {"user_id": 1, "post_ids": [1,2,3]} → 排序结果
- Python 服务不可用时降级为简单评分排序
4. A/B 测试框架:
- experiments 表:id, name, description, variants (JSON), traffic_split (JSON),
start_date, end_date, status (draft/running/stopped)
- 用户分桶:hash(user_id + experiment_id) % 100,按百分比分配到不同 variant
- 分桶结果缓存在 Redis:ab:{experiment_id}:{user_id} → variant_name
- Feed 请求时检查用户所在分桶,使用对应的排序策略
- 埋点:记录每次曝光和互动的 experiment_id + variant,存入 events 表
- 分析接口:GET /api/experiments/:id/results 返回各 variant 的 CTR、互动率等指标
5. Feed 预计算流水线:
- 定时任务(每 30 分钟)为活跃用户预计算排序后的 feed
- 流程:拉取候选帖子 → 计算特征 → 调用排序模型 → 写入 Redis 排序后的 feed 列表
- 预计算通过 Kafka 分发任务,多个 worker 并行处理
- 用户请求 feed 时优先返回预计算结果,如果过期则实时计算
6. 效果监控:
- 核心指标看板:DAU、Feed CTR、人均互动数、平均阅读时长
- A/B 实验结果自动计算统计显著性(Z-test)
- 模型效果监控:线上 AUC、预测分布偏移告警
7. docker-compose.yml 包含 PostgreSQL、Redis、Kafka、Python 排序服务
  • 特征预计算任务执行后,Redis 中能查到 user_features 和 post_features
  • Python 排序服务接收候选帖子列表,返回按预测概率排序的结果
  • Python 服务不可用时,Feed 降级为简单评分排序(不报错)
  • 创建 A/B 实验后,不同用户被分配到不同 variant(哈希分桶一致性验证)
  • 同一用户多次请求始终在同一个分桶中(分桶结果稳定)
  • 实验结果接口返回各 variant 的 CTR 和互动率对比
  • 预计算 feed 写入 Redis 后,用户请求直接返回预计算结果(响应时间更短)
  • 推荐系统基础:特征工程、简单排序模型、线上推理的完整链路 → M9 数据处理
  • A/B 测试框架:用户分桶、流量分配、指标收集、统计显著性 → M11 综合实战
  • 预计算与缓存:离线计算 + 在线查询的架构模式,降低实时计算压力 → M4 缓存
  • 服务降级:依赖服务不可用时的 fallback 策略,保证系统可用性 → M8 扩展性
  • 数据流水线:Kafka 消息驱动的异步处理流程 → M5 消息队列