八股文 / 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,复合操作则使用 compute、merge 等原子 API。
JVM 与并发
9. ThreadLocal 如何实现线程隔离?为什么会内存泄漏?
来源:快手 Java 后端一面,3 月 24 日
每个 Thread 持有自己的 ThreadLocalMap,ThreadLocal 实例作为 key,业务值作为 value,因此不同线程访问的是不同槽位。key 是弱引用,ThreadLocal 没有外部强引用后可能被回收;value 仍被线程强引用,线程池中的线程又长期存活,就会形成 key 为 null 的陈旧条目。正确做法是在 finally 中调用 remove(),同时避免在线程本地变量中保存大对象。
10. synchronized 和 ReentrantLock 怎么选?
来源:快手 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 或不可跨线程共享的会话状态。线程池会复用线程,所以必须在 finally 中 remove();异步任务切线程时,值不会自动正确传播,盲目使用可继承 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 日
先看系统整体:uptime、top、vmstat;再看进程和线程:ps、pidstat;查内存和磁盘:free、iostat、df、du;查网络和端口:ss、lsof;查日志:journalctl、tail、rg。后台运行可使用 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 找热点线程,jstack 或 jcmd 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 使用 EXPLAIN 或 EXPLAIN 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 全栈二面
- 纯内存操作:数据全在内存,无磁盘 IO
- 单线程模型:无锁竞争、无上下文切换(6.0 后 IO 多线程,命令执行仍单线程)
- IO 多路复用:epoll 监听多个连接,非阻塞
- 高效数据结构:ziplist、skiplist、intset 等针对小数据量优化
- 简单协议: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 服务端一面
标准缓存回填流程:
- 查本地缓存(Caffeine/Guava)→ miss
- 查 Redis → hit → 返回数据 + 异步回填本地缓存
- 设置本地缓存较短 TTL(如 30s),Redis 较长 TTL(如 30min)
- 注意:本地缓存容量有限,要配淘汰策略(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 自动装配原理?
来源:嘉立创一面
@SpringBootApplication包含@EnableAutoConfiguration@EnableAutoConfiguration通过@Import(AutoConfigurationImportSelector)导入配置AutoConfigurationImportSelector读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports- 根据
@Conditional注解条件判断哪些自动配置类生效 - 生效的配置类注册对应的 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 服务端一面
- 网卡收到数据帧 → DMA 拷贝到内核 Ring Buffer
- 网卡触发硬中断通知 CPU
- CPU 执行中断处理(上半部),调度软中断
- 软中断(NAPI)从 Ring Buffer 取数据包
- 经过网络协议栈逐层解封装(链路层→IP层→TCP层)
- 数据放入对应 socket 的接收缓冲区(sk_buff)
- 唤醒阻塞在该 socket 上的用户进程(或 epoll 回调)
76. 游戏网络包为什么常用二进制序列化?如何设计协议?
来源:灵犀互娱 Java 实习面经,6 月 4 日
二进制格式通常比文本体积小、解析快,适合高频位置和状态同步。包头至少包含魔数、版本、消息类型、长度和序列号,包体采用 Protobuf/FlatBuffers 等成熟格式,并设置最大长度和校验,防止粘包拆包错误及恶意内存分配。协议演进要遵循字段兼容规则,未知字段可跳过,发布时支持新旧版本并存。
MySQL 与数据一致性
77. 数据库索引从各个角度介绍
来源:武汉风行在线 Agent 开发一面;字节 AI Agent 研发一面
物理存储:
- 聚簇索引:叶节点存完整行数据,一表一个
- 非聚簇索引:叶节点存主键值,需回表查完整数据
底层数据结构:
- B+ 树索引:范围查询友好,InnoDB 默认
- Hash 索引:等值查询 O(1),不支持范围,Memory 引擎用
- 全文索引:倒排索引,适合文本搜索
业务逻辑:
- 主键索引、唯一索引、普通索引
- 联合索引(最左前缀原则)
- 覆盖索引(查询字段全在索引中,无需回表)
78. 慢查询如何定位和优化?
来源:武汉风行在线 Agent 开发一面
定位:
- 开启慢查询日志(slow_query_log)
- 设置阈值(long_query_time)
- 用 mysqldumpslow 或 pt-query-digest 分析
EXPLAIN 核心字段:
- type:ALL(全表扫描)→ index → range → ref → const(越右越好)
- key:实际使用的索引
- rows:预估扫描行数
- Extra:Using filesort / Using temporary / Using index
三个方向优化:
- SQL 语句:避免 SELECT *、减少子查询、用 JOIN 替代 IN
- 索引:添加缺失索引、用联合索引覆盖高频查询、避免索引失效场景
- 数据库结构:分表分库、冷热分离、读写分离
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. 常见索引失效场景?
来源:嘉立创一面
- 对索引列做函数/运算:
WHERE YEAR(create_time) = 2026 - 隐式类型转换:
WHERE phone = 13800138000(phone 是 varchar) - LIKE 以 % 开头:
WHERE name LIKE '%张' - 联合索引不满足最左前缀
- OR 某一分支没有可用索引,导致优化器放弃索引合并
- 使用
!=、NOT IN后选择性太差,优化器认为全表扫描成本更低 IS NULL/IS NOT NULL是否走索引取决于可空性、数据分布和成本估算
“索引失效”不是语法黑名单。最终应通过 EXPLAIN ANALYZE 和真实数据分布确认访问路径。
84. 大表分页查询优化?
来源:嘉立创一面
问题:LIMIT 1000000, 10 实际扫描 100 万 + 10 行。
优化方案:
- 游标分页:
WHERE id > last_id LIMIT 10(适合连续翻页) - 延迟关联:先查主键
SELECT id FROM t LIMIT 1000000,10,再关联取数据 - 覆盖索引:如果只需要索引列,直接走索引不回表
- 业务限制:不允许翻到特别深的页(如最多看前 100 页)
消息队列与分布式系统
85. 分布式锁如何实现?
来源:武汉风行在线 Agent 开发一面;字节中国交易与广告 AI 应用开发一面;字节 AI Agent 研发一面
Redis 实现:
SET key value NX EX(原子设置 + 过期时间)- Redisson 封装:看门狗机制(自动续期防止业务未完成锁过期)、可重入锁(计数器)
- RedLock(多节点):半数以上节点加锁成功才算成功
注意事项:
- 必须设过期时间(防止死锁)
- value 用唯一标识(释放时验证是自己加的锁)
- 释放用 Lua 脚本保证原子性(判断 + 删除)
86. Kafka LAG 排查思路?
来源:小红书 PE 后端一面
LAG = 生产者最新 offset - 消费者已提交 offset。LAG 持续增大说明消费跟不上。
排查步骤:
- 确认是否有消费者线程挂掉(consumer group 状态检查)
- 查看是否某个 partition 消费卡住(offset 不动)
- 分析单条消息处理耗时是否异常增大
- 检查是否触发了 rebalance(频繁 rebalance 导致消费停顿)
- 确认下游依赖是否变慢(DB/RPC 超时传导)
解决:扩 partition + 扩 consumer 实例 / 优化消费逻辑 / 异步处理 + 本地缓冲
87. Kafka 如何保证消息有序?
来源:杭州滴滴 CTO 面
- 单 partition 内有序(offset 严格递增),跨 partition 无全局顺序
- 保证业务有序的做法:同一业务 key 的消息发到同一 partition(通过 key hash)
- 消费端单线程消费或按 key 分组有序消费
- 注意:rebalance、重试、死信会打乱顺序,需要额外设计
88. MQ 消息丢失怎么办?如何保证不丢?
来源:嘉立创一面
三个环节防丢失:
- 生产者:开启 confirm 模式 / 事务消息,确认消息到达 Broker
- Broker:消息持久化到磁盘(RocketMQ 同步刷盘/异步刷盘 + 主从同步)
- 消费者:手动 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.1 或 h2。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 + checkout、git 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 日(付费截断来源,仅保留公开题干) |
下一篇建议继续看: