公司动态
Java大厂面经:Spring Cloud微服务+MCP+Agentic RAG三连问,水货程序员谢飞机大型翻车现场
Java大厂面经Spring Cloud微服务MCPAgentic RAG三连问水货程序员谢飞机大型翻车现场 前言江湖传闻有一号人物姓谢名飞机Java圈内人称「八股文朗诵艺术家」——简单问题倒背如流复杂问题当场宕机。今天这位传奇水货程序员来到了某互联网大厂的面试现场迎接他的是三道技术大关电商高并发、AIGC智能客服、支付金融安全。让我们搬好小板凳一起围观谢飞机的表演。 第一轮电商高并发场景业务背景面试官所在团队负责一个日活千万级的电商平台正在备战双十一大促需要招聘一名有高并发实战经验的Java开发工程师。面试官推了推眼镜面无表情「谢飞机是吧我看你简历上写着精通高并发那今天第一轮我们就聊聊电商场景。先来个简单的热热身——你们电商系统的商品详情页怎么用Redis做缓存的缓存击穿和缓存雪崩怎么处理」谢飞机眼睛一亮这可是他的舒适区「这个简单我们用的是Spring Cache注解配Redis在 Service 层加Cacheable先查缓存查不到再查数据库。缓存击穿嘛就是热点Key过期瞬间大量请求打到数据库——我们用互斥锁setnx抢锁重建缓存。缓存雪崩就是大面积Key同时过期我们给过期时间加随机值比如baseTime random(1, 300)秒防止集体失效」面试官微微点头「不错基础挺扎实的。那你再说说——如果秒杀场景下前端突然涌入10万QPS你们的消息队列Kafka是怎么抗住的消息积压了怎么办」谢飞机自信值飙升「这个我们也做过秒杀请求先进Kafka削峰填谷订单服务慢慢消费。Kafka 的优势是顺序写磁盘吞吐量贼高单机就能扛几十万QPS。消息积压的话……呃……就加消费者实例增加分区数……」面试官追问「分区数能随便加吗消费者超过分区数呢」谢飞机语速开始放慢「这个……分区只能增加不能减少消费者超过分区数的话……多出来的消费者就……闲着就像我们公司之前裁员裁完发现活还是那么多剩下的兄弟加加班也能干完……」面试官忍住笑继续「比喻挺生动。接下来问个有深度的——下单场景涉及订单服务、库存服务、支付服务你怎么保证分布式事务一致性你们用的是什么方案」谢飞机额头开始冒汗「分布式事务啊……我们用过Seata的 AT 模式就是那个……两阶段提交……」面试官追问「AT模式和TCC模式有什么区别你们为什么选AT」谢飞机疯狂挠头「AT……就是自动的不用写补偿代码……TCC要自己写Try、Confirm、Cancel……选AT是因为……因为项目经理说简单其实我也觉得TCC太麻烦了写了三个月没写对后来组长说算了用AT吧反正出问题了就发公告说系统异常请稍后重试……」面试官扶额「行吧……最后一个问题你们的数据库连接池用的什么HikariCP 为什么比 C3P0 快MyBatis 和 JPA 你们怎么选的」谢飞机重新振作「必须HikariCP它快是因为……呃字节码级别的优化用的ConcurrentBag无锁设计还做了很多微优化。MyBatis适合复杂SQL、多表关联、动态查询我们电商系统复杂查询多基本都用 MyBatis。JPA适合简单的 CRUD但一到多表关联就抓瞎那个ManyToMany注解能把你绕晕……」面试官露出一丝认可「HikariCP 的微优化能具体说说吗比如 FastList」谢飞机一拍大腿「对对对FastList它去掉了rangeCheck因为连接池的 List 大小是固定的不需要每次检查越界省掉一个判断。还有把synchronized换成ThreadLocal来存连接……反正快就完事了」面试官「好第一轮就到这里你先休息一下我们开始第二轮。」 第二轮AIGC智能客服与企业协同场景业务背景面试官所在团队正在搭建企业内部智能客服系统基于大模型实现文档问答、工单自动化处理涉及 Spring AI、MCP协议、RAG架构等前沿AI技术栈。面试官翻开新一页简历「谢飞机我看你简历上还写了熟悉AI技术栈。好那我们聊聊——假设你要给公司搭一个智能客服系统用户上传一个PDF产品手册然后可以用自然语言问这个产品的保修期是多久系统自动回答。你会怎么设计」谢飞机AI这块他也背过八股文「这个我熟用RAG——检索增强生成。先把PDF做文档加载用 Apache Tika 或者 PDFBox 提取文本然后切分成 chunks调用Embedding 模型比如 OpenAI 的text-embedding-3-small或者本地 Ollama 跑一个把文本向量化存到向量数据库里我们用的是Milvus也调研过Chroma和Redis的向量搜索模块。用户提问的时候同样把问题向量化在向量库里做语义检索召回 Top-K 个最相似的文档片段拼到 Prompt 里一起发给大模型最后生成答案。」面试官露出赞许的表情「可以啊谢飞机这个答得很完整。那我接着问——这个方案里有个问题如果用户问上个月的订单怎么退款这个知识不在PDF里在业务数据库里大模型能回答吗你怎么让大模型去查数据库」谢飞机开始慌了「这个……可以让大模型……调用API就那个Function Calling……」面试官「具体怎么实现MCP协议你了解吗」谢飞机肉眼可见地紧张「MCP……模型上下文协议……就是……Anthropic提的一个标准让大模型可以调用外部工具……就像给大模型装了个USB接口插上什么工具就能用什么工具……具体实现的话……嗯……要定义tools列表每个工具描述……参数schema……然后大模型会返回一个tool_use客户端执行完再把结果返回……」面试官追问「那MCP 和 A2A 协议有什么区别什么场景用哪个」谢飞机彻底慌了「这个……MCP是工具调用标准化A2A是Agent之间通信……就像……MCP是人和工具之间的协议A2A是Agent和Agent之间的协议……具体场景……如果是一个Agent调用数据库用MCP如果是两个Agent聊天商量怎么干活用A2A……大概就是这样」面试官「差不多方向是对的。再问一个——Agentic RAG 和传统 RAG 有什么区别Agentic RAG 怎么解决 AI 幻觉Hallucination问题」谢飞机疯狂喝水掩饰尴尬「Agentic RAG……就是让Agent自己去判断要不要检索、检索什么、检索几次……传统RAG是用户问一句就检索一次Agentic RAG可以多轮检索、交叉验证。至于AI幻觉……就是大模型胡说八道……用RAG本身就能缓解啊因为给了它真实文档它就不容易瞎编了。但如果文档里没有它还是会编……所以要做置信度评估低于阈值就说我不知道……」面试官挑眉「那如果用户问了一个超出知识库范围的问题Agentic RAG 会怎么做你能描述一下它的决策流程吗」谢飞机进入胡言乱语模式「它……它可能会……先检索一下发现没有相关内容然后……可能会尝试改写查询再检索一次还是不行的话……就……Agent 就会说……亲这个问题我暂时无法回答建议您转人工客服……」面试官忍俊不禁「亲是怎么回事你们客服Agent还带淘宝腔的」谢飞机「啊不是……我的意思是……就友好地拒绝用户……」面试官「好的第二轮也差不多了。你对AI技术栈的基础理解还可以但Agentic RAG和MCP的细节还需要再深入。我们进入最后一轮。」 第三轮支付金融与安全风控场景业务背景面试官所在团队负责支付网关系统涉及资金流转对安全性、可观测性、分布式一致性要求极高。面试官神情变得更加严肃「谢飞机第三轮我们聊支付和安全。支付系统容不得半点马虎。第一个问题——你们的支付系统怎么做认证和授权Spring Security OAuth2 JWT 的完整流程能画出来吗」谢飞机感觉又回到了舒适区边缘「这个没问题用户登录成功后OAuth2授权服务器发一个JWTToken包含用户ID、权限列表、过期时间这些 Claims用 RSA 私钥签名。客户端每次请求把 JWT 放在Authorization: Bearer token头里。Spring Security的OncePerRequestFilter拦截请求验签、解析Claims、把权限信息塞到SecurityContextHolder里。后端通过PreAuthorize注解做方法级权限控制。」面试官「JWT 过期了怎么办Refresh Token 机制你设计过吗」谢飞机「Access Token 有效期设短一点比如15分钟。过期后用 Refresh Token 换新的。Refresh Token 存Redis可以随时吊销。如果有人盗用了后台把 Refresh Token 删掉就行……」面试官追问「那如果一个用户在两个设备同时登录JWT 怎么处理你们做设备踢下线吗」谢飞机又开始挠头「这个……我们没做那么复杂……一般就是把Refresh Token和设备ID绑定新设备登录就发新Token旧Token继续有效直到过期……其实说实话我们那个支付系统用户基本都只在手机上用两个设备同时登录的情况……产品经理说不用做……」面试官「好。接下来——支付系统出了故障你怎么快速定位Prometheus Grafana Micrometer Jaeger 这条链路怎么打通的」谢飞机努力回忆监控文档「我们用Micrometer做指标采集它是门面兼容Prometheus。在Spring Boot里引入micrometer-registry-prometheus框架自动暴露/actuator/prometheus端点Prometheus定时来拉。Grafana连 Prometheus 做可视化告警。链路追踪用JaegerOpenTelemetry SDK 自动埋点每个请求生成一个 TraceID跨服务传递出了故障能串起来看是哪个环节慢了……」面试官点头「不错。那如果你的支付回调延迟突然从 50ms 飙到 5秒你会怎么排查ELK Stack 在这时候起什么作用」谢飞机「先看Grafana大盘确认是哪个服务慢了。然后到Jaeger里找慢Trace看到底卡在哪个Span。ELK这边Elasticsearch存日志Logstash做日志收集和结构化Kibana做可视化搜索。我会去 Kibana 里搜那个 TraceID 关联的日志看看有没有异常堆栈。比如可能是数据库慢查询、Kafka重平衡、或者GC停顿……」面试官「挺好的。最后一个问题——Java 21 的虚拟线程Virtual Threads你用过吗如果支付系统有大量IO等待调用银行接口、查数据库虚拟线程能带来什么收益」谢飞机经典的水货时刻到来了「虚拟线程……就是……Project Loom……那个轻量级线程……内存占用只有2KB传统平台线程要1MB左右……」面试官「所以呢在支付场景下具体怎么用你实际在项目里用过吗」谢飞机彻底放弃挣扎「我们……还在用Java 8……升级Java 21要领导审批……我写了份升级方案被架构师打回来了说稳定优先……」面试官忍不住笑了「那你觉得如果用了虚拟线程能解决什么问题」谢飞机「就……可以开很多很多线程不用线程池了一个请求一个虚拟线程代码写起来跟同步一样但实际是异步执行的。比如调银行支付接口传统写法要么用 WebClient 异步要么用线程池Future回调地狱。虚拟线程直接httpClient.send()同步写法底层自动让出CPUIO完成后再唤醒QPS 能翻好几倍……我看网上有人测试同样的支付网关服务换成虚拟线程后 QPS 从 5000 飙到 10 万……」面试官「懂了你理论知识有但没实战。没关系至少你知道这个方向。」 面试尾声面试官合上简历面色如常「谢飞机今天三轮面试下来我对你的情况基本了解了。你的基础八股文背得不错Redis、Kafka、Spring Security 这些常规技术栈理解到位。但是——分布式事务的工程实践、MCP协议的架构理解、Agentic RAG的决策流程、虚拟线程的实战落地这几个方面还有不小的提升空间。」「这样吧你先回去我们内部评估一下有结果了HR会通知你。」谢飞机强装镇定站起来握手「好的好的谢谢面试官那我回去等通知……」走出会议室谢飞机掏出手机打开Boss直聘熟练地点开了海投按钮…… 答案解析从小白到进阶以下为12道面试题的详细解析面向Java初学者和中级开发者深入讲解业务场景和技术原理。 第一轮电商高并发场景答案Q1Redis缓存击穿和缓存雪崩怎么处理业务场景电商商品详情页的QPS可能是首页的10倍以上。比如某网红带货一款商品瞬间几万人涌入同一个商品页面。如果这个商品的缓存刚好过期所有请求都打到数据库数据库直接挂掉——这就是缓存击穿。更严重的是如果因为某种原因比如缓存服务器重启几百个热门商品的缓存同时过期数据库瞬间承受几十万QPS——这就是缓存雪崩。技术原理缓存击穿解决方案——互斥锁Mutex Lockpublic String getProductDetail(String productId) { String cacheKey product: productId; String data redisTemplate.opsForValue().get(cacheKey); if (data ! null) { return data; // 缓存命中直接返回 } // 缓存未命中加锁重建 String lockKey lock:product: productId; boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 10, TimeUnit.SECONDS); if (locked) { try { // 双重检查再次查缓存可能别的线程已经重建好了 data redisTemplate.opsForValue().get(cacheKey); if (data ! null) return data; // 查数据库 data productDao.findById(productId); // 写入缓存过期时间基础时间随机值 int expireTime 3600 new Random().nextInt(300); redisTemplate.opsForValue().set(cacheKey, data, expireTime, TimeUnit.SECONDS); } finally { redisTemplate.delete(lockKey); } } else { // 没抢到锁等一会儿再查缓存 Thread.sleep(100); return getProductDetail(productId); // 递归重试 } return data; }缓存雪崩解决方案核心是打散过期时间。不要所有缓存同时过期。给每个Key的过期时间加一个随机偏移量如 ±5分钟。另外还可以做缓存预热系统启动时提前加载热点数据、多级缓存本地Caffeine 远程Redis、限流降级超过阈值直接返回兜底数据。Q2Kafka如何扛住秒杀10万QPS消息积压怎么办业务场景双十一零点秒杀上万用户同一秒点击立即抢购。如果这些请求直接打到订单服务服务瞬间崩溃。所以前端请求先写入Kafka后端订单服务按自己的节奏消费这就是削峰填谷。技术原理Kafka为什么快顺序写磁盘Kafka把消息顺序追加到日志文件末尾避免了磁盘随机IO。顺序写的速度约600MB/s甚至比随机写内存还快。零拷贝Zero-Copy消费者拉取消息时数据直接从磁盘文件通过sendfile()系统调用发送到网卡不经过用户态内存减少两次数据拷贝。Page Cache消息先写入操作系统的Page Cache由OS决定何时刷盘写入操作几乎不阻塞。分区并行一个Topic可以有多个分区每个分区独立读写水平扩展。消息积压处理增加消费者消费者数量 ≤ 分区数多了也没用一个分区只能被一个消费者消费增加分区数只能增不能减Kafka的设计限制需要提前规划临时紧急处理写一个临时的Consumer不做业务逻辑直接把积压消息转发到新Topic增加分区数再用多消费者处理消息过期策略设置消息TTL超时自动丢弃适合对时效性要求不高的场景分区数 消费者数的场景多余的消费者确实会闲着——这叫空闲消费者。Kafka的分区分配策略保证每个分区只分配给一个消费者所以消费者数超过分区数不会提升消费速度。Q3分布式事务——订单、库存、支付三服务如何保证一致性业务场景用户下单流程①订单服务创建订单 → ②库存服务扣减库存 → ③支付服务创建支付单。三步必须全部成功或全部回滚。如果第二步扣减库存成功第三步支付单创建失败库存必须加回去。这就是分布式事务。技术原理Seata AT模式自动补偿 核心是两阶段提交的思想但做了优化一阶段各服务正常执行SQLSeata自动记录UNDO_LOG反向SQL比如INSERT变DELETE二阶段如果全部成功删除UNDO_LOG如果有失败根据UNDO_LOG执行反向SQL回滚GlobalTransactional // Seata注解开启全局事务 public void createOrder(OrderDTO order) { // 1. 订单服务创建订单 orderService.create(order); // 2. 库存服务RPC调用扣减库存 inventoryService.deduct(order.getProductId(), order.getQuantity()); // 3. 支付服务RPC调用创建支付单 paymentService.createPay(order.getOrderId(), order.getAmount()); // 任何一步抛异常前面的操作自动回滚 }Seata AT vs TCC| 特性 | AT模式 | TCC模式 | |------|--------|---------| | 侵入性 | 低SQL自动生成UNDO_LOG | 高需手动实现Try/Confirm/Cancel | | 适用场景 | 通用场景 | 对性能要求极高或有特殊资源管理 | | 脏写风险 | 存在一阶段提交后其他事务可见 | 无一阶段只预留 | | 开发成本 | 低 | 高 |最终一致性的备选方案本地消息表 定时任务每个服务把操作结果写本地消息表定时任务扫描未确认消息进行重试RocketMQ事务消息半消息机制先发Prepared消息本地事务执行成功后再确认发送Saga模式长事务拆成多个本地事务每个本地事务有对应的补偿操作类似TCC但更灵活Q4HikariCP为什么比C3P0快MyBatis vs JPA怎么选业务场景电商系统每次数据库查询都需要从连接池获取一个连接。连接池的性能直接影响系统吞吐量。HikariCP日语光的意思是目前最快的Java数据库连接池Spring Boot 2.x开始默认使用它。技术原理HikariCP的极致优化ConcurrentBag自定义无锁并发集合减少锁竞争FastList自定义ArrayList去掉了rangeCheck()检查因为连接池大小固定不会越界Proxy优化使用Javassist动态代理替代JDK反射代理性能提升显著精减代码量HikariCP代码量只有C3P0的1/10更少的代码意味着更少的BUG和更快的执行MyBatis vs JPA选择指南// MyBatis适合复杂查询SQL完全可控 Select(SELECT * FROM orders WHERE user_id #{userId} AND status IN (PAID, SHIPPED) AND create_time #{startTime} ORDER BY create_time DESC LIMIT #{offset}, #{limit}) ListOrder findUserRecentOrders(Param(userId) Long userId, ...); // JPA适合简单CRUD命名规范自动生成SQL public interface OrderRepository extends JpaRepositoryOrder, Long { // 方法名即查询不用写SQL ListOrder findByUserIdAndStatusIn(Long userId, ListString statuses); // 复杂查询用Query Query(SELECT o FROM Order o JOIN FETCH o.items WHERE o.userId :userId) ListOrder findUserOrdersWithItems(Param(userId) Long userId); }| 特性 | MyBatis | JPA/Hibernate | |------|---------|---------------| | SQL控制力 | ⭐⭐⭐⭐⭐ 完全手写SQL | ⭐⭐⭐ JPQL/HQL复杂查询难控制 | | 自动映射 | ⭐⭐⭐⭐ ResultMap | ⭐⭐⭐⭐⭐ 注解/JPA映射 | | 动态SQL | ⭐⭐⭐⭐⭐if,foreach| ⭐⭐ Criteria API较繁琐 | | 缓存 | ⭐⭐⭐ 两级缓存 | ⭐⭐⭐⭐ 一级/二级/查询缓存 | | 学习曲线 | ⭐⭐⭐⭐ 简单 | ⭐⭐⭐ 概念多持久化上下文、懒加载等 |实战建议电商系统以复杂查询为主推荐MyBatis-PlusMyBatis增强版既保留了SQL灵活性又提供了常用CRUD的自动生成。 第二轮AIGC智能客服场景答案Q5RAG架构如何实现企业文档问答业务场景公司有500份产品手册、技术文档、合同模板客服人员每天要花大量时间查找资料回答客户问题。用RAG搭建智能客服后用户自然语言提问系统自动从文档中检索相关内容结合大模型生成精准答案。技术原理——RAG完整流程┌─────────────────────────────────────────────────────────┐ │ 离线阶段Ingestion Pipeline │ │ │ │ PDF/Word/网页 → 文档加载 → 文本分割(Chunking) │ │ ↓ │ │ Embedding模型编码 → 向量存入向量数据库(Milvus/Chroma) │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ 在线阶段Query Pipeline │ │ │ │ 用户提问 → Embedding编码 → 向量数据库语义检索 │ │ ↓ │ │ Top-K相关文档片段 原始问题 → 拼成Prompt → LLM生成答案 │ └─────────────────────────────────────────────────────────┘Spring AI 实现示例RestController public class CustomerServiceController { Autowired private VectorStore vectorStore; // 向量数据库Milvus/Chroma/Redis Autowired private ChatClient chatClient; // 大模型客户端OpenAI/Ollama PostMapping(/ask) public String ask(RequestBody QuestionRequest request) { // 1. 语义检索从向量数据库召回Top-5相关文档 ListDocument relevantDocs vectorStore.similaritySearch( SearchRequest.query(request.getQuestion()).withTopK(5) ); // 2. 构建Prompt系统指令 上下文文档 用户问题 String context relevantDocs.stream() .map(Document::getContent) .collect(Collectors.joining(\n\n)); String prompt 你是一个专业的客服助手。请根据以下文档内容回答用户问题。 如果文档中没有相关信息请明确说根据现有资料我无法回答这个问题。 参考文档 %s 用户问题%s .formatted(context, request.getQuestion()); // 3. 调用LLM生成答案 return chatClient.call(prompt); } }Q6MCP协议是什么如何让大模型调用数据库业务场景用户问我上个月的第三笔订单什么时候到货这个问题需要查询数据库。RAG的文档里只有静态的产品信息没有用户订单数据。这时需要让大模型自动识别这需要查数据库然后调用订单查询API。技术原理MCPModel Context Protocol模型上下文协议是Anthropic提出的开放标准定义了AI模型与外部工具/数据源之间的通信规范。核心概念MCP Server工具提供方如数据库查询服务、文件系统、API网关暴露标准化的工具描述MCP ClientAI应用端如Spring AI应用将工具列表发送给大模型执行大模型的工具调用请求通信协议基于JSON-RPC 2.0支持本地进程通信stdio和远程HTTP通信SSE// Spring AI MCP 工具定义示例 Configuration public class McpConfig { Bean public ToolCallback orderQueryTool(OrderService orderService) { return ToolCallback.builder() .name(query_user_orders) .description(查询用户的历史订单参数userId(用户ID), month(月份)) .inputType(OrderQueryRequest.class) .callback(request - { // 实际的数据库查询逻辑 ListOrder orders orderService.findByUserIdAndMonth( request.userId(), request.month() ); return new ToolResponse(orders); }) .build(); } } // 对话中自动触发工具调用 Autowired private ChatClient chatClient; public String chat(String userMessage) { return chatClient.prompt() .user(userMessage) .tools(orderQueryTool, refundTool, productSearchTool) // 注册可用工具 .call() .content(); // 大模型会自动判断是否需要调工具调用后整合结果回复 }MCP vs A2A协议| 维度 | MCP | A2A | |------|-----|-----| | 提出方 | Anthropic | Google | | 目的 | 模型与工具之间的标准化通信 | Agent与Agent之间的标准化通信 | | 比喻 | 人AI使用工具API的接口规范 | 两个AI Agent之间聊天的协议 | | 使用场景 | 一个AI调用数据库、文件系统、API | 多个AI Agent协作完成复杂任务 |Q7Agentic RAG 和传统 RAG 有什么区别业务场景传统RAG是问一句 → 搜一次 → 答一次的简单流水线。Agentic RAG更像一个会思考的助手——它会判断这个问题需要检索吗检索一次够不够检索结果靠谱吗要不要换个角度再搜一次技术原理Agentic RAG的核心能力自主决策Agent自己判断是否需要检索、检索什么多步推理第一次检索结果不理想自动改写Query重新检索工具编排可能需要同时查向量数据库 调用API 查传统数据库Agent负责编排自我纠错检索到矛盾的信息时交叉验证并标记不确定的部分传统RAG流程 用户提问 → 检索 → 拼接 → LLM回答 一条直线 Agentic RAG流程 用户提问 → Agent思考 → ├─ 需要检索吗→ 是 → 检索1 → 结果满意吗 │ ├─ 满意 → 回答 │ └─ 不满意 → 换个角度检索2 → 回答 └─ 不需要检索 → 直接回答如你好AI幻觉Hallucination的缓解策略RAG本身就是最强的抗幻觉方案——给模型真实文档它就没必要编置信度阈值模型输出时附带置信度低置信度的回答标记为不确定溯源引用回答中标注信息来源哪个文档的第几段用户可自行验证多轮检索验证Agentic RAG可以从多个角度检索交叉验证信息一致性Q8Agentic RAG如何处理知识库外的问题业务场景用户问公司明年会裁员吗这显然不在知识库中。Agentic RAG应该如何优雅地处理Agent的决策流程Step 1: 分析问题 → 这是关于公司未来决策的问题知识库中可能没有 Step 2: 初次检索 → 在向量数据库检索裁员组织调整 → 无相关结果 Step 3: 改写查询 → 换个角度人员优化组织架构调整 → 仍无结果 Step 4: 多源验证 → 检查是否有相关公告、新闻 → 无 Step 5: 最终决策 → 生成回复根据现有的公开资料我无法回答关于公司未来决策的问题。 建议您关注公司官方公告或咨询HR部门。核心是设置检索质量的阈值如果所有检索结果的相似度都低于阈值如 cosine similarity 0.6Agent应该诚实地说不知道而不是强行拼接无关内容让大模型硬编。 第三轮支付金融与安全风控答案Q9Spring Security OAuth2 JWT 完整认证流程业务场景支付系统涉及资金安全必须确保每个请求都是经过认证的合法用户。用户登录后获得Token后续所有API请求携带Token访问。技术原理——完整认证链路┌────────────────────────────────────────────────────────────────┐ │ Step 1: 用户登录 │ │ POST /oauth/token │ │ Body: {username, password, grant_type: password} │ │ 返回: {access_token: eyJhbG..., refresh_token: xyz...} │ ├────────────────────────────────────────────────────────────────┤ │ Step 2: 携带Token访问API │ │ GET /api/orders │ │ Header: Authorization: Bearer eyJhbG... │ ├────────────────────────────────────────────────────────────────┤ │ Step 3: Spring Security 过滤器链 │ │ BearerTokenAuthenticationFilter → 解析Token │ │ → JwtDecoder 验签 → 提取Claims → 构建Authentication │ │ → SecurityContextHolder.getContext().setAuthentication(...) │ ├────────────────────────────────────────────────────────────────┤ │ Step 4: 方法级权限校验 │ │ PreAuthorize(hasRole(ADMIN)) │ │ → 从SecurityContext取出权限 → 比对 → 放行/拒绝 │ └────────────────────────────────────────────────────────────────┘JWT Refresh Token 双Token机制Service public class TokenService { // Access Token短期有效15分钟用于API访问 public String createAccessToken(UserDetails user) { return Jwts.builder() .subject(user.getUsername()) .claim(roles, user.getAuthorities()) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() 15 * 60 * 1000)) // 15分钟 .signWith(privateKey) // RSA私钥签名 .compact(); } // Refresh Token长期有效7天仅用于刷新Access Token public String createRefreshToken(UserDetails user) { String token UUID.randomUUID().toString(); // 存Redis可以随时吊销 redisTemplate.opsForValue().set( refresh: token, user.getUsername(), 7, TimeUnit.DAYS ); return token; } // 刷新Access Token public TokenResponse refresh(String refreshToken) { String username redisTemplate.opsForValue() .get(refresh: refreshToken); if (username null) { throw new InvalidTokenException(Refresh Token无效或已过期); } UserDetails user userDetailsService.loadUserByUsername(username); String newAccessToken createAccessToken(user); return new TokenResponse(newAccessToken, refreshToken); } // 踢下线直接删除Refresh Token public void revokeRefreshToken(String refreshToken) { redisTemplate.delete(refresh: refreshToken); } }Q10Prometheus Grafana Micrometer Jaeger 全链路监控业务场景支付系统出故障了用户付了钱但订单状态没变。运维需要快速定位是哪个服务挂了哪个环节慢了这次故障影响了多少用户技术原理——可观测性三支柱┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Metrics │ │ Tracing │ │ Logging │ │ 指标 │ │ 链路追踪 │ │ 日志 │ ├──────────────┤ ├──────────────┤ ├──────────────┤ │ Prometheus │ │ Jaeger │ │ ELK Stack │ │ Grafana │ │ Zipkin │ │ Elasticsearch│ │ │ │ │ │ Logstash │ │ 服务QPS多少 │ │请求经过了哪些 │ │ Kibana │ │ 延迟多少 │ │ 服务哪里慢 │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ ↓ ↓ ↓ └──────────────┬───┴──────────────────┘ ↓ TraceID 串联三者 在Grafana发现延迟飙升 → Jaeger找到慢Trace → 拿到TraceID → Kibana搜关联日志 → 定位根因Spring Boot集成示例# application.yml management: endpoints: web: exposure: include: health,metrics,prometheus metrics: export: prometheus: enabled: true tracing: sampling: probability: 1.0 # 全量采样生产环境建议0.1 zipkin: tracing: endpoint: http://jaeger:9411/api/v2/spansGrafana告警规则示例# 支付接口P99延迟超过2秒触发告警 histogram_quantile(0.99, rate(http_server_requests_seconds_bucket{uri/api/pay}[5m]) ) 2 # 支付失败率超过1%触发告警 rate(http_server_requests_total{uri/api/pay,status500}[5m]) / rate(http_server_requests_total{uri/api/pay}[5m]) 0.01Q11Java 21 虚拟线程在支付系统中的应用业务场景支付网关需要同时调用多个外部接口——银行扣款接口500ms、风控校验接口200ms、短信通知接口100ms。传统做法是开线程池异步调用但代码复杂CompletableFuture链式调用。虚拟线程让你用同步代码写法享受异步性能。技术原理虚拟线程的本质JVM层面的轻量级线程不一一映射到OS线程。当一个虚拟线程执行IO操作时JVM自动把它从载体线程Carrier Thread上卸下来载体线程去执行其他虚拟线程。IO完成后JVM再把虚拟线程挂回载体线程继续执行。这个过程对开发者完全透明。// 传统写法CompletableFuture异步编排回调地狱 public PaymentResult pay(PaymentRequest req) { CompletableFutureBankResult bankFuture CompletableFuture.supplyAsync(() - bankService.deduct(req)); CompletableFutureRiskResult riskFuture CompletableFuture.supplyAsync(() - riskService.check(req)); return bankFuture .thenCombine(riskFuture, (bank, risk) - { if (!risk.isPassed()) throw new RiskException(); return paymentDao.save(bank, risk); }) .join(); // 阻塞等待结果 } // 虚拟线程写法同步代码异步执行Java 21 public PaymentResult pay(PaymentRequest req) throws Exception { try (var executor Executors.newVirtualThreadPerTaskExecutor()) { FutureBankResult bankFuture executor.submit(() - bankService.deduct(req)); FutureRiskResult riskFuture executor.submit(() - riskService.check(req)); // 同步等待但不阻塞OS线程 BankResult bank bankFuture.get(); RiskResult risk riskFuture.get(); if (!risk.isPassed()) throw new RiskException(); return paymentDao.save(bank, risk); } }虚拟线程核心优势数据| 指标 | 传统平台线程 | 虚拟线程 | |------|-------------|----------| | 内存占用 | ~1MB/线程 | ~2KB/线程 | | 创建开销 | ~1ms | ~1μs | | 上下文切换 | OS级别昂贵 | JVM级别极轻 | | 最大并发数 | ~10000受限内存 | 数百万 | | IO密集型QPS | 5000 | 100000 |适用场景IO密集型任务调用外部API、数据库查询、消息队列收发。不适用场景CPU密集型任务计算、加密因为虚拟线程不会减少CPU时间。Spring Boot 3.2 开启虚拟线程spring: threads: virtual: enabled: true # Tomcat/Jetty自动使用虚拟线程处理请求Q12补充Docker Kubernetes CI/CD 部署流水线业务场景代码写好了如何从开发环境快速部署到生产环境现代Java项目的标准答案是Git Push → CI Pipeline → Docker镜像 → Kubernetes集群。Jenkins GitLab CI Docker Kubernetes流水线# .gitlab-ci.yml stages: - build - docker - deploy build: stage: build script: - mvn clean package -DskipTests artifacts: paths: - target/*.jar docker-build: stage: docker script: - docker build -t payment-service:$CI_COMMIT_SHA . - docker push registry.company.com/payment-service:$CI_COMMIT_SHA deploy: stage: deploy script: - kubectl set image deployment/payment-service payment-serviceregistry.company.com/payment-service:$CI_COMMIT_SHA - kubectl rollout status deployment/payment-serviceDockerfile最佳实践多阶段构建# 阶段1构建使用完整JDK FROM maven:3.9-eclipse-temurin-21 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline # 缓存依赖 COPY src ./src RUN mvn package -DskipTests # 阶段2运行使用精简JRE FROM eclipse-temurin:21-jre-alpine COPY --frombuilder /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar] 总结面试通关密码谢飞机虽然翻车了但他的经历告诉我们基础八股文必须背熟Redis、Kafka、Spring Security这些常规问题不能丢分AI技术栈是2025面试的绝对加分项MCP协议、RAG架构、Agentic RAG至少要能说清楚核心概念分布式系统要有实战深度不是说用过Seata就完了要能讲清楚AT vs TCC的取舍Java 21虚拟线程是高频考题2KB内存、百万并发、同步写法异步执行——这三个关键数据必须记住如果你也是谢飞机别灰心——八股文背得好技术深度不可少分布式要动手AI新技术得跟牢。 下一期预告《谢飞机复仇记6个月逆袭拿下字节跳动Offer他都学了什么》——敬请期待原创不易如果觉得有帮助欢迎点赞、收藏、关注三连支持