纷享销客后端面经:SingleFlight、线程池隔离与三数据源一致性
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
基础信息与项目背景
- 请做一个简单的自我介绍,并重点阐述你近期正在开展的工作内容。
- 你简历上提到的算法竞赛奖项具体是什么?你的英语水平如何?
- 你目前的实习诉求是什么?为什么想找实习?
- 你这个项目的来源是什么?是独立完成还是团队合作?你在团队中具体负责哪部分?
- 你的简历是否是 AI 生成的?你是如何撰写这份简历的?
- 你们团队规模多大?这个项目占团队工作量的百分之多少?
后端架构与并发控制
- 请解释 SingleFlight 机制,它在什么经典场景下使用?
- 在你的项目中,SingleFlight 是如何做到复用相同 AI 请求以节省 token 的?
- 为什么使用 Redis Lua 脚本?它解决了什么问题?
- 为什么分布式锁无法满足你的业务场景需求?
- 你如何处理请求超时或失败的情况?请说明你的错误分类策略。
- 当 owner 节点宕机后,你是如何解决「脑裂」导致的状态冲突问题的?
- 你引入了什么版本校验机制来保障数据一致性?
性能压测与极端场景
- 你展示的 P95 等数据是压测结果吗?如果不是,你的常规化恢复方案的时间成本和数据量是多少?
- MongoDB 在你的业务中存储的是什么数据?数据量大概多大?
- 如果数据量膨胀到几百兆的极端情况,你的方案如何应对?
- 如果缓存失效,同时触发大量并发恢复请求,你如何处理?
- 你提出的基于优先级的请求聚合方案具体是如何实现的?聚合条件是什么?
- 你的请求聚合方案如何避免重复恢复?最终实际会发出几次请求?
- 并发恢复状态时如果发生冲突,你的快照版本号和幂等机制是如何设计的?
- 在高并发场景下,你的方案还有哪些防御性设计?
线程池与资源管理
- 你的项目中线程池具体应用在哪些场景?
- 你是如何通过异步请求与
CompletableFuture等待结果的?心跳机制是如何做续约的? - 如果慢请求拖垮线程池,你有什么防范方案?
- 请说明你的线程池具体配置与优化策略。
- 当线程池发生堆积时,你的超时中断、拒绝策略和定时回收方案具体如何落地?
- AI 长耗时任务极易拖垮线程池导致服务宕机,你如何重点防范?
- 你提出将 AI 调用隔离至独立线程池并调整参数,这个方案有什么潜在风险?做技术选型时如何评估副作用?
数据一致性与多数据源
- MySQL、Redis 与 MongoDB 并存时,如何保证答题状态的一致性?
- 你的查询链路是如何设计的?降级策略是什么?
- 如果让你在三者的查询链路中去掉一个,你会去掉哪个?去掉后数据恢复怎么做?
- 如果把 MongoDB 快照迁移至 MySQL,会对恢复速率产生什么影响?你如何权衡?
AI Agent 与项目技术栈
- 你了解 AI Agent 和 RAG 技术吗?请谈谈你的理解。
- 你听说过「harness」这个概念吗?
- 在你参与的 AI 面试项目中,Agent 调用的完整流程是怎样的?
- 后台 Agent 设计中,请求是如何经过路由层判断类型并选择执行策略的?
- 工具是如何注入大模型进行循环调用与观察的?
- 你的项目中有哪些权限与容错机制?
- 你们团队使用了什么平台做低代码 Agent 编排?你在其中偏哪个方向?
《参考解析》
项目介绍与贡献边界:面试官连续追问项目来源、团队规模、你负责的部分,核心是确认真实性。回答用「项目一句话定位 + 团队分工 + 我负责的模块 + 一个可验证结果」。被问「简历是不是 AI 生成的」不用慌,说明你怎么组织素材、哪些经历可以展开、哪些数字有据可查即可。算法竞赛和英语水平如实回答,不要包装。
SingleFlight 与 Redis Lua:SingleFlight 解决「同一 key 并发请求只执行一次」,经典场景是缓存击穿、AI 接口按 token 计费、同一会话被重复提交。Lua 的价值是把「查结果、抢锁、写标记」合成一次原子执行,避免检查与写入之间的竞态;SET key owner NX PX ttl 本身原子,但结合「结果已存在直接返回」就需要脚本。分布式锁只能保证互斥,不能把「后来者」变成「结果复用者」,而且锁释放后下一个请求还会重新执行,所以不适合去重场景。
错误分类与超时:把错误分成四类:可重试(网络抖动、上游 5xx、限流 429)、不可重试(参数错误、鉴权失败、schema 不合法)、部分成功(流式输出中断、工具执行了一半)、未知(超时但可能已生效)。策略不同:可重试用指数退避加抖动、限制次数;不可重试直接失败并返回结构化错误;部分成功要能续传或回滚;未知必须先查状态再决定是否重试,写操作一律带幂等键。
脑裂与版本校验:锁过期或网络分区会出现两个 owner。解决用 fencing token:每次选举 token 单调递增,写结果时带 token,存储端只接受不小于当前 token 的写;旧 owner 醒来写结果会被拒绝。再配「租约」——owner 定期续约,续不上就主动放弃;以及「读己之写」校验,写完回读版本号确认。
极端数据量与请求聚合:MongoDB 存的是会话快照与消息,单会话几十 KB 到几百 KB,几百兆是异常膨胀(比如把整段音频或全量工具输出写进去了)。应对是分片存储(消息按序号分段)、大字段外置到对象存储、TTL 归档、压缩。缓存失效时的并发恢复用「按优先级聚合」:同一 sessionId 的恢复请求合并成一个批次,按「最近活跃、是否在面试中、付费用户」排优先级,低优先级排队;聚合窗口内只发一次恢复请求,结果广播给等待者。冲突用快照版本号 + 幂等键解决,恢复操作本身可重放。
线程池隔离与风险:AI 调用是长耗时、易阻塞的,不能和业务请求共用一个池,否则慢请求会把池占满,拖垮整个服务。做法是独立线程池 + 有界队列 + 明确的拒绝策略(快速失败或降级),核心线程数与最大线程数按「下游并发能力」而不是「CPU 核数」设,配合超时中断和心跳续约。风险是隔离后资源利用率下降、队列积压导致延迟上升、以及隔离池本身成为瓶颈;评估时要看下游配额、P95/P99 延迟、队列长度和拒绝率,做容量规划与压测,而不是拍脑袋调参。
三数据源一致性:MySQL 存强一致的业务数据(订单、用户、答题结果),Redis 存热状态与锁,MongoDB 存快照与消息。一致性策略是「以 MySQL 为准,Redis 做缓存与短期状态,MongoDB 做可重放的快照」:写路径先写 MySQL(事务),再更新 Redis(删除缓存或写新值),最后异步写快照;读路径先 Redis,未命中查 MySQL,再回填。跨源不做分布式事务,用「本地消息表 + 重试 + 幂等」保证最终一致;对一致性要求极高的答题状态,读的时候要带版本号,发现快照落后就以 MySQL 为准修复。
去掉一个数据源怎么选:如果只能留两个,去掉 MongoDB——它的快照能力可以用 MySQL 的大字段表或对象存储替代;Redis 也不能轻易去掉,因为锁、热状态、限流都依赖它。去掉 MongoDB 后恢复速率会受 MySQL 行写入与事务开销影响,但可以通过批量写、分区表、只存增量来缓解。反过来把 MongoDB 迁到 MySQL,恢复会变慢(文档转行、索引更多、写入更重),但一致性和运维复杂度更好,这是「性能 vs 一致性/成本」的权衡。
Agent 调用链路与路由:一次调用是「请求进入 → 路由层判断类型(问答、面试追问、评分、工具任务)→ 组装上下文(系统提示词、会话记忆、检索结果、工具清单)→ 调模型(流式)→ 解析输出(文本或工具调用)→ 执行工具并回填 → 循环直到收敛 → 落库与埋点」。路由层可以用规则 + 小模型分类,输出任务类型、置信度和执行策略(直接回答、检索增强、调工具、转人工);置信度低时走保守策略。
工具注入与 harness:工具以 schema 形式注入提示词,模型输出工具名和参数,运行时校验、执行、把结果作为观察追加到上下文,形成 ReAct 循环。harness 可以理解为一层执行框架:统一管理模型调用、工具注册、上下文装配、重试降级、权限与审计、trace 与评测,让业务只描述任务,不重复实现这些横切逻辑。权限上做工具白名单、参数校验、危险操作确认;容错上做超时、重试、熔断、预算和人工兜底。
低代码编排与个人方向:如果团队用 Dify/Coze/百炼这类平台,说明你负责的是节点编排、提示词与工具配置,还是把平台能力接进业务后端;个人方向可以答「偏后端工程化」——把 Agent 做成可观测、可回滚、可评测的服务,而不是只调提示词。