面灵AI→

字节面经-字节跳动后端开发岗面经-19

轮次
面试合集
时间
2026-10
来源
牛客网

《面试题目》

(面经 01)

  1. 自我介绍。
  2. 哪个项目参与最深?
  3. 介绍第一个实习 Agent 项目和个人工作。
  4. 项目最难或最亮眼的地方是什么?
  5. 方案为何这样定?
  6. 错召回、漏召回解决了吗?
  7. 效果多少?
  8. 第一个实习离职原因
  9. 审查「DB 查工单并缓存」的代码,有什么问题?
  10. 一批优惠券怎么保证每人限领一张,同时不超发?
  11. 审查 Java 转账代码,有什么并发与原子性问题?
  12. 如果给转账加事务,怎么加?
  13. 何时需要分布式事务,有哪些方案?
  14. 有 AI Coding 工具后,程序员还要会写代码吗?
  15. 从零设计云产品智能客服:知识、资源、排障和转人工。
  16. 岛屿数量算法题。
  17. 有哪些主动发现、推动并落地的问题?
  18. 之前的性能问题是怎样发现并推进解决的?

(面经 02)

  1. 请做自我介绍
  2. 目前是否仍在上一段实习
  3. 介绍实习
  4. AI Coding 的前端开发与交付方式
  5. 前端是否主要使用 AI Coding 开发
  6. AI Coding 是否涉及端到端交付
  7. Coding:下一排列
  8. 理解题意并给出初步思路
  9. 尝试编码并解释当前卡点
  10. 解释 A、B、C、D 数组的用途
  11. 你平时代码写得多吗
  12. 你是本科、28 年毕业吗
  13. 仍在上一段实习时为什么想换机会
  14. 课程、学业与实习如何兼顾

《参考解析》

一批优惠券如何保证每人限领一张且不超发

两个约束要分开解决:不超发是并发扣减问题,每人限领一张是幂等 + 唯一性问题,不能靠「先查后插」这种两步操作,因为查和插之间永远有窗口。稳妥的几种做法:① 数据库唯一索引兜底——建 (活动 id, 用户 id) 的唯一索引,同时用 update coupon_stock set remain = remain - 1 where id = ? and remain > 0 的单语句原子扣减(靠行锁 + affected rows 判断成功),插入冲突就回滚扣减;② Redis 原子操作——用 Lua 脚本把「判断用户是否领过(Set 或 bitmap)+ 库存预扣减」写在一个脚本里,成功后再异步落库,落库用唯一索引保证最终不重复;③ 分段库存 / 令牌——预先按 batch 取号,减少热点 key 竞争。要主动讲清一致性代价:Redis 预扣减成功但落库失败时,要么进重试队列补偿,要么给预占加超时释放,绝不能只扣不减。幂等键必须在最外层生成并透传,否则用户连点两次就是两张券。

Java 转账代码的并发与原子性问题

经典坑有五个,审查时按这个顺序看。① 读—改—写非原子:account.setBalance(account.getBalance() - amount) 这种在并发下必然丢更新,正确做法是数据库层的 update ... set balance = balance - ? where id = ? and balance >= ?,把判断和扣减合成一条语句交给行锁;② 锁对象不对:用 synchronized(this) 或对 Integer / String 常量加锁,锁的其实不是同一个对象(Integer 缓存、字符串常量池会导致意外互斥或完全没锁住),要用与账户一一对应且全局唯一的锁对象;③ 加减顺序不一致导致死锁:A 转 B 时先锁 A 再锁 B,B 转 A 时先锁 B 再锁 A,并发下互相等待,解法是按账户 id 排序后顺序加锁,或用一条 SQL 做双向更新;④ 检查与执行分离:先 select 判余额再 update,中间可能被别的线程改掉,必须让判断条件进 where;⑤ 异常吞掉导致事务不回滚:catch 里没重新抛出,@Transactional 默认只对 RuntimeException 回滚,受检异常要显式配 rollbackFor。

转账加事务与分布式事务的适用场景

单库单表加事务很简单:方法上加 @Transactional,把扣款、入账、流水写在一个事务里,保证原子性;要注意锁的顺序(按账户 id 排序避免死锁)、事务粒度尽量小、别在事务里做远程调用和耗时操作(长事务会占着行锁)。只有跨库 / 跨服务才需要分布式事务,常见方案按一致性强度排:2PC / XA(强一致、性能差、协调者单点,生产很少用)、TCC(Try 预留、Confirm 确认、Cancel 取消,业务侵入大但性能好,适合资金类)、本地消息表 / 事务消息(先落本地消息再投递,靠重试达到最终一致,实现简单,最常用)、Saga(一串本地事务加补偿,适合长流程)、最大努力通知(只保证通知到,适合对账类)。判据是:能不能接受最终一致、有没有补偿能力、调用链路有多长;大多数业务用本地消息表就够,资金类才上 TCC。别忘了所有方案都要求幂等 + 对账兜底。

