八股文 / 2026 上半年

2026 年上半年后端面经八股整理

本文汇总 2026 年上半年(1–6 月)后端与 AI 工程面经,共 112 道技术问题,按技术类型归档。算法与手撕题单另列,不计入正文题数。

按来源月份划分上下半年;同一题跨半年出现时,按已标注的最早月份归档并保留完整来源。未标日期的旧题沿用原月份篇或原批次归属,其中原六月篇题目归入上半年。

Java 与语言基础

1. Java 的基本数据类型有哪些?各占多少空间?

来源:神州信息后端开发岗,3 月 18 日

Java 有 8 种基本类型:byte 1 字节、short 2 字节、int 4 字节、long 8 字节、float 4 字节、double 8 字节、char 2 字节、boolean 的存储大小由 JVM 实现和使用场景决定,语言规范没有固定为 1 字节。包装类型是对象,会额外产生对象头、对齐和引用开销。


2. 重载和重写有什么区别?

来源:神州信息后端开发岗,3 月 18 日

重载发生在同一个类中,方法名相同但参数列表不同,编译期根据静态类型选择;仅改变返回值不能构成重载。重写发生在父子类之间,子类提供相同签名的方法实现,运行期通过动态分派选择实际对象的方法。重写不能缩小访问权限,也不能抛出比父类更宽的受检异常。


3. 类和接口怎么选?

来源:神州信息后端开发岗,3 月 18 日

接口表达能力契约,适合隔离实现、支持多实现和依赖倒置;抽象类适合共享稳定状态、受保护方法和模板流程。工程上通常优先面向接口并使用组合,只有存在稳定的 is-a 关系且确实需要复用状态或模板方法时才使用继承。


4. C++ 多态是如何实现的?

来源:恒生 C++ 一面,4 月 24 日

运行时多态依赖虚函数。含虚函数的对象通常保存虚表指针,虚表记录对应动态类型的函数地址;通过基类指针或引用调用虚函数时,运行期查表完成动态分派。构造和析构阶段的动态类型受限,基类析构函数应声明为虚函数,否则通过基类指针删除派生对象会产生未定义行为。


5. 智能指针怎么选?

来源:恒生 C++ 一面,4 月 24 日

unique_ptr 表达独占所有权,开销最小且支持移动;shared_ptr 通过控制块引用计数表达共享所有权,但原子计数和额外分配有成本;weak_ptr 不增加强引用计数,用于观察对象并打破环。默认先选 unique_ptr,只有生命周期确实共享时才用 shared_ptr,不要把智能指针当成可以忽略所有权设计的理由。


6. 重载和重写的区别是什么?

来源:线下 Java 小厂面经,5 月 14 日

重载是同一作用域中方法名相同、参数列表不同,调用目标在编译期确定;重写是子类重新实现父类可覆盖的方法,运行期按对象实际类型动态分派。重写要求签名兼容、访问权限不能更严格,返回类型只能相同或协变。静态方法是隐藏,不参与实例方法的动态分派。


7. HashMap 的底层结构和查找过程是什么?

来源:线下 Java 小厂面经,5 月 14 日

JDK 8 的 HashMap 由数组、链表和红黑树组成。先对 key 的 hash 做扰动,再用 (n - 1) & hash 定位桶;桶内比较 hash 和 equals。冲突过多且数组容量达到阈值时链表树化,扩容时容量翻倍,节点根据新增高位落在原位置或 原位置 + oldCap


8. HashMap 为什么线程不安全?

来源:线下 Java 小厂面经,5 月 14 日

并发写入时,桶插入、覆盖、size 更新和扩容迁移都不是一个原子事务,可能产生覆盖、丢失或读到中间状态。JDK 8 避免了旧版头插扩容易成环的问题,但没有让 HashMap 变成线程安全。并发读写应使用 ConcurrentHashMap,复合操作则使用 computemerge 等原子 API。


JVM 与并发

9. ThreadLocal 如何实现线程隔离?为什么会内存泄漏?

来源:快手 Java 后端一面,3 月 24 日

每个 Thread 持有自己的 ThreadLocalMapThreadLocal 实例作为 key,业务值作为 value,因此不同线程访问的是不同槽位。key 是弱引用,ThreadLocal 没有外部强引用后可能被回收;value 仍被线程强引用,线程池中的线程又长期存活,就会形成 key 为 null 的陈旧条目。正确做法是在 finally 中调用 remove(),同时避免在线程本地变量中保存大对象。


10. synchronizedReentrantLock 怎么选?

来源:快手 Java 后端一面,3 月 24 日

两者都提供互斥和可见性。普通临界区优先用 synchronized,语义简单且退出代码块会自动释放;需要可中断获取、超时、公平锁、多个 Condition 或非阻塞尝试时用 ReentrantLock。后者必须在 finally 中解锁。底层上,synchronized 依赖对象监视器和 JVM 优化,ReentrantLock 基于 AQS 状态与等待队列。


11. JVM 为什么需要垃圾回收?如何判断对象可回收?

来源:北京四维图新 Java 岗,3 月 6 日

GC 自动回收不可达对象,降低手工释放造成的泄漏、重复释放和悬空指针风险。HotSpot 主要从线程栈引用、静态字段、JNI 引用等 GC Roots 做可达性分析,不可达对象才成为候选。线上回答还应说明:GC 只能处理“不可达”,无界缓存或监听器持有对象仍然可达时,GC 无法替业务修复泄漏。


12. 互斥锁和读写锁分别适合什么场景?

来源:恒生 C++ 一面,4 月 24 日

互斥锁同一时刻只允许一个线程进入临界区,适合读写比例接近或临界区很短的场景。读写锁允许多个读者并发、写者独占,只有在读多写少且临界区足够大时才可能获益;写竞争、锁升级和饥饿会增加复杂度。选型应以基准测试和锁等待指标为准,而不是看到“读多”就机械替换。


13. JVM 如何完成垃圾回收?

来源:线下 Java 小厂面经,5 月 14 日

先从 GC Roots 做可达性分析,标记存活对象,再根据收集器采用复制、标记清除或标记整理。分代假说让新生代频繁回收短命对象,老年代处理长期存活对象;跨代引用通过记忆集减少全堆扫描。生产回答还应包含 GC 日志、分配速率、晋升、停顿和引用链分析,而不是只背收集器名称。


14. ThreadLocal 的正确使用方式是什么?

来源:京东 AI 应用开发岗,5 月 7 日

它适合保存一次线程执行链内的上下文,如 trace ID 或不可跨线程共享的会话状态。线程池会复用线程,所以必须在 finallyremove();异步任务切线程时,值不会自动正确传播,盲目使用可继承 ThreadLocal 还可能造成上下文污染。更清晰的方式是在边界显式传递上下文。


操作系统与 Linux

15. 进程、线程和协程有什么区别?

来源:腾讯后台一面,3 月 27 日

进程拥有独立地址空间,隔离强但创建和通信成本高;线程共享进程资源,内核调度,切换成本低于进程但要处理共享数据同步;协程由用户态运行时调度,适合大量 IO 等待任务。协程不会自动让 CPU 密集代码并行,仍受底层线程数量、语言运行时和 CPU 核数约束。


16. 为什么常说 CPython 多线程不能并行执行 Python 字节码?

来源:腾讯后台一面,3 月 27 日

CPython 的 GIL 让同一进程同一时刻通常只有一个线程执行 Python 字节码,简化了解释器对象内存管理。IO 阻塞和部分 C 扩展会释放 GIL,所以多线程仍适合 IO 密集任务;CPU 密集任务通常用多进程、释放 GIL 的扩展或支持自由线程的运行模式。不要把 GIL 误解成操作系统只能调度一个线程。


17. 线程、进程和协程的边界是什么?

来源:恒生 C++ 一面、蚂蚁效能研发一面,4 月 13–24 日

进程提供地址空间隔离,线程共享进程资源并由内核调度,协程则由用户态运行时在一个或多个线程上调度。进程崩溃隔离最好但 IPC 成本高;线程通信方便但需要同步;协程适合大量 IO 并发,但阻塞调用若没有被运行时接管,仍可能卡住承载线程。


18. 常用 Linux 排查命令怎么组织?

来源:美团 Java 后端一面,5 月 6 日

先看系统整体:uptimetopvmstat;再看进程和线程:pspidstat;查内存和磁盘:freeiostatdfdu;查网络和端口:sslsof;查日志:journalctltailrg。后台运行可使用 nohup command >app.log 2>&1 &,生产服务更应交给 systemd 或容器编排管理。


19. 进程和线程的区别是什么?

来源:线下 Java 小厂面经,5 月 14 日

进程是资源隔离和保护的基本单位,拥有独立虚拟地址空间;线程是 CPU 调度的执行单元,共享进程的代码、堆和文件描述符,但有自己的栈、寄存器和程序计数器。线程通信便宜但共享状态容易竞争,进程隔离强但 IPC 和切换成本更高。


20. epoll 和 select/poll 的区别?

来源:杭州滴滴 CTO 面;字节中国交易与广告 AI 应用开发一面

