面灵AI→

携程 Java 开发 AI 面面经(10.08,全程 50 多分钟)

轮次
AI面
时间
2026-10
来源
牛客网

《面试题目》

开场与动机

  1. 请简单做个自我介绍,包含基本信息、毕业院校、毕业时间和所学专业;如果有实习或兼职经历,可以重点介绍。
  2. 分享一段在实习过程中需要和团队成员协作完成任务的具体案例。
  3. 在这段协作过程中,有没有遇到过和对接的同学意见不一致的情况?你是怎么处理的?
  4. 你是如何理解携程这个岗位的?你选择应聘携程的原因是什么?

Java 与并发

  1. ConcurrentHashMap 是如何实现线程安全的?它的 size 计算是如何实现的?
  2. JDK8 中 HashMap 会锁链表头节点,那当桶内的链表已经转化为红黑树时,它是通过什么方式保证写操作线程安全的?
  3. 什么是 CAS?它的实现原理是什么?
  4. 高并发场景下,大量 CAS 操作持续重试会带来什么问题?
  5. Java 中通常是怎么解决 ABA 问题的?

MySQL 与网络

  1. MySQL 执行一次查询的完整流程是怎样的?针对查询过程可以做哪些优化?
  2. 如果一条 SQL 因为优化器选错了索引导致查询变慢,你会怎么排查和处理?
  3. MySQL 的统计信息里具体存储了哪些和索引选择相关的核心数据?
  4. 从浏览器输入 URL 到页面完整展示,中间经历了哪些完整过程?如果页面加载超时,你会怎么排查?
  5. 如果 HTTP 站点在建立连接阶段出现加载超时,TLS 握手环节的常见排查点有哪些?

系统设计:千万级积分排行榜

  1. 需要设计一个千万级用户的每日更新积分排行榜,要求支持高效的排名查询和积分更新,你会如何设计?
  2. 为什么要选用 Redis 的 ZSet?好处是什么?
  3. 你提到会按用户 ID 做分片来降低单节点压力,那分片之后用户的全局排名具体是怎么计算得到的?
  4. ZSet 分片预计算积分区间快照的方案,和直接使用单 key 全量承载千万级数据的方案,你是基于哪些因素做权衡和选型的?
  5. 如果遇到突发的积分批量更新,比如短时间内百万级用户同时完成任务获得积分,你会怎么调整方案来保障排行榜的查询和更新性能?

项目与 AI 学习

  1. 聊聊你的智能销售数据分析 Agent 项目,过程中有没有遇到过特别有挑战性的技术问题?难点在哪?怎么排查原因、敲定方案并解决?
  2. 你提到通过引入 Query 改写和多路召回优化了 RAG 的召回质量,落地过程中有没有遇到具体阻碍?比如多路召回带来的结果冗余,你怎么权衡和处理?
  3. 这个优化上线稳定运行之后,你有没有对这个链路做过后续迭代或优化?比如发现新的改进点,或基于线上运行数据做过调整?
  4. 你平时通过哪些路径学习 AI 相关技术?说说最近学习的一项 AI 技术的具体内容。
  5. 你提到最近在学习 AI Coding 的 Brainstorming Skill、开源人机协同项目 Multica、Loop Engineering 等,为什么先选这些而不是其他同类的 AI 技术?
  6. 学习这些技术内容时,你有没有摸索出适合自己的、能更快吃透技术思路的学习方法?
  7. 你把这些学到的 AI 技术落地到项目里时,做成了什么具体的事情?拿到了怎样的实际效果?

《参考解析》

ConcurrentHashMap 的线程安全要分三个时期讲,JDK8 之后是「CAS 加桶内加锁」。 JDK7 用 Segment 分段锁,把哈希表切成若干段、每段一把锁,并发度等于段数,缺点是并发度固定、内存开销大。JDK8 改成「数组 + 链表 / 红黑树」,锁的粒度细到单个桶:插入时先用 CAS 无锁地尝试把值放到空桶(casTabAt),只有桶非空时才用 synchronized 锁住该桶的头节点再插入;扩容(transfer)时用 ForwardingNode 标记迁移过的桶,其他线程遇到标记就协助扩容。这里正好接上面试官的追问:链表转成红黑树之后,锁的对象从链表头节点变成了树的根节点,插入或删除前对根节点加 synchronized,红黑树的旋转与重平衡都在锁内完成,所以并发安全不依赖链表结构本身。至于 size,JDK8 不再用带锁的段计数,而是用 baseCount 加一个 CounterCell[] 数组:无竞争时 CAS 更新 baseCount,一旦 CAS 失败说明有并发冲突,就按线程哈希分散到不同的 CounterCell 上各自累加,最终把 baseCount 与各 cell 求和得到近似值——这也是为什么 size() 返回的是「估计值」,并发下不保证精确,需要强一致时要靠 mappingCount() 或额外同步手段。