审查「DB 查工单并缓存」的代码

这类代码的问题几乎都在「缓存与数据库的关系」上,按清单挑:① 缓存 key 设计——有没有带租户 / 业务维度,会不会不同用户命中同一条数据;② 缓存穿透——查不到的工单有没有缓存空值或加布隆过滤器,否则恶意 id 每次都打库;③ 更新策略——是不是「先更新数据库再更新缓存」或「先删缓存再更新数据库」,正确姿势是先更新库再删缓存,且删除失败要有重试或 binlog 补偿;④ 过期时间——有没有设 TTL,是不是所有 key 一个时间点集体失效;⑤ 一致性窗口——同一个工单的读写在并发下会不会读到旧值,工单状态这种有状态流转的数据尤其敏感,必要时改成读主库或用版本号校验;⑥ 大 key 与序列化——工单对象是不是整个塞进缓存带了大量冗余字段;⑦ 异常处理——缓存挂了是不是直接让请求失败,正确做法是降级为直连数据库并限流;⑧ 事务边界——缓存操作被包在数据库事务里,事务回滚了缓存却写进去了。

Agent 项目的错召回与漏召回

先分清两类错误的成本:漏召回(该找的没找到)在多数场景是硬伤,用户直接拿不到答案;错召回(找来了不相关的)会让模型被噪声带偏,还可能引发生成幻觉。解决路径:① 建评测集,把「问题 → 正确的文档 / chunk」标出来,分别量化 recall@k 和 precision@k,不然优化全靠体感;② 召回阶段做混合检索——向量找语义相近、BM25 找专有名词和编号,两路取并集再 rerank;③ 切分是漏召回最大的来源,按语义 / 标题层级切、保留重叠、把标题面包屑拼进 chunk,避免答案被切断;④ 错召回靠 rerank(交叉编码器精排)、相似度阈值过滤、元数据过滤(时间、版本、业务线)收敛;⑤ 把用户 query 做改写 / 扩展,或先做一轮「意图 + 关键词抽取」再检索。回答「效果多少」时一定要给可复现的口径:评测集规模、指标定义、优化前后对比。

从零设计云产品智能客服

按「知识、资源、排障、转人工」四条线拆。① 知识:文档(产品手册、FAQ、SOP)走解析 → 切分 → 向量化入库,规范类内容做成结构化知识(资源类型、参数、限制条件),并且要有版本与生效时间,云产品文档变化快,过期知识比没有更糟。② 资源:把用户的资源清单(实例、配置、地域、账单)通过只读接口接进来,回答「我的机器为什么慢」这类问题时必须先知道「他有哪些机器」,做不到实时就把快照缓存并标注时间。③ 排障:把排障流程做成可执行的动作链——先查监控指标和事件、再比对配置基线、再看变更记录,让 Agent 按 SOP 一步步收敛,而不是一次性把上下文全塞给模型猜;每条结论都要带证据(指标截图、日志片段、变更单号)。④ 转人工:设定明确的升级条件(连续两轮未解决、涉及退款或账号权限、用户主动要求、检测到情绪激动),转接时把会话摘要、已排查项、已尝试动作一起带过去,别让用户重讲一遍。另外要有兜底:答不上就直说并引导转人工,不要硬编答案。

岛屿数量(LeetCode 200)

标准解法是网格上的连通块计数,两种都该会。DFS / BFS 洪水填充:遍历每个格子,遇到 '1' 就计数加一,然后从它出发把相连的 '1' 全部标记成 '0'(原地修改省去 visited 数组);DFS 递归写法最短,但网格很大时会爆栈,所以面试里最好主动说「大网格改用显式栈或 BFS 队列」。复杂度 O(m×n),每个格子最多进出一次。并查集:把所有 '1' 各自初始化成一个集合,向四个方向合并相邻的 '1',最后统计不同根的数量;优点是天然支持「动态加陆地」的变体(比如「岛屿数量 II」),代价是要额外维护 parent 数组且合并后不能再简单拆开。追问优化一般落在两点:空间上不额外开 visited 而原地修改;面积超大或稀疏时用方向数组代替四段重复代码,并考虑多方向剪枝(只往右下合并,每个格子最多处理两次)。

下一排列(next permutation)

