面灵AI→

字节跳动后端(中国交易与广告)一面:并发幂等与 TCP 四次挥手

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

《面试题目》

  1. 自我介绍。
  2. 有没有 offer?为什么没转正?
  3. 详细介绍实习项目涉及到的并发和幂等是怎么实现的?
  4. 为什么用并发、幂等?
  5. 一致性怎么保证?
  6. Kafka 怎么保证可用性?
  7. 详细讲一下业务知识问答助手你做了哪些事、怎么做的?
  8. 怎么保证回答质量?有没有遇到回答不好的 case?
  9. 怎么增量更新?
  10. 平时用什么语言多一点?(Go)
  11. 了解 Java 和 C++ 吗?能接受转吗?
  12. Java 并发怎么实现?
  13. C++ 的三大特性?
  14. TCP 四次挥手?为什么不能三次?
  15. 算法:数组中的数能构成的比 n 小的最大数。

《参考解析》

并发与幂等:先讲为什么,再讲怎么做

被问「为什么用并发、幂等」时,先答必要性再答实现。并发的必要性通常来自吞吐与延迟要求:原先是串行调用、总耗时等于各步骤之和,改成并行后取决于最慢的一步;但要注意并发带来的新问题——下游是否扛得住、是否有线程安全与顺序要求、失败后怎么收拾。幂等的必要性来自「重试不可避免」:网络超时、客户端重试、消息至少一次投递、用户重复点击,任何一条都会造成重复执行,所以写操作必须按业务唯一键去重。

实现上按层给:入口层给每次请求生成请求号(客户端生成或服务端发放),服务端用 Redis SET NX 或唯一索引做首次判定;业务层以「订单号/流水号」为幂等键,处理前查结果表、处理后写结果,用数据库唯一索引兜住并发插入;如果走消息队列,消费端也必须按业务键去重,因为至少一次语义下重复投递是常态。并发的实现则要讲清资源的划分:无共享状态优先(不可变对象、按 key 分片),其次才是加锁;用 ConcurrentHashMap、原子类、阻塞队列这些现成组件,比手写锁更安全;真要加锁就明确粒度、超时与顺序,避免嵌套锁。

一致性保证:按场景选强度

一致性不能只答「用事务」,要先说清需要什么强度。单库内用本地事务(注意 @Transactional 的失效场景与隔离级别带来的读现象);跨服务用补偿(TCC、Saga)或本地消息表 / 事务消息(先落本地库再可靠投递,消费端幂等),目标是最终一致;需要强一致又不想引入分布式事务时,可把相关数据放在同一分片、用同一个本地事务解决,这是很多系统的实际做法。业务层面还要配「对账」:定时比对两边的明细,自动修复可自动处理的差异并把其余告警,对账是最终一致系统里真正兜底的一环,答题时提到它很加分。

Kafka 的可用性

按「副本 → ISR → 生产与消费两端」来讲。分区有 leader 与 follower,写只走 leader、读可以走 leader(或按配置),follower 定期拉取保持同步;ISR 是「跟得上 leader 的副本集合」,acks=all 时 leader 要等 ISR 全部确认才算写成功,配合 min.insync.replicas 可以避免「ISR 只剩一个副本时仍然接受写入」导致的隐患;leader 挂掉由 Controller 从 ISR 里选新 leader(如果开启了 unclean 选举可能选到落后的副本,会丢数据,所以生产上通常关闭)。可靠性还要看生产端的重试与幂等(enable.idempotence,配合 max.in.flight.requests 保证分区内有序且不重复)、消费端的位点提交策略(自动提交会丢或重复,手工提交要在处理完成之后再提交)与再平衡策略(CooperativeStickyAssignor 减少停顿)。可用性之外常被追问「顺序」:Kafka 只保证分区内有序,所以业务上要保序就得把同一实体(同一用户、同一订单)路由到同一分区,靠 key 的哈希实现。

业务知识问答助手:质量、badcase 与增量更新

先说清它解决什么问题、用户是谁、原来怎么做,再讲你的部分。质量保证要落到具体机制:检索质量(文档切分与召回策略、混合检索)、生成约束(要求标注引用、限定只根据资料回答、不确定就说不确定)、以及评测(先建一批带标准答案的测试集,按维度打分——召回命中率、答案正确性、引用是否可点击,定期回归)。「有没有回答不好的 case」要举真例并给出归因分类:检索没召回(切分不合理或关键词不匹配)、召回对了但模型答偏(prompt 约束不足或上下文过长被稀释)、资料本身过期或互相矛盾、以及超出知识范围时的幻觉。每一类配一个已做的处理,比罗列 case 更有说服力。

增量更新是这类系统最容易出问题的部分,答题要覆盖:新文档的入库流程(切分、embedding、写向量库与元数据,同事务或可补偿)、更新与删除(版本号或文档 ID 覆盖旧段落,删除要同时删向量与缓存,不做会出现「已经撤下的内容仍被引用」)、缓存与索引的失效(检索结果缓存要按文档版本失效)、以及更新期间的可用性(双写或影子索引切换,避免重建期间检索质量下降)。最后补一句监控:命中率、无答案率、用户追问率、以及引用点击率,这些指标比主观感觉更早暴露问题。

Java 并发、C++ 三大特性与 TCP 四次挥手

Java 并发按「线程 → 同步 → 容器 → 工具」四条讲:创建方式(继承 Thread、实现 Runnable/Callable、线程池),同步手段(synchronized、volatile、Lock 体系、AQS、CAS 与原子类),并发容器(ConcurrentHashMap、CopyOnWriteArrayList、各种阻塞队列),以及组织并发的工具(线程池参数、CompletableFuture、ForkJoin)。C++ 的三大特性是封装、继承、多态;多态要补一句实现机制——虚函数表与动态绑定,以及为什么基类析构函数要声明为虚函数。

TCP 四次挥手的流程:主动方发 FIN 进入 FIN_WAIT_1,被动方回 ACK 进入 CLOSE_WAIT、主动方进入 FIN_WAIT_2,被动方把剩余数据发完后发自己的 FIN,主动方回 ACK 并进入 TIME_WAIT,等 2MSL 后关闭。为什么不能三次:挥手阶段被动方收到 FIN 后可能还有数据要发,所以 ACK 与 FIN 不能合并(ACK 要立刻回、FIN 要等数据发完),这是「半关闭」状态的必要性;对比建连时三次是因为握手阶段双方都没有待发数据,可以把确认与自己的 SYN 合并。另外要能解释 TIME_WAIT 的两个作用(保证最后的 ACK 能到达、让旧连接的延迟报文在网络中消散),以及 CLOSE_WAIT 堆积通常意味着应用没正确关闭连接。

算法:数组中的数能构成的比 n 小的最大数

这题属于构造类问题,核心思路是贪心加回溯:把给定数字排序(可重复取还是不重复取要先问清),从左到右逐位尝试填尽可能大的数字,同时保证剩余位数能填满且整体小于 n。做法通常是先比较位数——位数比 n 少时,直接填能让剩余位全部用最大值,构造出位数相同但数值最大;位数相同时按位贪心:每一位尝试从小到大(或从大到小)的候选数字,选一个使得前缀小于 n 的对应前缀、或前缀相等时后续还能凑出更小的方案;用回溯在无法继续时退位重试。边界要想清楚:没有比 n 小的情况返回什么、能否重复使用同一个数字、前导零是否允许。面试没写完时,正确的收尾是口述完整思路并说明复杂度(与位数和候选数字个数相关),而不是沉默——面试官通常会给思路分。