CAS 的原理、代价与 ABA 是一次成串的追问,要答出因果。 CAS 是「比较并交换」:compareAndSwap(期望值, 新值) 在硬件层面用一条原子指令实现(x86 上是 lock cmpxchg),JVM 通过 Unsafe 的本地方法暴露给上层,AtomicInteger、ReentrantLock 的 AQS 队列、ConcurrentHashMap 的空桶插入都建立在它之上。它的代价是自旋:如果竞争激烈,CAS 反复失败就变成忙等,白烧 CPU,而且在高争用下失败重试会加剧缓存行在核心之间来回同步(伪共享与总线风暴),此时分段累加(LongAdder)或直接加锁反而更快——所以「无锁一定优于加锁」是错的,要看竞争程度。CAS 还有三个边界:只能保证单个变量的原子性,多变量要打包成对象或上锁;存在 ABA 问题——值从 A 改成 B 又改回 A,CAS 会误判「没变过」。解决 ABA 的标准做法是加版本号,AtomicStampedReference 每次修改同时递增一个 stamp,比较时值和版本一起比;只关心「是否被改过」而不关心改了几次时可以用 AtomicMarkableReference 的布尔标记。另一条路是用不可变对象整体替换,AtomicReference 里存指向新对象的引用,从根上避免中间态。

MySQL 的查询流程与优化要连成一条链路来讲。 一条查询进来,依次经过连接器(鉴权、连接管理)、查询缓存(8.0 已移除)、解析器(词法与语法分析、生成解析树)、预处理器(校验表与列是否存在、展开视图)、优化器(决定连接顺序、索引选择、是否下推条件下沉,产出执行计划)、执行器(按计划调用存储引擎接口逐行取数并做过滤、排序、聚合),最后经存储引擎(InnoDB 通过聚簇索引和缓冲池读取页)返回结果。可优化的点对应这条链路:网络与连接层用连接池减少握手;SQL 层避免 select *、把过滤条件写成能走索引的形式(不在索引列上做函数运算、注意隐式类型转换、like 不以 % 开头);索引层做覆盖索引减少回表、用联合索引的最左前缀、把排序列纳入索引以省掉 filesort;架构层加缓存、读写分离、冷热分离。出现慢查询的标准排查路径是:先看慢查询日志确认耗时与扫描行数,再用 EXPLAIN(或 EXPLAIN ANALYZE)看 type、key、rows、filtered 和 Extra,定位是没走索引、走了错的索引、还是回表太多。

「优化器选错索引」这道题在考你是否知道统计信息是估算出来的。 MySQL 的优化器不看真实数据,而是基于统计信息估算每条执行路径的成本(I/O 成本加 CPU 成本)后选最低的那个,估算失准就会选错。最常见的原因是索引基数(cardinality)不准——它是采样估计出来的,InnoDB 默认采样 20 个数据页;当数据分布倾斜、或表刚做过大批量增删改而统计信息还没更新时,基数会严重偏离真实值。处理手段有几步:用 ANALYZE TABLE 重新采样刷新统计信息;用 EXPLAIN 加 SHOW INDEX FROM 表 对比估算行数与实际行数;如果确实要强制走某个索引,短期可以用 FORCE INDEX 或 STRAIGHT_JOIN 兜底,但那是止血不是治病;根因层面要考虑是否该补一个更贴合查询的联合索引、或者用 WHERE 条件把选择度提上去。统计信息具体包含:每个索引的基数(不同值的估计个数)、索引占用的页数、表的总行数与数据页数、以及 8.0 引入的直方图(对列的值分布分桶统计,帮助优化器估计非索引列或倾斜数据的选择率);采样页数由 innodb_stats_persistent_sample_pages 控制,调大能提高准确度但会增加 ANALYZE 的开销,这是一个典型的准确性与成本权衡点。

「从输入 URL 到页面展示」是背了无数遍的题,高分点在超时排查那一段。 完整链路:浏览器解析 URL 并查缓存(浏览器缓存、Service Worker、系统 DNS 缓存)→ DNS 解析(本地 hosts、递归查询到权威服务器)→ 建立 TCP 连接(三次握手)→ TLS 握手(协商版本与密码套件、验证证书、交换密钥,TLS 1.3 只需一个往返)→ 发送 HTTP 请求并等待响应(可能经过 CDN、负载均衡、网关、应用服务)→ 浏览器解析 HTML 构建 DOM,遇到 CSS、JS、图片等资源继续发起请求 → 合成 CSSOM、执行 JS、构建渲染树、布局、绘制、合成上屏。排查超时要按阶段切分而不是笼统地看「打不开」:先用 curl -w 或浏览器 DevTools 的 Network 看耗时分布(DNS、连接、TLS、首字节 TTFB、内容下载各自多少毫秒),再看 ping/traceroute 判断网络可达性,看 nslookup 判断解析是否正常,服务端侧看网关与应用的访问日志、线程池与连接池是否打满。TLS 阶段的常见问题有几类:证书链不完整或过期(浏览器报错,但一些客户端只在部分环境失败)、SNI 未正确配置导致 CDN 或网关回落到默认证书、TLS 版本或密码套件不匹配(老旧客户端协商失败)、中间设备做深度包检测或需要额外握手往返导致超时、系统时间偏差导致证书有效期校验失败。把「TLS 握手失败」和「TLS 握手慢」分开处理,前者看证书与协商,后者常是网络往返次数或 OCSP 校验被墙导致。