维度 select/poll epoll
数据结构 线性扫描 fd 集合 红黑树 + 就绪链表
fd 上限 select 1024,poll 无硬限 无限制(系统内存为限)
通知方式 每次调用都遍历所有 fd 回调机制,只返回就绪的 fd
复杂度 O(n) O(1)(就绪事件)
集合维护 每次调用传入并扫描关注集合 内核持久维护关注集合,就绪事件单独返回

epoll 适合大量连接但活跃连接少的场景(典型:Web Server)。它仍需要通过系统调用把就绪事件返回用户态,并不是“mmap 零拷贝”;常说的红黑树 + 就绪链表也是 Linux 实现细节,不应背成 POSIX 接口保证。


21. 进程间通信(IPC)有哪些方式?

来源:华为暑期一面

方式 特点 适用场景
管道(Pipe) 单向、有亲缘关系 父子进程简单通信
命名管道(FIFO) 无亲缘关系也可用 不相关进程间
消息队列 有格式的消息、异步 结构化数据传递
共享内存 最快、需要同步机制 大数据量高频通信
信号量 同步/互斥 控制共享资源访问
Socket 跨机器、双向 网络通信、分布式系统

22. 为什么操作系统要区分用户态和内核态?什么时候会切换?

来源:虾皮 Data Infra 大数据平台研发一面,6 月 7 日;字节 AML / 火山方舟 AI Infra 一面,8 月 26 日

区分两种权限级别是为了最小权限和故障隔离:普通应用不能任意改页表、控制设备或读写内核地址,否则一个 Bug 就能破坏全机;内核集中管理 CPU、内存、文件和网络,通过稳定的系统调用接口提供受控服务。硬件特权级、页表权限和内核入口共同执行边界,单靠语言级检查不够。

系统调用、异常和硬件中断会进入内核态,例如 read、缺页异常、时钟或网卡中断;处理完成后可返回用户态。进入内核态需要切换特权级和栈、保存必要现场并执行安全检查,因此高频小系统调用会有可观测开销。模式切换不等于进程/线程上下文切换:同一线程执行系统调用可以只发生用户态/内核态切换,也可能因阻塞进一步触发调度。


23. 常见进程调度算法有哪些?

来源:虾皮 Data Infra 大数据平台研发一面,6 月 7 日

  • 先来先服务:实现简单,但长任务会阻塞短任务。
  • 最短作业优先:平均等待时间低,但需要估计运行时间,长任务可能饥饿。
  • 时间片轮转:响应公平,时间片过小会增加切换,过大则退化为先来先服务。
  • 优先级与多级反馈队列:兼顾交互任务和吞吐,需要老化机制防止低优先级饥饿。

Linux 的 CFS 通过虚拟运行时间近似公平分配 CPU;实时任务另有调度类。面试回答要区分教科书算法和实际内核实现。


24. 僵尸进程和孤儿进程分别是什么?

来源:虾皮 Data Infra 大数据平台研发一面,6 月 7 日

子进程退出后,父进程尚未调用 wait 回收退出状态时形成僵尸进程,它不再运行,但占用 PID 和进程表项。父进程先退出时,仍运行的子进程成为孤儿进程,会被 init/systemd 等收养并最终回收。大量僵尸进程应修复父进程的信号处理和 wait 逻辑,不能靠杀死已经退出的子进程解决。


25. Java 进程卡住如何排查?

来源:虾皮 Data Infra 大数据平台研发一面,6 月 7 日

先确认是 CPU 高、无响应、锁等待还是下游 IO 卡住。用 top -Hp/pidstat 找热点线程,jstackjcmd Thread.print 连续采样线程栈,观察死锁、阻塞点和线程池队列;再结合 GC 日志、堆/直接内存、连接池、网络超时和下游监控。单次线程快照可能误判,至少对比多次采样并关联请求 trace。


网络、HTTP 与 RPC

26. OSI 七层模型和 TCP/IP 模型如何对应?

来源:网新软件后端二面,4 月 14 日

OSI 从下到上是物理、数据链路、网络、传输、会话、表示、应用;工程上常用 TCP/IP 四层:网络接口层、网际层、传输层、应用层。以 HTTPS 为例,以太网/Wi-Fi 承载链路帧,IP 负责路由,TCP 提供可靠字节流,TLS 负责加密认证,HTTP 定义应用语义。分层是职责抽象,不代表每个请求都由七个独立程序顺序处理。


27. SSE 和普通 Token 流式输出有什么区别?断线怎么恢复?

来源:Agent 小厂 Java 岗,4 月 9 日

SSE 是基于 HTTP 的服务端单向事件流;Token 流是业务内容粒度,可以承载在 SSE、WebSocket 或其他协议上。步骤级 SSE 应给每个事件分配递增 ID,并持久化任务状态;客户端重连时携带 Last-Event-ID,服务端重放缺失事件或返回当前快照。只保持内存连接不能跨实例、跨重启恢复。


28. 从输入 URL 到收到响应经历了什么?

来源:美团 Java 后端一面,5 月 6 日

客户端解析 URL 并检查缓存,经过 DNS 得到地址,再进行路由和 ARP/NDP,建立 TCP;HTTPS 还要完成 TLS 握手和证书校验。随后发送 HTTP 请求,经负载均衡、应用和存储处理后返回,浏览器再解析渲染。回答时要把 DNS、连接复用、TLS、网关和服务端处理串成因果链。


29. TLS 如何保证机密性、完整性和身份认证?

来源:美团 Java 后端一面,5 月 6 日

客户端验证服务端证书链和域名,双方通过密钥交换协商会话密钥,后续用对称加密保护数据,并用 AEAD 等机制校验完整性。现代 TLS 通常使用 ECDHE 提供前向保密。证书不是“用来加密所有业务数据”,它主要绑定身份和公钥;真正的大量数据使用协商出的对称密钥传输。


30. TIME_WAIT 为什么存在?

来源:美团 Java 后端一面,5 月 6 日

主动关闭方等待 2MSL,一是让最后 ACK 丢失后仍能响应对端重传 FIN,二是让旧连接的延迟报文消失,避免污染相同四元组的新连接。数量过多通常应先排查短连接、连接池和 keep-alive,而不是直接粗暴缩短内核参数。


31. RPC 和 HTTP 是什么关系?

来源:京东 Java 岗,5 月 7 日

HTTP 是应用层通信协议,RPC 是“像调用本地函数一样调用远端服务”的编程抽象。RPC 可以基于 HTTP/2,例如 gRPC,也可以使用私有协议;REST over HTTP 则更强调资源语义和跨语言可理解性。内部高性能强契约调用常用 RPC,对外开放和浏览器生态常用 HTTP API,但最终应按兼容性、治理和调试需求选型。


MySQL 与数据存储

32. 慢 SQL 的标准排查流程是什么?

来源:神州信息后端开发岗,3 月 18 日

先从监控和慢查询日志确认耗时分布、调用量和锁等待,再对代表性 SQL 使用 EXPLAINEXPLAIN ANALYZE 查看访问类型、实际行数、索引和临时表/排序。随后检查索引选择、回表次数、数据分布、隐式转换、深分页和事务持锁。优化后必须用相同数据量压测并观察 P95/P99,不能只看单次执行时间。


33. 联合索引为什么遵循最左前缀?

来源:快手 Java 后端一面,3 月 24 日

B+ 树按联合索引定义的列顺序排序,例如 (a,b,c) 先按 a,再按 b,最后按 c。因此只查 b 时,全局上并没有按 b 排序,通常不能完成有效定位。a=? AND b>? 可以利用 a,b 定位范围,但范围列之后的列通常不能继续缩小扫描边界,是否可下推过滤要看执行计划。


34. ACID 分别由什么机制支撑?

来源:网新软件后端二面,4 月 14 日

原子性依赖 undo log 回滚;隔离性依赖锁和 MVCC;持久性依赖 redo log 的 WAL 和刷盘策略;一致性是事务机制、数据库约束和业务规则共同作用的结果。回答时要避免说“redo log 保证原子性”这类错位,也要说明不同刷盘参数会在性能与故障丢失窗口之间取舍。


35. MyBatis 的 #{}${} 有什么区别?

来源:网新软件后端二面,4 月 14 日

#{} 使用预编译参数占位,驱动负责类型转换,通常能避免 SQL 注入;${} 是文本替换,适合无法参数化的表名、列名或排序方向,但必须使用白名单映射,不能直接拼用户输入。动态 SQL 的重点不是“能拼出来”,而是参数安全、可读性和执行计划稳定性。


36. MySQL 为什么使用 B+ 树索引?

来源:恒生 C++ 一面,4 月 24 日

B+ 树内部节点只存键和子指针,同一页能容纳更多分支,树高更低;完整记录集中在叶子节点,叶子又按序相连,点查、范围扫描和排序都较稳定。InnoDB 聚簇索引叶子存整行,二级索引叶子存主键,因此查询非覆盖列通常需要回表。


37. PostgreSQL JSONB、Redis 和关系表怎么选?

来源:Agent 小厂 Java 岗,4 月 9 日

