面灵AI→

京东后端一面复盘:RAG 知识库、向量库选型与 CAS 八股

轮次
一面
时间
2026-09
来源
牛客网

《面试题目》

一面(约 20 分钟)

  1. 自我介绍,简单介绍自己的技术背景
  2. 项目部分:RAG 知识库的业务场景是什么?
  3. 针对高并发写热点问题,你有哪些优化思路?
  4. 向量库为什么选 Milvus?
  5. 你了解过 Code Graph 这类开源项目吗?
  6. 知识库更新后,怎么保证内容及时同步?
  7. Agent 怎么识别用户意图?
  8. 项目的核心创新点是什么?
  9. 简历写了性能提升,具体怎么测出来的?
  10. 如果线上检索结果不准,怎么定位问题?
  11. Java 八股:CAS 是什么,有什么问题?
  12. ABA 问题怎么解决?
  13. 线程池核心参数怎么设置?
  14. ThreadLocal 底层原理
  15. B+ 树为什么适合做 MySQL 索引?
  16. 慢 SQL 怎么排查和优化?
  17. 平时是否会使用 AI 工具辅助开发?你认为目前 AI Coding 最大的问题是什么?
  18. 缓存击穿怎么解决?
  19. Kafka 如何保证消息顺序?
  20. 后续有什么实习或者职业规划?
  21. 反问环节

《参考解析》

  1. RAG 知识库的业务场景要讲到「谁在什么情况下用」:别停在「把文档灌进向量库做问答」。完整的答法是——用户是谁(内部客服 / 销售人员 / 终端用户)、他们原来怎么做(翻文档、问同事,平均耗时多久)、现在期望的产出形态(带出处的答案还是只给链接)、服务的知识范围与更新频率。再补一条边界:哪些问题明确不该由这个知识库回答(涉及权限、涉及实时数据),这些要靠路由转出去。把场景讲到这个颗粒度,后面所有技术选型才有落点。

  2. 高并发写热点:热点写通常集中在少数行或少数分片上,思路按「打散 → 削峰 → 换结构」三层来:打散是把单点计数拆成多行/多桶(如 update ... where bucket = rand(N))再汇总,或按业务键做一致性哈希分散到多个库表;削峰是把同步写改成写队列 + 批量落库(合并窗口内同一 key 的更新,只落最终值),并在前端做防抖限流;换结构是把高频更新的状态从行存挪到内存或 Redis,落库只做持久化快照。要提醒的是打散之后读要聚合、多桶计数会有短暂不一致,得说清业务能不能接受。

  3. 向量库为什么选 Milvus:给一组比较维度比背参数有用——部署形态(Milvus 是分布式架构、支持存算分离与水平扩展,适合亿级向量;FAISS 是库不是服务,PgVector 适合小规模省运维)、索引类型(HNSW、IVF、DiskANN 等可选,内存与召回率能权衡)、过滤能力(元数据标量过滤与向量检索的结合方式,这决定多租户和权限场景好不好做)、生态与运维成本(是否已有团队在用、备份与监控是否成熟)。再补一句取舍:数据量小、已有 PostgreSQL 时 PgVector 往往更划算,别为了「先进」提前上分布式集群。

  4. 知识库更新后的同步与检索不准的定位:同步要做成「变更事件驱动 + 可对账」,文档系统或数据库变更时发消息,消费者完成解析、切分、向量化、写索引,并记录每个文档的版本号与处理状态;同时保留一条定时全量对账兜底,防止消息丢失导致索引与源数据漂移。检索不准要按链路逐段排查:先看标准答案所在的 chunk 有没有被正确切分和写入索引(把答案片段手工喂给模型,看它能不能答对,就能区分是检索问题还是生成问题),再看 query 改写与分词、向量模型是否适配该领域、元数据过滤是否误杀、最后才是排序与 rerank 策略。这个「手工喂证据」的隔离实验是这道题最值钱的一句话。

  5. CAS 与 ABA:CAS 是「比较并交换」,靠 CPU 的原子指令实现——读当前值,与预期值比较,相等才写入新值,失败则重试。常见问题有三个:ABA(值从 A 改成 B 又改回 A,CAS 察觉不到中间变化,在链式结构里可能导致节点被错误复用,解法是加版本号,如 AtomicStampedReference);自旋开销(高竞争下大量线程空转,要设自旋上限或改用锁/分段);只能保证单个变量的原子性(多个变量要么合并成一个对象用 AtomicReference,要么改用锁)。

  6. ThreadLocal 底层原理:每个 Thread 内部持有一个 ThreadLocalMap,key 是 ThreadLocal 对象(弱引用),value 是线程私有的值,所以查找不需要加锁。两个容易踩的点:一是内存泄漏——key 是弱引用会被回收,value 是强引用,线程池里线程长期存活就会一直挂着没人清的 value,所以用完必须 remove();二是哈希冲突用的是开放定址(线性探测)而不是链表,且 ThreadLocal 的 hash 值经过魔数处理后分布更均匀。回答时把「线程池 + 忘记 remove = 泄漏」这条因果讲出来就够扎实。

  7. B+ 树为什么适合做索引、慢 SQL 怎么排查:B+ 树的价值在三点——非叶子节点只存键,一个磁盘页能放更多键,树高更矮、磁盘 IO 次数更少;叶子节点之间用链表相连,范围查询和排序可以顺序扫描,不用回到根节点;所有数据都在叶子层,查询路径长度稳定。慢 SQL 的排查顺序固定为:先开慢查询日志确认真实的慢语句 → explain 看访问类型、命中索引、扫描行数与是否用到临时表/文件排序 → 针对性地改(补联合索引并注意最左前缀、避免在索引列上做函数或隐式类型转换、把 select * 收窄、大分页改游标或延迟关联)→ 结构性问题再考虑加缓存、读写分离或分表。

  8. 缓存击穿与 Kafka 顺序性:缓存击穿是某个热点 key 过期瞬间大量请求同时打到数据库,解法是互斥重建(只有一个线程去查库并回填,其他等待)、热点数据逻辑过期(不设物理 TTL,后台异步续期)、以及提前预热 + 随机化过期时间;要和穿透(查不存在的数据,用空值缓存和布隆过滤器)与雪崩(大批 key 同时失效,用过期时间打散 + 多级缓存)区分开。Kafka 只保证分区内有序,所以「保证顺序」的实际做法是:让需要有序的消息用同一个 key,被投递到同一个分区,消费者端单分区单线程处理,并关闭 max.in.flight.requests 带来的重排风险(或设为 1);如果全局有序是硬需求,那只能退到单分区,吞吐会明显受限,所以要反问业务是否真的需要全局序。