千万级每日积分排行榜,主线是「Redis ZSet 承载热榜 + 分片解决容量与热点 + 快照解决跨片排名」。 ZSet 的底层是跳表加哈希表,ZINCRBY 更新分值、ZREVRANGE 按排名取区间、ZREVRANK 取某人的名次,都是 O(log N) 级别,天然贴合排行榜的三种访问模式(更新、取 TopN、取名次),这是选它的核心理由;同时分值相同的情况下会按成员字典序排序,需要处理同分并列的名次语义。千万级用户全量放一个 ZSet,内存与单节点写入都会成为瓶颈,所以按用户 ID 分片成若干个 ZSet(例如按 uid % 128,或者按积分区间分流)。分片带来的直接问题是全局排名:常规做法是「片内名次 + 其他片的人数贡献」——全局排名 = 本片内 ZREVRANK 得到的名次 + 所有比自己分数高的片内元素个数,而后者可以用每个分片维护的分数直方图 / 有序分数快照快速算出:每个片子定期把「分数 → 该分数的人数」做成分桶统计,查询时对所有分片的桶做二分累加即可得到「分数高于我的总人数」,再叠加同分内的次级排序规则。查询 TopN 则更简单,从每个分片各取 N 个候选做一次归并即可。选型权衡要摆出三组矛盾:单 key 方案的实现最简单、排名天然准确,但受单节点内存与网络带宽限制、扩容需要 resharding、并且是明显的热点;分片加预计算方案的容量与吞吐可线性扩展,代价是排名变成了近似或需要额外维护快照、以及跨片归并带来的延迟;维护成本上,快照要定期刷新,刷新频率决定排名的时效性。百万级突发更新的应对要在写入侧做削峰:把积分变更先写消息队列或 Redis 的缓冲结构(如按用户哈希的待更新列表),由后台按批 ZINCRBY(pipeline 批量提交减少往返),必要时对同一用户的多次变更先本地合并再落 ZSet;读取侧则用「短 TTL 的 TopN 结果缓存 + 名次惰性计算」,并对非关键的名次查询降级到上一次快照,把写放大与读放大的压力错峰。另外要记得榜单是「每日更新」的,可以按天建 key 并设置过期时间,用定时任务做快照与归档,避免单个 key 无限增长。

RAG 多路召回的冗余问题,标准解法是「融合排序 + 去重 + 重排」三层。 多路召回(向量召回、关键词 BM25、Query 改写后的多次召回、按元数据过滤召回)各自带不同的偏置,简单拼接会出现同一文档被多次命中、或者高度相似的片段挤占名额。工程上的做法是:先在合并阶段按文档 ID 或内容指纹去重、同一文档的多个片段只保留最高分并做聚合;再用融合算法统一各路的分数尺度——RRF(Reciprocal Rank Fusion)只用排名不用原始分数,score = Σ 1/(k + rank_i),天然规避了不同召回通道分数不可比的问题,实现也简单;最后进重排(cross-encoder 或 LLM 精排)对 TopK 做细粒度打分,只把最优的少数片段送进生成。要判断有没有改进,不能只看「召回率涨了」,而要建一套离线评测集,跟踪最终答案的正确率、引用命中率与冗余率,同时盯线上指标(首答采纳率、追问率、延时);上线后还能继续做的迭代包括 Query 改写的策略收敛(哪类查询值得改写)、召回通道的权重调参、片段切分粒度调整,以及把线上 badcase 回流进评测集。回答这题时主动提到「多路召回一定会带来冗余,关键是让排序阶段而不是召回阶段来做取舍」,会显得真的落地过。

AI 面试的最后一段是行为面,准备方式和八股完全不同。 AI 面试官会顺着你上一句的答案追问,所以回答里不要主动引入自己讲不深的名词——提到 Multica、Loop Engineering 这类具体项目,就要准备好被追问「它解决什么问题、和你做的事有什么关系、你实际用了哪一部分」。学习路径的问题可以按「信息源 + 实践方式」组织:论文与技术博客、开源仓库与源码、社区讨论,再配合最小闭环——挑一个小场景把它跑通、复现一遍问题、写下失败与修正。落地效果要给可验证的结果而不是「感觉变好了」:比如某个内部流程的处理时间、某类问题的准确率、或者省掉的人工步骤。整场 AI 面 50 多分钟的体验也值得记下来:机器面试官会连续追问,答得越具体、被追问的空间越大,所以每段回答收尾时最好主动给一个可继续的抓手(做了什么、结果如何),而不是留下含糊的说法让对方无从下手。