面灵AI→

百度 Agent Harness 一面面经

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

  1. 客户端向服务端发送链接并返回成功,但服务端说没有收到,如何排查?
  2. 联合索引在 where 条件下的失效情况有哪些?
  3. 同样的代码,执行 1 万个输入需要 200ms,但 10 万个却需要 20s,如何排查?
  4. 操作系统里 read 和 mmap 读大文件有什么区别?
  5. 定时任务批量处理的场景该怎么设计?有哪些典型问题?
  6. 手撕:实现一个 LRU 的变种。
  7. 请把整个 agent 从 query 到返回结果讲一遍,包括中间节点的输入输出、你的工作、降级和排查。
  8. 检索融合和精排的参数是怎么设置的?

《参考解析》

「客户端说成功、服务端说没收到」要按层排查,先定位责任边界。 这类问题不能从「网络不好」开始猜,而是沿链路每一跳找证据。第一段是客户端到网关:客户端拿到的是 TCP 写入成功还是应用层响应成功?如果是 SDK 里 fire-and-forget 的异步发送,所谓成功只是写进了本地缓冲区,进程退出或断网就丢了;对照客户端日志里的请求 ID、时间戳与重试记录,看是否有重试、是否命中了连接池里的坏连接。第二段是网关与负载均衡:查访问日志里有没有这个请求 ID 的记录(没有就是没到服务端,命中在 DNS、证书、防火墙或端口不通),有记录看返回码与被转发到的实例,注意 4xx/5xx 被网关吞掉或者幂等重放导致的重复。第三段是服务端:多实例部署时要按请求 ID 聚合所有实例日志,常见原因是请求打到了另一个实例、日志采样丢了、或者服务端把请求排进队列后异步处理、在真正落库前就崩了。第四段是应用与存储之间:事务没提交、消息没有真正投递、写入了主库但读到从库(主从延迟)、写入被唯一约束静默忽略。定位手段是统一的链路追踪(trace id 贯穿客户端到落库)、客户端与服务端时钟对齐、以及在两端都记录「请求接受」和「业务落库完成」两个明确事件点。最后一定要给出防复发措施,比如发送端确认改为应用层 ACK、加幂等键与重试、把「已接受」和「已处理」在协议上分开。

联合索引失效:本质是「能不能用上索引的最左前缀 + 能不能在索引上完成过滤」。 最左前缀被打破的典型是不从第一列开始查(比如只对第二列做条件)、中间列用了范围条件(>、<、between、like 'x%')导致后续列无法用于定位(只能做索引下推过滤)。索引列上做运算或函数(where date(create_time) = ...、where id + 1 = ?)会让优化器无法用 B+ 树的有序性,要改写成范围条件或建函数索引。隐式类型转换是最隐蔽的一类:列是字符串却用数字比较(或反之),MySQL 会把列转成数字,索引直接失效。like '%x' 前导通配符、or 连接的两侧有一侧没索引、!= 与 not in 的选择性过低、以及查询条件选择性太差(优化器估算全表扫描更便宜)也会导致不走索引。工程上的正确姿势是看执行计划(explain 的 type、key、rows、filtered、Extra 里的 Using index condition / Using filesort),并结合区分度设计索引顺序:等值条件列在前、范围条件列在后,覆盖索引可以避免回表,但索引不是越多越好——每多一个索引就多一份写放大和存储成本,最终要让读写一起算账。

1 万输入 200ms、10 万输入 20s:先判断是复杂度问题还是资源问题。 数据量放大 10 倍、耗时放大 100 倍,正好是 O(n²) 的形状,所以第一件事是把代码里的嵌套循环、每行一次的数据库或 HTTP 调用、List.contains 这类线性查找找出来,改成哈希索引、批量接口、或排序后归并,这类修改通常一步就能把曲线拉回线性。第二件事是确认有没有「隐式的二次方」:循环里拼字符串、循环里创建大对象导致 GC 频繁、循环里多次序列化、日志把每条数据都打出来(IO 放大),以及 ORM 的 N+1 查询——这些在 1 万条时看不出来,10 万条就被放大成灾难。第三件事是排除阈值效应:连接池被打满、队列满后开始阻塞、内存达到 GC 阈值后进入 full GC 循环、或者缓存命中率骤降——这些会让曲线在某个点突然陡起来,用监控(GC 日志、连接池活跃数、锁等待、CPU 与内存曲线)区分。排查顺序建议是「先量再猜」:分别在 1 万、3 万、10 万三档打点计时,做一次火焰图或采样剖析(async-profiler、perf、Chrome DevTools 的 profiler 都行),把耗时归到具体方法上;再用排除法二分(注释掉一半逻辑看耗时变化)确认主因。最后要补一句:修完要重新测三档数据确认曲线形状变了,而不是只看 10 万那次变快了。

read 与 mmap 的取舍:一次拷贝换缺页中断。 read 的语义是把数据从内核页缓存拷贝到用户态缓冲区,每次都要一次内存拷贝,但接口简单、可控——你可以自己决定读多少、缓冲区多大,适合顺序读、小文件、或者需要边读边处理的流式场景。mmap 是把文件页映射进用户地址空间,读文件时触发缺页中断由内核把页装进来,用户态直接访问,省掉了「内核到用户」的那次拷贝,随机访问大文件、需要多进程共享同一份数据时优势明显(共享库、数据库的存储引擎都在用)。它的代价也很实在:建立映射、页表项和维护 VMA 有固定开销,小文件反而是负收益;缺页中断与页表操作的成本在大页/小页、随机/顺序不同访问模式下差异很大(顺序读大文件用 madvise 预读或直接 read 往往更快);文件被截断或写回失败时会触发 SIGBUS,比 read 的返回错误难处理;映射过大还会占用虚拟地址空间并影响内存统计。所以结论不是谁替代谁,而是按访问模式选:顺序流式读取用 read(必要时配合直接 IO 绕过页缓存),随机访问大文件与需要共享内存用 mmap,并且要说清楚「省掉的是拷贝,付出的是页管理」。

Agent 从 query 到返回:可观测、降级和检索参数要讲成一套工程机制。 面试官要的不是流程图而是「你怎么知道它在哪一步坏了」。链路上每个节点(查询改写、工具选择、检索、重排、生成、后处理)都要记录输入摘要、输出摘要、耗时、token 数、命中缓存与否、以及失败原因,用 trace id 串起来,这样线上问题能一眼定位到节点。降级要分级:检索超时就用缓存结果或只走向量召回的一路,重排服务挂了就用原始排序并降权,模型超时/报错就切备用模型或退化成模板化回答,工具连续失败就熔断并直接告诉用户「这一步没做成」——关键是降级后的结果要打标记,别让用户以为这是完整答案。检索融合与精排的参数设置则要按目标调:融合层如果是多路召回加权求和,权重按各路的离线指标与线上点击反馈调,用 RRF 这类基于排名的方法可以避免不同路分数尺度不可比的问题;候选数(各路取多少、融合后保留多少)要在召回率和延迟之间取平衡,通常先多召回、再靠精排把列表压到十到几十条;精排的 top-k 决定进入生成上下文的量,k 太大引入噪声、成本与延迟上升,k 太小又漏信息,实践上按评测集上「答案覆盖率」随 k 变化的拐点来定,并把参数做成可配置、按场景分档,而不是写死常量。最后一点加分项:这些参数都要有离线评测集加线上 A/B 两套验证,否则调参就是盲猜。