杉川 后端开发 一面
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 你设计的脚本原子化秒杀流程,能详细说一下经过哪些步骤吗?
- 检查库存、判断重复购买、数据库落单确认消息这些,哪些在脚本里,哪些不在?
- 当只剩一张优惠券时,有两个人同时抢,你的脚本怎么处理?怎么判断和避免超卖?
- 如果数据库已经写入了订单,但还没收到确认消息,消费者就崩溃了,你有做恢复操作吗?恢复后会发生什么?怎么避免再产生一个订单?
- 介绍一下这个 RAG 项目解决了什么问题?
- 详细讲一下流程,离线的时候做了什么,在线的时候又做了哪些工作?
- 这边用的是语义化检索,有了解或使用过混合检索吗?你认为有更大的提升吗?
- 你做了一个摘要式对话记忆,假如 N 等于 10,按你的实现,到第 10 条消息时哪些内容会被总结保留下来?
- 第 11 条消息会发送哪些东西?最终发给模型的是什么内容?
- 第二次总结时(比如第 20 条消息),输入的是过去所有历史消息,还是旧的摘要加上新增的消息?
- 用旧的摘要能做到避免重复和遗漏吗?
- 你这里用到 WebFlux,从模型产生第一段文本到浏览器显示,中间会经历哪些步骤?
- 如何让你的 AI agent 稳定输出 JSON 格式?
- 了解过意图识别这个概念吗?
- 如果让你做一个自动解 bug 的 AI Agent:测试人员把问题放在 JIRA 上,研发到 Java 平台上去解 bug,你会怎么设计?
- 怎么理解进程和线程?
- 同一个进程的不同线程共享哪些内容?
- 有了解过协程这个概念吗?
- 有了解过哪些进程间的通信方式?
- 平时 AI 用得多吗?一般用什么 AI 工具?
《参考解析》
秒杀脚本的边界怎么划:面试官连问三层,实际在考「原子性的范围」。典型设计把「校验 + 扣减」放进 Lua:库存 key 是否充足、用户是否已购(用 Set 或 Hash 记录 user_id)、扣减库存、把 user_id 记入已购集合,一起原子执行;返回成功后再由应用层发消息、落单。数据库写单、消息投递、订单超时回滚这些「重活」不放脚本里——脚本只能做 Redis 内存内的原子操作,塞进去会拖长执行时间、阻塞单线程。防超卖靠的就是「判断与扣减在同一段 Lua 内」,Redis 单线程执行保证不会被插队;双保险是数据库层对库存加 stock > 0 的条件更新或唯一索引。
崩溃恢复与幂等:订单已落库但确认消息没消费,本质是「本地事务与消息投递的原子性」问题。做法是本地消息表(写订单和写消息在同一事务里)或事务消息,再由定时任务扫描未确认的消息重投;消费端必须幂等,用订单号做唯一索引或「消息去重表 + 状态机」保证重复消费不会产生第二张订单。恢复流程要能回答:重投多少次、超过上限怎么办(进死信队列 + 告警)、以及如何人工对账修补。
RAG 的离线与在线:离线阶段做文档采集与解析(PDF/网页/表格)、清洗去重、按语义切分成 chunk、生成 embedding 入库,同时建立关键词索引、附加元数据(来源、标题、时间、权限),可以预先做摘要或问题生成来提升召回。在线阶段是 query 理解(改写、指代消解、意图分类)→ 检索(向量、关键词或混合)→ 重排 rerank → 上下文组装 → 生成 → 引用与校验。混合检索(BM25 + 向量)几乎总是比单路好:关键词擅长精确匹配(型号、专有名词、报错码),向量擅长语义相近表达,两者用 RRF 或加权融合再精排,召回率和鲁棒性都会提升,这也是面试官想听到的答案。
摘要式记忆的追问怎么答:这类问题没有唯一答案,考的是你有没有想过细节。到第 10 条时通常会保留最近 K 轮原文 + 对更早内容的摘要;第 11 条发送的是「系统提示 + 摘要 + 最近 K 轮原文 + 本轮」。第二次总结有两种实现:用「旧摘要 + 新增消息」滚雪球,成本低但会累积语义漂移、遗漏早期细节;用全量历史重建摘要,准确但成本随轮数增长。折中做法是滑动窗口内用原文、窗口外用摘要,并每隔若干轮用原文重建一次摘要,另外把关键事实(用户偏好、订单号、结论)单独结构化成槽位保存,避免被摘要压掉。回答「用旧摘要能否避免重复和遗漏」时,直接承认会漂移,并给出缓解手段,比硬说「能」更可信。
WebFlux 流式链路与结构化输出:从模型产生第一段文本到浏览器显示,链路是:模型 SSE 流 → 后端 WebFlux 的 Flux<String>(非阻塞、背压由 Reactor 管理)→ 通过 SSE / WebSocket 或 StreamingResponseBody 推给前端 → 前端 fetch / EventSource 逐块读取并用 ReadableStream 解析 → 增量渲染(Markdown 流式解析,注意代码块跨 chunk 的拼接)。中间要注意的是别在流式链路上做阻塞调用(会卡住 event loop)、要处理断连与取消、以及 CDN / 网关的超时(长连接保活)。让模型稳定输出 JSON 的手段按强度排序:优先用厂商的结构化输出 / function call / JSON schema 能力;其次在提示词里给出 schema 与示例、要求「只输出 JSON」;再做后处理兜底(剥 markdown 代码块、截取第一个完整 JSON、用宽松解析器),失败就重试并带上错误信息。生产上还要加 schema 校验和降级路径。
进程线程与 IPC:进程是资源分配的基本单位,拥有独立地址空间;线程是调度的基本单位,共享进程的地址空间。同一进程内线程共享代码段、堆、全局变量、文件描述符,不共享栈、寄存器和线程局部存储,所以共享数据要加锁。协程是用户态的轻量调度单元,切换不经过内核、开销远小于线程,适合 IO 密集和高并发(Go 的 goroutine、Kotlin 协程、Python 的 asyncio),代价是默认不并行的 CPU 计算和需要配合非阻塞 API。进程间通信方式有管道 / 命名管道、消息队列、共享内存(最快但要自己做同步)、信号量、信号、socket(可跨机)、文件与 mmap。
这一轮的特点:问题密度高、每问都追细节,是典型的「项目深挖 + 基础兜底」面试。作者被追着问的几个点(脚本边界、崩溃恢复、摘要漂移)共同指向一个能力:能不能把系统的失败路径想清楚。准备这类面试的最好方法是给自己项目做「故障注入」——把每个组件假设成会崩、会重复、会超时,逐条写出应对方案。