作业帮 AI 应用开发实习一面+二面:排查、一致性与抽奖系统设计
- 轮次
- 一面+二面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
一面
- 自我介绍。
- Linux 命令了解过吗?怎么查看网络连接状态呢?
- 接口特别慢,你一般怎么排查?
- HTTP 状态码了解吗?
- Docker 命令了解过吗?主要是有哪些呢?
- 有没有做过内存泄漏分析?pprof 知道吗?
- 分布式事务了解吗?有哪些保证数据一致性的手段呢?
- 场景题:怎么设计一个抽奖系统?
- 反问。
二面
- 自我介绍。
- 挑一个项目你自己讲一下。
- 追问:你这个设计的取舍是什么?为什么这么做呢?
- 问实习多久。
- 讲一下一个 URL 输入到页面返回的过程。
- 为什么四次挥手要四次而不是三次?
- 你说的是适用于一个通用的情况,那假如我等到服务端再发送完数据再 ack+fin 发回去呢?
- 给你一个需求:打开投屏,VibeCoding。
- 反问。
《参考解析》
接口变慢的排查要按层级切分,先量再改。 从请求入口往外分层:客户端与网络(DNS、TLS、RTT、丢包)→ 网关/Nginx(连接数、超时、上游耗时)→ 应用(线程池排队、锁竞争、GC、连接池耗尽、序列化)→ 依赖(DB 慢查询、缓存命中率、下游 RPC、重试放大)。方法上先看监控与日志的分位耗时(P50/P95/P99,单看均值会被长尾掩盖),再用 traceId 串出链路、找耗时最大的 span;应用内用 jstack 看线程栈、GC 日志看停顿、pprof 看热点;DB 侧看慢查询日志加 EXPLAIN(缺索引、回表、深分页、N+1)。关键是「用一个已知慢请求逐段加分位计时来验证假设」,而不是凭经验直接加缓存。定位到瓶颈后再优化,优先级一般是:加/改对索引、减少返回与循环调用(批量、合并请求)、加缓存并设好失效、异步化与削峰、限流与降级;上线后用同一份度量对比前后。
内存泄漏分析与 pprof 的用法(顺带 Linux、Docker 排查命令)。 Go 的 pprof 走 net/http/pprof 或 runtime/pprof,go tool pprof http://host/debug/pprof/heap,先看 inuse_space 还是 alloc_space 占主导,用 top/list/peek 定位到代码行,两次采样做 -base 差分看净增长,web 出火焰图;配合 GODEBUG=gctrace=1 或 runtime.ReadMemStats 看堆趋势。判断「泄漏还是正常缓存」的标准是:稳定负载下 GC 后堆是否持续单调上升。常见原因:全局 map/切片只增不减、无界缓存、goroutine 泄漏(channel 阻塞、忘记 cancel、Ticker 未 Stop、resp.Body/文件未 Close)、闭包持有大对象;goroutine 泄漏看 goroutine profile 的数量与栈。Linux 常用命令:查网络连接 ss -tulnp(取代 netstat)、lsof -i :端口、netstat -anp、ip addr/ip route,连通性用 curl -v/telnet/tcpdump;资源用 top、free -m、iostat、df -h。Docker:ps -a、logs -f --tail、exec -it、inspect、stats、images、compose up -d、system prune,配合 restart policy 与 healthcheck。
分布式事务:先问一致性要求,再谈方案。 强一致的路子有 2PC/XA(准备 + 提交两阶段,阻塞、性能差、协调者单点,金融核心偶尔用)和 TCC(Try 预留资源、Confirm 确认、Cancel 补偿,业务侵入大但要能扛住高并发)。大多数业务用最终一致:本地消息表(业务与消息同库同事务,投递失败由定时任务重试)、事务消息(RocketMQ 半消息 + 回查)、Saga(长事务拆成一串本地事务,失败按反向补偿)、MQ + 幂等消费,最后一定有对账兜底(定时比对双方流水,补单或冲正)。答题的重点是配套三件事:幂等(唯一业务键、去重表、状态机只允许单向前进)、重试(退避 + 上限 + 死信队列)、可观测(每笔事务有流水号能追到卡在哪一步)。选型的原则是:先明确能不能接受短暂不一致、跨不跨库跨服务、补偿动作是否可逆,再决定上哪套,别一上来就 XA。
一次 URL 到页面的全过程,和四次挥手的追问。 URL 链路:补全与解析 URL → 查缓存(浏览器、hosts、系统 DNS、递归解析器、CDN 调度)→ TCP 三次握手(HTTPS 再加 TLS 握手、证书校验、ALPN 协商协议)→ 发 HTTP 请求(方法、头、Cookie、缓存协商 If-None-Match/If-Modified-Since)→ 服务端处理(网关、负载均衡、应用、缓存与 DB,SSR 时服务端还要再发下游请求)→ 返回响应(状态码、压缩、缓存头)→ 浏览器解析 HTML 构建 DOM/CSSOM → 渲染树 → 布局 → 绘制与合成,JS 可能阻塞解析(defer/async 可缓解)。收尾可以补一句「首屏慢该看哪一段」,把知识落成排查方法。四次挥手:TCP 是全双工,双方各自独立关闭,主动方发 FIN 只表示「我不再发数据」,被动方先回 ACK,然后可能还有数据要发,所以它的 FIN 得等数据发完再单独发,这就是第三、四次。面试官追问的「等到服务端发完数据再 ack+fin 一起发」确实存在——被动方在收到 FIN 时已无数据要发,且延迟确认(delayed ACK)把 ACK 与 FIN 合并,就表现为「三次挥手」;但它取决于应用是否已经 close、有无未发数据以及延迟 ACK 定时器(约 40ms~200ms),不是普遍规律,所以标准答案仍是四次。再加分点:TIME_WAIT 等 2MSL 是为了保证最后 ACK 可重传并让旧报文消散,大量 TIME_WAIT 说明是本端主动关闭、CLOSE_WAIT 堆积说明应用没正确 close。
抽奖系统设计:先问需求,再拆库存、概率、幂等与防刷。 先把问题问清楚:奖池与奖品、概率是固定还是动态、库存与预算、单用户次数上限、预期并发与峰值、能接受多强的一致性、要不要实时排行榜。核心流程是「校验资格 → 扣库存 → 定奖品 → 发奖 → 记账」,难点全在并发下的正确性:库存放 Redis 用 Lua 脚本原子扣减(或 DECR + 预扣),DB 侧以扣减记录为准并通过对账任务修正;幂等靠「用户 + 活动 + 奖品」唯一键或每次请求带抽奖流水号,重复请求直接返回上次结果;概率用权重区间 + 预生成区间表或别名算法(Alias Method)避免浮点误差与 O(n) 扫描;高并发把库存拆成多个桶分散热点,抽奖结果与发奖异步化(写 MQ 削峰)以保证接口响应;防刷做频次限制、设备指纹与黑名单、风控前置校验;降级方案是无库存时返回「谢谢参与」而不是报错,并留开关随时关停活动。最后要说清「为什么这么设计」的取舍:强一致换性能还是最终一致 + 对账,以及热点库存怎么打散。