TME酷狗后台开发二面凉经
《面试题目》
一面:
- 自我介绍
- 简历上缓存工具类是怎么设计的?在 MySQL 读写分离场景下,从库更新延迟导致缓存读到旧数据,如何解决?
- DeferredResult 与 WebSocket 长连接相比各有什么优劣?
- 讲一下 RAG 的完整链路
- 文档拆分(Chunk)策略有哪些?
- Rerank 策略如何实现?发现效果不好怎么优化?
- 消息队列消息可靠性如何保证?
- Linux 服务器负载高如何排查?
- MySQL 存储引擎有哪些,各有什么区别?
- 最左前缀原则是什么?
- 设计索引的思路是什么?
- B+ 树的结构与原理
- 聚簇索引与非聚簇索引的区别
- 为什么推荐主键自增?
- Redis 常用数据结构有哪些?
- 如何用 ZSet 实现滑动窗口限流?
- 了解 Bitmap 等其他数据结构吗?
- Redis List 的底层实现是什么?
- 反问:面试官最看重候选人哪方面表现?组里做什么业务?
二面:
- 是否了解 Go?
- Go 的内存管理和垃圾回收与 Java 相比有何不同?
- C++ 为什么必须让开发者手动做内存管理?
- Go 三色标记回收器的原理(灰色节点队列/BFS维护)
- 在三色回收进行时,GMP 调度模型在做什么?
- 设计一个高并发系统(面试官全程打断并质疑方案)
- 联合索引相关八股
- 你与其他候选人相比有什么优势?
《参考解析》
-
读写分离下的缓存脏读问题:核心矛盾在于从库存在复制延迟(通常毫秒到秒级),若此时读从库并写入缓存,缓存会短暂存储旧数据。解法:方案一,强制读主库后再更新缓存(适合一致性要求极高场景);方案二,对写后短时间内的同一 key 强制走主库(路由策略);方案三,延时双删——更新主库后先删缓存,等从库追平后再次删缓存。
-
DeferredResult vs WebSocket:DeferredResult 是 HTTP 长轮询,无需保持持久连接,单次完成后连接释放,适合低频异步回调;WebSocket 是全双工持久连接,适合高频双向推送,但服务器需维护大量连接状态。实际项目中,异步任务结果通知用 DeferredResult 更轻量,实时聊天/推送用 WebSocket。
-
RAG 效果不好的优化方向:召回层——检查 Chunk 切分粒度(太大失去精度、太小丢失上下文)、混合检索(向量+关键词 BM25)、调整 TopK 和相似度阈值;Rerank 层——引入交叉编码器(CrossEncoder)对召回结果重排;生成层——优化 Prompt 模板,给模型更明确的指令约束,减少幻觉;评测——用 RAGAS 框架从召回率、精确率、答案相关性三个维度量化改进效果。
-
Go 三色回收与 GMP 的关系:三色标记(白/灰/黑)是并发 GC,在标记阶段会触发 STW(Stop The World)暂停 GMP 的 P(处理器),完成根对象扫描后恢复;并发标记时 GMP 继续运行,但写屏障(Write Barrier)会拦截指针修改,保证黑色对象不直接指向白色对象(三色不变式)。G goroutine 在被 STW 时会在安全点(safepoint)挂起。
-
为什么主键推荐自增:InnoDB 采用 B+ 树聚簇索引,主键决定数据物理顺序。自增主键保证新数据始终追加到叶子节点末尾,避免页分裂;非自增(如 UUID)会频繁触发页分裂,导致大量碎片,写入性能下降。
-
ZSet 实现滑动窗口限流:以用户 ID 为 key,score 为毫秒时间戳,member 为请求唯一 ID(如 UUID);每次请求先
ZREMRANGEBYSCORE删除窗口外的旧记录,再ZADD添加当前请求,ZCARD获取窗口内总数与阈值比较,超限则拒绝;整个操作用 Lua 脚本保证原子性。