前言
最近都这么卷吗,怎么都去跑外卖了??? 我没有电驴怎么办啊😭。还好最近接了个私活,帮人做个社交类小程序。
拿到代码一看,功能虽然不少,但逻辑写得跟 💩 一样:又重又卡,体验离谱。点进首页,接口加载老半天,图片加载一半出不来,完全没有缓存,全靠数据库硬抗。看完之后我陷入沉思,脑子里只有一个想法: "与我无瓜。"
在优化代码的时候,我想起了前两年在前公司从 0 到 1 搞社交 App 的那段经历。那时候首页也是坑多如山,用户转化率全死在第一屏,钱烧得飞快,人还没看到首页图就退了......
这次借着重构的机会,干脆把当年我们摸爬滚打出来的一套首页抗压方案重新梳理了一遍,也顺手分享给那些正在做社交产品、推荐系统、首页架构的胸弟们...... 不保证通用,但至少能少踩点坑。
特别说明:不算教程,纯吐槽
先说清楚哈:
这不是一篇"十分钟教你搞定高并发首页"的技术教学。
这里看不到什么"最佳实践"、"三层缓存模型"、"限流降级图解"。
只有几个程序员在首页崩完之后,一边打补丁一边吵架,一边复盘总结的"技术自述"。
我们踩了坑,也填了坑,填完还回头看自己当初多蠢。
如果有正好也在做首页、做推荐、扛并发这种类似的架构设计的话.... 可以看下我们的"翻车现场"和"缝缝补补",看看是否有灵感....
也许看完你不会直接毕业,但至少,能少掉几根头发。
首页为啥最容易崩?
做过社交类 App 的同学应该都有体会,有个地方特别容易出事------首页。
为啥?
很简单:流量最大、责任最重、内容最杂、用户最挑。
它是用户打开 App 第一眼看到的东西,加载慢一点,用户直接就跑了;推荐错了,还会觉得"这 App 不懂我"。你加班做得再辛苦,用户只会留下一句评价:
"这破 App,真垃圾。"
说白了,首页就是那种"不能慢、不能错、还不能缓存"的狠角色。
我当初踩过这些坑,现在一看别人的首页炸了,基本都逃不过下面这些原因:
1.首页流量太大,压根顶不住
所有人点进 App 的第一屏就是它。
尤其是做冷启动投放的时候,用户一上来就给你塞进首页,加载慢点直接就流失。
有些用户连封面都没看到就退了,广告费等于烧空气。。
2.内容个性化,根本没法复用
每个用户看到的首页其实都不太一样:
- 新用户有针对性的引导内容;
- 老用户更倾向看到熟人的动态;
- 高质量用户还有专属推荐位和运营位照顾。
一句话:不同用户,不同首页,你根本复用不了什么缓存。
结果就是:一人一缓存,一页一构建,服务器直接哭给你看。
3.数据来源杂得离谱
推荐系统只返回 ID,展示还得拼出头像、昵称、年龄、距离、职业等等。
前端还要拿到 Banner、运营活动、附近人、各种按钮和入口......
"首页"看着像一页,实际上是十几个服务拼出来的"技术债拼图"。
4.用户一上线就要看得到
比如女用户刚上线,男用户的首页就得立马刷出来,不然转化直接掉一截。
资料一改要同步,状态一变就要更新------
不然首页展示的都是"不在线的人 + 过时的资料",用户一看就觉得 App 像死了一样,谁还愿意聊?
5.调用链太长,一慢全挂
推荐系统 → 用户服务 → 内容服务 → 数据服务......
每个系统都说"我很快,忍一下",结果串起来就变成"平均加起来几秒多"。
你想加缓存吧,运营说"这块要实时";
你想做懒加载吧,产品说"这块是首屏重点"。
最后背锅的还是我们研发,一天天的叫不停:'首页怎么又崩了?'
项目初期,首页的设计
刚上线的时候,首页的逻辑可以说是简单得不能再简单了。
什么是推荐算法?没听过。
什么是缓存预热?没用过。
什么是动态规则配置?啥玩意儿?
运营要展示啥,我们后端就怎么 CRUD 接口拼出来,
该查的表查,该 for 的 for,查完怼给前端,能跑就行。
那时候谁提"优化"两个字,都是搞笑。我们连接口日志都不全,优化个锤子。
结果呢?刚开始内部公测的时候,一切都很完美。
首页能跑,接口能回,老板笑得跟刚融资了一样,说这产品牛逼,"有希望"。
还能说啥?打钱,投放,买广告------上线!
钱是越烧越多,用户也越来越多。
然后问题也来了, "并发"这俩字,终于不是面试题上面的词了。
首页是直连 MySQL 的,一到晚上那些没体验过爱情的苦的用户就开始扎堆登录,数据库压力直接拉满。
开始的时候是首页加载慢,后来是首页直接摆烂 ,啥也不展示。
更离谱的是,首页一挂,连带着其他接口也跟着崩了,一整片红。
QPS 飙升、慢查堆积、连接池炸裂,主库差点被掏空,现场就差没拉黄警戒线了。
解决办法呢?
很无奈、很简单、很朴素:重启,重启,再重启。
哪个服务炸了就拉起来,拉不起来就 roll 一下部署,运气好还能挺一会。
用户反馈最多的一句话: "这什么垃圾玩意儿?"
我们最常说的一句话: "已经再重启了 等一会儿。"
老板人都傻了,从那以后再也没有见过老板那菊花般灿烂的笑容了......
没办法,想了想......
首页再这么整下去,大家都别玩了 直接掀桌子跑路吧。
所以首页优化的第一步很明确:
先优化一个程序员,然后......把数据库救回来。
首页撑不住了,先救救主库吧
首页影响全站,我们第一时间想到的办法是:分主从,做读写分离。
于是就有了我们的第一步操作:首页走从库,主库继续抗写入。
想法很对,理论也很对。
结果一上线------延迟直接爆炸。
最离谱的一幕是这样的:
用户已经新增了一大堆,后台数据库看着人是进来了,
可前端首页一个都不显示,整个推荐列表是空的,像刚开服没人注册一样。
我们都懵逼了,一开始还以为是前端缓存没刷,
后端看代码、查日志,也找不到 bug。
最后查数据库一对比才发现:
从库数据压根没同步上来,延迟严重,首页查到的是"几分钟前的世界"。
最根本的原因:
主从服务器配置严重不一致。
主库是高配,带 SSD、大内存,干活快。
从库是凑合用的老机器,I/O 拉胯,卡得要命,
同步延迟直接冲到好几百秒。
主库写入再快,从库同步不过来也白搭。
首页一查,查的是"历史快照",人自然不显示。
问题1:从库延迟高得离谱
首页查从库之后,并没有快起来,反而经常卡半天才返回。
起初我们怀疑是查询太多、SQL 写烂了,
但仔细看监控才发现:根本不是 SQL 的锅,而是从库严重掉队。
主库是旗舰配置,大内存 + SSD,写入飞快;
从库是办公剩下来的老机器凑的,I/O 拉垮,卡得像翻车现场。
主库刚写完一条数据,从库还在发呆,
同步延迟高达几百秒,直接查出一堆历史数据。
首页查的,是"过时的快照",自然一个人都刷不出来。
问题2:主库压力没降,反而更高了
我们原以为把首页切到从库,主库压力就能缓解,
结果现实啪啪打脸:
首页读少了,主库的写入压力一下子被放大了出来。
用户一操作发动态、改资料、打招呼,
主库就得跟首页抢连接数、争锁,
以前是首页拖后腿,现在是"写入雪崩拖垮主库"。
原来首页不是"罪魁祸首",
只是那根压垮骆驼的最后一根稻草。
怎么办?继续拆、继续补救
1. 从库换机器,配置提上来
别再拿测试服机器凑数了,我们直接把从库换成主库同级配置:
内存翻倍、换 SSD、提升 I/O,延迟终于拉下来了。
2. 多从扩容,读分流
首页请求量大,我们把从库扩成两台:
一台负责推荐,一台负责关注流。
读流量分开走,互不影响,并发稳定不少。
3. 主库限流 + 写入优化
该合并的字段合并、能延迟落库的缓一缓、
某些数据打成批处理,一起刷。
目标就一个:
别让首页拖死主库,也别让主库拖死首页。
最终优化:我们做了一堆优化,最后......上云了
首页读写分离、从库扩容、主库限流,我们把脑子里学到的能用的都用了,
查延迟、看慢 SQL、调连接池、熬大夜拉日志......
真是把能用的"土办法"全用了一遍。
最终,还是技术老大成功把老板忽悠......哦不,说服了:
"哥,别省了,花钱上云吧。"
数据库迁到云上之后,读写分离、主从同步、冷热备份全自动,
从此不再半夜拉起数据库重启,
是的,我们用了三周手撸的优化,云厂商三秒钟就配好了。
冷备、热备、从库负载、IO 调优------
云服务全帮你搞定,稳定还送监控图。这也太贴心了叭!
就连老板都开始傻笑,就觉得这钱花的好值,就问还有谁!!!
我们回头看看这三周优化记录,
心里只剩一句话:
程序员不应该换命做 DBA。
缓存的坑,踩了还得继续用
可能有同学会问: "你首页搞那么多数据库优化,为啥一开始不用缓存?"
用过啊,怎么可能没用过。
不然怎么配得上"20 岁三十年经验"这行当。
只是那会儿我们整得比较直接......
整个首页,一个缓存,all in one。
为啥这么干?也不是不想细化,主要是当时负责的同事大脑比较单细胞,没想那么多,还有就是真的是赶时间 + 赶投放。
当时老板钱烧得飞快,就跟钱不是他的一样🤔,用户量也是一波一波往里进,
首页如果不实时一点,新用户根本没法看内容,直接劝退。
那缓存咋设置?
- 你说短点吧,刚缓存进去就过期了,还不如不设置 而且QPS 高得像要上热搜;
- 你说长点吧,用户刷到的都是"昨天的朋友圈"。新用户咋展示?
那时候最怕的两件事:
- 用户看到"新人列表:......emmm....八百年不更新 是不是app没人了"
- 老板看到"数据分析:用户活跃下降 转化率低的可怜 工资都发不起了"
所以干脆就粗暴上线:缓存形同虚设,几乎不用,靠直连查库硬抗。 还是查库吧,能查就查,炸了就重启......嗯,熟悉的配方。
后来真的扛不住了,我们终于做了一件正确的事:
又优化了一个程序员。
不是开玩笑,是老板直接调了个老哥来接首页,说是"有经验"。
这哥们一来,第一件事就拍板:重构!必须重构!首页必须得上细粒度缓存。
优化第一步:一人一个缓存,定制化走起
他提了个思路------既然首页对每个用户都不一样,那就 "一个用户一份缓存"。
用户一进首页,先查缓存,没命中就构建一个新的,缓存五分钟。
听起来不错,效果也很明显,新用户终于能看见自己的内容了。
但问题也很快来了:
Redis 快顶不住了。
- 每个用户一个缓存,Key 一下子成千上万;
- 刚开始几十万用户,缓存量飙升;
- 缓存构建频繁,Redis 变成另一个瓶颈。
优化第二步:一个用户多份缓存,模块分区
我们继续优化。
首页分多个模块,推荐、新人、附近、关注......
那我们就按模块拆开缓存,一个用户多个缓存 Key。
好处是显而易见的:
- 推荐和关注可以异步构建;
- 新人模块更新快,就设置更短的过期;
- 用户每次刷新不会全部 miss,降低压力;
但------Key 更多了。
Redis 的内存预警开始"唱歌",我们开始听不懂报警的含义。
优化第三步:数据拆分 + 滑动缓存,Redis喘口气了
还没完,我们还得继续榨干缓存的优化空间。
这时候,老哥又丢出一个"狠招":
首页模块缓存只存用户 ID,详情另查,分页靠 pop。
操作如下:
- 首页缓存不再直接存整块用户数据,只存一串用户 ID 列表;
- 存 Redis 的
list
结构,一页 10 个 ID,一次滑动就LPOP
10 个; - 用户信息(头像、昵称、签名)走用户详情缓存,支持全站复用;
好处显而易见:
- 首页缓存变轻了,一个用户几十字节就能搞定;
- 下滑就 pop,列表越来越短,越用越省内存;
- 用户详情走独立缓存,热用户命中率拉满,推荐页、资料页都能复用;
- 弹性强,用户下滑快就快刷,慢就慢刷,Redis 负载更稳定;
我们一看监控,Redis 的内存使用终于不再像过山车......
从 ICU 病床边缓缓坐起,问我们要了杯热水。
你以为这样就结束了吗?
当然没有。
接下来我们还得继续面对:
- 缓存预热怎么做? ------不预热就等着上线当天炸;
- 缓存穿透怎么拦? ------一堆恶意 ID 刷穿 Redis,直接打主库;
- 热门用户缓存怎么合并? ------全站都查同一个热门用户,缓存还没建完人先崩了。
这些问题我们一开始也没想到,不是我们聪明,是我们出过事。
别问我们怎么知道这些点该优化,
问就是踩过坑、挂过服务、挨过批。
但不管怎么说:
从"一个缓存糊全站",到"精细拆分分模块",
我们至少已经能自信地说一句......
"这波不是重启能解决的,我们是认真的。"
缓存预热这事儿,不是上线当天再想的
前面几轮优化,我们终于把首页缓存做成了模块化、轻量化、还能复用的版本。
Redis 的报警不响了,服务也不崩了,甚至我们一度以为自己"战胜了首页"。
但你知道的,幻觉就跟自信一样,来得快,走得更快。
因为又一个问题被我们忽略了:
问题:没预热缓存,上线当天首页又炸了
某天凌晨,新一批大促上线,几万个用户同时打开首页......
瞬间,Redis 空空如也,所有缓存 Key 都是 miss,
首页并发瞬间全压回数据库,
MySQL 主库的 CPU 曲线直接拉满,报警响成了迪厅。
最惨的是,我们上线前还信心满满:
"放心,有缓存,抗得住!"
结果发现......有缓存不等于有"内容"。
优化第一步:模块级预热,先喂新用户一口饭
老哥又出手了:"上线之前,先把首页几个热门模块的缓存给预热起来。"
于是我们搞了个小工具:定时构建模块级"公用缓存池" ,提前喂好几个核心内容板块:
- 推荐流:拉算法 Top500 热门用户 ID,做成 Redis List,分页 pop;
- 新人榜:选近几天注册的新用户,按热度排序塞进去;
- 附近的人:各大省市一个缓存 key,方便就近查人。
新用户第一次打开首页时 ,专属缓存可能还没生成好,
这时候就先从公用缓存里抓一页内容顶上,不白屏、不空列表,体验不掉分。
老用户如果有缓存就直接命中,没缓存也一样兜底走公共推荐。
缓存构建是异步的,一进来后台就开始建,下次再刷就命中了。
当然,一开始我们也天真地想搞"每人都预热",
后来发现:
老用户三天两头不上线,你预热也没人来用;
Redis 胀得飞起,白白浪费空间还没命中。
于是我们一拍大腿:干脆公用预热,新用户兜底,老用户慢慢补。
现在一上线,首页请求不抖了,Redis 也没爆,
就连老板都说我们这次"终于不像在重启服务器了"。
优化第二步:挡住穿透 + 稳住热点
上线后,我们刚准备躺一会儿,Redis 却又开始告警了。
不是内存暴涨,是请求飙升、CPU爆高、缓存命中率骤降。
我们愣了几秒,意识到问题来了:
问题1:缓存穿透,查不到就查库,一查一大片
有些用户刚注册没多久就被拉进推荐,
但缓存还没来得及构建,前端一个请求打过来,Redis 查不到......
于是请求直接打穿到数据库,一下涌入几千个"未知 ID"查库操作。
更糟的是......有些请求的 ID 根本不存在,是前端乱请求或者埋点异常。
这些查库操作,不但白查,还白拖了整个首页的响应时间。
问题2:热门用户缓存爆炸,几十万人都在查同一个人
你永远想不到,热门用户的"用户详情缓存"能被访问成什么样。
一个用户头像一换,全站首页都有他。
几万个人点进首页,同时查这个人的昵称、头像、签名、标签......
缓存确实命中了,可 Redis 被并发锤到顶。
一人走红,全 Redis吃灰。
怎么办?两个字:防抖
我们继续优化,补了两道"止血布":
1.穿透防护:查不到也得给我"有个返回"
给缓存加了一层"空值保护",
如果查不到数据,就写一个"空值占位",给个短 TTL(比如 1 分钟)。
下一次再查这个不存在的 ID,Redis 一查就知道不用继续往下了,
数据库保住,服务稳住。
2.热点合并:别人人查一份,红人查一份
我们为热门用户打了"热点缓存"标签,
将他们的详情缓存放到专属的热点 Redis 分区,并设置合理过期时间,
定时异步刷新,大家查的都是同一份,减少重复构建。
还有一招压轴的:
- 用户详情缓存不止在首页用,用户页、消息通知、卡片弹窗全要用;
- 所以我们把这份详情缓存统一做成"公共模块"复用,减少重复调用
优化第三步:运营插队,算法打架
就在我们以为缓存已经够稳了,Redis 也躺着喘气的时候------
运营来了,带着一个朴素而沉重的诉求:
"我们挑了一批优质用户,希望他们每次都能在前几页刷到。"
说得也没错:这批用户转化率高、付费能力强、颜值能打。
但问题也明显:
我们首页推荐的缓存,是按算法权重动态构建的,
强行"插队",不仅破坏排序逻辑,还可能影响其他人的曝光。
问题:推荐和人工干预冲突,排序打架
推荐列表是按用户兴趣、距离、热度综合打分算出来的,
但现在要硬塞几个"指定用户"进前几条,怎么办?
- 算法不愿意:你这一塞,打乱了模型判断;
- 工程师不情愿:缓存都构建好了,你还要插;
- 运营不退让:不插转化低,投流就白烧了。
解决方案:二级合成,插队不打架
我们最终采取了一个"两头都不骂我"的做法:
1.推荐缓存构建时,不直接插入,而是打标签
运营指定的用户 ID,我们不直接放进推荐列表,
而是提前标记为"置顶候选",单独做一份置顶池。
2.用户刷首页时,前端渲染前动态合成
最终返回首页数据时,
我们前几条位置会从置顶池中抽人,按权重插入算法结果前部。
- 置顶 + 算法,合并输出;
- 插队有人管,排序还有逻辑;
- 用户刷到转化高,运营开心了;
- 算法依然保留排序机制,团队也保住了尊严。
当然,这一套也不是没代价:
- 置顶缓存需要动态维护,位置插入要实时计算;
- 用户刷首页时,多了一道合成逻辑,稍有不慎就穿帮;
- 算法组依旧时不时来敲门:你们又偷偷插了几个?
但------
程序员的工作,从来就不是做选择,而是做平衡。
优化第四步:缓存刷新,冷热交替
首页缓存再怎么优化,也逃不过一个问题:
数据会变,缓存会老。
- 有人刚注册就改了昵称,结果首页还是老头像;
- 有人在线活跃,但首页还在展示他三天前的标签;
- 更离谱的是,有用户被封了,首页还把他顶在了推荐位。
问题很明确:
缓存过期不及时,用户体验直接掉头往下走。
问题:旧数据太多,缓存更新太慢
我们首页缓存设计是「每个用户一份 + 多模块拆分」,
缓存一多,更新就成了灾难。
- 定时刷新太慢,可能半天才轮一圈;
- 实时刷新太耗,热点用户更新频率太高;
- 手动清理靠人盯,一不盯就凉。
怎么办?
解决方案:冷热分级 + 异步重建
我们又卷出了几个方案:
1.热用户高频刷新,冷用户延迟更新
根据活跃度打分,高活跃用户进入"热榜",
缓存有效期缩短为 1 分钟,动态重建;
普通用户有效期保持 5~10 分钟,冷用户就更长。
这样保证首页展示永远"核心用户"最及时。
2.异步重建机制,写入延迟不打断
当某个用户触发了变更,比如换头像、改资料,
我们不会立刻清空缓存,而是:
- 给这个用户打个缓存"失效"标记;
- 放进异步消息队列,由后台服务慢慢处理;
- 后台重建缓存后再覆盖原有内容,整个过程对用户透明。
不打断主流程,也避免缓存穿透
3.缓存写入做幂等 + 去抖
针对同一用户短时间内重复修改的情况,
我们加了去抖动机制,同一个人 1 分钟内只更新一次缓存。
否则用户狂点编辑按钮,Redis 就变成了"个人小仓库",
不是缓存,是他改名的测试环境了。
这些优化加起来,终于让首页缓存具备了"活力":
不老、不炸、不穿透,还能抗住运营插队和用户突击。
当然,每一次优化,背后都是一次上线出事、一次紧急修复、一次复盘之后的血泪教训。
最后:首页缓存的本质,其实是控"变"
从最开始的「一个缓存糊全站」,到现在的「分模块、分用户、分层级缓存」,我们绕了一大圈,终于意识到:
缓存最怕的是"变" :用户变、数据变、逻辑变。
而我们做的所有事,都是为了对抗这个"变":
- 为了数据变 → 我们加了分模块 + 缓存拆分 + 去冗余;
- 为了用户变 → 加了新用户兜底 + 缓存异步构建;
- 为了流量变 → 做了预热机制 + 限流 + Redis 扩容;
- 为了运营插手带来的"人为变" → 加了插队能力 + 权重优先级;
- 为了用户频繁操作带来的"突变" → 加了幂等 + 去抖。
所以首页缓存抗压的核心不是"加缓存",
而是识别变动 + 控制波动 + 拆分成本 + 快速恢复。
一切的目标,只有一个:
让首页"看起来什么都没发生",但其实我们做了很多事。
以及最重要的一点:老板得有钱......
因为每一次缓存崩了、首页挂了,都是一次"烧钱灰飞烟灭现场";
虽然能用技术稳住一次,但是挡不住下一波运营提需求、用户猛刷榜、老板突然发疯...
------这系统,不光要能抗,还得扛得起"花钱优化"的代价。
最后,真心感谢老板愿意买单,
让我们在一次次危机里,有机会把技术做对、做深,也做稳。