猎聘全栈面试:Agent 编排与分布式锁
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 你接触过 AI 和 Agent 开发吗?
- 具体介绍一下你做过的 AI Agent 项目。
- 你的项目是基于 LangChain 还是 LangGraph?
- RAG 的检索是怎么实现的?
- 能详细介绍一下检索过程吗?
- 你们的召回准确率能达到多少?
- 项目中遇到过什么技术难点?做过哪些优化?
- 你对 LangGraph 的运行机制、Agent 编排和工具调用了解多少?
- 你们的上下文压缩是怎么实现的?
- Agent 的记忆怎么分层?约束是什么?哪些信息不能压缩?
- 你的异步结算链路、消费幂等、失败异常和并发抢占是怎么做的?
- SETNX 唯一性兜底是在哪一层做的?MySQL 还是 Redis?
- 数据库更新失败以后,Redis 预占的名额怎么恢复?
- 你了解 Redis 分布式锁吗?怎么实现?
- 分布式锁怎么处理重入、续期和释放?
- 怎么避免释放别人的分布式锁?
- 你了解 Kafka 吗?RabbitMQ 和 Kafka 有什么核心区别?
- RabbitMQ 和 Kafka 在消息顺序性方面有什么区别?
- RabbitMQ 和 Kafka 的延迟队列、死信机制有什么区别?
- Kafka 能进行消息追溯和重复消费吗?
- 你在实习中怎么使用 AI Coding?做过哪些优化?
- SDD 是什么框架?你使用过哪些工具?
- 团队有没有自研过 SDD 或类似的 AI Coding 框架?
- 团队有没有沉淀 Rules 或 Skills?
- 除了本地 AI Coding 工具,你们有云端平台吗?
- 你觉得 AI Coding 最难的环节是什么?
- 你接触过前端开发吗?实习中有没有做过前端?
- 你的全栈项目主要面向 C 端用户还是内部 B 端用户?
- React 或 Vue 的代码你能看懂吗?能维护现有代码吗?
《参考解析》
RAG 的检索要按「离线建库 + 在线召回 + 重排」三段讲,召回率必须给出口径。 离线段:文档解析与清洗(去页眉页脚、表格转结构化)、按语义而非固定长度切块(带重叠、保留标题层级作为元数据)、embedding 后入向量库并保留 doc_id/版本/权限字段。在线段:query 改写(多轮对话里的指代消解、口语化补全)、混合检索(向量召回 + BM25 关键词召回,两者用 RRF 或加权融合,纯向量在专有名词和编号上很弱)、元数据过滤(按租户、时间、权限先过滤再检索,避免越权召回)。重排段:用 cross-encoder 或大模型对候选集精排,取 Top-N 进上下文。召回率不能说「90% 多」这种没有分母的数,正确说法是先定义评测集(人工标注的 query-答案对,覆盖高频与长尾),再报 Recall@k 和 MRR/NDCG,并说清 k 取多少、基线是什么、优化前后差多少。追问常是「召回率不高怎么查」,答法是从四个方向定位:切块粒度是否把答案切断了、embedding 模型与领域是否匹配、是否缺关键词通道、过滤条件是否过严。
记忆分层与上下文压缩要落到「什么可以丢、什么绝不能丢」。 分层一般是三层:会话内的短期记忆(最近若干轮原文,保证连贯)、会话级的摘要记忆(超出窗口后压缩成要点)、跨会话的长期记忆(用户画像、偏好、历史结论,存结构化存储或向量库,按需检索注入)。压缩的常见做法:滑动窗口保留最近 N 轮、对早期轮次做摘要替换、把工具调用结果只保留结论而丢掉原始大 JSON、对长文档用检索代替全文注入。绝不能压缩或必须原样保留的:用户明确给出的约束与偏好(地域、预算、时间)、系统指令与安全边界、当前任务的目标与已确认的决策、以及工具调用的原始关键字段(ID、金额、状态)。一个常被追问的点是「压缩后怎么保证不出错」——做法是把「结构化状态」和「自然语言摘要」分开存:状态用字段存、可校验,摘要只用于语气和背景,不承载事实。
LangGraph 与 Agent 编排要讲清「状态机 + 显式状态」这个核心差异。 LangGraph 把流程建模成有向图:节点是动作(调用模型、调用工具、做判断),边(含条件边)决定下一步去哪,所有节点读写同一份 state,因此循环、分支、重试、人工介入都是显式的,失败可以从某个检查点恢复,也方便做可观测。LangChain 的链式结构适合线性流程,遇到需要回退和多次尝试的场景就很别扭。工具调用要能说清一轮的完整过程:模型输出工具名与参数 → 框架按 schema 校验 → 执行并把结果作为新消息塞回上下文 → 模型继续推理直到给出最终答案。追问点通常在:怎么防止死循环(步数上限、状态去重、重复调用检测)、工具失败怎么处理(结构化错误返回给模型让它换参重试,而不是抛异常中断)、以及怎么限制工具权限(写操作要人确认、按用户隔离凭据)。
异步结算链路的答案要把「幂等、抢占、补偿」三件事拆开说。 幂等:消费端以业务唯一键(订单号 + 事件类型)建唯一索引或写幂等表,重复消息命中即直接返回成功,而不是靠「消息不会重复」的假设。并发抢占:名额扣减走 Redis 的原子操作(Lua 脚本或 DECR)做第一层拦截,最终以数据库唯一约束或 UPDATE ... WHERE remain > 0 的行锁为准,Redis 只做拦截、不做真相。失败补偿:数据库更新失败时,Redis 里预占的名额必须回滚,靠的是「预占记录带状态与过期时间 + 定时对账任务」——把预占写成带 TTL 的 key 并记录流水,失败路径立刻显式释放,投递到 MQ 失败进重试队列,更极端的情况由对账任务按流水与数据库实际结果对齐(数据库已扣就补记,未扣就释放)。要主动承认这套设计的取舍:允许极短时间内存在「Redis 说没了、数据库其实还有」的不一致,用对账窗口收敛,而不追求强一致——这是大多数名额/库存场景的正确答案。
「SETNX 在哪一层」这类问题的标准答法是:Redis 做拦截层、数据库做兜底层,两层都要有,缺一不可。 只有 Redis 会在主从切换或进程重启时丢锁/丢键,造成超卖;只有数据库则会在高并发下把压力全打在行锁上。所以 Redis 的唯一性 key 是性能手段,数据库的唯一索引或乐观锁是正确性手段,两层用同一把业务键对齐,冲突时以数据库结果为准并回滚 Redis 侧。这个「拦截 + 兜底 + 对账」的三段式几乎能覆盖所有库存、名额、券码类问题。
Redis 分布式锁要按「加锁、续期、释放」三处讲透。 加锁用 SET key value NX PX ttl 一条命令(分开 SETNX 再 EXPIRE 会留下永不过期的锁),value 用带客户端标识的随机串。续期用看门狗(Redisson 的 watchdog 默认续到 30 秒)或自己在业务内定期续,续期的前提是「锁还是自己的」——先校验 value 再续。释放必须用 Lua 脚本做「比较 value 再删除」的原子操作,否则会出现「A 的业务超时后锁过期、B 拿到锁,A 执行完把 B 的锁删了」这个经典事故。重入用 Hash 结构存(key → {客户端 ID: 重入次数}),计数减到 0 才真正删除。必须主动说清单机 Redis 锁的局限:依赖单点,主从异步复制下锁可能丢失,强一致场景要用 Redlock 或直接退化为「数据库唯一约束 + 短事务」,很多团队的最后结论就是「能用数据库约束解决的不要上分布式锁」。
Kafka 与 RabbitMQ 的对比要围绕「消息顺序、投递语义、延迟与死信」展开。 顺序性:Kafka 只保证同一 partition 内有序,所以按业务键(订单号)做分区键才能保证同一实体的消息有序,消费者并发度受 partition 数限制;RabbitMQ 在单队列单消费者下有序,多消费者并发消费同一队列就无序,要保持顺序得靠单个队列加单消费者或者一致性哈希交换机。死信与延迟:RabbitMQ 原生有死信交换机(DLX)与 TTL + DLX 实现的延迟队列(也可用延迟插件),机制清晰、消息级 TTL 支持;Kafka 没有消息级的 TTL 和死信队列,延迟要靠业务层做时间轮、或者单开一个按时间分区的 topic 再回灌,重试通常做成多个重试 topic 逐级退避。重复消费与追溯:Kafka 靠 offset 保留 + 消费者组重放实现「消息追溯」,天然是至少一次投递,重复消费是常态,所以幂等必须由消费端保证;RabbitMQ 的 ack 后消息即删,追溯靠死信队列或业务侧落库。选型结论也要给:日志/流式/高吞吐与重放场景选 Kafka,复杂路由、优先级队列、单条延迟消息、需要精细 ack 的业务选 RabbitMQ。
AI Coding 这块要答「我改了什么流程」,而不是「我用它写代码」。 可讲的具体优化:把任务拆成「先出方案与接口 → 再分段实现 → 每段跑测试」的节奏,控制单次上下文规模;把项目规范、目录约定、常用命令、已知坑写成 Rules/Skills 常驻上下文,减少重复纠偏;用 spec 或文档先固化输入输出与验收标准(SDD 类框架的核心就是这个:先写规格,再让模型按规格实现并自检),把「对不对」从人读代码变成可执行的断言;代码生成后强制跑 lint、类型检查、单测,把不通过的结果直接回灌给模型修;对生成结果做 diff 审查,只接受最小改动。团队沉淀这块,本地有 Rules/Skills、云端有平台,都可以如实说。问「最难环节是什么」,值得给一个有观点的答案:不是生成代码,而是「验证与上下文供给」——模型写得再快,没有可执行的验收标准和准确的上下文,就会把 review 成本推高;其次是跨文件重构时的一致性。这题答出自己的判断比答得全更重要。
前端与全栈能力问题本质是「你能不能独立把一条链路拉通」。 答「能看懂、能改现有代码、能查文档」是安全的,但更好的答法是给出具体范围:能写 React 函数组件与 hooks、能处理路由与状态管理、能看懂构建配置、能定位接口联调问题;Vue 能读能改,但主动说明「如果是从零搭项目,我选自己更熟的那套」。如果确实只在实习里碰过少量前端,如实说清「做过哪几个页面、遇到的具体问题怎么解决的」,比笼统地夸大有用。C 端还是 B 端的问题要答出差异意识:C 端重性能与体验(首屏、SEO、埋点、兼容性),B 端重权限、表单与流程的复杂度,说清自己在项目里真正处理过哪一类。