字节跳动搜广推引擎架构一面面经
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 请介绍一下实习项目的整体技术链路是什么样的?
- 系统、子系统和最小粒度模块如何划分?
- 项目效果的提升具体做了哪些工作?
- 如果 Agent 必须输出指定格式,例如 JSON,应该怎样设计,保证输出符合要求?
- 如果你的项目有 100000000 个人用,怎么办?
- 了解 HNSW 算法吗?
- 你是计算机专业的吗?
- 进程、协程和线程这三个概念,你大概说一下自己的理解。
- 线程之间共享地址空间,那么哪些内容是线程私有的?
- 假如 TCP 只有两次握手:一个旧连接请求在网络中滞留很久,客户端重新发起连接,正常通信并断开后,旧请求才到达服务端,这时可能发生什么不好的事情?
- 现在有大量用户及订单,要按用户当天的购买数量、金额等计算价值分,评分方法简单但可能变化;需要生成每天的 Top K 日榜并向用户展示。你会怎么设计?
- 手撕:给定一个网格,从左上角走到右下角,每次只能向右或向下移动,返回路径数字之和的最小值。
《参考解析》
- 进程、线程与协程:进程是资源分配的单位,有独立地址空间;线程是调度的单位,共享进程的地址空间,但各自保有独立的栈、寄存器上下文和线程局部存储,这也是「线程私有」那题的答案。协程是用户态自己调度的执行流,切换不陷入内核,适合高并发 I/O 场景,代价是一个线程上的协程如果执行了阻塞调用,会把同线程的其它协程一起卡住。
- TCP 两次握手的缺陷:两次握手意味着服务端收到一个连接请求就直接进入连接状态,没有反过来确认客户端当前是否真的想连。旧连接请求在网络里滞留很久后到达时,服务端会为一个早已放弃的客户端分配资源并维护连接,形成半开连接、白白占用 backlog 与内存;如果带上数据还可能把过期请求当成新业务处理。三次握手让客户端再确认一次,正是为了同步双方序号并排除这类历史报文。
- Top K 日榜的设计:关键是写少读多、且评分规则会变。实时累加部分用 Redis 的 ZSet 或 Hash 按
uid存当天的购买件数与金额,写入时只做自增;计算时先把当天的聚合结果按当前评分公式算成分数,再取 Top K,避免把公式固化在写入路径上,规则变化时重算即可。全量用户每天算一次 Top K 可以用堆或者分桶,达到千万级时按uid哈希分区并行算再归并;榜单结果写进缓存并设置当天有效期,展示侧只读这份结果。要注意的是跨天切换和榜单并发更新的一致性,可以按日期分 key 做版本隔离,切日时原子替换。 - 让 Agent 稳定输出指定格式:分三层做——输出侧用结构化约束(工具调用或受约束的解码)把格式限制在语法层,而不是只在提示词里写「请输出 JSON」;解析侧做校验和重试,解析失败时把错误信息回灌给模型重试有限次数;业务侧对字段值做语义校验(枚举、范围、必填),不合法就判失败而不是带着脏数据往下走。追问通常落在「重试几次、每次都失败怎么办」,答案是降级为人工确认或者返回明确的失败,不要静默兜底。
- HNSW 的取舍:它是分层的可导航小世界图,上层稀疏、负责长距离跳转,下层稠密、负责精细定位,查询时从入口点逐层贪心下降。优点是召回率高、查询是近似对数复杂度、不需要训练;代价是索引常驻内存、构建比 IVF 慢,删除只能标记,参数
M和efSearch直接决定内存占用与召回率的平衡。工程上常和倒排检索配合,先粗排再精排。 - 网格路径最小和:经典的动态规划,
dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]),第一行与第一列只能从一个方向过来需要单独初始化,最后答案是右下角。空间可以优化成一维数组,滚动更新时注意dp[j]在覆盖前代表的是上一行的值、dp[j-1]已经是本行的值。面试官常接着问「如果允许上下左右四个方向移动」——那就变成了最短路问题,需要 Dijkstra 而不是 DP。