JSONB 适合结构变化较快但仍需要事务、持久化和字段索引的状态;Redis 适合低延迟临时状态、TTL 和队列,但内存成本高且持久化语义需明确;关系表适合字段稳定、约束和关联查询强的核心数据。选型取决于查询模式、生命周期、一致性和恢复目标,而不是只比较单次读写速度。


38. MySQL 主从复制的基本流程是什么?

来源:美团 Java 后端一面,5 月 6 日

主库提交事务并写 binlog,从库 IO 线程拉取事件写 relay log,SQL 线程或并行复制线程回放。异步复制吞吐好但主库故障时可能丢未同步数据;半同步只保证至少一个从库收到日志,不等于已经执行。读写分离还要处理复制延迟和读己之写问题。


Redis 与缓存

39. Redis 为什么快?

来源:快手 Java 后端一面,3 月 24 日

核心原因是数据主要在内存中、命令执行路径短、数据结构针对场景优化,并用 IO 多路复用处理大量连接。经典命令执行采用单线程,避免共享数据的锁竞争;Redis 6 之后可用多线程处理网络读写,但命令执行仍以串行为主。大 Key、慢命令、阻塞式删除和持久化抖动仍可能拖慢整个实例。


40. 缓存穿透是什么?怎么治理?

来源:腾讯后台一面,3 月 27 日

缓存穿透是持续查询不存在的数据,请求每次绕过缓存落到数据库。治理顺序通常是参数校验和鉴权、对空结果设置较短缓存、用布隆过滤器挡住确定不存在的 key,再配合限流和异常流量识别。布隆过滤器存在误判且不擅长删除,不能替代数据库校验。


41. Redis 哨兵能解决什么问题?

来源:神州信息后端开发岗,3 月 18 日

Sentinel 负责监控主从节点、通过多数 Sentinel 完成客观下线判断、选举领导者并执行主从切换,同时向客户端提供新主节点地址。它解决高可用,不解决水平分片;容量和写吞吐需要 Redis Cluster 或业务分片。异步复制还意味着故障切换时可能丢失尚未同步的数据。


42. 缓存和数据库如何保证最终一致?

来源:网新软件后端二面,4 月 14 日

常用 Cache Aside:读时先查缓存,未命中查数据库并回填;写时先提交数据库,再删除缓存。删除失败要进入可靠重试或通过 binlog/CDC 异步补偿。高并发热点还要防止旧请求在写后回填旧值,可结合版本号、短 TTL 或串行化热点更新。延迟双删不是万能公式,延迟量必须有业务依据。


43. Redis 为什么快,但仍然可能成为瓶颈?

来源:美团后端一面,4 月 23 日

Redis 依靠内存访问、高效结构、IO 多路复用和短命令路径获得低延迟。瓶颈常来自大 Key、KEYS 或复杂集合运算等慢命令、热 Key、网络带宽、持久化 fork/写盘以及内存碎片。排查应看命令延迟、慢日志、Key 分布、CPU、网络和 fork 耗时,不能只回答“单线程所以快”。


44. Redis 为什么快?

来源:小红书 PE 后端一面 / 字节 AI 全栈二面

  1. 纯内存操作:数据全在内存,无磁盘 IO
  2. 单线程模型:无锁竞争、无上下文切换(6.0 后 IO 多线程,命令执行仍单线程)
  3. IO 多路复用:epoll 监听多个连接,非阻塞
  4. 高效数据结构:ziplist、skiplist、intset 等针对小数据量优化
  5. 简单协议:RESP 协议解析开销极低

45. Redis 大 Key 怎么解决?

来源:小红书 PE 后端一面

发现:redis-cli –bigkeys 扫描 / memory usage 命令 / 监控慢日志

解决

  • String 超大 → 拆分为多个小 key,或用 Hash 分片存储
  • Hash/Set 元素过多 → 按业务维度拆分(如按日期、用户分片)
  • List 过长 → 按时间窗口拆分为多个 list
  • 删除大 Key → 用 UNLINK(异步删除),避免 DEL 阻塞主线程

46. 缓存穿透、击穿、雪崩分别是什么?怎么解决?

来源:嘉立创一面 / 快手一面

问题 现象 解决方案
穿透 查不存在的数据,缓存和 DB 都 miss 布隆过滤器 / 缓存空值(短 TTL)
击穿 热点 key 过期瞬间大量请求打到 DB 互斥锁重建 / 逻辑过期(不设 TTL,后台更新)
雪崩 大量 key 同时过期 / 缓存宕机 TTL 加随机值 / 多级缓存 / 限流降级

47. Redis 过期删除策略和内存淘汰策略?

来源:嘉立创一面;字节 AI Agent 研发一面

过期删除

  • 惰性删除:访问时检查是否过期(省 CPU,但可能占内存)
  • 定期删除:每 100ms 随机抽取一批 key 检查过期(折中方案)

内存淘汰(达到 maxmemory 时)

  • noeviction:不淘汰,写入报错
  • allkeys-lru:全局 LRU
  • volatile-lru:仅对设了过期时间的 key 做 LRU
  • allkeys-random / volatile-random
  • volatile-ttl:淘汰 TTL 最短的
  • allkeys-lfu / volatile-lfu(Redis 4.0+)

48. Redis rehash 过程?

来源:PDD 服务端一面

  • Redis 使用两个哈希表(ht[0] 和 ht[1])
  • 渐进式 rehash:不是一次性迁移,而是每次 CRUD 操作时顺带迁移一个桶
  • 扩容触发条件:负载因子 > 1(无 BGSAVE)或 > 5(有 BGSAVE)
  • rehash 期间:新增写 ht[1],查询先 ht[0] 再 ht[1],直到迁移完成

49. 本地缓存未命中但 Redis 命中,怎么处理?

来源:PDD 服务端一面

标准缓存回填流程:

  1. 查本地缓存(Caffeine/Guava)→ miss
  2. 查 Redis → hit → 返回数据 + 异步回填本地缓存
  3. 设置本地缓存较短 TTL(如 30s),Redis 较长 TTL(如 30min)
  4. 注意:本地缓存容量有限,要配淘汰策略(LRU/LFU)

多级缓存一致性:更新 DB 后先删 Redis,再广播通知各节点清本地缓存(pub/sub 或消息队列)。


50. 缓存和数据库一致性策略有哪些?

来源:华为暑期一面 / 携程二面;字节 AI Agent 研发一面

策略 流程 优缺点
Cache Aside 读:先缓存→miss 查 DB→回填;写:先更新 DB→删缓存 最常用,但有短暂不一致窗口
Read/Write Through 缓存代理所有 DB 操作 一致性好,但缓存层复杂
Write Behind 异步批量刷 DB 性能最高,但可能丢数据

延迟双删:先删缓存→更新 DB→延迟 N ms 再删一次缓存。解决“更新 DB 前有读请求回填了旧值”的问题。N 通常 = 从库同步延迟 + 业务读耗时。


51. Redis 海量数据去重统计——HyperLogLog 和 BitMap?

来源:杭州滴滴 CTO 面

场景:统计每日 UV(独立访客数)

方案 原理 内存 精确度
Set 存所有 user_id 大(1亿用户 ~1.6GB) 精确
BitMap user_id 作为 bit 偏移量 固定(1亿用户 ~12MB) 精确(需要 id 是数字且连续)
HyperLogLog 概率算法,估算基数 极小(12KB 固定) 误差 ~0.81%

追问:id 比位图长怎么办?

  • 用 hash 函数将 id 映射到固定范围的整数
  • 或者用布隆过滤器做近似去重
  • 如果需要精确:分片 BitMap(按 id 前缀路由到不同 BitMap)

分布式与工程设计

52. 分布式事务有哪些常见方案?

来源:北京四维图新 Java 岗,3 月 6 日

强一致且参与者少时可考虑 XA/2PC,但锁持有时间长、协调者和可用性成本高。业务系统更常用 TCC、Saga 或本地事务 + Outbox/消息最终一致:本地事务原子写业务数据和事件,再异步投递,下游依赖幂等消费、重试、对账和补偿。选型先明确一致性目标,不能只说“上分布式事务框架”。


53. 一个服务从开发到上线要经过哪些环节?

来源:北京四维图新 Java 岗,3 月 6 日

需求与接口评审后完成设计、编码、单元/集成测试和安全扫描,构建不可变制品,经测试环境验证后采用灰度或蓝绿发布。上线阶段要有配置与数据库变更顺序、健康检查、监控告警、容量基线、回滚条件和负责人。发布完成还要观察错误率、延迟、资源水位和核心业务指标,而不是“容器启动成功”就结束。


54. 新增数据如何保证幂等?

来源:Agent 小厂 Java 岗,4 月 9 日

先定义业务唯一键,例如订单号或请求号,并在数据库建立唯一约束,这是并发下最后的正确性防线。入口可用幂等 token 或 Redis SET NX 降低重复请求,但不能只依赖缓存;写入时捕获唯一键冲突并返回已有结果。涉及多步骤时还要用状态机限制合法流转,并让重试可以安全恢复。


55. 常见设计模式如何避免为了模式而模式?

来源:恒生 C++ 一面,4 月 24 日

