字节 AI 全栈一面:Agent 资金流转、幂等与消息队列 ACK
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 自我介绍。
- 讲一下第一个实习 Agent 项目的全流程,以及你负责的部分。
- 场景题:Agent 里的资金流转怎么设计?
- 检索增强(RAG)链路怎么搭的?
- 意图识别怎么做?
- Agent 各节点之间怎么做校验?
- 工具调用有哪些约束?
- 链路里涉及的算法参数如何设置?评测标准是什么?
- 幻觉问题如何治理?
- 面对用户的鲁棒性行为(乱输入、越权请求)怎么办?
- 幂等键是怎么设计的?
- 并发情况怎么解决?重复扣款和网络抖动如何解决?
- Agent 循环次数、工具调用次数、token 成本怎么控制?
- 怎么用 JSON 参数校验和 schema 约束工具入参?
- 知识库如何保证时效性?
- 什么情况下走人工兜底?
- 后端项目里 Redis 限流怎么做的?
- 如何保证扣费和任务处理的平衡?
- Lua 脚本在项目里具体做什么?
- Redis 的 key 是怎么设计的?
- 场景题:多级缓存、缓存一致性、并发冲突。
- 消息队列:消费者消费完成了但没有返回 ack,会怎么处理?
- 超时重传为什么可能让下游更慢?怎么解决?
- 进程和线程有什么区别,分别适合什么任务?
- 进程间通信方式有哪些?
- p99 延迟上涨、平均延迟不变,怎么排查?
- 手撕:数组中出现概率最大的前 k 个值。
《参考解析》
Agent 里的「资金流转」本质是把一次有副作用的操作做成可重放的事务。一次下单/扣费要拆成「生成业务单号 → 调用工具 → 记录结果」三段,幂等键用「用户 + 业务类型 + 业务唯一标识」拼出来,写进 Redis 或数据库唯一索引;同一个键重复进来直接返回第一次的结果,而不是再扣一次。网络抖动导致的重复请求、模型重试导致的重复工具调用,都靠这个键收敛。关键是要把「执行前落状态、执行后落结果」分开记,否则进程挂在中途就无从判断到底扣没扣。
消息队列「消费完成但没回 ack」不同中间件行为不一样。RabbitMQ 靠连接是否存活判断,连接断了消息重投,连接还在就继续等;RocketMQ 靠返回状态判断,没返回成功就重试,重试到上限进死信;Kafka 靠提交 offset 标记,没提交下次从头再来。共同点是消息不丢、但大概率重复消费,所以消费端必须幂等。业务上要配死信队列 + 告警,别让一条毒消息无限重投把队列堵死。
超时重传放大流量,是 p99 恶化的常见原因。下游变慢时,客户端超时重试会让同一批请求翻倍打到下游,下游更慢,形成正反馈。做法是:只对幂等接口重试、按错误类型区分(业务错误不重试,只有超时和连接失败重试)、指数退避加随机抖动避免重试风暴、设置重试预算和熔断,必要时在入口限流保住数据库。
多级缓存的核心是「每一层的失效策略」而不是多加一层。本地缓存扛热点、Redis 扛共享、数据库兜底,读路径先本地再 Redis 再回源;写路径先更数据库再删缓存(或延迟双删),并用版本号/逻辑过期避免并发回填把旧值写回去。缓存击穿用互斥锁或单飞,穿透用布隆过滤器或空值缓存,雪崩给过期时间加随机抖动。
p99 涨、均值不涨,说明是长尾而不是整体变慢。排查顺序是:先分实例、分接口、分依赖看 p99 落在哪一段,再看 GC 停顿、连接池排队、慢 SQL、锁等待、下游超时重试;均值被大量快请求稀释,长尾往往来自少数请求的排队或重试,所以要看分位数的分布而不是平均值。