Feed流系统需分层设计:发布者数据用Tablestore/TiDB分片索引,关注关系用MySQL联合索引,Feed列表用时序KV存储;推拉模式由存储能力决定,避免混合模式。

Feed流系统在 Go 中不是靠堆协程或加缓存就能扛住亿级流量的,核心在于数据分层是否清晰、读写路径是否分离、冷热是否分治。不按这三点设计,后期扩容和排查会非常痛苦。
Feed流三类数据必须拆到不同存储层
发布者的数据、关注关系、接收者的 Feed 列表,本质是三种访问模式完全不同的数据,混存在一个数据库里迟早出问题。
-
发布者的数据:按user_id分片 + 时间倒序索引,适合用 Tablestore 或 TiDB,要求强一致性、支持范围扫描;不能用 Redis 缓存全量,否则删帖/编辑无法及时生效 -
关注表:高频随机读写(关注/取关)、需支持双向查询(查我关注了谁 / 谁关注了我),建议用 MySQL + 唯一联合索引(follower_id, followee_id),必要时加followee_id单列索引加速“被关注”场景 -
同步库(Feed 列表):只存最近 7 天热门内容,TTL 自动过期,必须用支持时间分片的 KV 存储(如 Redis Streams 或自研基于 LSM 的时序 KV),避免用 String 类型拼接 ID 列表——一旦用户关注 5000 人,单次LRANGE就可能超时
推模式(fan-out)还是拉模式(pull)?看吞吐与实时性权衡
推模式写放大,拉模式读放大,Go 服务本身不决定选型,而是由下游存储能力反推。别迷信“推拉结合”,混合模式会让一致性校验逻辑爆炸式增长。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 纯推模式适用场景:
日均发帖 且 <code>人均关注数 。发帖时遍历关注列表,向每个粉丝的 <code>feed_timeline_{uid}写入一条post_id:timestamp,用pipeline批量写 Redis,失败走异步补偿队列(如 Kafka) - 纯拉模式适用场景:
大 V 集中(Top 1% 用户产出了 80% 内容)。首页 Feed 直接查followee_ids,再并发请求各发布者的最新 N 条(用sync.WaitGroup控制并发上限,防雪崩),结果合并后按时间归并排序——注意别用sort.Slice对全量做排序,应改用heap包实现多路归并 - 关键陷阱:
Redis ZSET存 Feed 时,score 不能直接用 Unix timestamp,必须转成int64并做毫秒级对齐(如ts / 1e6),否则浮点 score 在集群模式下分片不一致;同时要预留字段存post_id和score的复合键,避免 score 冲突时覆盖
Go 层如何避免成为性能瓶颈
Go runtime 很轻量,但写法不当会让 GC 和锁成为隐形杀手。Feed 流请求的 P99 延迟必须压在 200ms 内,否则用户会明显感知卡顿。
立即学习“go语言免费学习笔记(深入)”;
- 禁止在 HTTP handler 里做任何阻塞 IO:数据库查询、Redis
LRANGE、HTTP 调用都必须设context.WithTimeout,超时直接返回兜底 Feed(比如最近热门) - 慎用
sync.Mutex保护共享 map:高并发下争抢严重,改用sync.Map或分段锁(shard map),例如按user_id % 64分 64 个桶 - 序列化别碰
json.Marshal:Feed 数据结构固定,用easyjson或go-json生成静态 marshaler,实测可降低 40% CPU 占用;返回前用bytes.Buffer预分配空间,避免多次扩容 - 分页参数必须校验:
offset超过 1000 就强制跳转到第一页,防止深度翻页触发全表扫描;limit上限设为 50,前端用游标(cursor)代替 offset
最易被忽略的是「冷热分离」的落地粒度——不是简单按时间切分,而是要按用户活跃度+内容热度双维度打标。比如一个沉寂 30 天的用户,其 Feed 列表可以降级走本地缓存+定时异步刷新,而新注册用户必须强一致。这点在 Go 里没有现成框架帮你做,得自己在业务逻辑里埋点判断。

















