北京都学网后端面经:项目贡献边界、SingleFlight 与长时面试数据保护
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
项目职责与边界
- 你在 AI 面试平台项目中具体负责哪部分?Agent 模块是谁做的?
- 简历里写的「负责完成」,具体边界是什么?
- 这个项目是实验室开源项目吗?团队几个人?
- 另一个 AI Coding Agent 项目是你独立搭建的吗?
- 这两个项目目前是持续维护阶段,还是已经完结的历史项目?
技术实现深挖
- 为什么引入 SingleFlight 机制?具体怎么实现的?
- SingleFlight 的底层实现细节是什么?去重与复用逻辑是怎么做的?
- 长时面试数据丢失风险怎么解决?为什么引入 MongoDB 做热存档?
- 除了热存档,还有哪些前置防护机制?比如幂等判断、答题锁?
- 讯飞 ASR 语音识别转发做了哪些去重和优化处理?
- AI Coding Agent 的创新点是什么?为什么做这个项目?
- 上下文四层压缩与任务边界切换具体怎么实现?
- 任务边界切换中,轻量级判别模型返回什么?性能损耗怎么解决?
- 任务标识(哈希值)怎么存、怎么共享状态?
- 子 Agent 有哪些?它们之间是什么关系?上下文是否独立?
业务匹配与规划
- 后续有什么改进计划?
- 能稳定实习多久?是否可以到岗?
《参考解析》
贡献边界怎么答:这类追问不是要否定你,而是在验证「你是不是真的做过」。最稳的结构是「我负责的模块 + 我做的具体决策 + 可验证的结果」。例如:Agent 模块的会话编排与工具调用是我写的,RAG 检索和前端是队友做的;我主导了 SingleFlight 的 key 设计和降级方案,压测数据是我跑的。不要把所有功劳揽到自己身上,也不要因为怕被追问就把自己缩成「只写了几个接口」——面试官更看重你能否清楚说出边界、以及边界内你解决了什么问题。被问「是不是实验室开源项目」时如实说,开源项目也能讲出自己的增量。
SingleFlight 为什么引入、怎么实现:动机是「相同请求在并发时只执行一次,其余请求等待并复用结果」。典型场景是缓存击穿、AI 接口按 token 计费、同一个会话被多次重试。单机实现用一个 map[key]*call 加互斥锁:第一个请求创建 call(含 sync.WaitGroup 和结果字段)并执行,后来的请求拿到同一个 call 后 Wait(),执行完统一读结果;要处理 panic,保证 Done() 一定被调用,否则等待者会永久阻塞。分布式场景要把「选举」放到 Redis:SET key owner NX PX ttl 抢锁,抢到的执行并把结果写回结果 key,没抢到的轮询或订阅结果;关键是锁的过期时间要覆盖执行时间并支持续约,结果要有明确的成功/失败状态,不能让等待者读到半成品。
长时面试为什么用 MongoDB 热存档:一场面试可能持续几十分钟,状态包括当前题目、已答内容、评分中间结果、工具调用记录。只放内存,进程重启或连接断开就丢;只放 MySQL,高频小写入会让行锁和事务开销变大,而且状态结构经常变、字段不固定。MongoDB 的文档模型适合存这种半结构化快照,写入是追加/整文档替换,按 sessionId 建索引读取快,配合 TTL 或归档策略控制体量。设计上要做「写穿」:关键状态变更先同步写热存档再返回,或者用本地队列 + 定时刷盘,并记录版本号防止旧快照覆盖新状态。
前置防护:热存档是兜底,前面还要有三道。① 幂等:每个答题动作带 sessionId + questionId + attemptId 的唯一键,重复提交直接返回已有结果,避免重复计费与重复评分;② 答题锁:同一会话同一题同一时刻只允许一个写者,用 Redis 锁或数据库乐观锁(版本号)实现,防止用户多端同时提交;③ 心跳与断线重连:WebSocket 断开后按 sessionId 恢复,服务端返回最后确认的状态版本,客户端从该版本继续,而不是从头再来。再加一条:所有写操作先落「意图日志」再执行,崩溃后可以重放。
讯飞 ASR 转发的去重与优化:流式识别返回的是带序号的分片结果,可能重复、乱序、跨连接重发。去重靠「会话 ID + 分片序号」做幂等,已经处理过的序号直接丢弃;拼接要按序号排序,遇到缺口先缓存等补包,超时再标记缺失,不能把乱序文本直接拼进上下文。优化上:按 VAD 切句、静音段不转发;连接断开时用最后确认的序号续传;把中间结果和最终结果分开存,最终结果才进面试记录;对同一句话的多次修正取最后一次定稿,避免上下文里出现重复语义。
AI Coding Agent 的创新点:不要只说「用了大模型」。可讲的点:把「任务边界识别」做成显式步骤——先判断当前请求属于哪个任务、需要哪些上下文,再决定加载哪些文件与工具,避免把整个仓库塞进上下文;上下文分层压缩(系统约束、任务目标、近期对话、工具结果各自有不同的保留策略);工具调用带沙箱与权限,写文件前先做 diff 预览;用子 Agent 做「探索/修改/审查」分工,降低单次上下文污染。为什么做:现有助手在长任务里容易跑偏、重复读文件、改一处坏一处,想验证「边界 + 隔离」能不能提升一次通过率。
上下文四层压缩:可以按「不可丢 / 可摘要 / 可检索 / 可丢弃」分层。第一层是系统约束与用户硬性要求,原样保留;第二层是任务目标与当前计划,压缩成结构化列表;第三层是历史对话与工具结果,按时间与相关性做摘要,保留结论、丢弃原始输出,需要细节时再按引用回查;第四层是临时观察(日志、文件片段),只保留最近若干条或转成向量索引。压缩的触发点按 token 预算而不是按轮数,压缩后要保留「谁在什么时候决定了什么」,否则模型会重复已经否决的方案。
任务边界切换与轻量判别模型:判别模型接收最近的对话与工具调用摘要,输出「任务标签 + 置信度 + 是否需要切换」这样的结构化结果,而不是自由文本;置信度低于阈值时不做切换、走保守路径。性能上不用每次请求都跑:用规则先过滤(关键词、工具名、文件路径),命中歧义再调小模型;小模型只做分类、输入做截断,结果按「对话轮次 + 摘要哈希」缓存,避免重复推理。判别失败要能回退到「沿用当前任务」,不能因为分类错误把上下文清空。
任务标识与子 Agent:任务标识用「会话 ID + 任务类型 + 目标对象(文件/模块)」的稳定哈希,存在会话状态里,同时写 Redis 供多个进程共享;子 Agent 启动时继承任务标识但不继承完整上下文,只拿到任务目标、约束和必要的文件引用,结果以结构化摘要回传主 Agent。子 Agent 之间上下文隔离,避免互相污染;共享的是「事实层」(文件内容、命令结果)而不是「推理过程」。常见拆分是探索者(读代码、定位)、修改者(写代码)、审查者(跑测试、挑问题),主 Agent 负责编排与最终决策。
改进计划与实习安排:改进计划要具体到「下一步做什么、怎么衡量」:比如把 SingleFlight 的等待超时与降级做成可配置、给 Agent 加工具调用预算与死循环检测、把上下文压缩从按轮数改成按 token 预算、补一套离线评测集。实习时间如实回答,能到岗就说到岗时间与每周天数;如果还有课程,说明如何协调,不要含糊其辞。