面灵AI

百度后端开发岗面经 13:缓存分层、Feed 流与线程安全

轮次
多轮面试合集
时间
2026-09
来源
牛客网

《面试题目》

  1. MySQL 如何承载海量请求?
  2. 本地缓存和 Redis 分别适用于哪些场景,需要考虑哪些机器数量和成本因素?
  3. 大量相同 Query 请求如何使用缓存或 Singleflight 合并处理?
  4. 进程内缓存承载不下时,内存和磁盘分别适用于哪些场景?
  5. 给定一个有向图,如何判断图中是否存在环?
  6. 介绍项目和实习经历
  7. 什么是 MySQL 覆盖索引?
  8. MySQL 深分页问题如何解决?
  9. 大模型幻觉为什么会出现,如何使用 RAG 缓解?
  10. 是否了解 MCP?
  11. 场景题:高风险规则下发可能影响算法准确率,如何设计一套基于 RAG 的规则下发方案?
  12. 自我介绍
  13. 链表反转
  14. k 个一组链表反转
  15. 朋友圈 Feed 流是怎么做的?
  16. 游标分页怎么做?追问:为什么不用传统分页?
  17. 点赞系统怎么做?
  18. 点赞数据存在哪里?追问:为什么先存在 Redis?
  19. 通过 MQ 写 MySQL,怎么保证数据一致性?
  20. 端用户反馈“消息发不出去”,你怎么定位?
  21. 没有全局异常,只有一个用户出问题,怎么沿链路排查?
  22. 如果定位为网络抖动造成的数据丢失,怎么处理?
  23. 知识库是怎么构建、优化和测评的?
  24. 讲一下 Go 的垃圾回收原理
  25. 一个 1 MB 日志文件,怎么统计 Top-K IP?
  26. 用一条 Linux 命令实现呢?
  27. 如果日志超过 100G,怎么做?
  28. 自我介绍,重点讲实习期间产出
  29. Java 创建线程的方法有哪些?
  30. 线程池按照自身特点可以归纳为哪几大类?
  31. 线程安全的集合有哪些?
  32. 什么时候用 Map,什么时候用 List?
  33. 当前场景需要用 List 且要保证线程安全怎么办?
  34. synchronized、ReentrantLock 这些关键字和可重入锁,它们的区别是什么?
  35. 常见的排序算法有哪些?
  36. 快排的算法原理?
  37. 一个项目里如果有同(原帖在此截断)

《参考解析》

1. MySQL 承载海量请求的分层思路。数据库前面的每一层都是为了把请求挡在更便宜的地方:最前面是 CDN 与静态化,其次进程内缓存(Caffeine,纳秒级、无网络开销,但多实例间不一致且容量受堆限制),再是 Redis(共享、可扩容,代价是一次网络往返和序列化),最后才落到 MySQL。MySQL 自身要做的是把无效请求变少:覆盖索引避免回表、读写分离把分析类查询挪到从库、热点行拆分、大事务切小、连接池限流。成本上要算清楚的是命中率拐点——缓存容量继续加大但命中率不再上升时,多加的机器就是纯浪费。

2. Singleflight 合并相同请求。同一个 key 在极短时间内被大量并发请求时,只让第一个请求真正回源,其余请求挂在同一个结果上等待,这就是 Singleflight(Go 的 golang.org/x/sync/singleflight 是标准实现)。它和缓存的区别是:缓存解决「重复的读」,Singleflight 解决「同一瞬间的重复读」,两者叠在一起才能挡住缓存击穿。要注意的是它只对同一实例内的并发有效,多实例下仍需靠 Redis 分布式锁或逻辑过期来兜底。

3. 有向图判环。两种标准解法:DFS 三色标记,节点分未访问、访问中、已完成,遍历中遇到「访问中」的节点说明存在后向边即存在环;或者拓扑排序(Kahn 算法),不断把入度为 0 的点入队删除,若最终删掉的节点数少于总数则说明剩下的节点互相成环。DFS 更好写,拓扑排序顺便能得到一个合法的执行顺序——如果面试官追问「环上的节点是哪些」,拓扑排序剩下的那部分就是答案。

4. 深分页为什么慢,怎么改LIMIT 1000000, 20 的执行方式是先取出前 1000020 行再丢掉前 1000000 行,翻得越深越慢,而且回表次数跟偏移量成正比。三种改法:基于游标(记住上一页最后一条的 id 或排序键,WHERE id > ? ORDER BY id LIMIT 20),走覆盖索引先只查主键再回表关联,或者业务上直接限制最大翻页深度。游标分页的代价是不能跳页,这也是追问「为什么不用传统分页」时要答的取舍。

5. 点赞系统与 MQ 写库一致性。点赞是先写 Redis 再异步落库的典型场景:Redis 里存业务维度的计数和「谁赞过谁」的集合,读路径完全不碰 MySQL,写路径把事件投到 MQ 再批量合并入库。之所以先存 Redis,是为了把高频写打在内存上、避免每点一次赞就打一条 UPDATE。一致性上要做到最终一致:MQ 至少一次投递 + 消费端幂等(用「用户 + 目标」的唯一键或 Redis set 的幂等性),入库存绝对计数或明细二选一;如果业务要求强一致,就得接受同步写库的性能代价,把取舍讲清楚比给一个万能答案更重要。

6. 1 MB 与 100G 日志统计 Top-K IP。1 MB 可以整份读进内存,awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20 一条管道就够;一次读入时用哈希表计数是 O(n),比排序的 O(n log n) 更快,但 1 MB 量级下差别可以忽略。100G 装不下内存,标准答案是 MapReduce 思想的分治:按 IP 哈希(比如 hash(ip) % 1000)切成 1000 个小文件,保证同一 IP 一定落在同一个文件里,然后逐个文件用哈希表统计出各自的 Top-K,最后对 1000 组 Top-K 做一次归并排序取全局前 K。如果 IP 分布集中在少数几个值上导致分片倾斜,可以切两轮或者换更细的哈希,面试官通常会追问这一点。

7. Go 的垃圾回收。Go 用的是并发三色标记清除,配合写屏障保证标记期间对象状态一致,GC 与用户协程并发执行,只在很短的阶段暂停(STW)。触发条件是内存增长达到 GOGC 设定的比例(默认 100%,即堆翻倍时触发)。因为不做分代也不做压缩,它的停顿是毫秒级以下,但代价是内存占用偏高、吞吐不如分代回收器。面试里可以补一句调优手段:GOGC 调大减少 GC 频率、GOMEMLIMIT 给堆设上限避免内存爆掉。