策略模式适合在运行时替换算法,责任链适合让多个处理器按顺序尝试,工厂适合隔离对象创建,观察者适合一对多事件通知。回答必须落到变化点:哪个依赖需要隔离、扩展时减少了哪些修改、引入了什么调试和调用链成本。没有稳定变化方向时,直接代码往往更清晰。


56. 服务降级、熔断和限流分别解决什么问题?

来源:美团 Java 后端一面,5 月 6 日;京东 Java 岗,5 月 7 日

限流控制进入系统的请求速率,保护容量;熔断在下游持续失败时快速失败,阻止故障放大;降级在资源不足或依赖不可用时关闭非核心能力、返回兜底。三者通常组合使用,并配合超时、隔离、重试预算和监控。没有超时边界的重试会制造流量放大。


57. 常见限流算法怎么选?

来源:京东 Java 岗,5 月 7 日

固定窗口简单但有边界突刺;滑动窗口更平滑但状态更多;漏桶严格匀速,适合保护固定吞吐的下游;令牌桶允许受控突发,适合多数 API。分布式限流可用 Redis + Lua 保证单次判断原子性,但还要明确时钟、故障降级、热点 key 和多机本地配额误差。


58. 如何设计请求幂等?

来源:小红书 Java 岗,5 月 5 日

为请求分配业务唯一 ID,在数据库建立唯一约束,并保存处理结果;重复请求命中相同 ID 时直接返回既有结果。Redis SET NX 可以减轻重复执行,但必须设置合理过期并考虑处理超时后的恢复。支付、订单等状态变更还要使用状态机、乐观锁和对账补偿。


59. 为什么在异步事件场景选择 Redis Streams?

来源:阿里国际暑期实习一面,5 月 3 日

Streams 提供持久化消息、消费者组、待确认列表和显式 ACK,比 Pub/Sub 更适合需要恢复和重试的轻量事件流;同时运维成本低,适合系统已经依赖 Redis 且吞吐规模可控的场景。它不能直接等同于 Kafka:分区扩展、超长保留、跨机房复制和大规模消费生态较弱。选型要说明消息量、保留周期、丢失容忍度和失败恢复。


Java、JVM 与 Spring

60. HashMap 为什么是线程不安全的?

来源:字节 AI 全栈研发二面

  • 多线程同时 put 触发扩容时,可能导致链表成环(JDK 7)或数据覆盖(JDK 8)
  • size++ 非原子操作,并发写导致计数不准
  • modCount 检测到并发修改会抛 ConcurrentModificationException,但这是 fail-fast 不是线程安全

61. 怎么判断单例 Bean 是否线程安全?怎么解决?

来源:字节 AI 全栈研发二面

判断:如果 Bean 内部存在可变的共享变量,且多线程可以对其修改,则不是线程安全的。

解决方案:

  • 无状态设计(最佳):Bean 不持有可变字段,只有方法调用
  • ThreadLocal:每个线程持有独立副本
  • 改为多例(@Scope(“prototype”)):每次注入新实例
  • 加锁:synchronized / ReentrantLock(性能最差,最后手段)

62. 一个进程中有 1000 个 ConcurrentHashMap 并发更新,为什么会 GC 频繁?怎么解决?

来源:杭州滴滴 CTO 面

原因:每个 CHM 扩容时创建新数组(2倍),1000 个 CHM 同时扩容 → 大量大对象分配 → 频繁触发 GC(尤其是 Young GC 晋升到 Old 区)

解决

  • 预估容量初始化(initialCapacity),减少扩容次数
  • 控制 CHM 数量,能合并就合并
  • 使用对象池或预分配策略
  • 调整 GC 参数(增大 Young 区、使用 G1 的 Region 机制)

63. JVM 内存区域和 GC 类型?G1 和 CMS 区别?

来源:杭州滴滴 CTO 面 / 嘉立创一面

运行时数据区

  • 堆(Heap):对象实例,GC 主战场
  • 方法区/元空间(Metaspace):类信息、常量池
  • 虚拟机栈:线程私有,方法调用栈帧
  • 本地方法栈:Native 方法
  • 程序计数器:当前执行字节码地址

G1 vs CMS

维度 CMS G1
目标 最短停顿时间 可预测停顿时间(设目标暂停毫秒)
内存布局 连续分代(Young/Old) Region 化(不连续,每个 Region 可以是任何代)
碎片 标记-清除,有碎片 标记-整理(Region 级别 compact),无碎片
Full GC 退化为 Serial Old,STW 很长 Mixed GC 逐步回收,Full GC 是最后兜底

64. ThreadLocal 原理?内存泄漏怎么回事?

来源:杭州滴滴 CTO 面;淘宝闪购 AI 应用研发二面字节中国交易与广告 AI 应用开发一面

  • 每个线程持有一个 ThreadLocalMap(key=ThreadLocal 引用,value=存储值)
  • get/set 操作只在当前线程的 map 中进行,天然线程隔离

内存泄漏

  • ThreadLocalMap 的 key 是弱引用(WeakReference),GC 后 key 变 null
  • 但 value 是强引用,如果线程池中线程长期存活,value 永远不会被回收
  • 解决:用完务必调 remove()

65. Spring Bean 生命周期?三级缓存解决循环依赖?

来源:嘉立创一面

生命周期:实例化 → 属性注入 → Aware 接口回调 → BeanPostProcessor 前置 → InitializingBean/init-method → BeanPostProcessor 后置 → 使用 → DisposableBean/destroy-method

三级缓存

  • 一级:singletonObjects(完整 Bean)
  • 二级:earlySingletonObjects(半成品 Bean,已实例化未注入)
  • 三级:singletonFactories(Bean 工厂,用于创建代理对象)

流程:A 依赖 B,B 依赖 A → A 实例化后放三级缓存 → 注入 B 时发现需要创建 B → B 注入 A 时从三级缓存获取 A 的早期引用 → B 完成 → A 完成


66. SpringBoot 自动装配原理?

来源:嘉立创一面

  1. @SpringBootApplication 包含 @EnableAutoConfiguration
  2. @EnableAutoConfiguration 通过 @Import(AutoConfigurationImportSelector) 导入配置
  3. AutoConfigurationImportSelector 读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  4. 根据 @Conditional 注解条件判断哪些自动配置类生效
  5. 生效的配置类注册对应的 Bean 到容器

本质:约定优于配置 + SPI 机制 + 条件装配


并发与语言运行时

67. ConcurrentHashMap 底层的读写如何处理并发冲突?

来源:小红书 PE 后端一面

:value 和链表 next 指针设为 volatile,修改时直接操作公共内存,每个线程都能取到最新数据,读不需要加锁。

:先判断有无冲突——无冲突 CAS 插入;有冲突 synchronized 锁住头节点遍历链表插入。

扩容:支持多线程协同扩容。将原数组桶迁移到新数组(容量翻倍),系统把任务拆成 TransferRegion,线程领取后迁移。扩容期间读操作正常(最终一致),写操作协助扩容或等待。


68. Synchronized 和 ReentrantLock 怎么选?底层有什么区别?

来源:小红书 PE 后端一面

Synchronized

  • 使用简单,可加在代码块和方法上
  • 只能非公平锁
  • JDK 1.6 引入锁升级机制,底层通过对象头 Mark Word 实现:
    • 无锁 → 偏向锁(记录线程 ID)→ 轻量级锁(CAS 竞争)→ 重量级锁(ObjectMonitor 队列排队)

ReentrantLock

  • 基于 AQS 实现,底层有 state 记录重入次数 + FIFO 双向链式队列
  • 支持公平/非公平锁切换
  • 公平锁先查队列再竞争,非公平锁先尝试获取失败才入队

选型:简单同步用 synchronized(JVM 优化够好),需要公平锁/可中断/超时/多条件变量时用 ReentrantLock。


69. RPC 调用场景下线程池怎么配置?

来源:小红书 PE 后端一面

  • IO 密集型(RPC 调用为主):线程大多时间在等待外部响应,CPU 利用率低 → 线程数设大(通常 2N ~ 4N,N 为 CPU 核数)
  • CPU 密集型(计算为主):很少阻塞 → 线程数 = N+1,多了反而增加上下文切换开销

70. Go 的 GMP 调度模型?

来源:滴滴花小猪 Agent 开发一面

  • G(Goroutine):用户态协程,轻量级(2KB 初始栈)
  • M(Machine):操作系统线程,实际执行 G
  • P(Processor):逻辑处理器,持有本地运行队列

调度流程:P 从本地队列取 G 绑定到 M 执行。本地队列空时从全局队列或其他 P 偷取(work stealing)。G 阻塞时 M 释放 P,P 绑定新 M 继续调度。


71. Go Map 是线程安全的吗?sync.Map 底层?

来源:滴滴花小猪 Agent 开发一面

不安全。并发读写 map 会 panic(fatal error: concurrent map read and map write)。

sync.Map 底层

  • 两个 map:read(只读,无锁访问)+ dirty(读写,需加锁)
  • 读操作先查 read,命中直接返回(无锁);未命中才加锁查 dirty
  • 写操作加锁写 dirty
  • 当 dirty 被查询次数超过阈值时,提升为 read(dirty → read)
  • 适合读多写少场景;写多场景不如 RWMutex + map

