我按“简历 + 当前仓库代码”做了一版真实面试的自问自答示例。先说结论:从简历和项目证据看,你现在更像“有项目深度、但还在实
习生/初级候选人区间”的候选人,适合把回答重点放在“你具体怎么做的”,而不是只讲概念。
代码证据主要落在这几处:app/src/main/java/interview/guide/modules/resume/service/ResumeUploadService.java:47、app/src/
main/java/interview/guide/common/async/AbstractStreamConsumer.java:64、app/src/main/java/interview/guide/modules/
knowledgebase/service/KnowledgeBaseQueryService.java:104、app/src/main/java/interview/guide/common/aspect/
RateLimitAspect.java:66、app/src/main/resources/application.yml:6。
示例面试
面试官:你简历里写“同步快速响应 + Redis Stream 异步处理”,能结合当前项目讲清楚完整链路吗?
示例回答:这个项目里,用户上传简历后,请求不会同步等待 AI 分析完成。入口在 ResumeUploadService#uploadAndAnalyze,先做
文件校验、类型识别、去重、文本解析,然后把原文件传到对象存储,再把简历记录写库并标记成 PENDING,最后通过
AnalyzeStreamProducer 把分析任务发到 Redis Stream,接口立即返回任务状态。真正的消费逻辑在抽象模板
AbstractStreamConsumer 里,它统一处理消费循环、ACK、重试、失败状态落库,具体业务子类只关心解析 payload 和处理业务。这
样做的核心收益是把 LLM 这种高延迟任务从主链路拆出去,接口响应时间更稳定,也方便失败重试和状态追踪。
面试官点评:这就是合格回答。你不是只说“用了异步”,而是把入口、状态、消息投递、消费者模板、ACK/重试都讲出来了。
面试官:你简历里写“接口响应从 15s 降到 200ms”,如果我追问这个优化为什么成立,你怎么答?
示例回答:成立的前提不是单点代码提速,而是链路改造。原来最耗时的是文件解析后紧接着调用大模型分析,HTTP 请求会被整个任
务阻塞。现在主链路只保留轻量步骤,像文件校验、解析、存储、写库、投递消息,这些都属于可控时延;重任务转到 Stream 消费
端,所以用户只拿到受理结果。与此同时,application.yml 里还开启了 Java 21 虚拟线程,适合 I/O 密集场景,进一步减少阻塞线
程的成本。不过如果让我更严谨一点,我会说明:简历上的 15s 和 200ms 属于项目压测或观测结论,面试里最好补一句“这是在本项
目场景下的结果,不是所有接口都会稳定到这个数值”。
面试官点评:这类题最怕回答成“因为用了 Redis 所以快了”。真正好的回答要区分“架构解耦”与“单次执行变快”是两回事。
面试官:你简历里写了 RAG 检索增强和 Query Rewrite。那你在这个项目里具体怎么控制召回质量?
示例回答:核心逻辑在 KnowledgeBaseQueryService。查询进来之后,不是直接拿原问题检索,而是先做问题归一化,再根据配置决定
是否做 rewrite,形成多个候选 query。然后根据问题长度动态选择 topK 和最小相似度阈值,比如短问题召回更多,长问题阈值更谨
慎。服务会遍历候选 query 去做相似度搜索,只要命中有效文档就停止继续扩散,避免无意义召回。拿到文档后,再把上下文拼进
prompt 调模型生成答案。流式接口里还做了输出归一化,目的是减少模型前段“废话”对体验的影响。这个设计本质上是在平衡召回
率、准确率和响应延迟。
面试官点评:这里你已经比只会背“RAG=检索+生成”的候选人强很多了。因为你能落到“候选 query、动态 topK、minScore、有效命中
判断”这些实现点。
面试官:你说做了限流组件,为什么是 Redis + Lua + AOP,而不是只在 Java 里写个计数器?
示例回答:因为这是分布式服务场景,本地计数器只能限制单机,扩容后就失效。这个项目里 RateLimitAspect 用 AOP 拦截带
@RateLimit 的方法,在切面里根据维度生成 key,比如 GLOBAL、IP、USER,然后调用预加载到 Redis 的 Lua 脚本做原子判断。Lua
的意义是把“读计数、校验窗口、写入状态”变成一个不可分割的操作,避免并发下超发。AOP 的意义是把限流和业务解耦,业务方法只
需要声明注解,不需要重复写模板代码。另外这里还考虑了 Redis Cluster 的 slot 问题,用 hash tag 让同一方法的 key 落到同一
slot。
面试官点评:这是很像真实面试里的高分回答。因为你同时解释了“为什么不能只用本地内存”“为什么要 Lua”“为什么要 AOP”。
面试官:如果我是面试官,我会质疑你简历里写了 USER 维度限流,但项目里似乎没有完整鉴权体系,你怎么回应?
示例回答:这个质疑是成立的。我会如实说,当前项目的 USER 维度是组件能力预留,代码里已经支持从 request attribute 或
header 提取 userId,但仓库里还没有完整用户系统,所以线上真正稳定可用的是 GLOBAL 和 IP 维度。这个时候我不会硬说“已经完
全落地”,而是明确区分“已实现的组件能力”和“当前项目已完整闭环的业务能力”。这样回答比强行圆更可信。
面试官点评:这是你后面要重点练的地方。面试不怕承认边界,怕的是把“预留能力”说成“完整上线能力”。