News Feed — 信息流
V1:做个团队内部的动态墙,大家发发日报
Section titled “V1:做个团队内部的动态墙,大家发发日报”你们团队有 8 个人,每天站会大家口头同步进展,经常有人忘了昨天做了什么。 组长说:“搞一个简单的动态墙吧,每个人把日报发上去,大家随时看。” 你不想折腾服务器和数据库,先做个纯前端版本,大家各自在自己浏览器里发日报看日报。 反正团队就几个人,先用起来再说——数据不互通的问题以后再解决。
你要解决什么
Section titled “你要解决什么”用纯前端实现一个信息流页面,支持发布动态和按时间排序展示,数据存在 localStorage。
给 AI 的 Prompt
Section titled “给 AI 的 Prompt”帮我创建一个纯前端的团队动态墙,要求如下:
1. 单个 index.html 文件,内联 CSS 和 JS,不用任何构建工具或外部依赖2. 功能: - 顶部有一个发布区域:输入昵称(记住上次输入的)、标题、内容(多行文本框) - 点击"发布"后,动态出现在下方列表中 - 动态列表按发布时间倒序排列,显示:昵称、标题、内容、发布时间(相对时间如"5分钟前") - 每条动态有删除按钮 - 简单的统计:今日发布数、总发布数3. 数据存储: - 所有动态存在 localStorage,格式为 JSON 数组 - 每条动态结构:{id, nickname, title, content, created_at} - 昵称存在 localStorage 里,下次打开自动填充4. 界面风格: - 类似微信朋友圈的单列信息流布局 - 每条动态用卡片样式展示,圆角 + 阴影 - 响应式布局,手机上也能用- 打开页面,输入昵称和内容,发布成功,动态出现在列表中
- 刷新页面,之前发布的动态还在,昵称自动填充
- 发布多条动态,按时间倒序排列(最新的在最上面)
- 删除某条动态,列表即时更新
- 在手机浏览器打开,布局正常可用
你学到了什么
Section titled “你学到了什么”- 前端数据建模:用 JSON 数组存储列表数据,每条记录有唯一 id → M2 数据模型
- 时间处理:Date 对象、相对时间计算(几分钟前/几小时前)→ M9 数据处理
- localStorage 容量限制:约 5MB,超了怎么办?自然引出后端需求 → M2 数据模型
V2:团队用起来了,但大家的数据不互通
Section titled “V2:团队用起来了,但大家的数据不互通”团队真的用起了你的动态墙。但问题来了:每个人只能看到自己发的,因为数据存在各自的浏览器里。 小张发了条日报,小李看不到——这就失去了”团队动态墙”的意义。 你需要加一个后端,让大家的数据存在同一个地方。用户量就 8 个人,用 SQLite 足够了,不需要装数据库服务器。 顺便加个最简单的身份识别——输入用户名就行,先不搞密码和登录。
你要解决什么
Section titled “你要解决什么”搭建 Go 后端 + SQLite 存储,让团队成员可以共享同一份动态数据。
给 AI 的 Prompt
Section titled “给 AI 的 Prompt”帮我创建一个团队动态墙的后端服务,要求如下:
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
- 统计接口返回正确的总数和今日数量
- 关闭服务器再重启,数据不丢失
你学到了什么
Section titled “你学到了什么”- 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用混合策略。
你要解决什么
Section titled “你要解决什么”重构信息流为推模型架构,用 Redis 缓存用户 feed,消息队列做异步推送,大V用混合模式。
给 AI 的 Prompt
Section titled “给 AI 的 Prompt”帮我重构信息流系统为推模型架构,要求如下:
1. 技术栈:Go + Gin + PostgreSQL + Redis2. 数据库设计: - 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 key6. 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 内容正确更新
你学到了什么
Section titled “你学到了什么”- 推模型 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 列表,随着用户增多越来越慢。
你要解决什么
Section titled “你要解决什么”增加通知偏好设置、简单内容评分排序、Redis 计数器优化未读数、邮件摘要功能。
给 AI 的 Prompt
Section titled “给 AI 的 Prompt”帮我为信息流系统增加个性化过滤和通知能力,要求如下:
1. 技术栈:Go + Gin + PostgreSQL + Redis + SMTP2. 通知偏好设置: - 新增 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 最高的帖子,格式正确
你学到了什么
Section titled “你学到了什么”- 个性化过滤:用户偏好驱动的内容过滤策略,屏蔽与推荐的平衡 → 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 个月前的帖子还躺在那里占着内存。
你要解决什么
Section titled “你要解决什么”实现混合推拉模型,优化缓存策略,引入游标分页,后台清理过期 feed 数据。
给 AI 的 Prompt
Section titled “给 AI 的 Prompt”帮我优化信息流系统的推模型架构,要求如下:
1. 技术栈:Go + Gin + PostgreSQL + Redis2. 混合推拉模型: - 活跃用户(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_cursor5. 后台清理任务: - 每天凌晨 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 统计
你学到了什么
Section titled “你学到了什么”- 混合推拉模型:根据用户活跃度动态切换策略,平衡性能和资源消耗 → M4 缓存
- 游标分页:Cursor-based 分页解决深分页性能问题 → M2 数据模型
- 缓存生命周期管理:TTL、定时清理、lazy cleanup 的组合策略 → M4 缓存
- 资源优化思维:不为不活跃用户浪费资源,按需加载的设计哲学 → M8 扩展性
V6:要做智能推荐,不只是时间排序
Section titled “V6:要做智能推荐,不只是时间排序”用户规模突破 10 万,产品经理提出了新目标:“提升用户留存率和互动率。” 目前的简单评分排序效果有限——所有用户看到的排序逻辑是一样的,没有真正的个性化。 产品想做 A/B 测试来验证不同的排序策略效果,但现在没有实验框架。 更大的挑战是:10 万用户的 feed 实时计算评分太慢了,需要预计算和离线处理。
你要解决什么
Section titled “你要解决什么”引入基于机器学习特征的排序模型、A/B 测试框架和 feed 预计算流水线。
给 AI 的 Prompt
Section titled “给 AI 的 Prompt”帮我为信息流系统增加智能推荐和实验框架,要求如下:
1. 技术栈:Go + Gin + PostgreSQL + Redis + Python(排序模型)+ Kafka2. 排序特征工程: - 帖子特征:发布时间衰减、点赞数、评论数、分享数、内容长度、是否含图片 - 作者特征:粉丝数、近 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 后,用户请求直接返回预计算结果(响应时间更短)
你学到了什么
Section titled “你学到了什么”- 推荐系统基础:特征工程、简单排序模型、线上推理的完整链路 → M9 数据处理
- A/B 测试框架:用户分桶、流量分配、指标收集、统计显著性 → M11 综合实战
- 预计算与缓存:离线计算 + 在线查询的架构模式,降低实时计算压力 → M4 缓存
- 服务降级:依赖服务不可用时的 fallback 策略,保证系统可用性 → M8 扩展性
- 数据流水线:Kafka 消息驱动的异步处理流程 → M5 消息队列