AI+Java中级开发面试题库
生成时间:2026-05-20 23:32 | 题目数:10 | 比例:AI 5 + Java 5
【面试题】1. RAG系统中检索到的文档片段和用户问题相关度不高,导致大模型生成的回答"跑偏",你会从哪些环节优化?
【标准答案】这是一个很典型的RAG效果问题,我会从四个环节逐层优化:
第一,检索前优化——改进Chunking策略。不能简单按固定字数切分,要采用语义切分,比如按段落、按主题边界切,或者用滑动窗口+重叠区保证上下文完整性。另外查询侧可以做Query Rewrite,把用户的口语化问题改写为更精确的检索query,还可以用HyDE(让大模型先模拟一个答案,用模拟答案去做检索),命中率会高很多。
第二,检索中优化——从纯向量检索升级为混合检索(Hybrid Search),向量检索抓语义相似,BM25抓关键词精确匹配,两者融合排序效果更好。同时可以引入Reranker模型,比如bge-reranker,对初筛的top-k结果做精排,把真正相关的排到前面。
第三,检索后优化——做相关性过滤。对rerank后的结果设一个相似度阈值,低于阈值的直接丢掉,不要把噪声喂给模型。还可以做上下文压缩,用大模型把长文档压缩成只包含关键信息的摘要再送入prompt。
第四,模型侧兜底——在system prompt中明确告诉模型"如果检索内容与问题无关,请直接说明无法回答",避免模型强行关联生成错误内容。
实际项目中一般先做混合检索+Reranker,效果提升最明显,再逐步优化切分和查询改写。
【面试题】2. Spring AI和LangChain都是AI应用开发框架,它们的定位和设计理念有什么核心差异?Java团队应该怎么选?
【标准答案】核心差异在于生态定位和设计哲学:
LangChain是Python生态的AI开发框架,核心优势是生态丰富、迭代快、社区活跃,几乎各种大模型、向量库、工具都有现成集成。它的设计偏向"灵活组合",Chain、Agent、Tool可以自由编排,但灵活的代价是抽象层多、调试困难,版本迭代也经常breaking change。
Spring AI是Spring生态的AI扩展,核心理念是用Spring的方式做AI开发——依赖注入、自动配置、统一抽象。它的设计更偏向"企业级稳定性",API风格和Spring Boot一脉相承,对Java团队来说学习成本极低。比如用@Bean配置ChatModel,用Advisor做拦截链,都是Spring开发者熟悉的方式。
选型建议:
- 如果团队技术栈是Java/Spring全家桶,且AI功能是业务系统的组成部分(比如在现有微服务中加AI能力),选Spring AI——集成无缝、运维统一、团队不需要切换语言。
- 如果团队做的是纯AI产品原型、需要快速试错、大量调用最新AI工具链,LangChain更合适——生态领先半年以上。
- 如果是混合团队,可以LangChain做AI核心服务(独立部署),Spring AI做业务侧调用封装,通过API解耦。
我实际项目用的是Spring AI + 独立部署的Python向量服务,各取所长。
【面试题】3. Java中ConcurrentHashMap在JDK1.7和JDK1.8的实现有什么区别?为什么1.8要重新设计?
【标准答案】这是并发编程的经典问题,核心区别在并发控制机制和数据结构:
JDK1.7:Segment分段锁机制。整个Map被分成16个Segment,每个Segment本质上是一个小HashMap,用ReentrantLock加锁。不同Segment之间可以并发操作,理论上最大并发度就是Segment数(默认16)。数据结构是Segment数组+HashEntry数组+链表。
JDK1.8:取消Segment,改为CAS+synchronized,锁粒度细化到每个桶(Node)。数据结构是Node数组+链表+红黑树。当链表长度超过8且数组长度≥64时转红黑树,查找从O(n)优化到O(logn)。
为什么重新设计?三个原因:
- 并发度太低:1.7的Segment数量初始化后不可变,最大并发度固定为16,高并发场景不够用。1.8锁粒度到桶级别,并发度等于数组长度,随扩容动态增长。
- 查询效率:1.7链表过长时查询O(n),1.8引入红黑树后O(logn),hash冲突严重时差距明显。
- 内存效率:1.7的Segment层多了不少对象开销,1.8去掉后更紧凑。
还有一个细节:1.8用synchronized而不是ReentrantLock,是因为JDK1.6以后synchronized做了大量优化(偏向锁、轻量级锁),在低竞争场景下synchronized性能更好,而且省了ReentrantLock的内存开销。
【面试题】4. AI Agent的Function Calling和ReAct模式分别是什么?在开发中怎么选?
【标准答案】Function Calling是大模型原生的工具调用机制。你在请求时把可用工具的名称、参数schema告诉模型,模型判断需要调用工具时,返回一个结构化的工具调用指令(包含工具名和参数),你的代码执行后把结果返回模型,模型再继续生成。特点是流程确定、调用次数可控、成本可预测。OpenAI、通义千问等都支持。
ReAct模式是Reasoning+Acting的循环。模型在每一步先"思考"(Thought)当前应该做什么,然后"行动"(Action)调用某个工具,观察结果(Observation),再进入下一轮思考。特点是可以多步推理、动态决策,但token消耗高、可能陷入循环。
选型原则:
- 工具少、调用链短、逻辑简单→Function Calling,一次调用搞定,快速高效。
- 任务需要多步推理、中间结果影响后续决策→ReAct,比如"先查用户订单,发现异常再查库存,再决定是否补发"这种链式判断。
- 实际生产中,我会用Function Calling做基础,ReAct做增强——简单场景直接function call,复杂场景套一层ReAct循环,同时设max_iterations防止死循环。
Spring AI中可以用ChatClient的advisors机制实现类似的工具调用编排,LangChain的Agent默认就是ReAct模式。
【面试题】5. MySQL中InnoDB的MVCC机制是怎么实现的?它能不能解决所有的并发问题?
【标准答案】MVCC(多版本并发控制)是InnoDB实现高并发的核心机制,通过隐藏列+Undo Log+ReadView实现:
三个隐藏列:每行数据有DB_TRX_ID(最后修改的事务ID)、DB_ROLL_PTR(回滚指针指向Undo Log)、DB_ROW_ID(隐藏主键)。
Undo Log版本链:每次修改都生成一条Undo Log记录旧值,通过ROLL_PTR串成版本链,像git的commit历史一样。
ReadView:事务启动时创建的"快照",记录当前活跃事务ID列表,用来判断版本链上哪个版本对当前事务可见。规则是:如果某版本的TRX_ID小于ReadView中的最小活跃事务ID→可见;如果在活跃列表中→不可见,沿版本链继续找。
RC和RR的区别:RC级别每次SELECT都创建新ReadView,RR级别只在第一次SELECT创建,所以RR可重复读。
不能解决所有并发问题:
- 快照读(普通SELECT)→MVCC解决,无锁。
- 当前读(SELECT FOR UPDATE、UPDATE、DELETE)→不走MVCC,需要加锁,可能死锁。
- RR级别下的幻读→MVCC的快照读不会幻读,但当前读会,InnoDB通过Next-Key Lock(记录锁+间隙锁)来防止幻读。
- 写写冲突→两个事务同时更新同一行,MVCC管不了,必须靠行锁排队。
所以MVCC解决的是读写冲突,写写冲突还得靠锁。
【面试题】6. 大模型微调中LoRA和QLoRA的原理是什么?什么场景下需要微调而不是直接用Prompt Engineering?
【标准答案】LoRA(Low-Rank Adaptation):核心思想是冻结原始大模型参数,在Transformer的注意力层旁路注入两个低秩矩阵A和B,训练时只更新A和B。原始权重W,推理时W' = W + AB,其中A的维度是d×r,B是r×d,r远小于d(比如r=8或16)。这样可训练参数量从几十亿降到几百万,显存需求大幅降低,而且训练完可以把AB合并回W,推理无额外开销。
QLoRA:在LoRA基础上加了三招:①模型用4-bit NormalFloat量化加载(比普通4-bit精度更高);②计算时反量化到BF16做前向传播;③分页优化器,显存不够时自动把优化器状态换出到CPU内存。效果是在单张24G显存卡上就能微调65B参数的模型。
什么场景需要微调而不是Prompt Engineering?
| 场景 | 用Prompt | 用微调 |
|---|---|---|
| 格式控制、简单指令跟随 | ✅ | ❌ |
| 领域知识注入(医疗、法律术语) | 可能不够 | ✅ |
| 特定风格/语气模仿 | 勉强可以 | ✅效果好 |
| 减少幻觉、提升准确性 | 有限 | ✅ |
| 减少token消耗(长prompt→短prompt) | ❌ | ✅ |
| 复杂推理能力 | 无法提升 | 效果有限 |
简单说:Prompt解决"怎么问",微调解决"会什么"。如果prompt怎么写都不对、需要模型内化知识或风格,就该考虑微调了。
【面试题】7. Redis分布式锁用SETNX实现有什么问题?Redlock算法又是怎么回事?生产环境你推荐哪种方案?
【标准答案】基础SETNX锁的问题:
- 非原子性:SETNX + EXPIRE两条命令,如果SETNX成功后宕机,锁永远不过期→死锁。解决方案:用
SET key value EX seconds NX一条命令搞定。 - 误删别人的锁:A的锁过期自动释放,B拿到锁,A回来执行DEL把B的锁删了。解决方案:value设为唯一标识(UUID),DEL前先比对是不是自己的锁。但比对+删除又非原子,需要用Lua脚本保证原子性。
- 不可重入:同一线程重复获取同一把锁会失败。解决方案:用Hash结构记录持有者和重入次数。
- 单点故障:主节点挂了,锁数据还没同步到从节点,从节点升主后别人又能拿到同一把锁。
Redlock算法:在N个(通常5个)独立Redis实例上同时获取锁,超过半数(N/2+1)成功且总耗时没超过锁有效期,才算获取成功。解决单点问题但争议很大——Martin Kleppmann写过文章质疑其时钟假设不靠谱,Redis作者antirez反驳过,至今没有定论。
生产推荐:
- 单Redis实例+Lua脚本:大多数场景够用,简单可靠。加锁用
SET key uuid EX nx,解锁用Lua脚本比对后删除。加个看门狗(watchdog)自动续期。 - Redisson:直接用Redisson的分布式锁,内置看门狗续期、可重入、Lua脚本,开箱即用,别自己造轮子。
- 强一致性要求高:用ZooKeeper或etcd的分布式锁,基于CP系统,可靠性比Redis高,但性能差一些。
- Redlock:我个人不推荐,复杂度高、争议大、性能差,实际用得很少。
【面试题】8. 向量数据库的HNSW索引原理是什么?和IVF索引相比各有什么适用场景?
【标准答案】HNSW(Hierarchical Navigable Small World): 核心思想是构建一个多层导航图。最上层节点少、连接稀疏,用于快速跳跃到目标区域;往下每层节点增多、连接变密,用于精细定位。查询时从最上层入口开始,贪心搜索到当前层最近邻,然后往下层继续搜索,逐层精确定位。类似地图搜索——先看省份,再看城市,再看街道。
优点:查询速度快(log级别)、召回率高、不需要训练。 缺点:内存占用大(要存整个图)、构建索引慢、不支持增量删除(标记删除可以)。
IVF(Inverted File Index): 核心思想是先用K-Means把向量空间聚类成n个簇,每个簇建一个倒排列表。查询时先找最近的nprobe个簇中心,只在这些簇内暴力搜索。相当于先把搜索范围从全局缩小到局部。
优点:内存可控、支持增量添加、构建速度快。 缺点:需要训练(聚类)、召回率依赖nprobe参数(调高召回率但变慢)、数据分布不均时效果差。
选型:
- 数据量百万级以下、召回率要求高→HNSW,速度快召回好。
- 数据量亿级以上、内存有限→IVF,可以配合PQ(Product Quantization)压缩,牺牲一点召回率换内存。
- 实际产品中Milvus支持IVF+PQ和HNSW,可以根据场景切换;Chroma默认HNSW;Pinecone也是基于类似HNSW的图索引。
【面试题】9. Spring Cloud中服务熔断器Circuit Breaker的工作原理是什么?和限流有什么区别?实际项目中怎么配合使用?
【标准答案】熔断器原理——三个状态循环:
- CLOSED(正常):请求正常通过,同时统计失败率。
- OPEN(熔断):失败率超过阈值(比如50%),熔断器打开,后续请求直接走降级逻辑,不调用下游服务,给下游恢复时间。
- HALF_OPEN(半开):经过一个冷却时间窗口后,放少量请求试探。如果成功→关回CLOSED;如果仍然失败→重新OPEN。
Spring Cloud 2020以后官方推荐用Resilience4j替代Hystrix,Resilience4j基于函数式编程,轻量无依赖,提供了熔断、限流、重试、隔离舱等多种模式。
熔断和限流的区别:
- 熔断:保护下游——当下游服务出问题时,主动断开,防止级联故障(雪崩效应)。是对故障的响应。
- 限流:保护自身——控制自己服务的QPS不超过承载能力。是对流量的管控。
- 打个比方:熔断是保险丝,电路短路时自动断开;限流是闸门,控制水流量不超过管道容量。
配合使用:
- 网关层限流:在Spring Cloud Gateway用Redis + Lua实现令牌桶限流,控制入口总流量。
- 服务间熔断:Feign调用配置Resilience4j熔断器,下游异常时走fallback降级。
- 舱壁隔离:对不同下游服务的调用线程池隔离,一个下游慢不会拖垮所有线程。
- 典型配置:熔断失败率阈值50%、滑动窗口10秒、半开等待5秒、限流QPS按服务容量评估。
【面试题】10. 设计一个AI客服系统,用户问题先过知识库检索,检索不到再调用大模型自由回答,同时要记录所有对话用于后续分析,请给出整体架构设计。
【标准答案】整体架构分五层:
1. 接入层
- WebSocket长连接(Web端)+ REST API(移动端),通过网关做鉴权、限流。
- 用户消息先做敏感词过滤和意图分类(轻量分类模型或规则引擎),判断是闲聊、业务咨询还是投诉,不同意图走不同处理链路。
2. RAG检索层
- 用户问题→Query Rewrite(改写+扩展)→混合检索(向量检索+BM25关键词检索)→Reranker精排→相似度阈值过滤。
- 命中且相关度够高→组装context+question送入大模型,prompt中强调"基于以下知识回答,不要编造"。
- 未命中或相关度低→走大模型自由生成,prompt中标注"知识库未覆盖,请基于通用知识回答并提示用户信息仅供参考"。
3. 大模型推理层
- 模型选择:简单问答用小模型(7B级别,部署成本低),复杂推理用大模型。通过Router动态路由。
- 流式输出:SSE推送,首token延迟控制在1秒内。
- 安全兜底:输出做二次审核(关键词+分类模型),拦截有害内容。
4. 存储层
- 向量库:Milvus存知识库embedding,支持HNSW索引。
- 业务库:MySQL存用户信息、工单状态。
- 对话记录:按天写入ES(便于全文检索分析),同时归档到对象存储做冷备份。
- 缓存:Redis存热点FAQ答案和用户会话上下文,减少重复检索。
5. 分析层
- 对话记录入数据仓库,做意图分布统计、未命中知识库的问题聚类(发现知识库缺口)、用户满意度分析。
- 每日生成知识库补全建议:高频未命中问题→自动生成FAQ候选→人工审核后入库。
关键设计点:
- 检索和生成的边界要清晰,通过相关度分数自动判断,也可以让用户手动反馈"答案是否有帮助"来优化阈值。
- 对话上下文管理用滑动窗口,保留最近10轮,超出部分摘要压缩。
- 知识库更新要做版本管理,支持回滚。