72. volatile 关键字的作用?它保证的是什么有序性?

来源:杭州滴滴 CTO 面 / 嘉立创一面

  • 可见性:修改立刻刷回主内存,其他线程读取时从主内存取最新值
  • 禁止重排序:通过内存屏障防止指令重排(happens-before 规则)
  • 不保证原子性i++ 即使用 volatile 也不安全(读-改-写三步非原子)

volatile 保证的有序性是“对 volatile 变量的读写不会与其前后的操作重排序”,但不保证多个非 volatile 操作之间的顺序。


73. JDK 1.8 ConcurrentHashMap 为什么废弃分段锁?

来源:杭州滴滴 CTO 面

  • JDK 7 分段锁(Segment):将数组分为 16 段,每段独立加锁。问题:Segment 数量固定、内存浪费、锁粒度仍然偏粗
  • JDK 8 改为 CAS + synchronized 锁桶头节点
    • 锁粒度更细(一个桶一把锁 vs 一段多个桶共享锁)
    • 并发度更高(理论上并发度 = 桶数量)
    • 内存更省(去掉了 Segment 对象开销)

网络、HTTP 与实时通信

74. TCP 三次握手为什么不是两次?四次挥手为什么不是三次?

来源:PDD 服务端一面 / 华为暑期一面

三次握手(不能两次)

  • 两次:客户端发 SYN,服务端回 SYN+ACK 就建立连接
  • 问题:如果客户端的旧 SYN(网络延迟)到达服务端,服务端会误建连接
  • 三次的意义:客户端确认服务端的 ACK 才算连接建立,防止历史连接初始化

四次挥手(不能三次)

  • 原因:TCP 全双工,关闭两个方向的数据流需要独立确认
  • 服务端收到 FIN 后可能还有数据要发,不能立即关闭,所以 ACK 和 FIN 分开发

75. 数据从网卡到 socket 缓冲区的流程?

来源:PDD 服务端一面

  1. 网卡收到数据帧 → DMA 拷贝到内核 Ring Buffer
  2. 网卡触发硬中断通知 CPU
  3. CPU 执行中断处理(上半部),调度软中断
  4. 软中断(NAPI)从 Ring Buffer 取数据包
  5. 经过网络协议栈逐层解封装(链路层→IP层→TCP层)
  6. 数据放入对应 socket 的接收缓冲区(sk_buff)
  7. 唤醒阻塞在该 socket 上的用户进程(或 epoll 回调)

76. 游戏网络包为什么常用二进制序列化?如何设计协议?

来源:灵犀互娱 Java 实习面经,6 月 4 日

二进制格式通常比文本体积小、解析快,适合高频位置和状态同步。包头至少包含魔数、版本、消息类型、长度和序列号,包体采用 Protobuf/FlatBuffers 等成熟格式,并设置最大长度和校验,防止粘包拆包错误及恶意内存分配。协议演进要遵循字段兼容规则,未知字段可跳过,发布时支持新旧版本并存。


MySQL 与数据一致性

77. 数据库索引从各个角度介绍

来源:武汉风行在线 Agent 开发一面;字节 AI Agent 研发一面

物理存储

  • 聚簇索引:叶节点存完整行数据,一表一个
  • 非聚簇索引:叶节点存主键值,需回表查完整数据

底层数据结构

  • B+ 树索引:范围查询友好,InnoDB 默认
  • Hash 索引:等值查询 O(1),不支持范围,Memory 引擎用
  • 全文索引:倒排索引,适合文本搜索

业务逻辑

  • 主键索引、唯一索引、普通索引
  • 联合索引(最左前缀原则)
  • 覆盖索引(查询字段全在索引中,无需回表)

78. 慢查询如何定位和优化?

来源:武汉风行在线 Agent 开发一面

定位

  1. 开启慢查询日志(slow_query_log)
  2. 设置阈值(long_query_time)
  3. 用 mysqldumpslow 或 pt-query-digest 分析

EXPLAIN 核心字段

  • type:ALL(全表扫描)→ index → range → ref → const(越右越好)
  • key:实际使用的索引
  • rows:预估扫描行数
  • Extra:Using filesort / Using temporary / Using index

三个方向优化

  1. SQL 语句:避免 SELECT *、减少子查询、用 JOIN 替代 IN
  2. 索引:添加缺失索引、用联合索引覆盖高频查询、避免索引失效场景
  3. 数据库结构:分表分库、冷热分离、读写分离

79. 10 亿条数据,通过手机号后四位搜索用户,怎么设计?

来源:武汉风行在线 Agent 开发一面(业务面)

建表设计

  • 添加冗余字段:phone_suffix_4 存后四位
  • 反转字符串存储:便于 LIKE 'xxxx%' 走索引
  • 虚拟列(MySQL 5.7+):自动计算后四位建索引

存储架构

  • 分库分表:按手机号后四位做分区键(10000 个分区均匀分布)
  • 冷热分离:近期活跃用户热库,历史数据冷库
  • ES 辅助:全量数据同步到 ES,后四位作为 keyword 字段

查询方案

  • 带分区键查询 + MySQL 覆盖索引
  • 高频场景走 ES
  • 通过 Canal 监听 binlog 做数据同步

80. ClickHouse 和 MySQL 底层有什么区别?

来源:小红书 PE 后端一面

维度 MySQL ClickHouse
存储模型 行存储 列存储
适用场景 OLTP(事务处理) OLAP(分析查询)
写入模式 单行实时写入 批量追加写入
并发能力 高并发短查询 低并发长查询
事务支持 ACID 事务 无事务(最终一致)
压缩 一般 极高(列存 + 压缩算法)

ClickHouse 适合:日志分析、用户行为分析、指标看板等读多写少的分析场景。不适合高并发点查和事务场景。


81. MySQL redo log 是否一定安全?怎么保证?

来源:携程二面

  • redo log 默认配置 innodb_flush_log_at_trx_commit = 1:每次事务提交都 fsync 到磁盘 → 可保证不丢
  • 设为 0:每秒 fsync,宕机可能丢 1s 数据
  • 设为 2:写到 OS page cache,MySQL 挂不丢但机器断电会丢

要“一定安全”:sync_binlog = 1 + innodb_flush_log_at_trx_commit = 1(双 1 配置),牺牲性能换取零丢失。


82. 事务隔离级别?可重复读解决了什么?怎么实现的?

来源:小红书 PE 二面

级别 脏读 不可重复读 幻读
读未提交
读已提交(RC)
可重复读(RR) InnoDB 基本解决
串行化

可重复读比 RC 多解决:同一事务内多次读同一行结果一致(RC 下别人提交了你就能看到变化)。

实现:MVCC(多版本并发控制)

  • 每行有隐藏的 trx_id 和 roll_pointer
  • 事务开始时生成 ReadView(快照),只能看到小于自己 trx_id 的版本
  • RR 级别:整个事务复用同一个 ReadView → 可重复读
  • RC 级别:每次 SELECT 生成新 ReadView → 能看到最新提交

83. 常见索引失效场景?

来源:嘉立创一面

  1. 对索引列做函数/运算:WHERE YEAR(create_time) = 2026
  2. 隐式类型转换:WHERE phone = 13800138000(phone 是 varchar)
  3. LIKE 以 % 开头:WHERE name LIKE '%张'
  4. 联合索引不满足最左前缀
  5. OR 某一分支没有可用索引,导致优化器放弃索引合并
  6. 使用 !=NOT IN 后选择性太差,优化器认为全表扫描成本更低
  7. IS NULL / IS NOT NULL 是否走索引取决于可空性、数据分布和成本估算

“索引失效”不是语法黑名单。最终应通过 EXPLAIN ANALYZE 和真实数据分布确认访问路径。


84. 大表分页查询优化?

来源:嘉立创一面

问题:LIMIT 1000000, 10 实际扫描 100 万 + 10 行。

优化方案

  1. 游标分页WHERE id > last_id LIMIT 10(适合连续翻页)
  2. 延迟关联:先查主键 SELECT id FROM t LIMIT 1000000,10,再关联取数据
  3. 覆盖索引:如果只需要索引列,直接走索引不回表
  4. 业务限制:不允许翻到特别深的页(如最多看前 100 页)

消息队列与分布式系统

85. 分布式锁如何实现?

来源:武汉风行在线 Agent 开发一面;字节中国交易与广告 AI 应用开发一面字节 AI Agent 研发一面

Redis 实现

  • SET key value NX EX(原子设置 + 过期时间)
  • Redisson 封装:看门狗机制(自动续期防止业务未完成锁过期)、可重入锁(计数器)
  • RedLock(多节点):半数以上节点加锁成功才算成功

注意事项

  • 必须设过期时间(防止死锁)
  • value 用唯一标识(释放时验证是自己加的锁)
  • 释放用 Lua 脚本保证原子性(判断 + 删除)

86. Kafka LAG 排查思路?

来源:小红书 PE 后端一面

LAG = 生产者最新 offset - 消费者已提交 offset。LAG 持续增大说明消费跟不上。

