这份笔记用于后续学习和优化friend_zone项目:
https://github.com/CuSO41108/feed_example。
当前项目已经完成了朋友圈系统里最核心的 Timeline Feed 主链路:发布动态、写 MySQL、写 event outbox、Kafka 异步分发、Redis 热 inbox、MySQL 兜底、推拉结合查询和游标分页。后续学习时不需要刻意避开 Feed,相反要把 Feed 当作主干,再把发布审核、隐私权限、互动、缓存一致性、高可用和异常边界都接到这条主干上。
0. 总体架构主线
朋友圈系统的核心目标是在海量用户和高并发查询下,支撑动态发布、好友动态聚合查看、点赞评论互动,同时保证隐私安全、内容安全和体验流畅。
推荐总体架构:
MySQL 做持久化兜底,保存用户、好友关系、动态、媒体、互动、通知、审核记录等核心数据。
Redis 做高频读写缓存,保存用户 Feed 列表、动态详情、互动计数、点赞去重集合、评论列表等热点数据。
Kafka 做异步削峰,承接发布动态后的 Feed 分发、缓存清理、通知生成、审核回调、统计更新等异步任务。
API 服务保持无状态,方便水平扩容;worker 服务按 Topic 消费消息,独立扩展。
核心链路可以理解为:
用户发布动态,后端校验内容、隐私、频率和媒体状态。
MySQL 事务写动态主表、作者 outbox、事件 outbox。
outbox relay 将事件投递到 Kafka,避免“数据库写成功但消息丢失”。
worker 消费发布事件,按普通用户推、热点用户拉的策略更新 Feed。
Redis 保存活跃用户的 Feed 热列表,MySQL 的 inbox/outbox 作为兜底。
用户刷新朋友圈时,优先查 Redis,未命中再查 MySQL,并按时间游标分页。
读路径要二次校验好友关系、动态状态、审核状态和隐私权限,防止缓存脏数据越权展示。
一句话总结:以 MySQL 保证持久化,以 Redis 扛高并发读写,以 Kafka 做异步削峰,以 Feed 推拉结合做动态分发,再用权限校验和补偿任务兜住一致性。
1. 动态发布与审核流程
核心目标:
实时性:普通用户发动态后尽快返回成功,不让审核、媒体处理拖慢主流程。
内容安全:文字、图片、视频都需要审核,违规内容不能进入可见流。
数据一致性:动态主表、媒体表、审核记录、消息事件要能对齐,失败可重试。
推荐流程:
前端提交动态内容:文本、图片或视频、隐私级别、指定可见好友等。
媒体先上传对象存储,例如 OSS、S3、MinIO,得到 media URL 或 object key。
后端校验隐私配置,例如指定可见好友是否存在、是否互相关注、数量是否超限。
内容审核:
强管控场景:同步审核,通过后才发布。
轻量场景:先发布为
audit_pending或正常可见,再异步审核,失败后下架并通知用户。
生成动态 ID,建议继续使用雪花 ID,保证趋势递增和分布式唯一。
MySQL 事务写入动态主表、媒体表、可见范围表、event outbox。
outbox relay 将核心事件发送到 Kafka,触发后续分发、通知、统计等异步任务。
前端收到发布结果,刷新朋友圈或插入本地最新动态。
建议补充的数据模型:
posts或dynamics增加字段:visibility_type、audit_status、media_count、like_count、comment_count。post_media:保存媒体类型、object key、URL、宽高、时长、转码状态、审核状态。post_visibility:保存指定可见或不可见用户列表,也可以按分组扩展。content_audit_records:保存审核请求、审核结果、失败原因、供应商回调信息。publish_rate_limits或 Redis 限流键:限制用户单位时间发布次数。
进阶优化:
内容审核异步化:普通文字可先发后审,图片和视频建议至少先拿到媒体审核结果。
媒体文件预处理:上传后异步压缩、转码、生成封面、提取宽高和时长。
发布频率限制:例如每分钟最多 5 条,避免刷屏和垃圾内容。
状态机设计:
draft -> pending_audit -> published -> rejected -> deleted,状态变化必须可追踪。
2. 隐私权限设计
隐私模块决定“谁能看见这条动态”,是朋友圈系统里最容易出现边界问题的地方。
常见可见范围:
仅自己可见。
好友可见。
指定好友可见。
指定好友不可见。
分组可见或标签可见。
发布时要做的校验:
指定可见用户必须存在。
指定用户是否必须是好友,需要按产品规则决定。
可见范围数量不能无限大,否则会把发布接口拖成重操作。
可见配置要和动态一起持久化,避免后续查不到当时发布意图。
查询时要做的校验:
用户是否仍然有权查看该动态。
动态是否删除或审核失败。
作者账号是否正常。
当前用户是否被作者屏蔽。
好友关系变化的边界:
删除好友后,是否还能看删除前发布的动态。
新增好友后,是否能看添加前的历史动态。
拉黑或屏蔽后,缓存里的旧动态是否需要清理。
两种常见策略:
查询时实时校验好友关系:权限最准确,但查询成本更高,缓存命中后也不能完全跳过校验。
发布时快照可见范围:读路径更快,但关系变化后需要额外规则处理历史可见性。
建议项目先采用简单且安全的方案:读路径查出候选动态后,再做一次轻量权限过滤;等数据量上来后,再考虑关系版本号、可见范围快照和异步缓存清理。
3. Feed 推拉结合与动态分发
Feed 是朋友圈系统的主干。它解决的是“我打开朋友圈时,如何快速看到我有权限查看的好友动态,并按发布时间稳定排序”。
推模式:
普通用户发布动态后,系统异步把动态 ID 写入所有可见好友的 Feed 列表。
优点是读路径快,用户刷新时直接拿自己的 Feed 列表即可。
缺点是写放大明显,作者好友很多时会产生大量写入。
拉模式:
热点用户或大 V 发布动态后,只写自己的作者动态列表,不主动推给所有好友。
好友刷新朋友圈时,再从热点作者 outbox 拉取动态,与自己的 inbox 合并。
优点是避免推送风暴,缺点是读路径需要做聚合。
推拉结合:
普通用户走推模式,保障实时性。
热点用户走拉模式,控制写放大。
对热点用户的活跃好友,可以局部推送;对非活跃好友,读时再拉。
Redis 保存最近 Feed 热数据,MySQL 保存完整索引作为兜底。
兜底机制:
推模式推送失败时,自动降级为拉模式,好友查询时补拉该动态。
Redis Feed 缓存过期后,从 MySQL inbox/outbox 重新聚合。
历史动态超过保留窗口后,从 MySQL 归档表或历史表拉取,减少 Redis 压力。
项目当前已经完成的部分:
posts保存动态主表。author_outbox保存作者动态列表。user_feed_inbox保存用户 Feed 索引。event_outbox配合 Kafka relay 保证发布事件可靠投递。worker 分块 fanout,并把活跃用户最新 Feed 写入 Redis ZSET。
Feed 查询支持 Redis 优先、MySQL 兜底、热点作者 outbox 合并和游标分页。
后续可优化:
在
posts或author_outbox记录发布时的 fanout 策略,避免作者粉丝数变化后历史动态策略漂移。增加 fanout 任务进度表,支持失败后从上次 follower_id 继续推。
增加 Feed 最大翻页深度、历史归档和冷数据查询路径。
增加缓存命中率、fanout 延迟、Kafka 堆积、MySQL 回源次数等监控。
4. 实时互动功能
互动模块主要包括点赞、取消点赞、评论、回复、互动通知。它不属于 Feed 主链路,但会反向影响动态详情、列表计数和消息中心。
点赞
推荐流程:
用户点击点赞。
Redis 判断是否已点赞,可用 Set、Bitmap、Bloom Filter,项目初期用 Set 更直观。
未点赞时执行 Redis 原子操作:
添加点赞记录。
INCR动态点赞数。
异步写 MySQL
post_likes表。更新
posts.like_count或独立统计表。返回点赞成功,并可异步发送点赞通知。
取消点赞类似:
Redis 判断是否已点赞。
已点赞则移除点赞记录。
DECR点赞数,但要避免减成负数。异步更新 MySQL。
需要注意:
点赞必须幂等,重复请求不能重复加计数。
Redis 和 MySQL 最终一致即可,但要有补偿任务校准计数。
热门动态的点赞用户集合可能很大,可只缓存最近点赞用户和计数,全量记录落 MySQL。
建议表:
post_likes(content_id, user_id, status, created_at, updated_at),唯一键(content_id, user_id)。
建议 Redis key:
post:likes:{content_id}:Set,记录已点赞用户。post:stats:{content_id}:Hash,记录like_count、comment_count。
评论
推荐流程:
用户提交评论。
内容审核,至少对文本做敏感词或第三方审核。
生成评论 ID。
写 MySQL
post_comments表。Redis 增加评论数,并按评论时间缓存评论列表。
返回评论成功,并异步推送评论通知。
评论列表查询:
优先查 Redis 缓存,按评论时间排序。
未命中查 MySQL,并回填缓存。
支持分页,避免一次返回过多评论。
回复评论可用
parent_id关联主评论和子评论。
建议表:
post_comments(comment_id, content_id, user_id, parent_id, content_text, audit_status, status, created_at, updated_at)。
建议 Redis key:
post:comments:{content_id}:ZSET,score 为评论时间,member 为评论 ID。comment:detail:{comment_id}:Hash 或 JSON,缓存评论详情。
5. 消息中心与通知
消息中心承接点赞、评论、回复、审核失败、系统提醒等事件。
推荐事件来源:
点赞成功事件。
评论成功事件。
回复评论事件。
动态审核失败事件。
动态被删除或被系统处理事件。
建议流程:
互动或审核模块写业务表。
同事务写 notification outbox。
Kafka 消费者异步生成通知记录。
在线用户可以通过 WebSocket、SSE 或轮询获得未读消息。
建议表:
notifications(notification_id, receiver_id, actor_id, type, content_id, comment_id, read_status, created_at)。
项目初期可以先做站内未读列表,不急着接实时长连接。
6. 聚合查询与缓存优化
图片里提到的多级缓存和聚合优化,是当前项目后续可以强化的方向。当前项目已经有 Redis ZSET inbox,但还没有完整的多级缓存、热点动态详情缓存和历史数据分层。
多级缓存:
L1:应用本地缓存,例如 Caffeine 思路,缓存当前用户最近几次聚合结果,过期时间很短,例如 5 分钟。
L2:Redis,保存用户动态 ID 列表、热点动态详情、点赞评论计数,过期时间较短,例如 30 分钟。
L3:MySQL,作为最终兜底查询源,后续可分库分表。
分页优化:
Redis Sorted Set 使用发布时间作为 score。
用
ZRANGEBYSCORE或反向范围查询实现时间游标分页。只传输动态 ID,详情按批量接口补齐,减少网络传输。
动态聚合策略:
预聚合更新:结合推模式,用户发布动态后异步更新好友的 Redis Feed 列表,把写压力放到后台处理,提升读路径速度。
分页查询优化:Feed 列表只保存动态 ID 和时间分数,分页时先取 ID,再批量查询动态详情。
热点用户优化:热点作者单独维护作者动态缓存,好友查询时拉取热点作者动态,避免每次重新聚合。
历史懒加载:近期数据优先查 Redis,历史数据按时间游标查 MySQL 或归档表,再短期回填 Redis。
热点用户优化:
对好友多、发布频繁的用户,单独维护作者动态缓存。
好友查询时直接拉取热点作者动态,避免重复聚合。
热点用户动态不要无脑推送给所有人,否则写放大会很明显。
历史动态懒加载:
最近数据走 Redis。
超过保留窗口的数据走 MySQL。
查到历史数据后可短期回填 Redis,提高连续翻页体验。
项目可补点:
给动态详情增加 Redis Hash 缓存。
给点赞数、评论数增加 Redis 统计缓存。
给 Feed 查询增加缓存命中率、MySQL 回源次数等指标。
给历史 Feed 增加最大翻页深度或归档策略。
7. 媒体与大文件加载优化
朋友圈动态不只有文字,高清图片和视频会显著影响发布耗时、审核耗时和 Feed 加载体验。媒体能力建议独立建模,不要把所有媒体信息塞进动态主表。
发布侧:
图片和视频先上传对象存储,动态主表只保存媒体引用。
上传完成后异步做压缩、转码、生成封面、提取宽高、时长和文件大小。
同一媒体生成多种规格,例如缩略图、标清图、高清图、视频封面、短视频转 MP4。
媒体状态独立维护,例如
uploading、processing、ready、failed、rejected。
加载侧:
按需加载:根据设备、网络状态和页面位置选择合适分辨率,移动网络优先加载低清,WiFi 再加载高清。
CDN 加速:媒体文件放到 CDN,利用边缘节点减少延迟。
预加载:用户滑动 Feed 时,提前加载下一页动态的封面和缩略图。
懒加载:首屏只加载可见区域媒体,未进入视口的图片和视频延迟加载。
异常处理:
媒体处理失败时,动态可以展示文本和可用媒体,同时标记失败媒体。
视频转码未完成时,可以先展示封面和“处理中”状态。
审核失败的媒体要触发动态状态更新和缓存清理,避免违规内容继续展示。
项目可补点:
新增
post_media表,保存 object key、媒体类型、宽高、时长、转码状态和审核状态。前端 Feed 卡片支持图片缩略图和视频封面。
引入媒体处理 worker,独立消费
media.uploaded或post.published事件。
8. 高可用与容灾设计
服务层:
API 服务保持无状态,方便多实例部署。
使用 Nginx、SLB 或其他负载均衡分发请求。
单实例故障时,请求自动转发到其他健康实例。
多机房:
多个机房部署应用实例、Redis 集群、MySQL 主从或分片。
通过全局负载均衡 GSLB 做跨机房切换。
单机房故障时,流量切到健康机房。
降级与熔断:
Redis 故障时,Feed 和互动计数可降级查 MySQL,或者返回缓存过期提示。
Kafka 堆积时,优先保证发布和查询,延迟非核心通知。
审核服务不可用时,按风险等级决定是阻断发布,还是进入待审核状态。
评论回复、实时推送、消息提醒可以作为非核心功能优先降级。
推荐保留的核心能力:
登录鉴权。
动态发布。
动态查询。
点赞基础操作。
可优先降级的能力:
评论回复。
实时通知。
热门推荐。
媒体高级处理。
9. 异常与边界场景
好友关系变化导致动态可见性问题:
场景:用户删除好友后,仍能看到该好友删除前发布的动态;或者新增好友后,看不到该好友添加前的历史动态。
影响:违反隐私规则,用户体验不一致,严重时会造成越权查看。
实时同步缓存:删除好友时,异步删除当前用户 Redis Feed 列表中该好友的动态 ID,同时删除相关动态详情缓存;添加好友时,异步把该好友最近一段时间的历史动态聚合到当前用户 Feed 缓存。
查询时二次校验:即使缓存中存在动态,读路径仍要校验当前好友关系、动态状态、审核状态和可见范围,无权限则过滤。
历史动态批量处理:添加好友后的历史动态同步不要阻塞关注接口,可通过后台任务批量补齐最近 30 天或产品允许范围内的数据。
发布成功但消息未分发:
问题:动态主表已写入,但 Kafka 发送失败。
方案:继续使用 outbox 表,relay 定时重试;事件消费端保持幂等。
重复消费或重复写入:
问题:Kafka 消息重复导致 inbox、点赞、通知重复。
方案:唯一键、
INSERT IGNORE、业务幂等 key、消费记录表。
审核失败后的回收:
问题:先发后审的内容被判违规,但已经进入部分用户缓存。
方案:动态状态改为 rejected/deleted;发送删除事件;清理 Redis 和 Feed inbox;通知作者。
动态删除或修改后的缓存一致性问题:
场景:用户删除或修改动态后,MySQL 已更新,但 Redis Feed 列表或动态详情缓存未及时更新,其他用户仍看到旧内容或已删除内容。
影响:数据不一致,影响信息准确性和用户信任。
同步更新缓存:删除或修改动态时,先更新 MySQL,再发布删除/修改事件,由消费者删除 Redis 动态详情、Feed 列表中的动态 ID、评论缓存和通知引用。
延迟双删:删除缓存后延迟一小段时间再删一次,降低并发读导致旧缓存回填的概率。
缓存过期兜底:动态详情、Feed 列表、评论列表都设置合理 TTL,即使删除事件失败,缓存过期后也会回源加载最新数据。
读路径兜底:无论缓存是否命中,关键状态仍以动态状态为准,例如
deleted、rejected、audit_pending不应展示。
缓存与数据库计数不一致:
问题:Redis 点赞数、评论数和 MySQL 明细不一致。
方案:以 MySQL 明细为准,周期性校准 Redis 统计;热点数据允许短期最终一致;点赞和取消点赞要用 Redis 原子操作避免并发计数错误。
评论过多:
问题:热门动态评论列表过长,查询和缓存压力大。
方案:只缓存前几页;后续分页查 MySQL;评论详情按需加载。
媒体处理失败或加载慢:
问题:高清图片、1080P 视频等大文件加载慢,用户等待时间长,可能放弃查看。
方案:上传后异步压缩、转码、生成多分辨率版本;CDN 加速分发;Feed 滚动时预加载下一页媒体;客户端按网络状态选择清晰度。
删除动态后的清理:
问题:动态删除后仍出现在 Feed、评论列表、通知入口。
方案:发布删除事件;消费者清理 Redis、inbox、评论缓存和通知引用;读路径始终过滤
status != published。
历史动态查询性能下降:
场景:用户查询很早以前的动态,例如 1 年前的朋友圈,数据分布在历史分表或归档表中,查询和聚合耗时明显增加。
影响:用户体验差,可能放弃查询历史动态。
历史数据归档:超过一定时间的数据迁移到低成本存储或历史表,减少主表压力。
历史动态预聚合:按用户和月份预聚合历史动态索引,查询时直接查归档索引。
懒加载与小分页:历史查询采用懒加载,单页数量适当减小,例如 10 条,减少单次查询数据量。
缓存回填:用户连续翻页历史动态时,将查询结果短期写入 Redis,提高后续翻页速度。
高并发发布压力:
问题:热点时段大量用户发布动态,审核、写库、分发和通知都可能形成峰值。
方案:发布接口只做必要校验和主表写入;审核、Feed 分发、通知、统计全部走队列异步化;对单用户发布频率做限流;对非核心任务做降级。
10. 总结与面试回答要点
面试官最想听到的核心点:
需求痛点识别:朋友圈的难点不是简单发帖,而是动态聚合复杂、高并发查询、海量数据存储、隐私权限控制和缓存一致性。
架构选型逻辑:选择“消息队列 + Redis + MySQL”是为了平衡实时性、吞吐量、成本和可靠性。
Feed 推拉理解:普通用户推,热点用户拉,活跃用户可局部推,非活跃用户读时拉,避免写放大和推送风暴。
缓存设计细节:Redis Sorted Set 适合按时间排序的 Feed,Hash 适合动态详情,Set 适合点赞去重,多级缓存提升吞吐。
一致性思维:通过 outbox、异步双写、延迟双删、定时校验和读路径兜底,处理缓存和数据库不一致。
权限与安全:发布时保存可见范围,查询时二次校验,审核失败后实时下架并清缓存。
异常处理意识:好友关系变化、动态删改、Kafka 重复消费、Redis 故障、媒体处理失败、历史查询变慢都要有方案。
结构化表达:按“需求 -> 目标 -> 架构 -> 核心链路 -> 缓存与一致性 -> 异常 -> 总结”的顺序回答。
口述总结示例:
首先,朋友圈系统的核心价值是在海量用户和高并发场景下,高效支撑动态发布、好友动态聚合查看和实时互动,同时保证隐私安全和体验流畅。传统单库单表无法支撑高查询 QPS,单纯 Redis 发布订阅又存在消息不可靠问题,所以我会采用“消息队列 + Redis + MySQL”的混合架构。
核心模块上,Feed 使用推拉结合。普通用户发布动态后走推模式,异步把动态 ID 写入好友 Feed 列表,保障读路径速度;热点用户或好友数很多的用户走拉模式,只维护作者 outbox,好友刷新时再拉取并合并,避免推送风暴。Redis 用 Sorted Set 维护按时间排序的 Feed,用 Hash 缓存动态详情,用 Set 做点赞去重;MySQL 保存动态、好友关系、inbox/outbox 和互动明细,作为最终兜底。
一致性方面,发布动态时先写 MySQL 和 event outbox,再由 relay 投递 Kafka,worker 消费后分发 Feed,消费端通过唯一键和幂等逻辑避免重复写入。动态删除或审核失败时,发送删除事件清理 Redis、Feed inbox、评论和通知缓存;同时查询时二次校验好友关系、动态状态和可见范围,防止缓存脏数据导致越权展示。
异常场景上,好友关系变化通过缓存清理和查询时校验保证可见性正确;缓存不一致通过延迟双删、TTL 和定时校验兜底;大文件通过对象存储、CDN、转码压缩和预加载优化体验;历史动态通过归档表、按月预聚合和小分页懒加载降低查询压力。总结来说,这套设计以 Redis 扛高并发,以 Feed 推拉结合做动态分发,以 MySQL 做持久化兜底,再用工程化手段保证一致性、隐私安全和高可用。
11. 建议项目优化路线
第零阶段:夯实现有 Feed 主链路
记录发布时的 fanout 策略,避免热点用户身份变化影响历史动态分发判断。
增加 fanout 任务进度表,支持失败任务继续推送。
增加 Feed 查询、Redis 命中、MySQL 回源、Kafka 堆积和 worker 处理耗时指标。
给 Feed 服务补充单元测试和集成测试,覆盖游标分页、Redis 未命中、热点作者合并和删除动态过滤。
第一阶段:发布安全与隐私
给
posts增加审核状态和可见范围字段。增加隐私校验模块,例如
internal/module/privacy。增加发布频率限制。
增加内容审核记录表,先做 mock 审核服务也可以。
第二阶段:互动功能
增加点赞接口、取消点赞接口。
增加评论接口、评论列表接口。
增加点赞和评论计数缓存。
增加互动事件 outbox,为消息中心做准备。
第三阶段:消息中心
增加通知表。
点赞、评论、回复后异步生成通知。
增加未读数和已读接口。
第四阶段:媒体能力
增加
post_media表。支持图片和视频元数据。
增加媒体上传后的压缩、转码、封面、审核状态。
第五阶段:工程化可靠性
增加 Kafka 消费指标、DLQ 查看和重放机制。
增加缓存命中率、MySQL 回源次数、发布延迟、fanout 延迟监控。
增加降级开关,例如关闭实时通知、评论回复或媒体高级处理。
为核心模块补单元测试和集成测试。