从Spring Boot到Agentic RAG:互联网大厂Java面试三轮实战对话与详解
场景:一家头部内容社区 + AIGC 智能问答平台,招聘中高级 Java 开发。严肃的面试官 vs 搞笑的“水货程序员”小 Y,通过 3 轮、逐步加深的问答,覆盖 Java 基础、Spring 全家桶、微服务、缓存、消息队列、搜索、AI RAG 等核心技术。文末附完整答案解析,小白也能看懂。
一、业务背景设定
公司产品是一个“内容社区 + AIGC 问答 + 智能客服”的综合平台,类似:
- 用户在社区发帖、点赞、评论(内容社区与UGC)
- 后台有推荐系统、搜索系统、风控系统
- 新增 AI 问答与企业智能客服,支持 RAG 和 Agent 工作流(大数据与AI服务、企业协同与SaaS)
技术栈核心:
- 后端:Java 11/17,Spring Boot、Spring Cloud、Spring WebFlux
- 存储:MySQL + Redis + Elasticsearch + Kafka
- 容器&运维:Docker、Kubernetes、Prometheus、Grafana、ELK
- AI:Spring AI + RAG + 向量数据库(Milvus/Chroma/Redis)+ Agentic Workflow
面试分 3 轮,每轮 3~5 个问题,场景逐渐复杂。
二、第一轮:基础服务与业务接口(相对简单,小 Y 尚能发挥)
业务场景:实现“帖子发布与详情查看”服务,用户可以发帖、查看帖子详情与评论列表。
第一轮对话
面试官:小 Y,先从简单的来。我们帖子服务是一个 Spring Boot 应用,提供 REST API。请你说说:
- 你会如何设计发帖和查帖这两个接口?用什么 HTTP 方法、URL、以及大致的请求/响应结构?
- 在 Spring MVC 里,这两个接口你会怎么写?说下核心注解和参数绑定方式。
小 Y:这个简单,我发帖就用POST,比如/posts,请求体放 JSON:标题、内容、作者 ID。查帖子就GET /posts/{id},返回帖子详情和评论。Spring MVC 就@RestController,上面@RequestMapping,方法上@PostMapping和@GetMapping,参数用@RequestBody、@PathVariable绑定,返回一个对象就会自动转 JSON,用 Jackson。这个,稳的。
面试官:回答得还不错,基本 RESTful 思路是对的。再问下: 3. 假设发帖接口要做简单校验,比如标题不能为空、长度不能超过 100,你会怎么实现?用什么注解?
小 Y:啊,这个,用那个……好像是@NotNull之类的吧,我记得可以在字段上加几个注解,然后控制层就自动校验了,反正我以前项目里别人配好的,我就照抄。(笑)
面试官:好的,那最后一个: 4. 对于这些接口,你会怎么写单元测试?用到哪些测试框架?
小 Y:单元测试嘛,肯定是 JUnit 5,然后还可以用 Mockito 模拟服务层。我一般就是写几个测试方法,调一下 Controller,看能不能跑通。细节嘛……呃……这个回头我可以再查一下文档。(挠头)
三、第二轮:高并发、缓存与消息队列(开始暴露“水货”气质)
业务场景升级:帖子发布成功后需要:
- 异步刷写到推荐系统
- 增加作者的发帖计数
- 更新 Redis 缓存
- 发送消息给下游“搜索索引服务”(Elasticsearch)
第二轮对话
面试官:现在我们的发帖接口 QPS 比较高,帖子详情访问量也很大。考虑高并发性能,你会怎么设计缓存?比如热门帖详情,你打算怎么用 Redis?
- 说说你的缓存 Key 设计、过期策略、以及避免缓存击穿、雪崩、穿透的方案。
小 Y:Redis 我熟!缓存 Key 我就用post:{id},value 放帖子 JSON。过期时间嘛,随缘……呃不,是根据业务设置,比如 10 分钟。击穿和那几个,我记得是:
- 缓存雪崩就,多设置点随机过期时间。
- 穿透就用布隆过滤器。
- 击穿就加锁,或者用什么互斥锁策略吧。反正思路就是这些。
面试官:思路还行,那我们继续。 2. 发帖成功后要通知“搜索索引服务”更新 Elasticsearch 索引,你会选择用什么消息队列?Kafka、RabbitMQ、还是其他?说说你的考虑。
小 Y:这个看公司爱好吧。要是追求高吞吐,一般就是 Kafka。我感觉大家都用 Kafka,就……用 Kafka。理由嘛,主要是大厂都这么配的。(严肃脸)
面试官:如果你用 Kafka, 3. 大致的 Topic 设计、消息内容结构会怎么做?生产者、消费者在 Spring Boot 里怎么写?
小 Y:Topic 就叫post_created之类的,消息体就是 JSON,里面放帖子 ID、标题、内容。Spring Boot 里我记得加spring-kafka依赖,然后用配置写一下 broker 地址,生产者就用KafkaTemplate发送,消费者就@KafkaListener。我以前看同事写过,很像的。
面试官:好,那再往下。 4. 索引服务使用 Elasticsearch 时,怎么保证写入 ES 和写 MySQL 时的一致性?比如:MySQL 成功,ES 失败,你会怎么补偿或者保证最终一致性?
小 Y:这个……呃……我觉得一般不会失败吧?(小声)真失败了,就重试一下?可以写个定时任务,扫一遍没成功的,再写一次。具体细节我觉得可以后面慢慢优化嘛……
面试官:嗯,思路有一点,但比较模糊。最后一个: 5. 这些高并发场景下,你会用到哪些监控指标?比如 Prometheus + Grafana,你会重点监控哪些指标来判断系统是否健康?
小 Y:监控……我知道要看 QPS,还有 RT 响应时间。还有错误率,5xx 比例。Kafka 那边可以看看消费堆积。Prometheus、Grafana 这些我基本没配过,就是会看图。(尴尬笑)
四、第三轮:微服务、容器化与 AI RAG/Agent(彻底开始模糊应对)
业务场景再次升级:
- 我们要把“帖子服务”、“搜索服务”、“推荐服务”、“AI 问答服务”、“企业智能客服服务”拆成多个微服务
- 引入 RAG(检索增强生成)、向量数据库,实现“基于社区知识库的智能问答”和企业文档问答
- 要在 Kubernetes 上部署,配合 CI/CD 自动上线
第三轮对话
面试官:我们先从微服务说起。现在帖子服务、搜索服务、AI 问答服务都是独立微服务,你会用什么框架做服务治理和调用?Spring Cloud、Dubbo、还是 gRPC?请说明你的选择和典型使用场景。
小 Y:嗯……这个要看历史包袱和公司喜好。我之前听说 Spring Cloud 比较全家桶,有注册中心(Eureka/Consul)、网关(Spring Cloud Gateway/Zuul),再加个 OpenFeign 做调用。我个人觉得……同事们都用 Spring Cloud 的话,我也可以跟上。(中规中矩的“别人都这么做”式回答)
面试官:好,那如果我们用 Spring Cloud + OpenFeign,
- 你会怎么设计从“AI 问答服务”调用“知识检索服务”的接口?包括:
- Feign 客户端如何声明?
- 如何做超时和熔断(比如用 Resilience4j)?
小 Y:Feign 的话,就写个接口,然后加@FeignClient(name = "knowledge-service"),方法上@GetMapping或者@PostMapping,参数正常写。超时和熔断的话,好像在配置文件里可以设超时时间,熔断我知道 Resilience4j 能弄一些注解,比如@CircuitBreaker,但是我没亲自配过。(小声)
面试官:那我们具体到 AI 场景。现在我们要做一个 RAG(检索增强生成)系统: 2. 用户在社区问问题,“AI 问答服务”需要:
- 先在向量数据库(比如 Milvus/Chroma/Redis 向量索引)检索相关帖子内容
- 然后把检索结果和用户问题一起喂给大模型(Spring AI + OpenAI 模型)生成答案 你能简单描述一下整个调用链和关键组件吗?
小 Y:这个我最近在看,但还没做过。大概就是:先把帖子内容用 Embedding 模型向量化,存到向量数据库;用户提问时也向量化一下,然后做相似度检索,拿到几个相关文档,然后把这些内容拼到 Prompt 里,让模型生成答案。这叫 RAG。我知道有 Spring AI,可以帮我们调用模型和做提示填充。细节上嘛,向量数据库怎么配,我还需要再研究一下。(认真但略显虚)
面试官:那我们再进阶一点,Agentic RAG: 3. 我们希望“智能客服系统”可以根据问题自动选择工具,比如:
- 查订单(调用订单微服务)
- 查物流(调用物流微服务)
- 查社区知识(调用 RAG 检索)
- 查询风控结果(调用风控微服务) 你理解的 Agent、工具执行框架、工具调用标准化,大概是个什么概念?如何和现有 Java 微服务体系结合?
小 Y:Agent……我理解就是一个“更聪明的机器人”,它可以根据用户问题决定下一步用哪个工具。工具执行框架,就是预先定义好工具的接口,比如“查订单”是一个函数,Agent 可以决定要不要调用。工具调用标准化嘛,就是统一输入、输出格式,方便编排。怎么和 Java 微服务结合……呃,大致可以通过 HTTP 调用或者 gRPC 这些方式统一一下,具体实现,我还没真正做过,只是看过几篇文章。(继续模糊)
面试官:好,最后一个问题: 4. 对于这种多服务、多组件(MySQL、Redis、Kafka、向量数据库、模型服务)的复杂系统,你会如何在 Kubernetes 上部署,并利用 CI/CD(Jenkins/GitLab CI/GitHub Actions)实现一键发布?说说核心步骤和注意点即可。
小 Y:K8s 的话,我知道要写 Deployment、Service、ConfigMap、Secret 这些 YAML。CI/CD 就在 GitLab CI 或 GitHub Actions 里面写 pipeline:先打包 Maven、再用 Docker 打镜像,推到镜像仓库,然后 Kubectl 或者 Helm 更新。注意点……就注意配置要分环境、健康检查要配好,资源限制也要设一下。具体的 YAML 我平时是让 DevOps 大哥搞的,我主要关注业务代码。(尴尬又真诚)
面试官:行,今天就先到这儿,你回去等通知吧,我们后面会统一反馈。
五、面试问题详细解析(小白也能看懂)
下面对上文三轮问题逐个做详细解析,按照“业务场景 + 技术要点”的方式整理。
第一轮解析:Spring Boot + REST + 校验 + 测试
1. 发帖 / 查帖接口设计
业务场景:内容社区最基础的功能:发帖、看帖。
典型 REST 设计:
发帖:
POST /api/posts- Request Body(JSON):
{ "title": "如何学习 Spring Boot?", "content": "……", "authorId": 123 } - Response:返回新建帖子的详细信息(包含自增 ID):
{ "id": 1001, "title": "如何学习 Spring Boot?", "content": "……", "authorId": 123, "createdAt": "2024-06-01T10:00:00Z" }
- Request Body(JSON):
查帖详情:
GET /api/posts/{id}- Path Variable:
id - Response:帖子详情 + 评论列表(可拆成两个接口,也可聚合返回):
{ "id": 1001, "title": "如何学习 Spring Boot?", "content": "……", "authorId": 123, "comments": [ {"id": 1, "content": "写得不错", "userId": 456}, {"id": 2, "content": "收藏了", "userId": 789} ] }
- Path Variable:
REST 设计要点:
- 使用资源名复数
/posts表示“帖子集合” - 使用 HTTP 方法区分“动作”:
POST创建GET查询PUT/PATCH更新DELETE删除
2. Spring MVC 实现接口的核心注解
控制层示例:
@RestController @RequestMapping("/api/posts") public class PostController { private final PostService postService; public PostController(PostService postService) { this.postService = postService; } @PostMapping public PostDTO createPost(@Valid @RequestBody CreatePostRequest req) { return postService.createPost(req); } @GetMapping("/{id}") public PostDTO getPost(@PathVariable Long id) { return postService.getPost(id); } }核心注解说明:
@RestController:相当于@Controller + @ResponseBody,返回对象会自动序列化为 JSON(默认 Jackson)@RequestMapping:类级别路径前缀@PostMapping/@GetMapping:方法级别的 HTTP 路由@RequestBody:从请求体中读取 JSON 并绑定到 Java 对象@PathVariable:从 URL 路径中读取变量
3. 参数校验(Bean Validation)
使用 JSR 规范(Jakarta Bean Validation),在 DTO 上加约束注解,并在 Controller 使用@Valid触发校验。
public class CreatePostRequest { @NotBlank @Size(max = 100) private String title; @NotBlank private String content; @NotNull private Long authorId; // getter / setter }要点:
- 常用注解:
@NotBlank、@NotNull、@Size、@Min、@Max等 - 在 Controller 方法参数前加
@Valid才会触发校验 - 配合
@ControllerAdvice+@ExceptionHandler(MethodArgumentNotValidException)可以统一处理错误并返回友好 JSON
4. 单元测试与集成测试
在 Spring Boot 中常用:
- JUnit 5:基础测试框架
- Spring Boot Test:
@SpringBootTest、@WebMvcTest - Mockito:Mock service 或 repository
- AssertJ:链式断言
示例:只测试 Controller Web 层,不启动完整容器:
@WebMvcTest(PostController.class) class PostControllerTest { @Autowired private MockMvc mockMvc; @MockBean private PostService postService; @Test void testGetPost() throws Exception { PostDTO dto = new PostDTO(); dto.setId(1L); dto.setTitle("test"); Mockito.when(postService.getPost(1L)).thenReturn(dto); mockMvc.perform(get("/api/posts/1")) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(1L)); } }第二轮解析:Redis 缓存 + Kafka 消息 + ES 最终一致性 + 监控
1. 详情缓存设计与缓存三大问题
业务场景:热点帖子详情访问量很高,直接打 MySQL 会有性能瓶颈,于是使用 Redis 做缓存。
Key 设计:
post:{id},如post:1001- 值:帖子详情 JSON 字符串
过期策略:
- 一般为几十分钟到几个小时
- 加随机偏移(如在 30 分钟基础上加
0~5分钟随机数),避免大量 Key 同时过期导致缓存雪崩
三大问题与常见解法:
缓存穿透:大量请求访问根本不存在的数据(DB 也没有),导致每次都打 DB
- 方案:
- 布隆过滤器(比如在 Redis 或 Guava 里)过滤掉明显不存在的 ID
- 或者对查询结果为空的 ID 也写入缓存(短期空值缓存)
- 方案:
缓存雪崩:某一时刻大量 Key 同时过期,请求全部打到 DB
- 方案:
- 为每个 Key 的 TTL 加随机偏移
- 做多级缓存(本地 Caffeine + Redis)
- 方案:
缓存击穿:某个热点 Key 过期了,短时间大量请求打到 DB
- 方案:
- 使用互斥锁重建缓存(只有一个线程去查 DB,其余等待)
- 使用逻辑过期+后台线程异步刷新
- 方案:
2. Kafka 选型与 Topic 设计
业务场景:发帖成功后需要通知“搜索索引服务”更新 Elasticsearch,要求高吞吐、高可用、可回溯。
选 Kafka 的理由:
- 高吞吐量,适合日志、行为上报、搜索索引等场景
- 多副本和分区机制,弹性扩展和故障恢复
- 消费组机制支持水平扩展消费者
Topic 设计:
- 名称:
post-created、post-updated等,按事件类型命名 - 分区:根据 QPS 分配,按
postIdhash 到分区,可以保证同一帖子事件有序 - 消息内容:JSON 或 Protobuf
{ "eventType": "POST_CREATED", "postId": 1001, "authorId": 123, "title": "如何学习 Spring Boot?", "createdAt": "2024-06-01T10:00:00Z" }
Spring Boot 集成示例:
生产者:
@Service public class PostEventPublisher { private final KafkaTemplate<String, String> kafkaTemplate; public void publishPostCreated(PostCreatedEvent event) { String key = String.valueOf(event.getPostId()); String value = objectMapper.writeValueAsString(event); kafkaTemplate.send("post-created", key, value); } }消费者:
@Service public class PostEventConsumer { @KafkaListener(topics = "post-created", groupId = "search-indexer") public void onMessage(String message) { // 反序列化,写入 Elasticsearch } }
3. MySQL 与 Elasticsearch 的最终一致性
问题:如果写 MySQL 成功,但写 ES 失败,如何保证最终一致?
常见方案:
事务内只写 MySQL,然后通过消息队列(Kafka)异步写 ES
- 发帖服务在 DB 事务提交后发送事件到 Kafka
- 索引服务消费事件并写入 ES
- 如果写 ES 失败:
- 自动重试(带最大重试次数)
- 死信队列(DLQ)记录失败消息
- 定时任务扫描失败记录进行补偿
Outbox 模式:
- 在业务 DB 中增加
outbox事件表 - 在同一个事务中写业务表和 outbox 表
- 后台有一个可靠的“Outbox 消费者”轮询表,发送消息给 Kafka/ES
- 保证“写 DB”与“发送消息”具备事务性
- 在业务 DB 中增加
总体原则:放弃强一致,追求可观测的最终一致性。
4. 监控指标(Prometheus + Grafana + Micrometer)
关键指标:
应用层:
- QPS:接口调用次数/秒
- 延迟:P95、P99 响应时间
- 错误率:HTTP 5xx 占比
- 线程池使用情况(活跃线程、队列长度)
Redis:命中率、连接数、慢查询、内存使用
Kafka:
- Topic 的 lag(积压量)
- 生产/消费 TPS
- 消费者异常
DB(MySQL):
- QPS、慢查询数
- 连接池指标(HikariCP)
容器:CPU/内存使用率、重启次数
使用 Micrometer + Spring Boot actuator 暴露/actuator/prometheus,Prometheus 拉取,Grafana 展示图表和告警。
第三轮解析:微服务治理 + RAG/Agent + K8s/CI/CD
1. 微服务调用与治理(Spring Cloud / Dubbo / gRPC)
场景:帖子服务、搜索服务、AI 问答服务、知识检索服务、订单/物流服务等都是独立微服务。
Spring Cloud:
- 服务注册/发现:Eureka/Consul
- 配置中心:Spring Cloud Config / Nacos
- 网关:Spring Cloud Gateway / Zuul
- 声明式调用:OpenFeign
- 容错:Resilience4j
Dubbo:适合高性能 RPC,常用于内部服务调用
gRPC:跨语言、高性能二进制协议(Protobuf)
示例:AI 问答服务通过 Feign 调用知识检索服务
@FeignClient(name = "knowledge-service", path = "/api/knowledge") public interface KnowledgeClient { @GetMapping("/search") List<DocDTO> search(@RequestParam("query") String query); }配合 Resilience4j:
@Service public class QaService { @CircuitBreaker(name = "knowledgeService", fallbackMethod = "fallbackSearch") public List<DocDTO> searchDocs(String query) { return knowledgeClient.search(query); } public List<DocDTO> fallbackSearch(String query, Throwable t) { // 降级策略:返回空列表或兜底答案 return Collections.emptyList(); } }2. RAG(检索增强生成)架构
业务场景:基于社区帖子和企业文档,提供“智能问答”和“企业文档问答”。
RAG 典型流程:
离线构建知识库
- 文档加载:从数据库、Elasticsearch、OSS、企业网盘等读取文档
- 切分 Chunk:长文档拆成小片段(比如 500~1000 字)
- 向量化:调用 Embedding 模型(OpenAI / 本地模型 / Ollama)生成向量
- 存储:向量 + 原文片段保存到向量数据库(Milvus/Chroma/Redis Vector)
在线查询
- 用户问题向量化
- 在向量数据库中做相似度检索,找到 Top-K 相关片段
- 采用提示填充(Prompt Filling):
- 模板:
你是一个知识助手,根据下面给出的参考文档回答用户问题。 如果文档中没有相关信息,请明确说不知道,不要编造。 【用户问题】 {question} 【参考文档】 {context}
- 模板:
- 调用大模型生成答案
避免 AI 幻觉(Hallucination)
- 在 Prompt 中明确要求“没有证据不要乱编”
- 在答案中引用相关文档的片段或链接
- 对敏感场景加规则/风控
Spring AI 可以帮助统一“向量化 + 检索 + 模型调用”的开发体验,Java 开发只需要配置好:
- EmbeddingClient
- VectorStore(Milvus/Chroma/Redis)
- ChatModel(OpenAI/Ollama 等)
3. Agent 与工具执行框架(Agentic RAG)
业务场景:智能客服不仅要“查知识”,还要“查订单、查物流、查风控记录”等,需要调用多个后端系统。
Agentic RAG 的核心思想:
- 在 RAG 的基础上,让“Agent”拥有调用工具(Tool)的能力
- 工具的本质是“函数/接口”,例如:
getOrderStatus(orderId)→ 调订单服务getDeliveryInfo(orderId)→ 调物流服务ragSearch(question)→ RAG 检索
工具调用标准化:
- 每个工具有统一的:
- 名称
- 参数描述(类型、含义)
- 返回格式
- 模型可以根据用户意图决定是否调用工具,以及调用顺序(工作流)
与 Java 微服务结合方式:
- 在 Java 后端定义一层 Tool Adapter:
- 对内部微服务(HTTP/gRPC/Dubbo)封装为函数式接口
- 对外暴露统一的 JSON-RPC / HTTP API,供 Agent 调用
- 可以使用“工具执行框架”来统一调度,记录日志、重试、超时等
复杂工作流案例:
- 用户问:“我昨天买的那本《Java 并发编程实战》现在物流到哪了?”
- Agent 解析:需要先查订单→再查物流→再 RAG 检索售后规则
- 工作流:
- 工具 A:
getOrderByUserAndProduct(userId, productName) - 工具 B:
getDeliveryStatus(orderId) - 工具 C:
ragSearchAfterSalePolicy(productCategory)
- 工具 A:
Agentic RAG 的优势:
- 同时利用结构化数据(订单、物流)和非结构化知识(售后文档)
- 能完成更加复杂的“多步任务”
4. Kubernetes 部署与 CI/CD
K8s 部署要素:
- Deployment:描述应用副本数、镜像、启动命令
- Service:对外暴露端口(ClusterIP / NodePort / LoadBalancer)
- ConfigMap:配置文件(如
application.yml) - Secret:敏感信息(DB 密码、API Key)
- Ingress:域名路由
示例(简化版):
apiVersion: apps/v1 kind: Deployment metadata: name: post-service spec: replicas: 3 selector: matchLabels: app: post-service template: metadata: labels: app: post-service spec: containers: - name: post-service image: registry.example.com/post-service:1.0.0 ports: - containerPort: 8080 envFrom: - configMapRef: name: post-service-config - secretRef: name: post-service-secretCI/CD 流程(以 GitLab CI 为例):
- 代码提交到 Git 分支
- GitLab CI Pipeline:
mvn test:运行单元测试(JUnit/Mockito)mvn package:打包 Jardocker build:构建 Docker 镜像docker push:推送到镜像仓库kubectl apply或helm upgrade:更新到 K8s
- 生产环境需:
- 蓝绿部署或滚动升级
- 健康检查(liveness/readiness probe)
- 灰度发布、回滚策略
六、总结:从“会用 API”到“能设计系统”
这篇文章通过严肃面试官和搞笑“水货程序员”小 Y 的三轮对话,从一个内容社区 + AIGC 平台的场景出发,逐步覆盖:
- 第一轮:Spring Boot/Spring MVC、REST、Bean Validation、JUnit 测试
- 第二轮:Redis 缓存设计、Kafka 事件驱动、Elasticsearch + 最终一致性、Prometheus + Grafana 监控
- 第三轮:Spring Cloud 微服务治理、OpenFeign + Resilience4j、RAG/向量数据库、Agentic RAG、K8s 部署 + CI/CD
如果你正在准备互联网大厂的 Java 面试,建议:
- 按照本文的业务场景,自行画出整体架构图
- 把每一块技术点用自己的语言讲给别人听
- 至少用 Spring Boot 实现一套简化版 Demo(发帖、缓存、Kafka 事件、简单搜索)
- 再尝试加上一个简单的 RAG 服务,哪怕是用本地小模型
当你能把“技术关键字列表”串成一个完整的业务故事,并讲清楚设计取舍时,你离真正的大厂中高级 Java 工程师就不远了。