排查步骤

  1. 确认是否有消费者线程挂掉(consumer group 状态检查)
  2. 查看是否某个 partition 消费卡住(offset 不动)
  3. 分析单条消息处理耗时是否异常增大
  4. 检查是否触发了 rebalance(频繁 rebalance 导致消费停顿)
  5. 确认下游依赖是否变慢(DB/RPC 超时传导)

解决:扩 partition + 扩 consumer 实例 / 优化消费逻辑 / 异步处理 + 本地缓冲


87. Kafka 如何保证消息有序?

来源:杭州滴滴 CTO 面

  • 单 partition 内有序(offset 严格递增),跨 partition 无全局顺序
  • 保证业务有序的做法:同一业务 key 的消息发到同一 partition(通过 key hash)
  • 消费端单线程消费或按 key 分组有序消费
  • 注意:rebalance、重试、死信会打乱顺序,需要额外设计

88. MQ 消息丢失怎么办?如何保证不丢?

来源:嘉立创一面

三个环节防丢失:

  1. 生产者:开启 confirm 模式 / 事务消息,确认消息到达 Broker
  2. Broker:消息持久化到磁盘(RocketMQ 同步刷盘/异步刷盘 + 主从同步)
  3. 消费者:手动 ACK,处理完业务逻辑后再确认;失败进入重试队列

89. 消息重复消费怎么保证幂等?

来源:嘉立创一面

  • 数据库唯一键:利用业务唯一标识(订单号)做 INSERT 去重
  • Redis SETNX:消费前检查 key 是否存在
  • 状态机:只允许单向状态流转(待支付→已支付),重复消费时状态不匹配直接跳过
  • 乐观锁/版本号:UPDATE … WHERE version = x

90. 分布式锁除了 Redis 还有什么实现方式?

来源:杭州滴滴 CTO 面 / 华为暑期一面

方案 实现 优点 缺点
Redis SETNX + 过期时间 性能高 主从切换时可能丢锁
ZooKeeper 临时有序节点 + Watch 强一致,锁释放可靠 性能较低
MySQL 唯一键 INSERT / FOR UPDATE 实现简单 性能最差,不适合高并发
etcd 租约(Lease)+ Revision 强一致、高可用 部署运维复杂

系统设计与故障排查

91. 高并发限流有哪些算法?

来源:武汉风行在线 Agent 开发一面

算法 原理 优点 缺点
固定窗口 时间窗口内计数,超阈值拒绝 实现简单 窗口边界突发(两个窗口交界处可能 2x 流量)
滑动窗口 细粒度子窗口滚动统计 平滑,解决边界问题 内存占用稍大
漏桶 请求入桶,固定速率出桶 严格匀速,保护下游 无法应对突发流量
令牌桶 固定速率放令牌,请求需获取令牌 允许一定突发 实现稍复杂

分布式场景:用 Redis + Lua 实现(计数器/令牌桶),或用 Spring Cloud Gateway / Sentinel 等中间件。


92. 什么是聚合根?DDD 带来的收益和缺点?

来源:字节 AI 全栈研发二面

聚合根:一组相关对象的访问入口。外部只能通过聚合根操作内部实体,保证聚合内的业务规则一致性。例如 Order 是聚合根,OrderItem 只能通过 Order 操作。

收益

  • 业务逻辑内聚,不散落在 Service 层
  • 限界上下文隔离,团队可并行开发
  • 代码可读性高,领域模型即文档

缺点

  • 学习曲线陡峭,概念多(实体、值对象、领域事件、仓储…)
  • 简单 CRUD 场景过度设计
  • 聚合边界划分主观性强,不同人会有不同拆法
  • 跨聚合事务需要 Saga / 事件驱动,复杂度上升

93. 有状态服务如何改造成多节点部署?

来源:虾皮 Data Infra 大数据平台研发一面,6 月 7 日

先把会话、任务状态、文件和锁从单机内存剥离到具备明确一致性和恢复语义的共享存储,再让入口负载均衡。若协议需要连接粘性,应优先按稳定的用户或会话 ID 路由,不能依赖可能变化的客户端 IP;同时设计幂等请求、分布式锁/租约、故障转移、版本兼容和可观测性。共享 Redis 不是全部答案,它本身也要考虑分片、热点和故障。


近期新增:工程平台与语言运行时

94. TLS 1.3 在 HTTP/1.1、HTTP/2 与 HTTP/3 中如何完成认证、密钥协商和加密?

来源:字节 AI 开发实习一面,2026 年 2 月 27 日

先区分承载关系:HTTP/1.1 和 HTTP/2 的 HTTPS 通常先建立 TCP 连接,再在其上完成 TLS 1.3 握手;HTTP/3 则运行在基于 UDP 的 QUIC 中,TLS 1.3 握手被集成进 QUIC,不存在独立的 TCP 连接或 TLS record layer。三者都使用 TLS 1.3 的认证与密钥派生能力,但报文承载和数据保护层不同。

在 TCP + TLS 1.3 链路中,客户端通过 ClientHello 发送支持版本、密码套件、SNI、ALPN、supported groups 和临时 ECDHE key share;服务端在 ServerHello 中完成选择并返回自己的 key share。双方由 ECDHE 得到共享秘密,再通过 HKDF 结合握手 transcript 派生握手流量密钥。ECDHE 提供前向保密;TLS 1.3 密码套件主要选择 AEAD 和 HKDF 使用的哈希算法,签名算法与密钥交换参数分别协商,不是用证书公钥加密全部业务数据。

随后服务端发送证书链,并用证书私钥对当前握手 transcript 签名。客户端需要验证证书链能否连接到本地信任锚,同时检查有效期、SAN 主机名、Key Usage/Extended Key Usage、CA 约束和按策略要求的吊销状态;再验证 CertificateVerify,确认对端确实持有叶子证书对应的私钥。双方的 Finished 消息对完整握手摘要做密钥校验,防止协商参数或证书消息被篡改;需要双向认证时,客户端还会发送自己的证书和签名。

握手完成后,双方再派生应用流量密钥。HTTP/1.1 和 HTTP/2 数据进入 TLS record,由 AES-GCM 或 ChaCha20-Poly1305 等 AEAD 加密并校验完整性;ALPN 分别协商 http/1.1h2。HTTP/3 中,TLS 握手消息由 QUIC CRYPTO frame 承载并通过 ALPN 协商 h3,TLS 导出的秘密进一步生成 QUIC packet protection keys,由 QUIC 保护承载 HTTP/3 frame 的数据包。

证书解决“对方身份与公钥是否可信”,CertificateVerify 证明“对端持有对应私钥且握手未被冒充”,ECDHE 解决“如何得到共享秘密”,对称 AEAD 才承担批量数据保护。排障时必须先确认是 TCP + TLS 的 HTTP/1.1/2,还是 QUIC 内集成 TLS 1.3 的 HTTP/3,不能把“先 TCP、再 TLS”套到 HTTP/3。


95. AQS 的 state 和等待队列如何工作?ReentrantReadWriteLock 怎样实现共享读与独占写?

来源:字节 Agent 研发面经,2026 年 3 月 4 日

AQS 把同步器拆成两部分:一个 volatile int state 表示同步状态,子类通过 CAS 定义获取和释放条件;竞争失败的线程进入近似 FIFO 的 CLH 变体等待队列,在前驱状态允许时被 unpark 后重新尝试。独占模式同一时刻只允许一个所有者成功,共享模式则允许多个节点同时成功并向后传播唤醒。ConditionObject 另有条件等待队列,await 会先完整释放锁,收到 signal 后转移到同步队列重新竞争,因此 signal 不等于立即获得锁。

ReentrantReadWriteLock 的同步器把 32 位 state 分段使用:低 16 位记录写锁重入次数,高 16 位记录总读锁次数。写锁按独占模式获取,并额外记录 owner,只有写线程能重入和释放;读锁按共享模式获取,多个读线程可同时增加高位计数,同时还要维护每个线程自己的读重入次数,防止错误释放。存在其他线程持有写锁时读锁失败;读锁未清空时其他线程也不能获得写锁。

公平模式按队列先后抑制插队,非公平模式允许一定抢占以换吞吐,但写线程仍可能遭遇持续读流量带来的延迟。持有写锁的线程可以先获得读锁再释放写锁,实现锁降级;普通读线程不能安全地直接升级为写锁,否则多个升级者可能互相等待。state 只是同步协议的载体,线程安全还依赖 CAS、队列唤醒、owner/读计数和内存语义共同实现,不能把 AQS 简化成“一个整数加链表”。


96. 微服务中的分布式事务有哪些方案?应该如何选型?

来源:北京四维图新面经,2026 年 3 月 6 日

先判断业务是否真的需要跨服务强一致。能通过调整服务边界把更新放进一个本地事务,或把强约束收敛到单一事实源,通常比引入分布式事务更可靠。必须跨资源时,XA/2PC 由协调者组织 prepare 和 commit,可提供较强原子性,但会占用资源、依赖参与者协议并承受协调者故障和长事务阻塞;适合参与方可控、事务短且强一致收益高的场景,不适合任意外部服务。

TCC 把业务显式拆成 Try、Confirm、Cancel:Try 预留资源,后两步必须幂等并处理空回滚、悬挂和重复调用,控制力强但业务侵入和实现成本高,常用于资金、库存等可冻结资源。Saga 把长流程拆成本地事务与反向补偿,吞吐和可用性较好,但只能达到最终一致,补偿也未必等价于物理回滚。

Transactional Outbox 是把业务更新和一条待发布的 outbox 事件写入同一个本地数据库事务,提交后再由轮询发布器或 CDC 读取 outbox 并投递消息。CDC 本身只是从已提交的 binlog/WAL 等变更日志捕获记录,不参与前面的本地事务;若直接从业务表推导事件,会增加表结构与事件语义的耦合。两种投递路径通常都是 at-least-once,发布端要保存读取位点并处理顺序,消费者仍需按事件 ID 幂等。它们解决的是本地提交后可靠传播事件,不自动保证跨服务全局不变量。

选型要同时看一致性等级、隔离要求、事务时长、参与方数量、是否可补偿、峰值吞吐和故障恢复成本。无论采用哪种方案,都要有全局业务 ID、幂等键、状态机、超时与有界重试、去重、对账、人工修复和可观测性;补偿失败需要进入持续重试或人工处置,而不是吞掉异常。实际系统常混合使用,例如核心记账在本地强事务中完成,跨服务通知通过 outbox 最终一致,只有少数可冻结资源的步骤使用 TCC。


97. OSI 七层分别负责什么?数据如何逐层封装和解封装?

来源:网新软件面经,2026 年 4 月 14 日

OSI 从上到下是:应用层提供面向应用的网络服务,如 HTTP、DNS、SMTP;表示层负责数据表示、编码、压缩和加密;会话层管理会话建立、同步和恢复;传输层提供端到端传输、端口、可靠性和流量控制,如 TCP/UDP;网络层负责逻辑寻址和跨网络路由,如 IP;数据链路层负责同一链路上的成帧、MAC 寻址和差错检测,如 Ethernet;物理层把比特转换为电、光或无线信号。现实 TCP/IP 协议栈不会严格按七层实现,例如 TLS 常被视为位于应用与传输之间,因此七层更适合职责分析而不是进程边界图。

发送端把应用数据交给下一层:传输层加入 TCP/UDP 首部形成 segment/datagram,网络层加入 IP 首部形成 packet,链路层再加入帧头和通常的校验尾部形成 frame,最后由物理层发送比特。接收端反向检查并去除相应头部,把 payload 逐层交给上层。每层只把上层整体视为自己的载荷,这就是封装与解封装。

链路层封装只在一跳内有效:交换机主要按 MAC 转发,路由器收到帧后会去掉旧链路层头部、处理 IP 包,再为下一跳重新封装,因此 MAC 地址通常逐跳变化,IP 地址在无 NAT 时保持端到端不变。端口把报文交给目标进程,应用协议再解释业务语义。排障时按层定位更有效,例如链路是否通、IP 路由是否正确、TCP 是否建立、TLS 是否认证、HTTP 是否返回,而不是把所有“访问失败”都归因于网络不通。


98. Python GIL 为什么限制 CPU 密集型多线程?I/O 与 CPU 任务应如何选择并发模型?

来源:腾讯后台一面,2026 年 3 月 27 日;百度 Agent 二面(FastAPI 异步追问)

在传统 CPython 默认构建中,GIL 保证同一解释器进程内通常只有一个线程执行 Python 字节码,简化了引用计数和解释器内部状态保护。CPU 密集的纯 Python 线程即使分布在多个核心上,也要竞争 GIL,并产生切换开销,因此通常无法获得按核心数增长的并行加速。GIL 不等于业务数据天然线程安全:一次看似简单的复合操作仍可能跨多个字节码,I/O 和扩展代码也可能释放 GIL,共享状态仍需正确同步。

I/O 密集且调用是阻塞接口时,线程池简单实用,因为线程等待网络或磁盘期间会释放 GIL;连接数很大、协议库支持异步时,asyncio 用单线程事件循环和协程减少线程开销,但所有阻塞调用都必须隔离,否则会卡住整个循环。CPU 密集的纯 Python 代码优先用多进程或 ProcessPoolExecutor 利用多个核心,同时权衡序列化、进程内存和进程间通信成本;NumPy 等原生扩展若在重计算时释放 GIL,也可在线程中并行。

自由线程构建、子解释器或释放 GIL 的原生代码提供了额外选择,但要先验证依赖兼容性、共享状态安全和实际 benchmark,不能只看理论并行。混合服务通常分层处理:异步或线程承接 I/O,受限队列提供背压,CPU 阶段送入进程池或独立计算服务,并分别设置并发上限、超时和取消传播。模型选择取决于任务特征,而不是笼统地说“Python 不能多线程”。

落到 FastAPI 调 LLM 的场景:异步 HTTP 客户端等待远端模型时不占住事件循环,适合高并发 I/O;同步 SDK 应显式下沉线程池;Tokenizer、图片处理或本地推理等 CPU 密集工作若长期占用 GIL,应进入进程池或独立 Worker。线程池只能隔离阻塞,不能凭空增加下游模型配额,还要设置队列上限、Deadline 和取消传播;async def 与普通 def 的调度边界以 FastAPI 官方并发说明为准。


99. Java 的方法重载与方法重写有什么区别?

来源:线下某小厂面经,2026 年 5 月 14 日

重载发生在同一个类或可见的继承方法集合中:方法名相同,但参数列表的数量、类型或顺序不同。编译器根据调用点的静态类型选择最匹配签名,因此它是编译期多态;返回类型、参数名或 throws 列表不能单独构成重载。自动装箱、基本类型提升、可变参数和 null 可能让候选选择不直观甚至产生歧义,公共 API 应避免设计难以判断的重载组合。

重写发生在子类对父类可继承实例方法提供相同签名的实现,返回类型可协变。真正调用哪个实现由运行时对象类型决定,因此它是运行期多态。重写方法不能降低访问权限,不能声明比父方法更宽的受检异常;final 方法不能重写,构造器不能继承或重写,private 方法对子类不可见,static 方法只会按静态类型隐藏而不是动态重写。

@Override 能让编译器检查签名,避免因为参数细微变化而意外写成重载。面试中可用 Parent p = new Child() 说明:p.instanceMethod() 对重写进行动态分派,但选择哪个重载仍先由变量 p 的编译期类型和实参静态类型决定。区分两者的关键不是“名字一样”,而是签名关系、绑定时机和是否参与动态分派。


100. C++ 模板函数在什么时候编译和实例化?为什么实现通常放在头文件?

来源:三星 AI Infra 实习一面,2026 年 5 月 1 日

编译器先解析模板定义并检查不依赖模板参数的语法;遇到具体使用时,再用实参完成实例化并检查依赖类型的表达式。隐式实例化要求使用点能够看到完整定义,因此模板实现通常放在头文件。只在 .cpp 中定义而没有显式实例化,其他翻译单元往往只能看到声明,最终会出现链接错误。

显式实例化可以在一个翻译单元生成指定类型版本,配合 extern template 抑制其他翻译单元重复实例化,降低编译时间和代码膨胀;代价是支持的类型集合需要预先确定。模板的两阶段查找、依赖名、SFINAE/Concepts 会影响重载选择,但“模板在运行时生成代码”是错误说法。


101. 遍历 STL 容器时如何安全删除元素?

来源:三星 AI Infra 实习一面,2026 年 5 月 1 日

不能在 for (++it) 循环里调用 erase(it) 后继续使用旧迭代器。常见写法是 it = container.erase(it),由 erase 返回下一个有效迭代器;不删除时才执行 ++it。C++20 以后,按谓词批量删除还可以优先使用 std::erase_if

失效范围取决于容器:list 通常只使被删元素的迭代器失效;vector/deque 删除位置及其后的迭代器可能失效;关联容器通常只使被删节点失效。并发修改还需要外部同步,迭代器规则不提供线程安全。


102. 如何让函数在 main 之前执行?有哪些工程风险?

来源:百度 AI Infra 实习一面,2026 年 5 月 2 日

C++ 可通过具有静态存储期的对象构造函数完成初始化,也可以使用编译器扩展的 constructor attribute;C 常见实现依赖启动段或编译器属性。它们最终都由运行时启动代码在进入 main 前调用,不是操作系统直接调用普通业务函数。

跨翻译单元的静态初始化顺序通常不应依赖,这就是 static initialization order fiasco。更稳妥的做法是函数内静态对象、显式初始化入口或可控的注册表;初始化逻辑应避免依赖尚未构造的全局对象、启动线程或执行难以恢复的 I/O。


103. 条件断点、调用栈和内存观察分别适合排查什么问题?

来源:百度 AI Infra 实习一面,2026 年 5 月 2 日

条件断点只在表达式满足时停下,适合循环中特定索引、对象 ID 或错误状态;命中次数断点适合定位第 N 次异常。调用栈展示当前线程从入口到故障点的调用链和栈帧,可逐层查看参数、局部变量和返回地址。Watch/内存窗口用于持续观察表达式、地址和对象布局,硬件 watchpoint 还可以在某段内存被读写时暂停。