考的是对「字典序」的理解,三步走,必须能背下来并解释为什么。① 从右往左找第一个降序点 i,满足 nums[i] < nums[i+1]——i 右边是递减序列,已经是它能组成的最大排列,所以必须动 i;找不到说明整个数组降序,说明已是最大排列,直接反转整个数组返回最小排列。② 从右往左找第一个大于 nums[i] 的数 j,交换 i 和 j——因为右边递减,从右往左第一个大于 nums[i] 的就是「刚好比它大的那个」,这样增幅最小。③ 把 i 右边的序列反转——交换后右边仍是递减的,反转一下就变成升序,即为该前缀下的最小排列。整体 O(n) 时间、O(1) 空间。面试官问「A、B、C、D 数组的用途」通常是在引导你把步骤拆成状态变量讲清楚,所以写代码时变量命名和注释要能对上这三步,并能举 [1,3,2]、[3,2,1] 两个边界例子。

AI Coding 之后程序员还要不要会写代码

要答成「能力迁移」而不是表态。AI 把「写」的成本压低了,但没降低「判断」的成本,反而抬高了:代码生成得越快,出错的速度也越快,没有底子的人根本分不清哪段是幻觉 API、哪段是漏了错误分支。真正被削弱的是模板化、可检索的编码劳动(写 CRUD、写样板、拼 SQL);被放大的是需求拆解、接口与数据结构设计、边界与失败路径的思考、审查与验证、故障定位、以及把业务约束翻译成可执行规格的能力。可以补一个实证式的说法:读代码、写测试、调 bug、看监控这些「验证与运维」环节的时间占比会上升,所以「会写代码」的含义变成了「能读懂、能改对、能证明它对」,而不是「能默写 API」。

AI Coding 的前端开发与端到端交付

这题考的是协作流程的成熟度。前端是 AI Coding 落地最顺的场景:组件化、状态驱动、可即时预览,所以常见形态是「描述交互与状态 → 生成组件 → 本地起 dev server 看效果 → 按反馈迭代」,配合设计系统(组件库 + token)约束输出风格,避免每个页面各长一样。端到端交付意味着 AI 的产物不只到「页面能看」,还要覆盖接口联调(mock 到真实 API 的切换)、构建打包、环境变量、部署与回滚。要做到端到端,前提是把交付流水线标准化:一条命令本地起环境、一条命令构建、CI 自动跑 lint 与类型检查、预览环境可回滚;否则 AI 生成的代码越多,越容易在集成环节堆出问题。验收上要守住三条:改动小、可回滚、有测试或可复现的验证步骤——AI 生成的代码必须自己跑过再合入,不能只看 diff 觉得对。

性能问题的主动发现与推进落地

「主动发现」靠的是可观测性 + 常态化的对比,不是等用户投诉。具体做法:① 建立基线——把核心接口的 P95 / P99 延迟、错误率、CPU / 内存 / GC、慢查询数、队列积压做成分位曲线,有基线才谈得上「异常」;② 设阈值告警并定期巡检(周报里的 TopN 慢接口、慢 SQL、增长最快的表),让问题在变成故障之前先暴露;③ 用数据定位而不是猜——链路追踪看是哪个环节耗时,火焰图看热点函数,EXPLAIN 看执行计划,profiler 看 CPU / 内存分配。推进落地的关键是把它变成一件有主、有收益、有验收的事:给出量化的影响面(影响多少请求、多少用户、每月多花多少资源)、给出最小改动的方案与回滚路径、拆成可上线的小步骤、上线后拿同一套指标做前后对比并归档结论。面试里要具体举一个自己做过的例子,说清「怎么发现 → 定位到哪一层 → 改了什么 → 指标变化」,比讲方法论有说服力。

项目「最难 / 最亮眼」怎么答、方案为何这样定

面试官问这两句是在看你有没有真正的判断力,所以回答要落在取舍而不是功能罗列。挑一个能体现你独立决策的点,按「约束 → 方案 → 代价 → 结果」四段讲:先说清当时硬约束是什么(数据量、延迟要求、人力、时间、不能停机),再说你比较了哪两三个方案、为什么排除掉别的(这是拉分的地方,只说选中的方案会显得像背的),然后坦白这个方案的代价和风险,最后给可核验的结果(指标前后对比、上线后回滚过没有、后续又迭代了什么)。「最亮眼」和「最难」可以是同一件事也可以是两件,但不要答成「用了 XX 技术」——技术是手段。被追问「方案为何这样定」时,如果当时的决策依据是信息不足下的赌博,也可以照实说清当时的判断依据,比事后合理化更可信。