优化构建可能内联函数、删除变量或重排指令,导致源码行与现场不一一对应。生产问题要保留符号、构建 ID、核心转储和对应二进制,并结合日志、sanitizer、perf 等证据;不要为了方便调试就长期关闭所有优化后推断线上性能行为。


104. std::deque 的底层结构和迭代器失效规则是什么?

来源:百度 AI Infra 实习一面,2026 年 5 月 2 日

deque 通常由一张 map 指向多个固定大小的连续缓冲块,因此支持两端近似常数时间插入删除,也支持随机访问;它不像 vector 那样保证全部元素位于一段连续内存,常数开销和缓存局部性通常更差。具体块大小和 map 增长方式属于标准库实现细节。

中间插入删除可能移动元素并广泛使迭代器失效;两端操作对迭代器、指针和引用的影响要按所用标准版本与实现契约确认。需要连续存储和与 C API 互操作时优先 vector,需要稳定节点地址时考虑 list,不能只凭“大 O 相同”选容器。


105. shared_ptr 是线程安全的吗?

来源:抖音搜推 AI Infra 一面,2026 年 5 月 1 日

不同 shared_ptr 对象即使共享同一控制块,也可以在不同线程并发复制和销毁,因为引用计数更新具备所需同步;这不代表所指对象线程安全,也不代表多个线程可以无锁修改同一个 shared_ptr 变量。后两种情况仍可能发生数据竞争。

共享同一个指针变量时使用互斥锁,或使用标准提供的原子 shared_ptr 操作。引用计数只管理生命周期,不保护对象内部不变量;同时要用 weak_ptr 打破循环引用,并警惕频繁跨核更新控制块造成的缓存争用。


106. 什么是 Cache 竞争和 False Sharing?如何定位?

来源:摩尔线程 AI Infra 一面,2026 年 5 月 2 日

多个核心访问同一共享数据会触发一致性流量;即使线程修改的是不同变量,只要变量落在同一 Cache Line,也会让缓存行在核心间反复失效,这就是 False Sharing。它与容量不够导致的 capacity miss、映射冲突导致的 conflict miss、内存带宽饱和不是同一个问题。

先用硬件性能计数器、Profiler 和 CPU 亲和性实验确认 cache miss、HITM/一致性事件和带宽,再检查热点结构布局。常见优化包括按线程分片、局部累加后合并、填充或对齐热点写字段、减少共享写入;盲目 padding 会增大工作集,因此必须用基准验证。


107. FP16、FP32、FP64 的位宽如何分配?数值范围和精度如何权衡?

来源:飞腾 AI Infra 实习一面,2026 年 5 月 2 日

IEEE 754 binary16 通常为 1 位符号、5 位指数、10 位显式 fraction;binary32 为 1/8/23;binary64 为 1/11/52。正规数还包含隐含的最高有效位,因此有效精度比 fraction 位数多一位。全零和全一指数分别用于次正规数/零以及无穷/NaN 等特殊值。

指数位决定动态范围,fraction 位决定有效精度。低精度能降低存储、带宽和计算成本,却更容易溢出、下溢和累积误差。AI 训练常使用混合精度、FP32 累加和 Loss Scaling;具体硬件对 FP16、BF16、FP8 的吞吐与舍入支持不同,不能只比较位宽。


108. git fetch + checkoutgit pull 和远程跟踪分支有什么区别?

来源:飞腾 AI Infra 实习一面,2026 年 5 月 2 日

git fetch 只把远端引用和对象更新到本地,不修改当前工作分支;之后可以检查 origin/x,再创建或切换本地分支。git pull 相当于先 fetch,再按配置 merge 或 rebase 到当前分支,因此会直接改变当前分支历史和工作区状态。

远程跟踪分支如 origin/main 是本地记录的远端状态快照,不是能直接在本地提交的远端分支。团队使用前应明确 pull 的 merge/rebase 策略,操作前检查脏工作区和上游绑定;自动化环境更适合拆开 fetch、验证目标 commit,再执行明确的 merge/rebase。


109. Go 和 Java 的主要差异应从哪些维度比较?

来源:阿里云 Agent Infra 一面,2026 年 4 月 1 日

不要只回答“Go 快、Java 重”。语言层面,Go 强调较小语法、组合和 goroutine/channel,Java 提供类继承、泛型、注解和成熟的 JVM 生态;运行时层面,Go 通常静态编译并由自身 runtime 调度 goroutine,Java 编译到字节码后由 JVM 解释/JIT,线程与虚拟线程的模型也不同。

两者都有 GC、并发库、逃逸和性能调优问题。选型要看延迟/吞吐目标、启动和内存约束、团队库生态、诊断工具、部署方式以及业务框架。Go 常适合云原生服务和工具,Java 在复杂企业业务与中间件生态中优势明显,但具体结论必须用目标负载验证。


110. gRPC 为什么常使用 Protobuf 二进制编码?它一定比 JSON 快吗?

来源:阿里云 Agent Infra 一面,2026 年 4 月 1 日;拼多多 AI 全栈两轮技术面

gRPC 通常以 Protobuf 定义强类型契约,使用字段编号编码,消息一般比包含字段名的 JSON 紧凑,并提供代码生成、双向流和基于 HTTP/2 的多路复用。它适合内部服务间高频 RPC,但可读性、调试门槛、浏览器支持和协议演进纪律也要考虑。

二进制并不保证任何场景都更快:小消息可能被网络、TLS、排队和业务处理主导,压缩也有 CPU 成本。字段演进应保留编号、谨慎修改语义,并传播 Deadline、取消和 Trace。对外开放、浏览器直连或强调可调试性时,JSON/REST 仍可能更合适。


111. 按时间分表后,跨表查询如何实现全局排序和分页?

来源:虾皮 AI Infra 二面,2026 年 4 月 13 日

先根据时间条件路由到有限分表,在每个分片使用相同排序键执行有界查询,再在聚合层做 k-way merge。排序键必须全局稳定,例如 (created_at, id),否则相同时间戳会导致重复或漏项。深分页不宜对所有分片执行巨大 offset,优先使用基于上一页排序键的 keyset/cursor pagination。

Cursor 应携带各分片推进位置或一个能重新计算路由的全局边界,并绑定查询条件和版本。还要定义跨月写入、迟到数据、迁移和归档期间的一致性语义。若查询长期跨大量分片,应考虑二级索引、汇总表或搜索系统,而不是让在线请求无界 fan-out。


112. AI 流式请求如何传播超时、取消和背压?

来源:牛客 AI Infra 面试汇总,2026 年 6 月 3 日;拼多多 AI 全栈两轮技术面

Gateway 收到客户端断连或取消后,应沿调用链取消 HTTP/gRPC 请求、模型排队项和正在生成的序列,并释放 KV Cache、CPU buffer 和计费状态。每一层都使用绝对 Deadline 或可比较的剩余预算,避免层层独立超时导致总时长失控;操作已经产生副作用时,取消只代表“不再等待”,不等于副作用回滚。

背压要从最慢消费者向上游传播:限制单连接缓冲,合并过小 Token Chunk,设置写超时和慢消费者策略;跨服务流式透传时保留 request ID、sequence、finish reason 和 usage。断线续传只能从已持久化且有序的边界恢复,不能默认重放模型采样得到相同 Token;重试前还要区分“尚未接收首 Token”和“已向用户展示部分结果”。

算法与手撕题单

以下题目来自本篇归档的面经来源,单独列出,不计入正文技术题数量。跨半年出现的同一题保留完整来源,按已标注的最早月份归档。

题目 来源
无重复字符的最长子串(LC 3;含“最长无重复元素子数组”同义表述) 百度秋招后端一面,7 月 30 日;百度 AI Infra 一面;百度 AI Infra 提前批一面,4 月 13 日;腾讯 CDG AI Infra 框架侧一面BIGO 音频算法工程师一面字节 AI 应用开发二面
单链表去重 阿里云 AI Infra 一面,4 月 13 日
拓扑排序 / 课程表 II(输出可行顺序)、二叉树中序遍历、满二叉树性质 三星 AI Infra 实习一面,5 月 1 日;字节 Agent 后端终面
买卖股票的最佳时机 I / II 抖音搜推 AI Infra 一面,5 月 1 日;快手 AI Infra 面经(版本未注明)
冒泡、快速、归并、堆排序(含与标准 sort 对拍) 阿里云 Agent Infra 一面,4 月 26 日;AI Infra 春招面经,3 月 25 日;小马智行 AI Infra 实习面经(归并排序)字节 AI Infra 二面(堆排序)百川智能医疗大模型后训练一面(冒泡排序与对拍)算能科技 Agent 开发一面(快速排序)
柱状图接雨水 百度 AI Infra 暑期一面,5 月 1 日;阿里校招 AI Infra 一面
多线程交替打印(1/2、A1B2C3,含线程退出协调) 阿里云 Agent Infra 一面,4 月 26 日;百度 Coding Agent 三面
模拟死锁 虾皮 AI Infra 后端一面,4 月 13 日
手写 MHA(含 mask 与数值稳定性) 小鹏 AI Infra 一面,4 月 13 日(付费截断来源,仅保留公开题干)

下一篇建议继续看: