本附录是全书使用频率最高的一份材料。它把第 1 ~ 36 章「面试高频问题与陷阱」表里的问法抽出来,重排成正反两个方向的索引:正向按篇章查「这一章会被问什么」,反向按关键词查「这个问题在哪一章」。
注意
本附录的「章节」列一律写书内章节号,与正文章节一一对应(第 1 章 = ch01「存储层次与 CPU 微架构」,第 36 章 = ch36「场景设计题的答题框架」)。带点号的形式(如 22.4)表示该章的具体小节,便于回到正文定位推导过程。
B.1 用法说明
B.1.1 正向索引怎么用(按篇章查「这章会被问什么」)
正向索引(B.2)的组织单位是篇章 → 章 → 一张问法表,使用场景是「已确定要复习哪一块」:
- 先看每章的条目数:条目多说明考点密度高、被追问概率大,应多投入时间;条目少的只过要点。
- 逐条读「面试问法」,用**能否在 20 秒内说出「一句话要点」**自检。说不出或说得含糊,说明只记住了名词;「一句话要点」只是答案骨架,细节必须回正文。
正向索引适合成块复习:一次给一章 40 ~ 60 分钟,读完原文再拿表格自测。
B.1.2 反向索引怎么用(按关键词查「这个问题在哪一章」)
反向索引(B.3)的组织单位是类别 → 关键词 → 章节。它的使用场景是「我被问了一个词,我要立刻知道去复习哪一章」:
- 在六类中找到关键词所在的那一类(操作系统与 Linux / 网络 / 数据库 / Java 与并发 / 分布式与 MQ / 性能与排障)。
- 看该关键词的追问路径;拿到章节号后去读对应小节,三条路径都能答才算过关。
反向索引适合查漏补缺:简历上写了什么、项目里用了什么,就把对应的关键词过一遍,确认每一个都能撑住三层追问。
提示
反向索引的隐藏用法是做减法。把 120 多个关键词逐条打勾,剩下的空格就是你的缺口清单。面试前一晚不要从头看书,只看空格。
B.1.3 只剩 30 分钟时怎么用这份附录
只剩 30 分钟时的顺序是:0 ~ 5 分钟读 B.4 的 20 条清单逐条自测;5 ~ 20 分钟只补清单里说不出的那些条、回正文读推导;20 ~ 27 分钟用 B.3 把简历关键词过一遍;27 ~ 30 分钟合上材料,默写三条最容易崩的推导。
30 分钟的目标不是「多学几个点」,而是让已有的点不掉出来;优先做自测与检索,不要读新材料。
B.2 正向索引:按篇章列出高频问题
重要
下面的「一句话要点」是可背诵的答案骨架。背诵时只背核心判断 + 关键机制名,不要把整句话当标准答案复述;面试里复述得越像背书,追问来得越快。
第一篇 计算机组成与体系结构
第 1 章 存储层次与 CPU 微架构|10 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 为什么要有存储层次?延迟为什么是数量级递增而非线性增长? | 每级都是下级的缓存,容量放大使查找路径变长;用 AMAT = T + (1 − h) × M 算,缺失率的小改善收益大于命中延迟优化 | 第 1 章 1.1 |
| 缓存行为什么是 64 字节?改成 128 字节会怎样? | 行大小是空间局部性收益与带宽浪费的权衡;64 B 对应一次 DDR 突发长度,行越大随机访问下无用数据搬运越多、首次访问延迟越高 | 第 1 章 1.2 |
| 你说缓存没命中,那到底是哪种没命中?怎么证明? | 强制/容量/冲突三类缺失互斥;证明法是全相联重放消掉冲突缺失、无限容量重放消掉容量缺失,做对照实验 | 第 1 章 1.3 |
| 多核之间怎么保证缓存一致?说说 MESI。 | 四态嗅探协议,写之前必须拿到独占权(RFO);MOESI 多一个 O 态减少脏行写回,MESIF 的 F 态指定唯一转发者 | 第 1 章 1.4 |
| 两个线程写同一结构体的不同字段,会有什么问题? | 伪共享是性能问题不是正确性问题;只有写才触发,读—读共享不触发;解法是填充或 @Contended 并注意对齐 | 第 1 章 1.5 |
| load 之后紧跟一条用它的指令会发生什么?分支预测失败贵在哪里? | 前者是数据冒险,转发在数据到 MEM 末尾才有效时失效需停顿;后者误判代价与流水线深度成正比,五级约 1 ns、深流水约 5 ns | 第 1 章 1.6 / 1.7 |
| 乱序执行是怎么做到既乱序又不违反程序语义的? | 寄存器重命名消除 WAW/WAR,ROB 顺序提交保证精确异常;只有 RAW 真正限制并行度 | 第 1 章 1.8 |
| Spectre 和 Meltdown 的成因是什么?为什么修不干净? | 根因是架构状态可回滚、缓存等微架构状态不可回滚;KPTI/IBRS/lfence 各堵链条的一环,无法根治 | 第 1 章 1.8 |
| 为什么把线程绑到固定 CPU 上通常能提升性能? | 减少跨核迁移造成的 TLB 冲刷与私有 cache 冷启动,优化的是尾延迟稳定性;主因是内存亲和性不是减少上下文切换 | 第 1 章 1.10 |
| 一次内存访问和一次 L3 命中差多少?为什么这个差距能决定算法选型? | 主存约 100 ns、L3 约 15 ns,差约 6~7 倍;随机访存密集型算法的常数因子由缺失率主导,故要按缓存行与工作集大小设计 | 第 1 章 1.1 |
第 2 章 程序如何真正运行|11 条
| 面试问法 | 一句话要点 | 章节 |
|---|
gcc main.c 这条命令背后依次发生了什么?每一步的产物是什么? | 预处理 → 编译 → 汇编 → 链接 → 加载五阶段;.o 里已没有函数名调用,只有符号引用加重定位项 | 第 2 章 2.1 |
为什么 .bss 段不占可执行文件空间,但进程启动后内存却会涨? | 未初始化数据映射到匿名零页,按需分页且只读零页可共享,所以文件不占空间、触碰后才占物理页 | 第 2 章 2.2 |
| 动态链接比静态链接快还是慢? | 运行时省内存,但启动更慢、后续调用多一次间接跳转;混淆体积与启动速度是典型错误 | 第 2 章 2.3 |
延迟绑定是怎么实现的?第一次调用 printf 时发生了什么? | GOT 初始值存的是 PLT 桩地址,首次调用经 _dl_runtime_resolve 回填真实地址 | 第 2 章 2.3.1 |
| 不改任何一行代码,怎么替换一个库函数?哪些情况会失败? | LD_PRELOAD 插桩;RTLD_NEXT 不写会递归死循环,setuid 程序被忽略,内联与静态链接拦不住 | 第 2 章 2.4 |
| ASLR 是干什么的?它能防住栈溢出吗? | 只提高利用门槛、不能阻止;ASLR 与 NX、canary 各打断不同环节,且一个基址泄露即全失效 | 第 2 章 2.5 |
| 函数参数是怎么传递的?哪些寄存器由被调用者保存? | 前六个整数参数走 rdi/rsi/rdx/rcx/r8/r9;被调用者保存 rbx 与 r12~r15 | 第 2 章 2.6 |
| 栈溢出是怎么劫持控制流的?现在为什么很难利用了? | 覆盖 rbp+8 处的返回地址;canary/NX/ASLR/PIE 各打断一步,需绕过全部才能利用 | 第 2 章 2.6 |
| 网络字节序为什么规定为大端? | 规范成型时的历史选择,加上大端在抓包与规范书写上可读;转换只发生在收发边界 | 第 2 章 2.7 |
这个结构体的 sizeof 是多少?为什么不是字段大小之和? | 逐个成员列偏移算填充,并保证总大小是最大对齐的倍数;不能只背规则 | 第 2 章 2.8 |
| 内联函数为什么能提升性能?什么情况下反而更慢? | 主要收益是跨函数优化而非省调用开销;icache 压力与寄存器压力升高时会劣化 | 第 2 章 2.9 |
第 3 章 性能工程:从纳秒到毫秒|11 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 一次跨机房调用大概相当于多少次内存访问? | 约 0.5 ms 对 100 ns,是 10⁵ 倍即约 5000 次内存访问;由此推出第一杠杆是消除跨层访问 | 第 3 章 3.1 |
| 你怎么估算一个服务需要多少并发?请现场算一下。 | 用 L = λ × W,W 含排队;再校验利用率 ρ = λ × S 是否留出故障余量 | 第 3 章 3.2 |
| 吞吐和延迟是一回事吗?什么情况下加机器也提不高吞吐? | 延迟由最慢一环加排队决定,吞吐由瓶颈容量决定;串行段与下游容量才是上限 | 第 3 章 3.2 / 3.3 |
| 如果程序里有 5% 的代码不能并行,8 核能加速多少倍? | 上限约 1/0.05 = 20,8 核实际约 5.93 倍、效率 74%;不能拿上限当实际值 | 第 3 章 3.3 |
| Amdahl 定律和 Gustafson 定律矛盾吗? | 不矛盾:前者固定问题规模(强扩展),后者固定执行时间(弱扩展);分别对应延迟敏感服务与批处理 | 第 3 章 3.3 |
| 一台机器 CPU、内存、磁盘都不高但接口就是慢,你怎么查? | 排队与协调问题不体现在利用率上;先用 RED 定端点分位数,再用 USE 查资源,再用 off-CPU 查「在等谁」 | 第 3 章 3.4 / 3.7 |
| 平均响应时间 20 ms,这个服务算快吗? | 均值会被掩盖:99 × 10 ms + 1 × 1000 ms 均值 19.9 ms 但 P99 = 1 s;必须报分位数与样本量 | 第 3 章 3.5 |
| 一个接口调了 100 个下游,每个下游 P99 是 10 ms,接口 P99 大概是多少? | 1 − 0.99¹⁰⁰ ≈ 63.4%;独立性假设不成立时更差,对策是压 N、压 p、去相关、兜底 | 第 3 章 3.5 |
| 你做了个优化说快了 3 倍,怎么证明这个结论可信? | 声明固定配置、预热时长、样本量与窗口、压测机余量与 A/B 交叉执行,并用 Amdahl 检查收益与热点占比是否自洽 | 第 3 章 3.6 |
| 线上接口 P99 突然变高,你的排查步骤是什么? | 定义指标 → 取基线 → RED 定端点 → USE 定资源 → 剖析定代码 → 可证伪假设 → 单变量验证 → 回归与代价核算 | 第 3 章 3.7 |
| 优化之后除了看目标指标,还要回归什么? | 查全局无退化(P99 优化常抬高 P50)、极端场景(缓存失效、下游全挂、冷启动、数据放大 10 倍)与代价核算 | 第 3 章 3.7 |
第二篇 操作系统
第 4 章 进程、线程与写时复制|13 条
| 面试问法 | 一句话要点 | 章节 |
|---|
fork 一个 10 GB 的 Redis 进程,为什么毫秒级就完成了? | 只复制页表(约 21 MB)、数据页零复制;成本正比于映射页数与 VMA 数 | 第 4 章 4.2 / 4.3 |
| 写时复制具体怎么实现?内核在哪些地方动了手脚? | fork 把 PTE 的 W 位清零并失效 TLB,写入时缺页,内核按 _mapcount 判据决定复制或原地恢复可写 | 第 4 章 4.2 |
| COW 时引用计数怎么变?父进程的页表会改吗? | 区分 _refcount(总引用)与 _mapcount(映射数,COW 判据);父进程 PTE 不动 | 第 4 章 4.2 |
| 线程和进程在内核里到底差在哪? | 差别只在 clone 的 flags:CLONE_VM/FS/FILES/SIGHAND/THREAD 决定共享什么,CLONE_THREAD 隐含 CLONE_SIGHAND、后者隐含 CLONE_VM | 第 4 章 4.4 |
vfork 和 fork 有什么区别?为什么现在很少用? | vfork 不复制页表而是共享地址空间,子进程写会毁掉父进程的栈与已持有的锁 | 第 4 章 4.4 |
fork 之后马上 exec,中间为什么不能调 malloc? | fork 只复制调用线程,另一线程可能正持 malloc 锁且被冻结,子进程取锁即死锁 | 第 4 章 4.4 / 4.5 |
exec 替换旧映像时原来的内存怎么释放? | 按 VMA 与页表树整体拆除(exit_mmap 遍历 VMA),不是逐块 free;失败时旧映像完好 | 第 4 章 4.5 |
僵尸进程怎么产生的?kill -9 为什么杀不掉? | 僵尸只占 task_struct 与 PID,危害是 PID 表耗尽;它已无执行流,回收本质是父进程的读操作 | 第 4 章 4.6 |
我把 SIGCHLD 设成 SIG_IGN,子进程还会变成僵尸吗? | 不会,内核直接销毁子进程;但 SIG_DFL 与 SIG_IGN 语义不同,且此后 wait 返回 ECHILD | 第 4 章 4.6 |
装了 SIGCHLD handler,为什么线上还是大量僵尸? | 同类标准信号会被合并,必须 while + WNOHANG 循环回收,不能假设一子一信号 | 第 4 章 4.6 |
| 为什么守护进程要 fork 两次? | setsid 要求调用者不是进程组组长,双 fork 用「新建进程号」保证该条件必成立 | 第 4 章 4.7 |
| 一个进程起了 1000 个线程,会占多少内存? | 线程栈是虚拟保留加按需提交,不能按 8 MB × 1000 算物理内存;另有内核栈与 guard page | 第 4 章 4.8 |
| 有了线程池,为什么还要限制最大线程数? | 除了减少创建销毁开销,更重要的是把资源占用从「随请求数增长」改成「随池容量增长」 | 第 4 章 4.9 |
第 5 章 调度与 CPU 虚拟化|10 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 为什么说不存在完美的调度器? | 结构性而非工程性:最优策略依赖任务的未来行为(何时就绪、跑多久),未来不可知,故每个算法都有对抗负载 | 第 5 章 5.1 |
CFS 的 vruntime 是怎么算的?为什么它能实现按权重分配? | vruntime += 实际时间 × (1024/weight);两任务 vruntime 追平时 t_A/t_B = weight_A/weight_B | 第 5 章 5.3.1 |
| 为什么 CFS 用红黑树而不是堆? | 堆的取根同为 O(1);真正差别是删除任意键(阻塞、唤醒重入队、改优先级、迁移)在堆上要 decrease-key,红黑树 O(log n) 且取最小靠 rb_leftmost 缓存 | 第 5 章 5.3.2 |
一个进程睡了很久才醒来,它的 vruntime 很小,会不会独占 CPU? | 不会;睡眠期间 vruntime 不增长,正因如此才需要 place_entity() 把它拉到 min_vruntime 附近 | 第 5 章 5.4 |
一个 nice -20 一个 nice 19,各占多少 CPU? | CFS 无绝对优先级,按权重比分配(88761 : 15 ≈ 5917 : 1),低权重方仍拿非零份额 | 第 5 章 5.3.4 |
在一个核上跑 SCHED_FIFO 死循环会发生什么? | RT 队列非空时 CFS 队列根本不被查询;但默认 950000/1000000 会给普通任务留约 5% 带宽 | 第 5 章 5.5 |
| 多核上为什么有时绑核比自由迁移更快? | 绑核不减少切换次数,它减少跨核迁移造成的 TLB 冲刷与私有 cache 冷启动,主要优化尾延迟稳定性 | 第 5 章 5.6 |
上下文切换到底贵在哪一步?cs 高 us 却不高怎么判断? | 除保存恢复寄存器外还有 TLB 冲刷、L1/L2 污染、流水线前端冲刷;cswch 高指向 I/O 等待或 futex 竞争,nvcswch 高指向就绪队列过长或被 RT 抢占 | 第 5 章 5.7 |
| KVM 是全虚拟化还是半虚拟化?它怎么执行 guest 指令? | 依赖 VT-x,KVM_RUN 让物理 CPU 进非根模式原生执行,仅 VM exit 回 hypervisor;逐条翻译是 TCG 的行为 | 第 5 章 5.8 |
| CPU 是虚拟化里唯一需要虚拟化的资源吗? | 不是;内存(EPT + 超分 + KSM 的 COW)、I/O(virtio/VFIO/SR-IOV)、中断(虚拟中断控制器 + VT-d)、时间(TSC 直通与时钟偏差)都要处理 | 第 5 章 5.8 |
第 6 章 同步、锁与死锁|13 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 两个线程分别自增共享变量 100 万次,结果一定小于 200 万吗? | 不一定,竞态是「结果依赖不可控时序」;volatile 只保可见性不保原子性,改为「一定不大于 200 万」 | 第 6 章 6.1 |
| CAS 的 ABA 问题是什么?为什么 LL/SC 没有? | ABA 会撕裂无锁栈结构;LL/SC 有硬件关联,代价是虚假失败与 LL~SC 之间代码受限 | 第 6 章 6.2.1 |
x86 的 lock 前缀到底锁了什么? | P6 之前锁总线,之后是锁缓存行(RFO 加 MESI 独占);它同时自带全屏障 | 第 6 章 6.2.3 |
| 自旋锁和互斥锁怎么选?给我一个数字判据。 | 临界区 T_cs 与切换成本 1~5 μs 比较,分界约 1 μs;还要看多核、时间分布与自旋上界 | 第 6 章 6.3.2 |
| ticket lock 已经很公平,为什么高并发下还是不行? | 所有核自旋在同一个 now_serving 上抢缓存行;MCS 把自旋点搬到私有节点,释放时只失效一个核 | 第 6 章 6.3.4 |
| 信号量和互斥锁有什么区别?二元信号量能当互斥锁吗? | 信号量无所有权、可跨线程/进程、不可重入;当互斥锁误用会导致自死锁 | 第 6 章 6.4.1 |
条件变量为什么必须和互斥锁一起用?为什么必须 while? | 丢失唤醒的根因是非原子的等待;while 同时解决虚假唤醒与条件二次变假两条独立理由 | 第 6 章 6.4.2 |
x86 上为什么只需要 mfence? | lfence 管读序、sfence 管写序,sfence + lfence 不等于 mfence;编译期屏障不能替代 CPU 屏障 | 第 6 章 6.5 |
| 银行家算法怎么判断一个请求能不能批准? | 试分配后跑安全序列,而不只是比较 Request ≤ Available;「不安全状态」不等于「已死锁」,工程上很少用 | 第 6 章 6.6.3 |
| 什么是优先级反转?优先级继承和天花板有什么区别? | 中优先级线程间接压住高优先级线程;继承按需提升持锁者,天花板预先提升并限制可持锁集合 | 第 6 章 6.6.5 |
| 用了 CAS 就是无锁吗?无锁和无等待有什么区别? | 不同:无锁指系统级进展、无等待要求每线程有界步数;现实中多数结构只能做到无锁 | 第 6 章 6.6.6 |
| ABA 问题有哪几种解法?哪一种最彻底? | 加版本号只治标签;根因是节点被释放并复用,所以延迟回收(hazard pointer、RCU 宽限期、GC)最彻底 | 第 6 章 6.6.6 |
| 高并发下 CAS 循环为什么可能比加锁还慢? | RMW 需独占缓存行,失败者重新加载并重试,吞吐随核数下降;解法是分散热点(AQS 队列、LongAdder) | 第 6 章 6.6.6 |
第 7 章 文件描述符、VFS 与 I/O 多路复用|10 条
| 面试问法 | 一句话要点 | 章节 |
|---|
一个 int 类型的文件描述符在内核里到底代表什么? | 进程 fd 表 → struct file(含 f_pos/f_count/f_op)→ inode 三层;读写位置在中间层 | 第 7 章 7.1 |
父进程打开文件后 fork,父子进程的读写位置共享吗? | 共享,因为复制的是指向同一个 struct file 的指针;要独立就用 pread 或各自 open | 第 7 章 7.1.1 |
dup 和再 open 一次同一个文件,有什么本质区别? | dup 共享 f_pos 与 f_flags,因此无法单独把 dup 出的 fd 改成非阻塞;两次 open 完全独立 | 第 7 章 7.1.2 |
| 为什么硬链接不能跨文件系统,也不能指向目录? | inode 号只在单个文件系统 inode 表内唯一;目录硬链接会造环,破坏路径解析唯一性与单一父目录不变量 | 第 7 章 7.3.1 |
select 和 epoll 的差别到底在哪?为什么 poll 没有 1024 限制还是慢? | 拆成每次 O(n) 拷贝、内核 O(n) 扫描、用户态 O(n) 定位三层开销,epoll 分别用红黑树 + 就绪链表消除 | 第 7 章 7.4 |
epoll 为什么能只返回就绪的 fd?内部用什么数据结构? | epoll_ctl 维护红黑树,fd 就绪时 ep_poll_callback 把 epitem 挂入就绪链表,epoll_wait 只取链表 | 第 7 章 7.5 |
| 边缘触发(ET)为什么一定要把 fd 设成非阻塞? | 最后一次 read 只能靠 EAGAIN 结束,阻塞 read 会让线程卡死、事件循环停摆;read 返回 0 是 EOF | 第 7 章 7.6 |
write 返回成功,是不是说明数据已经安全落盘了? | 不是。write 只到 page cache,fsync 才到设备,设备写缓存策略决定是否真持久;这是组提交存在的原因 | 第 7 章 7.7 |
零拷贝有哪几种实现?sendfile 是 2 次还是 3 次拷贝? | 零拷贝指零 CPU 拷贝、DMA 拷贝必然存在;3 次与 2 次之分取决于网卡是否支持 scatter-gather DMA | 第 7 章 7.8 |
io_uring 相比 epoll 解决了什么问题? | epoll 解决「就绪通知」,io_uring 解决「发起与完成」;共享内存环形队列(SQ/CQ)消除系统调用本身 | 第 7 章 7.9 |
第 8 章 内存管理|11 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 虚拟内存到底解决了什么问题? | 三个独立需求:保护由 MMU 每次访存实施、容量幻觉来自按需分页、地址解耦来自页级映射 | 第 8 章 8.1 |
| 页大小为什么是 4 KB?换成 2 MB 会怎样? | 权衡内部碎片、页表开销与 I/O 粒度;巨大页把 TLB 覆盖范围乘上倍数,代价是换出与回收粒度变粗 | 第 8 章 8.2.1 |
| 为什么 64 位系统要用多级页表? | 单级需 2^(48−12) × 8 B = 512 GB,多级省的是地址空间的空洞;每级 9 位是为让一张表恰好一页 | 第 8 章 8.3 |
| TLB 命中还需要查页表吗?TLB 缺失的代价是什么? | 命中即完全绕过页表;缺失由硬件 PMH 走四级遍历,代价是多次访存 | 第 8 章 8.3.3 |
| 缺页有几种?段错误算缺页吗? | 四类;用 #PF 错误码的 P/W/U 位现场分派,段错误是「修不了的缺页」,内核不检查空指针 | 第 8 章 8.4 |
| FIFO 会不会出现内存越多缺页越多?为什么 LRU 不会? | FIFO 有 Belady 异常;LRU 与 OPT 满足栈性质,故缺页数随帧数单调不增 | 第 8 章 8.5.1 |
| 操作系统真的用 LRU 吗?精确 LRU 为什么不能用? | 精确 LRU 要求每次访存都更新序信息,与「翻译本为加速访存」直接冲突;实际用使用位 + Clock 与 active/inactive 双链 | 第 8 章 8.5.2 |
| 写时复制缺页和普通缺页有什么不同? | COW 在写入那一刻由缺页触发,且不需要磁盘 I/O;fork 只是把 PTE 清成只读并失效 TLB | 第 8 章 8.4 |
| 共享内存和 mmap 是什么关系? | 共享内存是「故意不 COW」:MAP_SHARED 让两个进程映射同一物理页,同步责任完全在使用者 | 第 8 章 8.8 |
| 为什么 free 之后内存没有还给操作系统? | 走堆的小块留在空闲链表,走 mmap 的大块可 munmap(约 128 KB 阈值),线程 arena 会保留 | 第 8 章 8.10 |
| 服务 P99 抖动,怎么判断是不是内存回收造成的? | 用 /proc/vmstat 的 allocstall/pgscan_direct 证明直接回收发生在分配路径上,而不是只看 si/so | 第 8 章 8.11 |
第 9 章 中断、系统调用与块层|11 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 为什么中断处理要分成上半部和下半部?上半部为什么必须短? | 三条物理约束:屏蔽同线中断、中断上下文的能做之事被限死、会打断任意临界区 | 第 9 章 9.2 |
| 软中断、tasklet、工作队列有什么区别? | 判据是运行上下文与「能否睡眠」;tasklet 在新内核已弃用,需要睡眠的活只能交给 workqueue | 第 9 章 9.2 |
ksoftirqd 是干什么的?为什么会有软中断风暴? | 它只是超出预算(约 2 ms、10 轮)后的承接者,per-CPU;风暴来自高频小包或重负载软中断处理 | 第 9 章 9.2.1 |
| 中断上下文为什么不能睡眠? | 没有任务实体,睡下去没人能唤醒它;判据是 in_interrupt() 的范围 | 第 9 章 9.3 |
| 网卡中断全压在 CPU0 上,你怎么查、怎么改? | /proc/interrupts 看分布、smp_affinity 改绑定、RSS 按流哈希分队列;单条流只能落一个队列 | 第 9 章 9.4 |
| 请完整说一次系统调用从用户态到内核再返回的路径。 | 寄存器约定(rax 号,rdi/rsi/rdx/r10/r8/r9 参数)→ MSR_LSTAR 入口 → pt_regs → sys_call_table 索引 → sysretq 返回 | 第 9 章 9.5 |
| 一次系统调用大概多贵?贵在哪里? | 约 100 ns ~ 1 μs;主要成本是特权级与地址空间切换及各类缓解措施,不是参数拷贝 | 第 9 章 9.6 |
gettimeofday 为什么不进内核?getpid 为什么不行? | vDSO 由内核写共享只读页、用户态直接读;仅对可无副作用读取的数据适用,seccomp 与时间命名空间下会失效 | 第 9 章 9.6.1 |
| 中断和系统调用谁更贵? | 中断更贵:它是不可预期的强制入口,需完整保存现场并可能打断临界区 | 第 9 章 9.7 |
| 信号被阻塞了会不会丢?未决和阻塞有什么区别? | 阻塞是「先收着、暂不投递」,忽略是「收到了不要」;标准信号不排队、未决只保留一个,实时信号才按值排队 | 第 9 章 9.8.1 |
fsync 保证了什么?它管不到什么? | 保证已提交数据到达设备;管不到硬件写缓存,父目录需单独 fsync | 第 9 章 9.9.3 |
第三篇 Linux
第 10 章 进程、信号与资源限制|12 条
| 面试问法 | 一句话要点 | 章节 |
|---|
kill -9 一定能杀死进程吗?杀不掉的情况有哪些? | 四类:D 态信号挂账、Z 态无执行流、PID 1 受内核保护、所有线程都在 D 态则无接收者 | 第 10 章 10.1.3 |
为什么 D 状态不能和 S 状态合并成一个「睡眠态」? | 唤醒条件集合不同(事件 ∪ 信号 vs 只有事件)、内核有不可半途而废的关键路径、负载统计只把 D 计入 | 第 10 章 10.1.1 |
ps 里的 VSZ 有几十 GB,是不是内存泄漏了? | VSZ 含未触碰的映射;判泄漏要看 RSS/PSS 的趋势,PSS 按映射者数均摊,故 Σ RSS 会超过物理内存 | 第 10 章 10.2.1 |
top 显示的 %CPU 是瞬时值还是平均值? | 默认是自启动以来的均值;区间值要用 Irix 模式或 pidstat;utime 单位是时钟节拍数,需除以 CLK_TCK | 第 10 章 10.2.2 |
/proc/PID/io 的 rchar 很大,是不是磁盘 I/O 压力大? | 不是,rchar 是经过 read 的字节数含 page cache 命中;物理块层口径是 read_bytes,两者可差几个数量级 | 第 10 章 10.3.2 |
| 阻塞和未决有什么区别?标准信号与实时信号有什么不同? | 阻塞是先收着暂不投递,SIG_IGN 才是不要;SIGRTMIN 之前按位合并,之后按值排队并可携带载荷 | 第 10 章 10.4.2 |
为什么信号处理函数里不能用 printf 和 malloc? | handler 可打断任意代码,被中断者可能正持 stdio 或分配器的锁,同线程非递归锁自锁后永久挂起;应设 sig_atomic_t 标志或走 self-pipe | 第 10 章 10.4.1 |
| 往一个已被对端关闭的 socket 写数据会发生什么? | 先产生 SIGPIPE,默认动作是终止进程;应忽略它并改用 EPIPE 判断 | 第 10 章 10.4.2 |
我已经把 ulimit -n 调到 65535,为什么还是 too many open files? | 生效层次是 limits.conf(仅 PAM 会话)→ systemd LimitNOFILE → 容器 --ulimit,非特权进程不能提高硬限制 | 第 10 章 10.5.1 |
| cgroup v1 和 v2 最大的区别是什么? | v1 每个控制器一棵独立树,v2 共用一棵统一树;cpu.max 是带宽配额,与 CPU 集合视图无关 | 第 10 章 10.6.1 |
| 容器里跑 Java,为什么线程池和 GC 线程数会开得过多? | nproc 仍显示宿主机核数,运行时读的正是后者(JDK 8u191 前完全忽略 cgroup);修法是 -XX:ActiveProcessorCount 或 cpuset | 第 10 章 10.6.2 |
docker exec 和 nsenter 有什么区别? | docker exec 走运行时并继承 seccomp/AppArmor,nsenter 直接 setns 且不继承安全策略;容器是共享宿主内核的普通进程 | 第 10 章 10.6.3 |
第 11 章 Linux 性能四板斧|13 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 平均负载是什么意思?那三个数字分别代表什么? | 可运行 R 加不可中断 D 的任务数,用指数衰减平均得到 1/5/15 分钟三值;要与核数相除才有意义 | 第 11 章 11.2 |
| 负载很高但 CPU 使用率很低,你会怎么查? | 三种成因:D 态 I/O 阻塞、线程频繁唤醒、cgroup 配额节流,每种各配一条验证命令 | 第 11 章 11.2.3 |
wa 很高是不是说明磁盘到瓶颈了? | 不是。wa 是「CPU 空闲且有人在等本地块设备 I/O」,归因给 CPU 不归因给盘;NFS 等待不计入 | 第 11 章 11.3.3 |
%util 到 100% 了,是不是磁盘瓶颈? | 不是。它只是「设备至少有一个 I/O 在处理的时长占比」,不带余量信息;判据是 await 对比基线加 aqu-sz | 第 11 章 11.6.2 |
| 上下文切换多少算高?高了一定是坏事吗? | 要按核数摊薄、按自愿/非自愿分工,并结合 CPU 使用率:cs 高而 us 低指向锁竞争或唤醒风暴 | 第 11 章 11.4 |
free 显示内存快用完了,是不是要扩内存? | 看 available 占 total 的比例;page cache 可回收,buff/cache 是合并列且 shared 已计入 cache | 第 11 章 11.5.1 |
| 你怎么判断系统是不是在用 swap? | 唯一可靠信号是 vmstat 的 si/so 是否持续非零;Swap used 只是存量,反映历史压力 | 第 11 章 11.5.2 |
| 云主机上同样的代码比物理机慢,你怎么定位? | 先看 %steal(虚拟化下才有、裸机恒为 0);它在容器内不可见,要从宿主机或节点监控读 | 第 11 章 11.3.5 |
| 你的服务 P99 突然变高但 P50 正常,怎么排查? | 说明只有部分请求命中某条慢路径;用四板斧逐层收敛到具体阻塞点,而不是归因于整体资源不足 | 第 11 章 11.9 |
perf top 能看到进程在等锁吗? | 不能。它采样的是正在 CPU 上执行的栈,等锁与等 I/O 的时间不在任何 CPU 上,要用 off-CPU 火焰图或调度事件追踪 | 第 11 章 11.8 |
| 你怎么判断一块磁盘的延迟是不是异常? | 先要有同型号在给定请求大小与队列深度下的基线;读写分开看 r_await/w_await | 第 11 章 11.6.3 |
netstat -s 里重传数很大,怎么判断是不是问题? | 取增量算重传率再比阈值;要区分链路丢包与对端窗口不足 | 第 11 章 11.7.2 |
listen queue overflow 涨了,是网络问题还是应用问题? | 已完成三次握手的连接因 accept() 不及时被丢弃,属于应用并发不足或 accept 循环被阻塞 | 第 11 章 11.7.2 |
第 12 章 内存、OOM 与 swap|9 条
| 面试问法 | 一句话要点 | 章节 |
|---|
free 显示 MemFree 只剩几百 MB,这台机器内存不够用了吗? | 不一定。要拆 Cached + Shmem + SReclaimable,注意 Shmem 无 swap 时不可回收 | 第 12 章 12.1 |
| OOM killer 会杀掉哪个进程? | 按 oom_badness() 打分,受 oom_score_adj 影响;cgroup 场景候选集只限本组 | 第 12 章 12.4 |
| 一个进程持续写文件,为什么写入延迟会周期性出现尖峰? | 达到 dirty_ratio 后由写进程自己同步回写,形成周期性停顿;不是单纯磁盘瓶颈 | 第 12 章 12.3 |
数据库为什么喜欢用 O_DIRECT?它比 buffered I/O 快吗? | 绕过的是 page cache 不是内核;代价是失去预读、需对齐、失去写合并,只有应用自带缓存时才划算 | 第 12 章 12.3 |
oom_score 是怎么算出来的?slab 算不算? | 由 RSS、swap、页表等折算,再乘 adj × totalpages / 1000;slab 不在公式里 | 第 12 章 12.4 |
为什么 fork 一个大进程之后,要过一会儿才 OOM? | COW 只复制页表,物理页在写入时才复制,占用逐步增长;这也是 Redis BGSAVE 建议 overcommit_memory=1 的原因 | 第 12 章 12.5 |
vm.swappiness 是什么?设成 0 是不是就不用 swap 了? | 它是 anon : file 的扫描权重、量纲到 200,设为 0 仍会换出;不是开关 | 第 12 章 12.6 |
| 服务 RSS 一直在涨,你怎么判断是不是内存泄漏? | 用 smaps_rollup 的 Anonymous/Private_Dirty 单调性做判据;valgrind 对 JVM/Go 无效 | 第 12 章 12.7 |
容器里 free 显示还有几十 GB 空闲,为什么容器被 OOM 杀了? | /proc/meminfo 本就没做 namespace 隔离;权威来源是 cgroup 的 memory.current/memory.max/memory.events | 第 12 章 12.8 |
第 13 章 文件系统、磁盘与 I/O 排查|8 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| ext4 的三种日志模式有什么区别?生产上一般用哪种? | writeback 的坏数据形态是「新元数据 + 旧块内容」;ordered 保证的是顺序而非数据进日志,一致性靠它、持久性靠 fsync | 第 13 章 13.1.1 |
df 和 du 的结果对不上,可能是什么原因? | 从 statfs 全局计数与按名字遍历的差异统一解释三类根因,并补上保留块;稀疏文件是 ls -l 与 du 的差异 | 第 13 章 13.3 |
已经 rm 掉的日志文件为什么空间没释放? | unlink 只删目录项,fd 仍持引用使 i_nlink 为 0 而块不回收;用 lsof -nP +L1 或 /proc/[pid]/fd 找 | 第 13 章 13.3 |
| RAID 5 的随机写惩罚为什么是 4?数据库为什么推荐 RAID 10 而不是 RAID 5? | 小随机写要读旧数据、读旧校验、写新数据、写新校验,整条带写时接近 1;RAID 10 写 IOPS 高 2 倍且重构期风险小,代价是容量腰斩 | 第 13 章 13.5.2 / 13.5.5 |
| 为什么测硬盘 IOPS 必须同时说明队列深度和块大小? | IOPS = Q / L、吞吐量 = IOPS × 块大小;4 KiB 与 1 MiB 是两套独立上限,不 direct 就是测内存 | 第 13 章 13.6.1 |
| ext4 的 inode 用完了能扩吗?NVMe SSD 上为什么还要关心 TRIM? | inode 表在 mkfs 时固定,只能扩容后靠 resize2fs 补建;TRIM 不加快删除,它让设备知道块已失效,减少 GC 搬运与写放大 | 第 13 章 13.1.3 / 13.6.4 |
服务报 too many open files,你怎么排查? | 分 EMFILE(进程级)与 ENFILE(系统级),用 fd 数的单调性区分「配置不足」与「泄漏」 | 第 13 章 13.8 |
iostat 里 %util 到 100% 是不是磁盘到瓶颈了? | 深队列设备上饱和不代表余量耗尽;应看 aqu-sz 与 await 的组合 | 第 13 章 13.6.3 |
第 14 章 命令行与 Shell 速查|13 条
| 面试问法 | 一句话要点 | 章节 |
|---|
一行日志出现多次 ERROR,grep -c 统计的是次数还是行数? | 行数。要计次数必须 grep -o ... | wc -l;grep -v -c 数的是补集行数 | 第 14 章 14.2.1 |
为什么 uniq -c 一定要写在 sort 后面? | uniq 只看相邻行,常量内存正是这个取舍换来的;不排序则非相邻重复项计数全是 1 | 第 14 章 14.4.2 |
| 80 GB 日志、2 GB 内存,怎么统计 TOP10 IP? | 区分 sort 的外部归并(吃磁盘)与关联数组(吃键基数);可用提前 head 终止上游 | 第 14 章 14.4.3 |
find ... -exec cmd {} \; 和 {} + 有什么区别? | \; 每文件一次 fork/exec,+ 按 ARG_MAX 分批;差几个数量级,且 {} 必须在末尾 | 第 14 章 14.5.1 |
文件名里有空格,find | xargs rm 会出什么问题? | xargs 默认按空白与引号分词,会拆错文件名;必须 -print0 | xargs -0,或用 -exec ... + 绕开 | 第 14 章 14.5.2 |
cmd > f 2>&1 和 cmd 2>&1 > f 一样吗? | 不一样。重定向是 fd 快照式的逐步求值,顺序决定 stderr 最终指向哪里,这是「日志里没有报错」的根因 | 第 14 章 14.6.1 |
脚本开头写 set -e 就能保证出错就停吗? | 不能。if/while/&&/||/! 上下文不生效,local x=$(cmd) 掩盖失败,管道要 pipefail 才暴露前段失败 | 第 14 章 14.7.1 |
"$@" 和 "$*" 有什么区别?转发参数该用哪个? | $* 用 IFS 首字符拼成一个词;未加引号的 $@ 会二次分词与通配符展开,转发必须 "$@" | 第 14 章 14.7.2 |
为什么 ss 比 netstat 快这么多? | ss 走 NETLINK_SOCK_DIAG 由内核侧过滤,netstat 读 /proc/net/tcp 再遍历 /proc/*/fd,开销与进程数 × fd 数相关 | 第 14 章 14.3.1 |
shell 里判断进程是否存在,kill -0 $pid 可靠吗? | 不可靠:EPERM 说明进程在但没权限;僵尸进程对它仍返回成功 | 第 14 章 14.3.2 |
| 同一个循环里累加的变量,循环结束后为什么变成 0 了? | cmd | while 的循环体在子 shell 中执行;改用 while ... done < <(cmd) 或 lastpipe 保留状态 | 第 14 章 14.6.2 |
[[ ]] 和 [ ] 该用哪个? | [[ ]] 是语法关键字,内部不做分词与通配符展开,支持 =~ 与 &&;[ ] 是 test 命令需逐处加引号 | 第 14 章 14.7.3 |
| 怎么用一条命令求出 Nginx 日志的 TOP10 IP 与每个 IP 的 TOP1 URL? | 两段 `sort | uniq -c |
第四篇 计算机网络
第 15 章 体系结构与分层|13 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 分层到底解决了什么问题,又带来了哪些坏处? | 本质是接口约定与关注点分离;代价是头部开销(IPv4 + TCP 至少 40 字节)、跨层优化受阻、中间设备越层窥探 | 第 15 章 15.1 |
| OSI 七层和 TCP/IP 四层怎么对应,工程里为什么讲五层? | 表示层与会话层没有独立协议实体,职能并入应用层;OSI 术语体系仍被保留 | 第 15 章 15.2 |
| 浏览器发起一次请求,从应用层到网线上发生了什么? | 目的 MAC 只指向下一跳并逐跳重写,IP 除 NAT 外端到端不变;路由器不看端口号 | 第 15 章 15.3 |
| 交换机和路由器各解封到哪一层? | 交换机二层、路由器三层;NAT 与七层负载均衡必须越层,这是绕不开的例外 | 第 15 章 15.4 |
| MSS 是多少,怎么算出来的? | MSS = MTU − 20 − 20 = 1460,带 12 字节时间戳选项为 1448;由接收方在 SYN 中声明,发送方取最小值 | 第 15 章 15.5.1 |
| IP 分片和 TCP 分段有什么区别?为什么 TCP 不愿意被分片? | 分段在传输层、分片在网络层;片偏移单位 8 字节,丢一片则整个报文重传,还有分片重叠的攻击面 | 第 15 章 15.5.2 |
| 大文件上传到一半就卡住,小请求却正常,可能是什么原因? | PMTUD 黑洞:设了 DF = 1 而「需要分片」的 ICMP 被防火墙丢弃,大包静默丢失;对策是黑洞检测与 MSS 钳制 | 第 15 章 15.5.3 |
| 有了 IP 地址为什么还需要 MAC?ARP 怎么工作? | IP 用于端到端选路、MAC 用于同一广播域内逐跳投递;ARP 请求广播、应答单播,缓存无验证故可被欺骗 | 第 15 章 15.6 |
| NAT 是怎么让多台内网主机共用一个公网 IP 的? | 靠 NAPT 用端口区分:五元组建表、回程需命中表项、表项超时因此要保活 | 第 15 章 15.7.1 |
| 为什么 P2P 和 WebRTC 有时候连不上? | 锥型 NAT 可打洞,对称型不可行必须 TURN 中继;STUN 只做地址发现,ICE 负责编排与优先级 | 第 15 章 15.7.3 |
| 拥塞控制为什么不能放到路由器上做? | 路由器只有单包视图、无法维护逐流状态、无法可靠通知发送方;ECN 只标记不决策正是这个折中 | 第 15 章 15.8 |
| 为什么加带宽不能让 ping 变快? | 探测的是传播时延,只由距离与介质决定:同城 100 km 光纤约 0.5 ms、跨大洲 15000 km 约 75 ms | 第 15 章 15.9 |
| 什么是带宽时延积,为什么跨大洲传大文件要开窗口扩大因子? | BDP = 带宽 × RTT,吞吐上界 = 窗口/RTT;65535 字节窗口在 RTT = 150 ms 时只能跑到约 3.5 Mbps | 第 15 章 15.9.3 |
第 16 章 TCP 连接管理|14 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 为什么 TCP 三次握手不能改成两次? | 连接是双方共同状态;延迟 SYN 到达时客户端已无状态,两次握手下服务端会误建连接,客户端回 RST | 第 16 章 16.3 |
| 为什么三次握手不是四次? | 标志位可合并:SYN 与 ACK 能同时置位,且同时打开与延迟确认捎带都不违反任何约束 | 第 16 章 16.2 |
| TCP 的初始序号为什么要随机化? | 两条独立理由:防旧报文误收与防序列号猜测攻击(命中概率约 1/2³²) | 第 16 章 16.2 |
| 第三次握手可以携带数据吗? | 协议允许但默认不发;因服务端 cwnd 初值小,丢包会连带重传数据 | 第 16 章 16.2 |
ss -lnt 里 LISTEN 状态的 Send-Q 和 Recv-Q 是什么意思? | Recv-Q 是 accept 队列当前长度,Send-Q 是它的上限;贴上限即溢出 | 第 16 章 16.4 |
CLOSE_WAIT 很多是什么原因? | 应用没调 close() 的代码 bug,不是内核参数问题;按连接池的 finally/try-with-resources 定位 | 第 16 章 16.6.2 |
TIME_WAIT 为什么必须有,为什么是 2MSL? | 两条理由:重发最后的 ACK;让旧报文在网络中消失以免污染同四元组新连接。MSL 是单程,故需 2 倍 | 第 16 章 16.6.3 |
SO_REUSEADDR 能不能减少 TIME_WAIT? | 不能。它只解决 bind 被 TIME_WAIT 拒绝的问题,TIME_WAIT 数量不变 | 第 16 章 16.6.4 |
tcp_tw_recycle 为什么被移除? | NAT 后多客户端共源 IP 而时间戳不同步,服务端按时间戳丢弃正常 SYN 导致随机连不上 | 第 16 章 16.6.4 |
syncookies 是怎么防 SYN Flood 的?代价是什么? | 把连接状态编码进 ISN 的 MAC 实现无状态化;代价是选项协商退化、cookie 有效期有限 | 第 16 章 16.5 |
shutdown(SHUT_WR) 和 close 有什么区别? | shutdown 只关发送方向、接收仍可读;close 是减引用计数,dup/fork 共享时可能根本不发 FIN | 第 16 章 16.7 |
SO_REUSEADDR 和 SO_REUSEPORT 有什么区别? | 前者管能否 bind;后者是内核按四元组哈希做负载均衡、消除惊群 | 第 16 章 16.7 |
服务端大量连接处于 SYN_RCVD 说明什么? | 三种成因:客户端不回第三次 ACK(可能有防火墙丢包)、真实 SYN Flood、tcp_max_syn_backlog 偏小 | 第 16 章 16.4 |
| 同时关闭时状态怎么走? | FIN_WAIT_1 → CLOSING → TIME_WAIT,前提是双方都成为主动关闭方 | 第 16 章 16.6.1 |
第 17 章 TCP 可靠传输与拥塞控制|8 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| TCP 的 RTO 是怎么算出来的?为什么不能直接用平均 RTT? | 要平滑 SRTT 并累加偏差量 RTTVAR,RTO = SRTT + 4 × RTTVAR;4 × 是工程裕度而非推导值 | 第 17 章 17.2.2 |
| 重传之后还能用这次 RTT 更新 RTO 吗? | Karn 算法禁止用;时间戳选项(TSecr 回显)解除了该限制,且退避值不参与平滑公式 | 第 17 章 17.2.3 |
| 为什么快速重传要等 3 个重复 ACK?窗口字段只有 16 位怎么撑起长肥管道? | 阈值是误重传与恢复速度的权衡,SACK 时代它退化为保底计数;带宽靠窗口扩大因子 wscale,它只在握手协商且上限 S ≤ 14 | 第 17 章 17.3.1 / 17.4.3 |
| 接收方通告了零窗口,发送方就一直等着吗? | 不能等,窗口更新会丢且不重传形成死锁;要靠 persist timer 周期性发 1 字节探测 | 第 17 章 17.4.2 |
为什么很多 RPC 框架默认设置 TCP_NODELAY? | Nagle 与延迟 ACK 互等:客户端等在途数据的 ACK,服务端等第二个段才回 ACK,形成最长约 40 ms 的延迟 | 第 17 章 17.5.3 |
| 超时重传和快速重传对拥塞窗口的影响有什么区别? | 超时意味着一个 RTO 内没有任何 ACK、信号彻底中断,故 cwnd = 1 MSS 重新慢启动;3 dup ACK 说明网络仍在工作,砍半即可 | 第 17 章 17.6.3 |
| CUBIC 比 Reno 强在哪? | 增长函数 W(t) = C × (t − K)³ + W_max 的横轴是真实时间因而不受 RTT 影响;接近 W_max 时反而更慢,β = 0.7 比 Reno 的 0.5 温和 | 第 17 章 17.7.1 |
| 为什么无线链路上 TCP 表现差?BBR 怎么解决? | 随机误码被误判为拥塞导致无谓减半;BBR 用 BtlBw 与 RTprop 建模、不因丢包降窗,代价是公平性争议 | 第 17 章 17.7.2 |
第 18 章 HTTP 演进与 HTTPS|7 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| HTTP/1.1 的队头阻塞到底是什么?为什么浏览器要开 6 个连接? | 应用层串行的根因是响应没有请求 ID、无法重排;开多连接是绕过而非解决 | 第 18 章 18.3 |
| HTTP/1.1 的管线化看起来能提升性能,为什么实际没人用? | 响应必须按序返回、中间设备兼容性差、缺少请求标识导致出错无法恢复 | 第 18 章 18.2.2 |
| 域名分片能提升并发,为什么 HTTP/2 下反而要避免? | 分片会让 HPACK 动态表与多路复用失效,应把域名收敛回主域 | 第 18 章 18.3 |
| HTTP/2 的多路复用怎么实现?它已经多路复用了,为什么还需要 HTTP/3? | 9 字节帧头含 31 位 Stream ID,帧属于流、消息由帧组成;但单条 TCP 连接丢一个包会阻塞所有流,多开连接又破坏多路复用前提 | 第 18 章 18.4 / 18.5 |
| QUIC 为什么要基于 UDP?它不怕不可靠吗? | 中间设备对 TCP 扩展与协议号僵化,QUIC 在用户态自己实现重传、拥塞控制与流控,比 TCP 更复杂 | 第 18 章 18.6 |
| 0-RTT 有什么风险?什么请求可以用? | 可重放且对 0-RTT 数据无前向保密,只能用于幂等无副作用的请求 | 第 18 章 18.6.3 |
| SNI 是干什么的?它有什么隐私问题? | HTTP 的 Host 在加密之后、握手时看不到域名,故需 SNI 指明证书;它明文泄露访问域名,需要 ECH 缓解 | 第 18 章 18.9.3 |
第 19 章 DNS、CDN 与网络排障|9 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 请完整讲一下 DNS 的解析过程,递归和迭代分别发生在哪一段? | 本地 DNS 服务器是分界点:客户端到它通常递归(RD/RA 标志),它到根与权威是迭代,根服务器从不做递归 | 第 19 章 19.1.1 |
| 为什么 CNAME 记录不能和 MX 记录共存? | 名字一旦是 CNAME 就不能承载其他类型;根域名/区顶点必须有 NS 与 SOA,工程替代是 ANAME/ALIAS | 第 19 章 19.2.2 |
| 我们准备把服务切到新机房,为什么上线前要先把 TTL 调小? | TTL 是「最坏生效上界」,必须提前一个旧 TTL 时长调整;代价是解析器查询量上升 | 第 19 章 19.1.3 |
| 用 DNS 做负载均衡有什么问题? | 摘除受 TTL 约束,DNS 协议里没有回传服务端负载的字段;它只适合机房级粗粒度分流 | 第 19 章 19.3 |
ping 通是不是说明服务就是好的?traceroute 里的 * 是不是说明那一跳坏了? | ICMP 常被策略丢弃,通只说明网络层可达;* 只表示该跳不回超时报文,有意义的是末跳丢包 | 第 19 章 19.8 |
| 用户反馈访问慢,你怎么排查? | 先把问题切到 DNS → 建连 → TLS → TTFB → 传输五段,用 curl -w 的 time_* 差值定位,并多次采样 | 第 19 章 19.6 |
| 抓包看到响应很慢,能说明是服务端慢吗? | 不能,还要看抓包点、中间链路与客户端接收窗口;应在两端同时抓并比对时间戳 | 第 19 章 19.7 |
time_appconnect 是 0 说明 TLS 没耗时吗?CDN 命中率下降从哪查? | 所有 time_* 都是累计量且非 TLS 请求为 0,必须做减法;命中率要查随机参数、Vary/Cookie、Cache-Control 与请求合并 | 第 19 章 19.6.1 / 19.5 |
为什么 dig 比 nslookup 更适合排障? | 因为 nslookup 不显示 flags/TTL/AUTHORITY 段,无法判断答案来自缓存还是权威;dig +trace 才能复现迭代链路 | 第 19 章 19.4 |
第五篇 数据库
第 20 章 MySQL 架构与存储引擎|8 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| MySQL 的逻辑架构分几层?一条查询经过哪些组件? | 连接层 → SQL 层(解析、预处理、优化、执行)→ 存储引擎层;权限校验在预处理阶段,执行器要对每行做条件复核 | 第 20 章 20.1 |
| 查询缓存为什么在 MySQL 8.0 被删掉了? | 失效是表级的,写密集场景命中率趋零,且它还占一次全局互斥 | 第 20 章 20.1.2 |
| 优化器为什么会选错索引? | 统计信息来自索引页随机采样(默认 20 页),回表代价被按内存成本折算,代价模型与真实 I/O 有偏差 | 第 20 章 20.3 |
ANALYZE TABLE 到底做了什么?能随便在生产上跑吗? | 重新采样并写统计表;大表上耗时与表大小成正比且持表级 MDL,会阻塞 DDL 甚至引发排队雪崩 | 第 20 章 20.3.4 |
| InnoDB 的 buffer pool 是怎么淘汰页的? | 分段 LRU 加防扫描污染:young/old 分区,innodb_old_blocks_time 控制「读入后多久才可晋升」,避免全表扫描冲刷热数据 | 第 20 章 20.5.1 |
| change buffer 为什么不能用于唯一索引? | 唯一性必须在写入前读页确认,页既然已读入就无需再缓冲;写多读少且二级索引多时收益最大 | 第 20 章 20.5.2 |
| InnoDB 的页里到底长什么样?页内怎么找到一条记录? | Infimum/Supremum 伪记录加 next_record 单向链表,Page Directory 每槽 4 ~ 8 条记录,两段式查找(目录二分加链内遍历) | 第 20 章 20.7 |
一条 UPDATE 语句从接收到落盘,中间发生了什么? | 改 buffer pool、写 undo、写 redo(prepare)、写 binlog、提交;顺序由两阶段提交的崩溃恢复要求决定 | 第 20 章 20.9 |
第 21 章 redo log、undo log 与 binlog|10 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 为什么数据库要先写日志再写数据? | 两条独立理由:5 个脏页 5 次随机写变成 1 次顺序写;避免部分页写成功造成的物理不一致。日志落盘后脏页可随便延迟刷,这才是 D | 第 21 章 21.1.1 |
innodb_flush_log_at_trx_commit 设成 0、1、2 分别会丢多少? | 0 连 write 都不做(进程崩溃也丢);2 进了 OS page cache(进程崩溃不丢、掉电丢);1 每次提交刷盘 | 第 21 章 21.1.2 |
| redo log 到底是物理日志还是逻辑日志? | physiological:页内物理加操作逻辑;这样既重放幂等又能让日志足够短 | 第 21 章 21.2.1 |
| LSN 有什么用?它在哪些地方出现? | 页头的 FIL_PAGE_LSN 判断该页是否已刷盘,checkpoint LSN 界定重放起点,形成 write pos/checkpoint 环形关系 | 第 21 章 21.2.2 |
| redo log 写满了会发生什么? | 推进 checkpoint 并强制刷脏,期间新写入阻塞等待;日志容量应容纳 1 ~ 2 小时写入量 | 第 21 章 21.2.3 |
| 组提交是怎么提升性能的?undo log 有什么作用? | 组提交把多事务的刷盘与 fsync 合并,单事务延迟不降反升;undo 既回滚事务,又为 MVCC 快照读提供历史版本,长事务会使它膨胀 | 第 21 章 21.2.4 / 21.3 |
| 为什么 insert undo 提交后能立刻删,update undo 不行?binlog 三种格式怎么选? | 判据是活跃 ReadView 是否还需要旧版本,不是日志大小;binlog 选 ROW 因为 STATEMENT 下 NOW() 与无 ORDER BY 的 LIMIT 重放会不一致 | 第 21 章 21.3.2 / 21.4.1 |
| 两阶段提交过程中崩溃,MySQL 怎么知道该提交还是回滚? | 决断依据是「redo 处于 prepare 且 binlog 完整」,而不是只看 redo 有没有 commit 标记;靠 binlog 校验和判断事件组完整性 | 第 21 章 21.5.2 |
| 为什么必须做两阶段提交?不做会怎样? | 先 binlog 后 commit 会让从库多数据;先 commit 后 binlog 会让从库少数据。前者更危险,破坏了「从库可由主库重建」的底线 | 第 21 章 21.5.4 |
| 崩溃恢复的完整流程是什么? | checkpoint → 前滚 redo → 处理 prepare 事务 → 按 binlog 决断 → undo 回滚未提交;顺序写错或漏掉第 3 步就会不一致 | 第 21 章 21.7 |
第 22 章 事务、隔离级别与 MVCC|12 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 事务有 ACID 四个特性,各自靠什么机制实现? | A 靠 undo、D 靠 redo 加 fsync、I 靠锁加 MVCC、C 靠约束与触发器;一致性是目标而非机制 | 第 22 章 22.1 |
| MVCC 是怎么实现的?请把用到的数据结构说完整。 | 三要素:隐藏字段 DB_TRX_ID 与 DB_ROLL_PTR、undo 版本链、ReadView;DB_ROW_ID 只在无主键时存在 | 第 22 章 22.3 |
| ReadView 里有哪几个字段?讲一下可见性判断的过程。 | m_ids/min_trx_id/max_trx_id/创建者事务 ID;判断边界是与 min_trx_id、max_trx_id 比较再查 m_ids,不可见则沿 DB_ROLL_PTR 回溯到可见版本为止 | 第 22 章 22.4 |
| RC 和 RR 的差别到底在哪? | 可见性判断算法完全相同,差别只在 ReadView 的创建时机;且 RR 的 ReadView 是第一次快照读时建的,不是 BEGIN 时 | 第 22 章 22.5 |
| 那 RR 隔离级别下还会不会出现幻读? | 会。当前读绕过 ReadView:快照读 3 行、FOR UPDATE 看到 4 行、再快照读又回到 3 行 | 第 22 章 22.6.1 |
| 为什么 MySQL 默认是 RR 而 PostgreSQL 默认是 RC? | MySQL 早期 STATEMENT 复制的约束要求 RR;PG 用语句级快照且 vacuum 压力不同,故默认 RC | 第 22 章 22.2.1 |
| 长事务有什么危害? | undo 旧版本无法回收、purge 被挡住、危害波及其后所有事务;只读长事务同样有害且更隐蔽 | 第 22 章 22.7 |
| 为什么用 RC 的时候要把 binlog 格式设成 ROW? | STATEMENT 记录 SQL 文本、从库重放要重新求值,而 RC 下同一事务两次读结果可能不同,重放不一致 | 第 22 章 22.8 |
innodb_autoinc_lock_mode = 2 会不会导致主从自增 ID 不一致? | 模式 2 只在 binlog_format = STATEMENT 下不安全;配 ROW 时自增值随行镜像一起记录,8.0 默认就是 2 | 第 22 章 22.8 |
| 我把隔离级别设成 READ UNCOMMITTED,为什么脏读好像没生效? | InnoDB 下 RU 的快照读不做可见性判断直接读最新版本,脏读确实存在;隔离级别只是「读的规则」,不取消行锁,写—写仍互斥 | 第 22 章 22.2 |
同一事务里先 SELECT 再 UPDATE,为什么影响行数和查到的不一样? | SELECT 是快照读、UPDATE 是当前读,看到的数据集天然可以不同;唯一性校验必须靠唯一索引而不是「先查再插」 | 第 22 章 22.6 |
一条 UPDATE 没走索引会锁多少行? | 会锁住扫描路径上的全部记录与间隙;这是「当前读 + 无索引」组合下锁范围膨胀的直接后果 | 第 22 章 22.6 |
第 23 章 索引原理与设计|11 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 为什么 MySQL 的索引用 B+ 树而不是 B 树? | B 树数据散落各层导致范围查询要中序遍历,且非叶存数据使扇出变小、树更高 | 第 23 章 23.1 |
| 三层 B+ 树大概能存多少行?为什么查询是稳定的? | 按「扇出 × 扇出 × 每页行数」估算;所有数据同层意味着任何查询的路径长度恒定 | 第 23 章 23.2 |
什么是回表?为什么 SELECT * 会影响性能? | 二级索引叶页只存索引列加主键,取其他列要按主键再查聚簇索引;SELECT * 会破坏覆盖索引 | 第 23 章 23.3 |
| 为什么推荐自增主键而不是 UUID? | 随机插入导致页分裂与填充率下降;且二级索引要复制主键,主键越长索引体积越大 | 第 23 章 23.4 |
| 最左前缀原则是怎么来的? | 联合索引按 (a,b,c) 排序:a 全局有序、b 只在 a 固定时有序,因此接上范围条件后后续列无法用于定位 | 第 23 章 23.6 |
WHERE a = 1 AND b > 2 AND c = 3 用得上 (a,b,c) 索引吗? | 只有 a 参与定位、b 用于范围,c 只能靠索引下推在引擎层过滤,无法再缩小扫描区间 | 第 23 章 23.7 |
Using index 和 Using index condition 有什么区别? | 前者是覆盖索引、根本不需要回表;后者是索引下推、减少了回表次数但仍要回表 | 第 23 章 23.5 |
为什么在索引列上用函数或用 LIKE '%x' 会让索引失效? | 索引是原值的有序副本;函数结果在索引中不存在,前导通配符无法定位起点 | 第 23 章 23.8 |
只要出现 != 或 NOT IN 索引就失效吗? | 不是。这是优化器基于选择性的代价判断:排除少量行时仍会走索引 | 第 23 章 23.8 |
| 联合索引的列顺序怎么定? | 等值列在前、范围列靠后、排序列要能接上;区分度最高的列不一定放最前,低区分度等值列有时反而应靠后 | 第 23 章 23.9.1 |
既然哈希索引等值查找是 O(1),为什么 InnoDB 不做显式哈希索引? | 它不支持范围与排序,且需独立持久化与恢复维护;自适应哈希只是可丢弃的内存缓存 | 第 23 章 23.10 |
第 24 章 锁、加锁规则与死锁|11 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 为什么 RR 需要间隙锁,而 RC 不需要? | RC 每条语句重取快照、当前读不再承诺范围稳定;RC 下间隙锁被关闭是高并发写入系统主动降级隔离级别的第一动机 | 第 24 章 24.3 |
| 唯一索引和非唯一索引做等值查询,加锁范围一样吗? | 不一样。非唯一索引必须向后扫到第一个不满足条件的记录才敢停,锁范围多出一整段区间 | 第 24 章 24.4 |
SELECT ... FOR UPDATE 锁不住新插入的行,那 RR 怎么防幻读? | 当前读不走快照,靠间隙锁挡住 INSERT;间隙锁之间兼容,只有插入意向锁与它冲突 | 第 24 章 24.3 |
一条 ALTER TABLE 为什么能把整张表的查询打死? | MDL 队列不插队:写锁排到队首后,后面兼容的读锁请求也全部排队;长事务才是持锁者 | 第 24 章 24.2 |
mysqldump --single-transaction 为什么可以不加全局锁? | 靠一致性快照,但只对事务型表有效、必须在 RR 下、期间不能有 DDL,且仍持 MDL 读锁会挡住 DDL | 第 24 章 24.1 |
INSERT 为什么不加显式锁,别的会话怎么被它挡住? | 插入意向锁彼此兼容、不提供互斥;靠记录头 trx_id 形成的隐式锁,由后续当前读的会话代为加锁 | 第 24 章 24.6 |
REPLACE INTO 和 INSERT ... ON DUPLICATE KEY UPDATE 该选哪个? | REPLACE 有删除语义:主键会变、自增跳号、会触发级联删除、加锁范围更大 | 第 24 章 24.6.3 |
死锁了怎么办?把 innodb_deadlock_detect 关掉行不行? | 关掉后死锁仍在,只是靠 innodb_lock_wait_timeout(默认 50 s)兜底,会挂满连接池;环检测在热点行上 CPU 会膨胀 | 第 24 章 24.7 |
| 乐观锁和悲观锁你怎么选? | 冲突率低用 CAS、高用悲观锁、能用唯一索引就把串行化下沉到数据库;高冲突下乐观锁会退化成重试风暴 | 第 24 章 24.8 |
innodb_lock_wait_timeout 和 lock_wait_timeout 有什么区别? | 前者管 InnoDB 行锁等待(默认 50 s,报 1205);后者管 MDL 等待,默认一年量级,所以 DDL 会长期挂着 | 第 24 章 24.8 |
| 意向锁会不会造成死锁? | 不会。意向锁之间全兼容,它的唯一作用是让表锁不必逐行检查是否存在行锁 | 第 24 章 24.5 |
第 25 章 SQL 优化与执行计划|7 条
| 面试问法 | 一句话要点 | 章节 |
|---|
EXPLAIN 结果里 type 有哪些取值,你怎么判断一个计划好不好? | 十级排序;index 与 ALL 都是全扫,index_merge 常比单索引 range 更贵 | 第 25 章 25.1.2 |
key_len 怎么算?为什么要关心它? | 它反推索引实际生效的列数:范围条件之后的列不计入,NULL 标志位与 VARCHAR 长度前缀要单独加 | 第 25 章 25.1.3 |
EXPLAIN 的 rows 是精确值吗? | 不是。基数由索引页随机采样估算,默认只采 20 页,所以偏差是常态 | 第 25 章 25.1.4 |
慢查询日志里几千条慢 SQL 先优化哪条?LIMIT 1000000, 20 为什么慢? | 按 Query_time 总耗时占比排序而非单条最慢;OFFSET 语义要求生成并丢弃前 N 行,可延迟关联、键集分页或覆盖索引 | 第 25 章 25.3 / 25.4 |
COUNT(*) 和 COUNT(1) 哪个快?为什么 InnoDB 不缓存行数? | 二者语义相同,都会选最小的二级索引扫;MVCC 使「行数」对不同 ReadView 不唯一,无法缓存 | 第 25 章 25.5 |
为什么 NOT IN 有时候查不出数据? | 三值逻辑:x <> NULL 得 UNKNOWN,整个条件对任何 x 都不成立,结果恒为空 | 第 25 章 25.7 |
JOIN 的驱动表怎么选?LEFT JOIN 和 INNER JOIN 有区别吗? | 按 rows × filtered 估算输出小的一侧做驱动表;WHERE 里对右表列的非 NULL 过滤会把 LEFT JOIN 改写成 INNER JOIN | 第 25 章 25.6 |
第 26 章 复制、高可用与分库分表|11 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 半同步复制和异步复制的区别是什么?半同步能保证不丢吗? | 只等 1 台从库 ACK,超时退化为异步;AFTER_SYNC 把「丢失」换成「重复」 | 第 26 章 26.1 |
rpl_semi_sync_master_wait_point 的 AFTER_SYNC 和 AFTER_COMMIT 差在哪? | 差别是引擎提交相对 binlog 发送的先后;AFTER_COMMIT 会产生「读到过又消失」的可见性反转 | 第 26 章 26.1 |
| 从库为什么需要 relay log?GTID 相对 binlog 位点好在哪? | relay log 解耦拉取与重放、支持崩溃位点、便于并行重放;GTID 能自动找位点、跳过重复事务,但 gtid_purged 误操作会永久失联 | 第 26 章 26.2 |
主从延迟很大怎么排查?WRITESET 为什么比按库并行粒度细? | 先分类:单线程重放、大事务、从库读压力、DDL、硬件;WRITESET 用行级哈希集合判交确定依赖,无主键表会退化为串行 | 第 26 章 26.3 |
Seconds_Behind_Master 显示 0,是不是说明从库没延迟? | 不是。主库静默、时钟漂移、积压偏小都会让它显示 0;替代方案是 pt-heartbeat 或 GTID 差集 | 第 26 章 26.4 |
| 读写分离下刚写完就读不到,怎么解决? | 强制走主库(需把标记跨请求传递)、GTID 等待、MGR 一致性级别、会话粘滞、缓存兜底,各有代价 | 第 26 章 26.5 |
| 主库挂了自动切换,会不会出现双主写入? | 会。网络分区下判定与事实不一致;靠多数派、fencing/STONITH、super_read_only 与账号回收防脑裂 | 第 26 章 26.6 |
| 分库分表常听说「单表 2000 万行」,它是硬阈值吗? | 不是 MySQL 的限制。它是三层 B+ 树估算下的经验值,真正决定因素是行宽与 buffer pool 大小 | 第 26 章 26.7 |
分库分表后跨库分页和跨库 JOIN 怎么办? | 跨库分页用二次查询法;跨库 JOIN 用冗余字段、广播表、ER 分片;聚合时 AVG 与 COUNT(DISTINCT) 必须改写 | 第 26 章 26.8 |
| XA 事务有什么问题?为什么互联网场景更爱最终一致? | prepare 后持锁阻塞、协调者单点、中间态在备份与切换中很麻烦;补偿方案换来了可用性与吞吐 | 第 26 章 26.9 |
| 数据库扩容怎么做到不停机? | 全量加 binlog 增量、双写幂等、影子表、分批校验、灰度切流与回退预案;gh-ost 管表变更、canal 管增量订阅 | 第 26 章 26.10 |
第 27 章 Redis 原理与缓存架构|14 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| Redis 为什么用单线程还这么快?6.0 的多线程又解决了什么? | 关键是内存操作加无锁加高效事件循环;6.0 的 I/O 线程只并行 read/write 与协议解析,命令执行仍串行 | 第 27 章 27.1 |
| 线上 Redis 突然卡住几百毫秒,你会从哪些地方排查? | KEYS、FLUSHALL、大 key DEL、大 ZSET 范围查询、appendfsync always、fork 停顿;替代是 SCAN/UNLINK/分页/everysec | 第 27 章 27.1.3 |
String 的 embstr 和 raw 分界是多少字节? | 44 字节:64 − 16(robj) − 3(sdshdr8) − 1(\0) 恰好填满一个分配单元;转换是单向的,降回阈值不会自动变回 | 第 27 章 27.2 |
| listpack 为什么要替代 ziplist? | ziplist 的 prevlen 从 1 字节变 5 字节会引发连锁更新,listpack 改用长度编码消除了这个级联 | 第 27 章 27.3.2 |
| 跳表为什么不用红黑树实现? | 范围查询可走底层链表、实现更简单、平均层数约 1.33;红黑树做范围查询要中序遍历 | 第 27 章 27.3.4 |
| 渐进式 rehash 期间的增删改查怎么处理? | 查询两表都查、新增只写 ht[1]、删除两表都做;不是加锁也不是拒绝写入 | 第 27 章 27.3.5 |
| 过期删除和内存淘汰是同一件事吗?LRU 是真 LRU 吗? | 两套独立机制:过期是被动清理,淘汰是达到 maxmemory 后的主动取舍;noeviction 是默认值,volatile-* 在无 TTL 时等于无法淘汰;LRU 是 maxmemory-samples 默认 5 的采样近似 | 第 27 章 27.4 |
| 开了 AOF 是不是就不会丢数据了? | 不是。everysec 最多丢约 1 秒;always 才不丢但性能塌陷,且 AOF 本身还受刷盘策略与硬件写缓存影响 | 第 27 章 27.5.2 |
| 从库重连为什么有时做全量同步? | repl_backlog 中的偏移已被新写覆盖就只能 +FULLRESYNC;容量应为 backlog ≥ 写入速率 × 容忍断线时间 | 第 27 章 27.6 |
| 哨兵的主观下线和客观下线有什么区别?Cluster 为什么是 16384 个槽? | 单个哨兵判定是主观、达 quorum 才是客观,选主还需多数派;16384 是槽位图 2 KB 与 8 KB 的心跳带宽权衡 | 第 27 章 27.7 |
MOVED 和 ASK 有什么区别? | MOVED 表示槽已永久迁移、客户端应更新映射;ASK 表示只是临时重定向到迁移中的目标节点,不改本地映射 | 第 27 章 27.7.2 |
| 缓存穿透、击穿、雪崩分别是什么? | 穿透是查不存在的数据(空值缓存、布隆过滤器);击穿是热 key 过期(逻辑过期、互斥重建);雪崩是大量 key 同时失效(打散 TTL、多级缓存) | 第 27 章 27.8 |
| 先更新数据库再删缓存,和先删缓存再更新数据库,哪个对? | 前者更常用;后者的读线程可能横跨写过程把旧值回填,延迟双删的 sleep 无理论下界,靠重试加 TTL 兜底才收敛 | 第 27 章 27.9 |
| 怎么发现热 key 和大 key? | 热 key 用客户端统计或代理采样,不要用 MONITOR;大 key 用 UNLINK 加 HSCAN 分批处理 | 第 27 章 27.10 |
第六篇 Java、JVM 与并发
第 28 章 Java 语言与对象模型|13 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 为什么说重载是编译期绑定、重写是运行期绑定? | 重载在编译期选定签名(五条 invoke* 指令),重写靠 vtable:子类复制父类表后同槽位覆盖 | 第 28 章 28.1.3 |
| 子类与父类定义了同名字段,用父类引用访问取哪个? | 取父类的。字段按声明类型静态绑定,没有 vtable 参与,与重写规则不同 | 第 28 章 28.1.2 |
一个只含两个 int 的对象占多少字节? | 12 字节对象头加 8 字节数据等于 20,按 8 字节对齐后为 24 字节 | 第 28 章 28.2 |
| 为什么建议堆不要刚超过 32 GB? | 压缩指针靠 8 字节对齐右移 3 位,2³² × 8 = 32 GB 是上限;超过后对象头与引用各多 4 字节 | 第 28 章 28.2 |
只重写 equals 不重写 hashCode 会发生什么? | HashSet 去重与 HashMap 查找会直接失效:先按 hashCode 定位桶、再按 equals 确认,两步各自失败 | 第 28 章 28.3 |
Integer a = 128, b = 128; a == b 是什么结果? | false。缓存范围是 -128 ~ 127;JLS 只要求下限,上限可用 AutoBoxCacheMax 调,Float/Double 无缓存 | 第 28 章 28.5 |
finally 里写 return 会怎样? | 会覆盖返回值并吞掉异常:finally 的 return 让保存异常引用的槽位永不被 athrow | 第 28 章 28.7.1 |
try-with-resources 里 close() 也抛异常,哪个异常被抛出? | 先抛出的为主异常,close() 的通过 addSuppressed 归入抑制异常 | 第 28 章 28.7.2 |
OutOfMemoryError 能被 catch 吗? | 语法上可以,它是普通对象;但捕获后不可靠,不该用 catch (Throwable) 吞掉 | 第 28 章 28.7 |
| Spring AOP 什么时候用 JDK 动态代理、什么时候用 CGLIB? | 取决于版本与 proxyTargetClass;final 方法会被静默跳过,类内自调用绕过代理 | 第 28 章 28.8 |
List<String> 和 List<Integer> 的 getClass() 相同吗? | 相同。泛型信息只留在 Signature 属性里,桥接方法与 TypeReference 靠它工作 | 第 28 章 28.9 |
为什么 new T[] 和 x instanceof T 都不允许? | 数组协变且运行期携带元素类型,需要能抛 ArrayStoreException;擦除后无法确定元素类型 | 第 28 章 28.9 |
不显式声明 serialVersionUID 有什么风险? | 默认值随字段与方法签名变化,一改就 InvalidClassException;显式声明的代价是放弃严格校验 | 第 28 章 28.10 |
第 29 章 集合框架原理|13 条
| 面试问法 | 一句话要点 | 章节 |
|---|
HashMap 的 hash 方法为什么要扰动,容量为什么必须是 2 的幂? | 容量是 2 的幂时索引只取低位,故用 h ^ (h >>> 16) 把高位混入;也只有 2 的幂才能让 (n-1) & hash 等价于取模 | 第 29 章 29.4.2 |
负载因子为什么是 0.75? | 时间空间折中:理想哈希下桶内达 8 个元素的概率约 6 × 10⁻⁸;改小则扩容频繁,改大则链表变长 | 第 29 章 29.4.3 |
树化阈值为什么是 8,退化阈值为什么是 6? | 8 是理想哈希下几乎不可能达到的线,6 与 8 之间的缓冲避免在边界反复转换 | 第 29 章 29.4.3 |
HashMap 扩容时元素位置要重新计算吗? | 不需要重算取模:一次 hash & oldCap 即可判断留原位还是迁到 j + oldCap | 第 29 章 29.4.5 |
为什么 for-each 里 list.remove() 会抛异常,Iterator.remove() 不会? | Itr.remove 最后执行 expectedModCount = modCount;删最后一个元素时也可能不抛 | 第 29 章 29.2.2 |
ConcurrentHashMap 在 JDK 8 为什么放弃分段锁? | 并发度被段数写死、每段都要额外分配数组与锁对象、内存开销大;改为 CAS + synchronized 按桶加锁 | 第 29 章 29.5 |
ConcurrentHashMap 的 get 为什么可以不加锁? | val/next 是 volatile、tabAt 用 getObjectVolatile;代价是弱一致读 | 第 29 章 29.5.2 |
CopyOnWriteArrayList 适合什么场景? | 读多写极少;写是 O(n) 复制、内存短暂翻倍,迭代器基于快照永不抛也看不到新元素 | 第 29 章 29.6 |
TreeMap 能用 hashCode 去重吗? | 不能。它靠 compare 导航,返回 0 即视为同一个键,后者会覆盖前者 | 第 29 章 29.7.2 |
怎么用 LinkedHashMap 实现 LRU? | accessOrder = true 让 afterNodeAccess 把访问节点移到尾部;afterNodeInsertion 里 removeEldestEntry 返回 true 才删表头 | 第 29 章 29.7.3 |
为什么 Arrays.sort(Object[]) 用 TimSort 而 int[] 用双轴快排? | 基本类型没有身份、稳定性无意义;对象数组按字段排序时相等元素的原有次序有语义 | 第 29 章 29.8 |
Comparison method violates its general contract 怎么产生的? | 比较器传递性被违反(常见是减法溢出导致),归并优化依赖传递性才暴露出来;用断言型比较器打印违规三元组 | 第 29 章 29.8 |
fail-fast 的 ConcurrentModificationException 在并发下一定能被发现吗? | 不能。modCount 不是 volatile、没有 happens-before 保证,规范也只承诺尽力而为 | 第 29 章 29.9 |
| 面试问法 | 一句话要点 | 章节 |
| — | — | — |
第 30 章 JMM、volatile 与 synchronized|11 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 既然硬件已有内存模型,Java 为什么还要自己定一套 JMM? | JMM 是「允许读到哪些值」的合同,举证责任在程序员;x86 是 TSO、ARM 是弱序,JVM 负责把 happens-before 翻译成各平台屏障 | 第 30 章 30.1 |
happens-before 的八条规则各是什么?它和时间上的「先发生」是一回事吗? | 不是时间关系而是可见性保证;规则 3 加传递性可组合出「用 volatile 发布对象、其内部普通字段也安全发布」 | 第 30 章 30.3 |
volatile 到底做了什么?x86 和 ARM 上编译出的指令一样吗? | 写插 StoreStore + StoreLoad,读插 LoadLoad + LoadStore;x86 上只剩 StoreLoad(lock 前缀),读侧零指令 | 第 30 章 30.4 |
volatile 能保证 i++ 线程安全吗? | 不能。i++ 是四步字节码,volatile 只保证每步各自语义,两次 putstatic 之间仍可交错造成丢失更新 | 第 30 章 30.5 |
双重检查锁定单例为什么必须加 volatile? | new 分三步:分配、invokespecial <init>、putstatic;后两步无依赖可重排,其他线程可能读到字段为默认值的半初始化对象 | 第 30 章 30.5.1 |
synchronized 锁的到底是什么?修饰实例方法和静态方法锁同一把锁吗? | 实例方法锁 this、静态方法锁 Class 对象,二者不互斥;锁 String 字面量或 Integer 缓存对象很危险 | 第 30 章 30.6.2 |
| 说说对象头里的 Mark Word 和锁升级的完整路径。 | 无锁 → 偏向 → 轻量级 → 重量级;hashCode 与偏向锁线程 ID 抢同一块位域;撤销偏向锁要 STW 到安全点,JDK 15 起默认禁用偏向锁 | 第 30 章 30.6.3 / 30.7 |
ObjectMonitor 里 wait/notify 会让线程在哪些队列之间迁移? | 持锁线程 wait 后进等待队列并释放锁,notify 把它移到同步队列,重新竞争到锁才从 wait 返回 | 第 30 章 30.8 |
final 域为什么能保证安全发布? | 写 final 域的指令被要求排在同构造器内的普通写之后,且构造完成前引用不可见,故读到的对象不会出现半初始化字段 | 第 30 章 30.9 |
ThreadLocal 会内存泄漏吗?为什么 key 用弱引用、value 用强引用? | 存在 Thread → ThreadLocalMap → Entry → value 强引用链;清理只在同线程再次访问该 map 时发生,线程池下必须 try/finally remove | 第 30 章 30.10 |
线程池里用 InheritableThreadLocal 为什么失效? | 它只在 Thread 构造时复制一次,线程池复用线程不会重新继承;要跨线程池传递需显式传递或专用方案 | 第 30 章 30.10.3 |
第 31 章 并发工具、线程池与 AQS|11 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 线程池的四种拒绝策略分别是什么? | AbortPolicy(默认,抛异常)、CallerRunsPolicy(提交线程执行形成背压)、DiscardPolicy(静默丢弃,最危险)、DiscardOldestPolicy | 第 31 章 31.3 |
| 请描述一下线程池的任务提交四步流程,为什么队列优先于扩容? | 核心线程 → 队列 → 最大线程 → 拒绝;队列优先是为了避免线程数在突发流量下瞬间冲高,代价是排队延迟上升 | 第 31 章 31.1 |
| 线程池线程数应该设多少?CPU 密集和 IO 密集分别怎么算? | CPU 密集约 N + 1;IO 密集约 N × (1 + W_w/W_c);混合型用 L = λ × W 反推,最后压测找拐点 | 第 31 章 31.5 |
请描述一下 AQS 的原理,state 是什么?队列是单向还是双向? | state 是 volatile int 加 CAS,语义由子类定义;队列改成双向是为了唤醒时能从尾部反向找到第一个有效节点 | 第 31 章 31.8 |
AQS 的 Node.waitStatus 有哪几个值?PROPAGATE 干什么用? | CANCELLED/SIGNAL/CONDITION/PROPAGATE 加默认 0;PROPAGATE 解决 doReleaseShared 里 ws == 0 分支导致的丢失唤醒 | 第 31 章 31.8.4 |
AQS 的独占与共享模式在 acquire 与 acquireShared 上差在哪? | 独占失败即入队阻塞,共享在成功唤醒后继节点时还要传播唤醒,PROPAGATE 正是为这个传播路径准备的 | 第 31 章 31.8.3 |
AQS 的 Condition 条件队列与同步队列怎么迁移? | await 释放锁并把自己加入条件队列,signal 把节点转入同步队列,重新 acquire 成功后才从 await 返回 | 第 31 章 31.8.5 |
ReentrantLock 公平与非公平差在哪?与 synchronized 怎么选? | 公平锁检查队列、非公平锁允许插队(吞吐更高但有饥饿风险);需要超时、可中断、多个条件队列时用 Lock | 第 31 章 31.9 |
CountDownLatch 和 CyclicBarrier 有什么区别? | CountDownLatch 减计数、一次性、主线程等子任务;CyclicBarrier 加计数、可重用、最后到达者执行 barrierAction;Semaphore 管许可数即限流 | 第 31 章 31.10 |
CompletableFuture 的 thenApply 和 thenCompose 有什么区别? | 区别在返回值是值还是 CompletionStage;不传线程池用 commonPool(并行度默认核数 − 1、全 JVM 共享),阻塞任务会把公共池连 parallelStream 一起拖死 | 第 31 章 31.11 |
AtomicLong 和 LongAdder 怎么选? | LongAdder 用 base + Cell[] 分散 CAS 热点,代价是 sum() 为近似汇总;适合监控指标不适合余额库存 | 第 31 章 31.12 |
第 32 章 JVM 内存结构与 GC|10 条
| 面试问法 | 一句话要点 | 章节 |
|---|
-XX:MetaspaceSize 到底是什么意思? | 它不是初始大小,而是首次元空间 GC 的阈值;调大等于推迟首次 GC、减少启动期 Full GC | 第 32 章 32.2 |
| 为什么 JDK 8 要用元空间替换永久代? | 永久代容量硬限制、只在 Full GC 回收、类卸载条件苛刻;字符串常量池搬家是 JDK 7 的事,不要混进 JDK 8 | 第 32 章 32.2 |
分代收集为什么还需要 Card Table?写屏障在其中做什么? | 为了让 Young GC 不必扫描整个老年代,只扫脏卡;配套机制是写屏障在跨代引用赋值时置脏 | 第 32 章 32.5.2 |
| 什么样的对象会直接进入老年代? | 大对象(PretenureSizeThreshold,只对 Serial/ParNew 有效)、动态年龄判定、Survivor 不足,以及年龄达阈值 | 第 32 章 32.6.1 |
为什么 CMS 会出现 Concurrent Mode Failure? | 并发清除期间应用继续分配晋升、老年代被填满,退化为 Serial Old 单线程整理、停顿剧增;用 CMSInitiatingOccupancyFraction 提前启动缓解 | 第 32 章 32.8 |
| 三色标记为什么会漏标?增量更新和 SATB 怎么解决? | 漏标需同时满足「黑指向白」与「灰到白的引用被删」;增量更新破坏前者(写后屏障),SATB 破坏后者(写前屏障),代价分别是重扫与浮动垃圾 | 第 32 章 32.7 |
| G1 什么时候会退化成 Full GC? | to-space exhausted、Humongous 分配失败、晋升失败、显式 System.gc() 四种;查 GC 日志关键词并用 jmap -histo 找大对象 | 第 32 章 32.9.1 |
ZGC 凭什么做到停顿与堆大小无关? | 着色指针加读屏障加并发转移;代价是吞吐下降与内存放大 | 第 32 章 32.10 |
| JDK 8 的默认垃圾收集器是什么?JDK 17 呢? | JDK 8 服务端默认 Parallel Scavenge + Parallel Old(CMS 要显式开启);JDK 9 起默认 G1,ZGC 在 JDK 15 转正 | 第 32 章 32.11 |
遇到 OutOfMemoryError: unable to create new native thread 怎么定位? | 与堆无关:查 ulimit -u、cgroup pids.max、-Xss 是否过大,用 jstack 数线程数 | 第 32 章 32.12 |
第七篇 消息队列、分布式与微服务
第 33 章 消息队列:可靠性三件套|15 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 你怎么保证消息不丢? | 三段防线:生产端同步确认加回调重试、Broker 端副本与刷盘、消费端「先处理再提交」;只答 acks=all 不完整 | 第 33 章 33.2 |
已经用 acks=all 了,为什么还会丢消息? | min.insync.replicas 默认 1 会让 acks=all 退化;unclean.leader.election = true 允许脏副本上位截断日志 | 第 33 章 33.2.2 |
HW 和 LEO 有什么区别?消费者为什么只能读到 HW 之前? | LEO 是本副本末尾,HW = min(ISR 所有 LEO);若允许读 HW 之后,该消息可能被新 leader 截断而「已消费的数据消失」 | 第 33 章 33.3.2 |
leader epoch 是干什么的? | 用日志自身的纪元元数据确定截断点,避免用「对端当前 LEO」判断而误删数据 | 第 33 章 33.3.3 |
| 消息为什么会重复?怎么保证幂等? | 至少一次语义下重复来自生产者重试与位点提交失败,无法消除;方案要按去重表、Redis、状态机、天然幂等对比,并说明是否与业务同事务 | 第 33 章 33.4 |
| 幂等键用什么生成?用 UUID 行不行? | 不行,重发会生成新 UUID 使去重失效;应用业务单据号(或单据号加操作类型)并随消息传递 | 第 33 章 33.4.3 |
| 去重表会一直增长,你怎么处理? | 按时间范围分区,保留最长重试窗口的冗余,用 DROP PARTITION 或分批删除;不要全表 DELETE | 第 33 章 33.4.3 |
| 怎么保证消息的顺序? | 先区分全局、分区、业务键三层保证,绝大多数只要业务键有序;再讲分区内乱序的两个来源 | 第 33 章 33.5 |
| 我配了顺序发送,为什么消费端还是乱序? | 生产者重试加 max.in.flight.requests.per.connection > 1 导致追加乱序(需 enable.idempotence);消费者多线程并发处理破坏顺序 | 第 33 章 33.5.1 |
| 线上消息积压了,你怎么处理? | 先用生产/消费速率与 Lag 曲线定性,再看实例数上限是分区数、分区只能增不能减、扩容约 N/(N+1) 的键要迁移、扩容会破坏顺序 | 第 33 章 33.6 |
| 延迟消息怎么实现?为什么只能延时到固定档位? | 固定档位是多级时间轮的设计取舍;RabbitMQ 队列级 TTL 有队头阻塞问题;通用方案是 ZSet 加轮询 | 第 33 章 33.7 |
| 时间轮的原理是什么?和最小堆相比优势在哪? | 槽位加 tickMs 加轮数记账,插入与到期均 O(1);多层是为了把精度与跨度分离;最小堆是 O(log n) | 第 33 章 33.7.1 |
| RocketMQ 的事务消息流程是怎样的?为什么需要回查? | 提交或回滚指令本身可能丢失,若不回查就会出现消息永久悬空;反查生产者以取得确定决议 | 第 33 章 33.8.1 |
| Kafka 的精确一次和业务幂等是一回事吗? | 不是。EOS 只约束 Kafka 内部读写,写外部数据库仍需要业务幂等 | 第 33 章 33.8.3 |
| 你们为什么选 Kafka 而不是 RocketMQ? | 先给约束(吞吐、延迟、是否需要事务与延迟消息、运维能力)再给结论,不要以「性能最好」开头 | 第 33 章 33.9 |
第 34 章 分布式理论:CAP、共识与一致性|12 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 请说说 CAP 定理,为什么说「三选二」? | P 在分布式系统中是前提而非选项,「三选二」是误读;真实选择只在分区期间于 C 与 A 之间,另一半账由 PACELC 记录 | 第 34 章 34.1 |
| CAP 里的 A 到底指什么?它和系统可用率 99.99% 是一回事吗? | A 指「每个非故障节点在有界时间内返回非错误响应」;超时与 503 都不算 A,返回陈旧数据算 A | 第 34 章 34.1.1 |
| 线性一致和顺序一致有什么区别? | 线性一致等于顺序一致加真实时间约束;「写在 t=10 返回、读在 t=20 却读到旧值」在顺序一致下合法、在线性一致下非法 | 第 34 章 34.2 |
| Raft 的 Leader 什么时候可以提交一条日志? | 只有当前任期的日志能凭多数派提交,旧任期只能被连带提交;否则已提交条目会被覆盖 | 第 34 章 34.3.4 |
| 为什么 Raft 的选举超时要在 150 ~ 300 ms 之间随机? | 打散同时超时、避免选票瓜分导致无限重试;每轮重试独立,冲突概率随之下降 | 第 34 章 34.3.2 |
| Paxos 和 Raft 是什么关系?ZAB 和 Raft 有什么区别? | Paxos 解决单值共识且 Basic 版有活锁,Raft 是可理解性导向的 Multi-Paxos 变体;ZAB 是主备复制协议 | 第 34 章 34.4 |
| 你们线上用的是 2PC、TCC 还是 Saga? | 要说出各方案的不一致窗口具体形态:2PC 的不确定态、TCC 的业务中间态、Saga 的中间态暴露;TCC 不是强一致 | 第 34 章 34.5 |
| TCC 的空回滚、幂等、悬挂分别是什么? | 空回滚是 Cancel 先于 Try 到达;用「防悬挂标记」同时解决空回滚与悬挂,且三个操作都必须幂等 | 第 34 章 34.5 |
| 雪花算法的 ID 结构是什么?时钟回拨了怎么办? | 41 位时间戳、10 位机器、12 位序列,故单机 4096/ms、可用约 69 年;回拨处理有等待、抛异常、备用位、ZK 校验四种 | 第 34 章 34.6 |
| 分布式锁用 Redis 还是 ZooKeeper?Redis 锁会出什么问题? | 主从异步复制导致双持有、GC 暂停导致锁过期;Redlock 争议在时钟假设与缺少 fencing token,保一致性必须配 fencing token | 第 34 章 34.7 |
| 固定窗口限流有什么问题?令牌桶和漏桶的区别是什么? | 固定窗口有临界点双倍问题;令牌桶允许突发、桶深 B 就是最大瞬时突发,漏桶以恒定速率流出不允许突发 | 第 34 章 34.8.1 |
| 一致性哈希解决什么问题?虚拟节点为什么必要? | 解决取模扩容时 N/(N+1) 的大规模迁移,一致性哈希约 1/(N+1);均匀分布要靠虚拟节点;Redis Cluster 用的是 16384 槽不是哈希环 | 第 34 章 34.8.2 |
第 35 章 微服务架构与治理|12 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 你们为什么把单体拆成微服务?拆完付出了什么代价? | 收益在组织层、代价在运行层;判据是团队规模、部署冲突、瓶颈能否独立扩展,要给得出一次过早拆分的反例 | 第 35 章 35.1 |
| 服务注册发现用客户端发现还是服务端发现? | 客户端发现少一跳但把逻辑压在客户端、需多语言实现;服务端发现集中管控但多一跳且是单点放大区 | 第 35 章 35.2.1 |
为什么 Eureka 是 AP 而 ZooKeeper 是 CP? | 分区时 CP 会让注册中心整体拒绝服务,而服务发现更怕「拿不到实例列表」 | 第 35 章 35.2.2 |
Eureka 的自我保护是 bug 吗?生产上要不要关掉? | 不是 bug 是有意设计;关闭后网络抖动会放大成全站故障,代价由客户端重试与熔断承担 | 第 35 章 35.2.4 |
| 四层和七层负载均衡有什么区别? | 不是升级关系,L7 解析开销更大;差别在 TLS 卸载位置、连接是否复用、超时是连接级还是请求级 | 第 35 章 35.3.3 |
| 熔断器有哪几个状态?失败率阈值设多少合适? | 关闭、打开、半开三态;参数要含滑动窗口、最小样本量、慢调用比例与受控探测,且同时看 RT | 第 35 章 35.4 |
| 熔断和限流有什么区别?只做限流行不行? | 限流保护自己、熔断保护调用方;只做限流时下游故障会经线程等待把上游拖死 | 第 35 章 35.4.3 |
Hystrix 为什么用线程池隔离?它为什么被淘汰? | 线程池隔离买到的是「调用方可以超时脱身」,代价是约 1 ~ 5 μs 上下文切换且每依赖一个池导致线程膨胀 | 第 35 章 35.4.4 |
| 超时应该怎么设?重试几次合适? | 逐跳递减的超时预算加只对可重试错误重试加退避抖动加重试预算封顶;否则 3 次重试会放大 27 倍 | 第 35 章 35.5.2 |
| 线上一个接口偶尔超时,你怎么定位? | 从入口 traceId 拉全链路、按实例维度对比、检查资源池三步;注意 NTP 偏差超过 100 ms 时跨机时序不可信 | 第 35 章 35.11 |
| 网关应该承担哪些职责?业务聚合能放网关吗? | 鉴权、限流、路由、灰度可以;聚合放 BFF、事务留在服务内;网关是全站单点放大区,且响应式网关里不能有阻塞调用 | 第 35 章 35.8 |
| 下单接口怎么保证幂等?幂等键用什么? | 业务单据号(重试可复现)加唯一索引;用 UUID 当请求号会因客户端重发而失效,还要辨析幂等与并发控制 | 第 35 章 35.10 |
第八篇 场景设计与面试复盘
第 36 章 场景设计题的答题框架|10 条
| 面试问法 | 一句话要点 | 章节 |
|---|
| 「设计一个秒杀系统。」你打算从哪里开始? | 先澄清需求:规模、读写比、一致性要求、峰值形态;不要一上来画架构图或写代码 | 第 36 章 36.1 |
| 「日活 1000 万的系统,QPS 大概多少?一天存储涨多少?」 | 给出「均值 → 峰值 → 索引 → 副本」的完整链条;注意日请求量除 86400 才是均值 QPS | 第 36 章 36.3 |
| 「秒杀库存为什么不能先查一下再减一?」 | 这是 TOCTOU 窗口:查与减之间可被并发插入;原子性要下沉到 Redis 的 Lua 脚本或数据库的条件更新 | 第 36 章 36.4.1 |
| 「扣减成功之后消息投递失败,库存怎么办?」 | 属于少卖:要有对账与回补,且回补本身必须幂等,不能只靠重试 | 第 36 章 36.4.2 |
「短链接用 301 还是 302?」 | 用 302,因为 301 会被浏览器与中间缓存永久记住,导致点击统计失真 | 第 36 章 36.5 |
| 「排行榜里两个人分数一样,Redis 会怎么排?」 | ZSet 同分按成员字典序排;需要稳定次序就把时间戳编码进分数低位 | 第 36 章 36.6 |
| 「在线人数统计你用什么结构?」 | 先问是否需要精确查某个用户;只要基数用 HyperLogLog(约 0.81% 标准误差),要判定成员只能走 SET/Bitmap | 第 36 章 36.9 |
| 「10 万 QPS、平均 200 ms,你要开多少线程和数据库连接?」 | 先算 L = λ × W,再由下游承载能力反推并加限流;不要拍数字或用核数乘固定倍数 | 第 36 章 36.3 |
| 「订单号怎么生成?如果发号器出现重复怎么办?」 | 要讲时钟回拨处理,更要用数据库唯一索引兜底;只说 Snowflake 不算答完 | 第 36 章 36.7 |
| 「你的设计里哪里会先崩?」 | 指出单点、热 key、有状态节点,并给出具体扩容与限流动作;「加机器就行」等于没答 | 第 36 章 36.11 |
B.3 反向索引:按关键词查考点
注意
每个关键词后面跟的就是追问路径:面试官从一个词切入,通常沿这三件事往下走。章节号是主归属章,个别关键词跨越两章时按主次列出。
操作系统与 Linux
- 写时复制(copy-on-write, COW) —— 页表只读标记怎么拦写;
_mapcount 判据与原地加速路径;为什么 10 GB 进程能毫秒级 fork | 第 4 章 4.2 - 缺页(page fault) —— 四种类型怎么分;COW 缺页为何不需要磁盘 I/O;段错误与缺页的关系 | 第 8 章 8.4
- TLB —— 命中是否还要查页表;缺失的硬件遍历代价;切进程与绑核为什么会冲刷它 | 第 8 章 8.3.3
- 多级页表与页替换算法 —— 单级为什么不可行(算出 512 GB);Belady 异常的反例;栈性质为什么让 LRU 单调 | 第 8 章 8.3 / 8.5
- CFS 与
vruntime —— 公式是 vruntime += 实际时间 × (1024/weight);为什么用红黑树而不是堆;nice 到权重的换算 | 第 5 章 5.3 - nice 与权重 —— nice 到 weight 的换算;
nice -20 对 nice 19 的比例;CFS 为什么没有绝对优先级 | 第 5 章 5.3.4 - 实时调度类 ——
SCHED_FIFO 死循环的后果;RT 带宽限制参数;如何避免饿死普通任务 | 第 5 章 5.5 - 上下文切换 —— 开销由哪几部分构成;自愿与非自愿的区别;
cs 高但 us 低说明什么 | 第 5 章 5.7 - 绑核与 CPU 亲和 —— 收益来自内存亲和还是减少切换;first-touch 的错配;什么负载绑核会变慢 | 第 5 章 5.6
- 虚拟化与 KVM —— KVM 是硬件辅助还是软件模拟;
KVM_RUN 与 VM exit;内存与 I/O 怎么虚拟化 | 第 5 章 5.8 - CAS(compare-and-swap) —— ABA 问题与解法;
lock 前缀锁了什么;高并发下 CAS 为什么可能比锁慢 | 第 6 章 6.2.1 - ABA 问题 —— 为什么会撕裂无锁栈;版本号的局限;延迟回收为什么最彻底 | 第 6 章 6.6.6
- 自旋锁 —— 与互斥锁的分界(约 1 μs);单核上为什么无意义;ticket lock 与 MCS 的演进 | 第 6 章 6.3
- MCS / CLH 锁 —— 缓存行争抢怎么被消除;为什么 AQS 队列要改成双向 | 第 6 章 6.3.5
- 信号量 —— 与互斥锁的三点本质区别;二元信号量当互斥锁的后果 | 第 6 章 6.4.1
- 条件变量 —— 为什么必须配互斥锁;为什么必须用
while;丢失唤醒的真正根因 | 第 6 章 6.4.2 - 内存屏障(memory barrier) —— 四种屏障的约束关系;x86 为什么只剩
StoreLoad;编译器屏障为什么不够 | 第 6 章 6.5 - 死锁四条件 —— 为什么满足四条件仍可能不死锁;逐条破坏的方法;银行家算法的安全序列 | 第 6 章 6.6
- 银行家算法 —— 怎么判断请求能否批准;不安全状态与已死锁的区别;工程上为什么很少用 | 第 6 章 6.6.3
- 优先级反转 —— 中优先级线程怎么压住高优先级;继承与天花板的区别 | 第 6 章 6.6.5
- 无锁与无等待 —— 进展保证的层级定义;用了 CAS 不等于无锁 | 第 6 章 6.6.6
- RCU —— 宽限期怎么确定可以回收;读侧为什么可以零开销 | 第 6 章 6.4.4
- 文件描述符 —— fd →
struct file → inode 三层;dup 与 fork 的共享语义;fd 耗尽怎么排查 | 第 7 章 7.1 - VFS —— 四类对象的分工;硬链接与软链接的本质区别;为什么目录不能硬链接 | 第 7 章 7.3
select / poll —— 三处 O(n) 开销分别是什么;poll 无上限为什么还是慢 | 第 7 章 7.4epoll —— 红黑树与就绪链表各服务谁;ep_poll_callback 的时机;LT 与 ET 的实现差异 | 第 7 章 7.5- 边缘触发(edge-triggered, ET) —— 为什么必须非阻塞;最后一次
read 怎么结束;返回 0 是 EOF 还是没数据 | 第 7 章 7.6 - 零拷贝(zero-copy) —— 三种实现;DMA 参与几次;2 次与 3 次拷贝的分野 | 第 7 章 7.8
sendfile —— 数据流路径;依赖 scatter-gather DMA;与 splice、mmap 的差别 | 第 7 章 7.8- page cache ——
write 返回成功是否落盘;fsync 管不到什么;O_DIRECT 绕过了什么 | 第 7 章 7.7 io_uring —— 与 epoll 解决的是不同层次的问题;SQ/CQ 为什么能消除系统调用 | 第 7 章 7.9- 中断上半部与下半部 —— 上半部为什么必须短;softirq/tasklet/workqueue 的判据;
ksoftirqd 的角色 | 第 9 章 9.2 - 软中断风暴 —— 什么时候发生;预算量级是多少;per-CPU 语义意味着什么 | 第 9 章 9.2.1
- 中断亲和性 —— 怎么看分布、怎么改绑定;RSS 多队列救不救单连接 | 第 9 章 9.4
- 系统调用 —— 完整路径与寄存器约定;开销量级与构成;vDSO 能省掉哪一类 | 第 9 章 9.5
- vDSO —— 依靠什么机制进不了内核;哪些调用能省、哪些不能;什么配置下会失效 | 第 9 章 9.6.1
- 信号(signal) —— 阻塞与忽略的区别;标准信号为什么不排队;handler 里为什么不能调
printf | 第 9 章 9.8 SIGCHLD —— 为什么会丢;SIG_IGN 与 SIG_DFL 的区别;为什么必须循环 waitpid | 第 4 章 4.6- I/O 调度器 —— 四种算法的适用面;NVMe 上为什么常用
none;延迟上界与公平性怎么取舍 | 第 9 章 9.9.1 fsync —— 保证与不保证的边界;硬件写缓存这个缺口;父目录为什么要单独刷 | 第 9 章 9.9.3- 进程状态机 ——
S 与 D 为什么必须分开;kill -9 杀不掉的四类情形;ps 视角与内核视角的错位 | 第 10 章 10.1 D 状态 —— 典型产生路径;为什么信号唤不醒;为什么负载统计把它计入 | 第 10 章 10.1.1VSZ / RSS / PSS / USS —— 四个口径的差别;共享页怎么分摊;为什么 Σ RSS 会超过物理内存 | 第 10 章 10.2.1/proc 文件系统 —— stat 计时字段的单位;rchar 与 read_bytes 差几个数量级 | 第 10 章 10.3ulimit 与 RLIMIT_* —— 软硬限制谁能改;改了不生效的层次原因;RLIMIT_NPROC 按什么统计 | 第 10 章 10.5- cgroup v1 与 v2 —— 层次模型的差别;
cpu.max 的语义;容器里 Java 看到的核数为什么不对 | 第 10 章 10.6 - namespace —— 八类隔离各隔离什么;容器与虚拟机的本质差别 | 第 10 章 10.6.3
- 平均负载(load average) —— 三个数字的口径;R 与 D 两列;高负载低 CPU 的三种原因 | 第 11 章 11.2
iowait(wa) —— 它的准确定义;为什么归因给 CPU 不归因给盘;哪类等待不计入 | 第 11 章 11.3.3%util、await 与 aqu-sz —— %util 为什么饱和不代表瓶颈;延迟基线怎么建立;读写为什么要分开看 | 第 11 章 11.6.2 / 11.6.3%steal 与 OOM killer —— 什么环境才有 %steal、为什么容器内看不到;OOM 候选集怎么定、oom_score 怎么算 | 第 11 章 11.3.5 / 第 12 章 12.4overcommit_memory —— 三种模式的区别;malloc 成功为何不等于内存可得;fork 大进程的风险 | 第 12 章 12.5swappiness —— 它是权重不是开关;量纲到多少;设为 0 会怎样 | 第 12 章 12.6- swap —— 判断是否在用的唯一可靠信号;为什么容器里通常要禁用 | 第 11 章 11.5.2
- 脏页与回写 ——
dirty_ratio 的后果;延迟尖峰为什么周期性出现;怎么观测 | 第 12 章 12.3 - 内存泄漏定位 —— JVM/Go 场景该用什么;
smaps_rollup 的哪个字段做判据 | 第 12 章 12.7 - inode —— 耗尽怎么发现;为什么不能动态扩;挂载时怎么预防 | 第 13 章 13.1.3
df 与 du 不一致 —— 三类根因;已删除但被持有的文件怎么找;稀疏文件造成的是哪组差异 | 第 13 章 13.3- RAID 写惩罚 —— RAID 5 为什么是 4;整条带写为什么接近 1;数据库为什么只用 RAID 10 | 第 13 章 13.5.2
- IOPS 与队列深度 ——
IOPS = Q / L 的用法;为什么必须同时报块大小;不 direct 测的是什么 | 第 13 章 13.6.1 - TRIM —— 它不加快删除;写放大与 GC 的关系;
discard 与周期性 fstrim 的区别 | 第 13 章 13.6.4 - 文件描述符耗尽 ——
EMFILE 与 ENFILE 的区别;怎么区分配置不足与泄漏 | 第 13 章 13.8 set -euo pipefail —— -e 在哪些上下文失效;pipefail 治什么病;local x=$(cmd) 的坑 | 第 14 章 14.7.1uniq -c —— 为什么必须排在 sort 后;它的常量内存从哪来 | 第 14 章 14.4.2find 与 xargs —— \; 与 + 的进程数差;空格文件名怎么处理;ARG_MAX 的作用 | 第 14 章 14.5
网络
- 分层模型 —— OSI 与 TCP/IP 的映射;分层的代价(头部开销、跨层优化受阻);越层设备的例外 | 第 15 章 15.1
- 封装与解封装 —— 每层加什么头;MAC 为什么逐跳重写而 IP 不变;路由器解到哪一层 | 第 15 章 15.3
- MTU —— 常见值是多少;与 MSS 的关系;路径 MTU 发现失败会怎样 | 第 15 章 15.5.1
- MSS —— 怎么算出来;带时间戳选项后是多少;由谁在什么报文中声明 | 第 15 章 15.5.1
- IP 分片 —— 与 TCP 分段的层次差别;片偏移单位;丢一片的后果 | 第 15 章 15.5.2
- PMTUD 黑洞 —— 症状是什么;为什么小请求正常;对策有哪些 | 第 15 章 15.5.3
- ARP —— 为什么有 IP 还要 MAC;请求与应答的广播/单播;缓存无验证的风险 | 第 15 章 15.6
- NAT 与 NAPT —— 靠什么区分内网主机;映射表项为什么会超时;NAT 破坏了什么原则 | 第 15 章 15.7
- UDP 打洞 —— 哪类 NAT 可行;对称型为什么不行;STUN/TURN/ICE 各自做什么 | 第 15 章 15.7.3
- 端到端原则 —— 拥塞控制为什么不能放路由器;路由器做不到的三件事;ECN 的折中 | 第 15 章 15.8
- 带宽时延积(BDP) —— 公式与算例;它决定什么;窗口扩大因子为什么必要 | 第 15 章 15.9.3
- 三次握手 —— 为什么不能两次;为什么不是四次;第三步能否带数据 | 第 16 章 16.2
- 初始序号(ISN) —— 为什么要随机化;两条独立理由;猜测成功的概率量级 | 第 16 章 16.2
- SYN 队列与 accept 队列 —— 两个队列分别装什么;
ss -lnt 的 Recv-Q/Send-Q 是什么;溢出怎么发现 | 第 16 章 16.4 - SYN Flood 与
syncookies —— 无状态化的思路;代价是什么;半连接队列相关参数 | 第 16 章 16.5 - 四次挥手 —— 为什么比握手多一次;每一步的状态迁移;同时关闭怎么走 | 第 16 章 16.6
TIME_WAIT —— 为什么必须是 2MSL;两条存在理由;主动关闭方与被动方处理有何不同 | 第 16 章 16.6.3CLOSE_WAIT —— 堆积说明什么;为什么是应用 bug;怎么定位到具体代码 | 第 16 章 16.6.2SO_REUSEADDR —— 它真实解决什么问题;能否减少 TIME_WAIT 数量 | 第 16 章 16.7SO_REUSEPORT —— 与 SO_REUSEADDR 的区别;它消除了什么现象 | 第 16 章 16.7- RTO 计算 —— 为什么不只用平均 RTT;平滑公式的形态;重传后还能否采样 | 第 17 章 17.2
- Karn 算法 —— 它禁止什么;时间戳选项如何解除限制 | 第 17 章 17.2.3
- 快速重传 —— 阈值为什么是 3;SACK 时代它变成了什么;尾丢包靠什么 | 第 17 章 17.3.1
- SACK —— 它解决了什么;与 DSACK 的区别 | 第 17 章 17.3.2
- 滑动窗口与流量控制 —— 发送窗口的四个区间;零窗口怎么处理;窗口扩大因子在哪协商 | 第 17 章 17.4
- 零窗口与坚持定时器(persist timer) —— 为什么不能只等窗口更新;探测报文的特征 | 第 17 章 17.4.2
- 拥塞控制 —— 四个阶段分别做什么;丢包为什么被当作拥塞信号;端到端的原因 | 第 17 章 17.6
cwnd —— 各阶段的增长方式;超时与快速重传的两种反应 | 第 17 章 17.6.3ssthresh —— 怎么折半;与 cwnd 的配合;恢复阶段怎么走 | 第 17 章 17.6.2- Nagle 算法 —— 与延迟 ACK 的互等死锁;三种解法;
TCP_NODELAY 为什么常开 | 第 17 章 17.5.3 - 延迟 ACK —— 它为什么存在;与 Nagle 如何形成最长约 40 ms 延迟;
QUICKACK 为什么非粘性 | 第 17 章 17.5.3 - CUBIC —— 增长函数的形式;为什么不受 RTT 影响;
β 与 Reno 的差别 | 第 17 章 17.7.1 - BBR —— 用什么建模;为什么不因丢包降窗;公平性争议 | 第 17 章 17.7.2
- HTTP/1.1 队头阻塞 —— 应用层串行的根因是什么;为什么浏览器开 6 个连接;管线化为什么失败 | 第 18 章 18.3
- HTTP/2 多路复用 —— 帧、消息、流的关系;
Stream ID 的作用;域名分片为什么反而有害 | 第 18 章 18.4 - HPACK —— 它压缩什么;动态表在域名分片下的后果 | 第 18 章 18.4.3
- TCP 层队头阻塞 —— 为什么 HTTP/2 解决不了;多开连接为什么不行 | 第 18 章 18.5
- QUIC —— 为什么基于 UDP;重传与拥塞控制放在哪里;协议号僵化问题 | 第 18 章 18.6
- TLS 1.3 —— 为什么只要 1-RTT;相比 1.2 少了哪些往返;0-RTT 有什么风险 | 第 18 章 18.8
- SNI —— 为什么需要它;有什么隐私问题;ECH 解决什么 | 第 18 章 18.9.3
- HSTS —— 它防什么;与 301 跳转的区别 | 第 18 章 18.9.3
- DNS 递归与迭代 —— 两段路径的分界点在哪;RD/RA 标志的含义;根服务器从不做递归 | 第 19 章 19.1.1
- DNS TTL —— 为什么是最坏生效上界;切换机房要提前多久调小;代价是什么 | 第 19 章 19.1.3
- CNAME 与区顶点 —— 为什么不能与 MX 共存;根域名为什么不能用;ANAME/ALIAS 是什么 | 第 19 章 19.2.2
- CDN 回源与命中率 —— 命中率下降怎么查;动态内容为什么该走动态加速 | 第 19 章 19.5
curl -w 时间指标 —— 哪些是累计量;怎么做减法定位;非 TLS 请求哪个值为 0 | 第 19 章 19.6.1tcpdump 抓包 —— 该看什么(握手、重传、RST、零窗口);抓包点为什么关键 | 第 19 章 19.7ping / traceroute / mtr —— 能力边界在哪;* 是否代表故障;ICMP 被丢时怎么判断 | 第 19 章 19.8
数据库
- MySQL 逻辑架构 —— 三层各自做什么;权限校验在哪一步;执行器为什么要复核条件 | 第 20 章 20.1
- 优化器与统计信息 —— 为什么会选错索引;基数怎么采样;
ANALYZE TABLE 的代价 | 第 20 章 20.3 - buffer pool —— 分段 LRU 的组织;
innodb_old_blocks_time 防什么;脏页刷盘与 checkpoint 的关系 | 第 20 章 20.5.1 - change buffer —— 为什么唯一索引不能用;什么场景收益最大 | 第 20 章 20.5.2
- 自适应哈希索引 —— 它是持久化的吗;为什么可丢弃 | 第 20 章 20.5.3
- InnoDB 页结构 —— 页内怎么找记录;Page Directory 的粒度;两段式查找的复杂度 | 第 20 章 20.7
- 行格式与行溢出 ——
VARCHAR 上限受什么约束;DYNAMIC 与 COMPACT 的差别 | 第 20 章 20.8 - redo log —— 为什么必须先写日志;物理还是逻辑;写满了会怎样 | 第 21 章 21.1
innodb_flush_log_at_trx_commit —— 0/1/2 各自的丢失窗口;和 binlog 的刷盘是否独立 | 第 21 章 21.1.2- LSN —— 出现在哪三处;如何判断页是否已刷盘;checkpoint 的作用 | 第 21 章 21.2.2
- group commit(组提交) —— 为什么能提升吞吐;三个阶段各合并什么;单事务延迟会怎样 | 第 21 章 21.2.4
- undo log —— 两个作用;insert 与 update undo 的区别;长事务的后果 | 第 21 章 21.3
- binlog —— 三种格式怎么选;为什么用 ROW;
sync_binlog 的作用 | 第 21 章 21.4 - 两阶段提交 —— 崩溃点分析;决断依据为什么是 binlog 完整性;不做会怎样 | 第 21 章 21.5
- 崩溃恢复 —— 完整步骤与顺序;
innodb_force_recovery 分级的真实含义 | 第 21 章 21.7 - MVCC —— 三要素是什么;undo 版本链怎么串;可见性判断的终止条件 | 第 22 章 22.3
- ReadView —— 四个字段各是什么;可见性判断流程;RC 与 RR 的创建时机差异 | 第 22 章 22.4
- 隔离级别 —— 四种级别与三类异常的对应;MySQL 与 PostgreSQL 默认值为什么不同 | 第 22 章 22.2
- 幻读 —— RR 下为什么仍会出现;当前读与快照读的差别;怎么用具体场景说明 | 第 22 章 22.6.1
- 快照读与当前读 —— 哪些语句是当前读;为什么同一事务两次读结果不同;唯一性校验该靠什么 | 第 22 章 22.6
- 长事务 —— 三类危害的因果链;只读长事务为什么也有害;怎么监控 | 第 22 章 22.7
- B+ 树 —— 为什么胜过 B 树与哈希;树高怎么估;为什么查询路径恒定 | 第 23 章 23.1
- 聚簇索引与二级索引 —— 物理布局差异;二级索引叶页存什么;自增主键为什么更优 | 第 23 章 23.3
- 回表 —— 什么情况必须回表;怎么避免;
SELECT * 的代价 | 第 23 章 23.3 - 覆盖索引 ——
Using index 的含义;与 ICP 的区别 | 第 23 章 23.5 - 最左前缀 —— 从排序性质推导;范围条件之后为什么断掉 | 第 23 章 23.6
- ICP(index condition pushdown) —— 它减少的是什么;与覆盖索引是否正交 | 第 23 章 23.7
- 索引失效 —— 各类情形清单;哪些是「语法不可用」哪些是「代价不选」 | 第 23 章 23.8
- 联合索引列顺序 —— 等值/范围/排序怎么排;区分度是不是越高越前 | 第 23 章 23.9.1
- 前缀索引 —— 怎么定长度;代价是什么 | 第 23 章 23.9.2
- MDL(metadata lock) —— 为什么必须有;排队雪崩怎么形成;Online DDL 的算法与锁范围 | 第 24 章 24.2
- 间隙锁与临键锁 —— RR 为什么必须加;RC 为什么关掉;唯一与非唯一索引的差别 | 第 24 章 24.3
- 加锁规则 —— 八条规则怎么用;
FOR UPDATE 锁不到新插入行说明什么 | 第 24 章 24.4 - 意向锁 —— 会不会互相阻塞;它存在的唯一理由 | 第 24 章 24.5
- 隐式锁 ——
INSERT 为什么不显式加锁;什么时候转成显式锁 | 第 24 章 24.6.1 - InnoDB 死锁 —— 四条件的具体形态;死锁日志怎么读;
innodb_deadlock_detect 关掉的代价 | 第 24 章 24.7 - 乐观锁与悲观锁 —— 冲突率怎么决定选择;高冲突下乐观锁为何退化 | 第 24 章 24.8
EXPLAIN —— type 十级排序;key_len 怎么算;Extra 里哪些值要警惕 | 第 25 章 25.1- 慢查询日志与深分页 —— 先优化哪一条(看
Query_time 总量);OFFSET 的语义代价与三种优化方案的边界 | 第 25 章 25.3 / 25.4 COUNT(*) —— 四种写法的语义与性能;InnoDB 为什么不缓存行数 | 第 25 章 25.5- hash join —— MySQL 哪个版本引入;与 BNL 的差别;什么时候会选它 | 第 25 章 25.6
- 驱动表选择 —— 按什么估算;
LEFT JOIN 什么情况下被改写成 INNER JOIN | 第 25 章 25.6 - 三值逻辑与
NULL —— NOT IN 为什么查不出数据;x <> NULL 的结果 | 第 25 章 25.7 - 在线 DDL ——
INPLACE 与 LOCK=NONE 的区别;首尾为什么仍需 MDL;大表的替代方案 | 第 25 章 25.9 - 主从复制模式 —— 异步/半同步/组复制各自的丢数据窗口;半同步超时退化成什么 | 第 26 章 26.1
- relay log —— 为什么需要它;不直接重放主库 binlog 的理由 | 第 26 章 26.2
- GTID —— 相比位点的优势;
gtid_purged 误操作的后果 | 第 26 章 26.2 - 主从延迟 —— 五个原因;并行复制两种粒度的差别;
Seconds_Behind_Master 为什么不准 | 第 26 章 26.3 - 并行复制 —— 按库与 WRITESET 的差别;无主键表为什么会退化为串行 | 第 26 章 26.3
- 读写分离延迟读 —— 强制走主库的标记怎么传;GTID 等待与会话粘滞的代价 | 第 26 章 26.5
- 脑裂 —— 网络分区下怎么发生;多数派与 fencing 的作用 | 第 26 章 26.6
- 分库分表 —— 拆分维度与路由;「单表 2000 万行」是不是硬阈值 | 第 26 章 26.7
- 跨库 JOIN 与分页 —— 二次查询法的步骤;冗余字段与广播表的取舍 | 第 26 章 26.8
- XA 事务 —— 2PC 的三个缺陷;与最终一致方案的取舍 | 第 26 章 26.9
- 在线扩容 —— 双写、影子表、分批校验、灰度切流;各工具管什么 | 第 26 章 26.10
- 渐进式 rehash —— 期间的增删改查怎么处理;为什么要两个哈希表 | 第 27 章 27.3.5
- listpack 与 ziplist —— 连锁更新是什么;listpack 怎么消除它 | 第 27 章 27.3.2
- quicklist —— 与 listpack 的分层关系;为什么要分层 | 第 27 章 27.3.3
- skiplist —— 为什么不用红黑树;平均层数;范围查询怎么走 | 第 27 章 27.3.4
- SDS —— 相比 C 字符串的三个优势;二进制安全指什么 | 第 27 章 27.3.1
- Redis 编码转换 ——
embstr 与 raw 的分界怎么算出来;转换是否可逆 | 第 27 章 27.2 - 过期删除与淘汰 —— 两套机制的区别;八种淘汰策略;默认值是什么 | 第 27 章 27.4
- RDB ——
BGSAVE 与 COW 的关系;fork 停顿来自哪 | 第 27 章 27.5.1 - AOF ——
appendfsync 三种策略的丢失窗口;AOF 重写怎么触发 | 第 27 章 27.5.2 - 混合持久化 —— 兼顾了什么;恢复时怎么用 | 第 27 章 27.5.3
- 哨兵 —— 主观与客观下线的区别;选主为什么需要多数派 | 第 27 章 27.7.1
- Cluster 槽 —— 为什么是 16384;
MOVED 与 ASK 的区别 | 第 27 章 27.7.2 - 缓存穿透 —— 根因是什么;空值缓存与布隆过滤器的取舍;误判率量级 | 第 27 章 27.8
- 缓存击穿 —— 与穿透的区别;逻辑过期与互斥重建的取舍 | 第 27 章 27.8
- 缓存雪崩 —— 根因;打散 TTL 与多级缓存的作用 | 第 27 章 27.8
- 延迟双删 —— 它能不能保证一致;
sleep 为什么无理论下界;怎么收敛 | 第 27 章 27.9 - 热 key 与大 key —— 怎么发现(不要用
MONITOR);怎么治理 | 第 27 章 27.10
Java 与并发
equals / hashCode 契约 —— 只重写一个会怎样;HashMap 查找两步各自怎么失败 | 第 28 章 28.3- String 常量池 —— 不可变性带来什么;
intern 的行为;JDK 7 搬家搬的是什么 | 第 28 章 28.4 - 自动装箱缓存 ——
Integer 的缓存范围;== 比较的结果;Float 为什么没有 | 第 28 章 28.5 - 泛型擦除 —— 擦到哪里去了;
Signature 属性的作用;桥接方法解决什么 | 第 28 章 28.9 - 动态代理 —— JDK 代理与 CGLIB 的判据;
final 方法会怎样;类内自调用为什么绕过 | 第 28 章 28.8 finally 与返回值 —— return 覆盖的机制;异常怎么被吞 | 第 28 章 28.7.1- 抑制异常 —— 哪个是主异常;
addSuppressed 的插入条件 | 第 28 章 28.7.2 - 序列化安全 —— 默认
serialVersionUID 的风险;readObject 危险在哪;治理动作 | 第 28 章 28.10 - 对象内存布局 —— 一个两字段对象多大;压缩指针的 32 GB 从哪来 | 第 28 章 28.2
HashMap 扩容 —— 容量为什么是 2 的幂;hash & oldCap 拆分技巧 | 第 29 章 29.4.5HashMap 扰动函数 —— 为什么要异或高位;不做会怎样 | 第 29 章 29.4.2- 树化阈值 —— 8 与 6 的来源;为什么退化阈值更小 | 第 29 章 29.4.3
ConcurrentHashMap —— 为什么放弃分段锁;get 为什么可以不加锁;弱一致读意味着什么 | 第 29 章 29.5CopyOnWriteArrayList —— 适用边界;迭代器为什么不抛异常;写代价 | 第 29 章 29.6modCount 与快速失败 —— 为什么 for-each 里 remove 会抛;modCount 为什么不是 volatile | 第 29 章 29.2.2LinkedHashMap 与 LRU —— accessOrder 的作用;哪个钩子做淘汰 | 第 29 章 29.7.3- TimSort —— 为什么对象数组用它而基本类型不用;契约违反异常怎么来的 | 第 29 章 29.8
- JMM(Java Memory Model) —— 为什么需要它;它规约了什么;x86 与 ARM 的差别 | 第 30 章 30.1
happens-before —— 八条规则;它是不是时间关系;怎么组合出安全发布 | 第 30 章 30.3- 内存屏障(JVM 视角) —— 四种屏障的约束;
volatile 写读各插什么;x86 为什么只剩 StoreLoad | 第 30 章 30.4 volatile —— 两条语义;为什么不保证 i++ 原子性;DCL 里为什么必须加 | 第 30 章 30.5- DCL 单例 —— 对象创建三步与重排;不加
volatile 会读到什么 | 第 30 章 30.5.1 - Mark Word 与锁升级 —— 位域怎么划分;
hashCode 与偏向锁为什么冲突;无锁→偏向→轻量级→重量级的成本 | 第 30 章 30.6.3 / 30.7 synchronized 锁对象 —— 实例方法与静态方法锁的是不是同一把;锁 String 字面量为什么危险 | 第 30 章 30.6.2ObjectMonitor —— wait/notify 在哪些队列间迁移;与 AQS 的差别 | 第 30 章 30.8ThreadLocal 泄漏 —— 引用链怎么形成;key 弱引用的作用;为什么必须 remove | 第 30 章 30.10InheritableThreadLocal —— 它解决什么;线程池下为什么失效;替代方案 | 第 30 章 30.10.3- 线程池四步流程 —— 核心、队列、最大、拒绝的顺序;为什么先入队而不是先扩线程 | 第 31 章 31.1
- 拒绝策略与工作队列选择 —— 四种拒绝语义与默认值;有界、无界、同步队列对行为的影响与
Executors 工厂方法的隐患 | 第 31 章 31.2 / 31.3 - 线程数计算 —— CPU 密集与 IO 密集的公式;混合型怎么用 Little 定律反推;压测看什么 | 第 31 章 31.5
- AQS ——
state 的设计;队列为什么是双向;独占与共享模式的差别 | 第 31 章 31.8 - CLH 队列 —— 原版 CLH 与 AQS 变体的差别;为什么改进 | 第 31 章 31.8.2
PROPAGATE 状态 —— 它解决哪个丢失唤醒分支;没有它会怎样 | 第 31 章 31.8.4Condition —— 条件队列与同步队列的迁移;await 为什么必须持锁 | 第 31 章 31.8.5ReentrantLock —— 公平与非公平的差别;与 synchronized 怎么选 | 第 31 章 31.9CountDownLatch / CyclicBarrier —— 谁等谁;能否重用;Semaphore 的语义 | 第 31 章 31.10CompletableFuture —— thenApply 与 thenCompose 的差别;默认线程池的隐患 | 第 31 章 31.11ForkJoinPool —— 工作窃取的机制;与普通线程池的适用差别 | 第 31 章 31.11LongAdder —— 分段累加的原理;sum() 为什么不是精确值 | 第 31 章 31.12- TLAB —— 它把分配变成了什么;什么时候会退化到共享分配 | 第 32 章 32.3.2
- 逃逸分析与标量替换 —— 什么情况下对象不上堆;它能消除什么开销 | 第 32 章 32.3.3
- GC Roots —— 清单有哪些;为什么不用引用计数 | 第 32 章 32.4.1
- 四种引用 —— 强/软/弱/虚的回收时机;各自典型用途 | 第 32 章 32.4.2
- Card Table —— 它避免扫描什么;写屏障的作用 | 第 32 章 32.5.2
- Minor GC 与晋升 —— 完整流程;四种直接进老年代的条件;动态年龄判定怎么算 | 第 32 章 32.6
- 三色标记 —— 漏标的两个条件;增量更新与 SATB 各破坏哪个条件 | 第 32 章 32.7
- SATB —— 它牺牲了什么换取正确性(浮动垃圾);写前屏障的时机 | 第 32 章 32.7.2
- CMS —— 四个阶段;
Concurrent Mode Failure 的成因与后果 | 第 32 章 32.8 - G1 —— Region 与 Remembered Set;
Mixed GC 的选择依据;什么时候退化成 Full GC | 第 32 章 32.9 MaxGCPauseMillis —— 它是期望值还是承诺;G1 怎么用它选收集集合 | 第 32 章 32.9.1- ZGC —— 停顿为什么与堆大小无关;着色指针与读屏障;代价是什么 | 第 32 章 32.10
- Metaspace ——
MetaspaceSize 的真实语义;为什么替换永久代;类卸载条件 | 第 32 章 32.2 - OOM 类型 —— 八种
OutOfMemoryError 的判据;unable to create new native thread 怎么查 | 第 32 章 32.12
分布式与 MQ
- 消息丢失与
acks 配置 —— 三个环节各自防线;acks=all 退化的条件与 unclean.leader.election 的风险 | 第 33 章 33.2 / 33.3.4 HW 与 LEO —— 两者的定义;消费者为什么只能读到 HW 之前;leader epoch 解决什么 | 第 33 章 33.3.2- ISR —— 怎么维护;收缩与扩张的条件 | 第 33 章 33.3.1
- 幂等 —— 重复为什么不可避免;四种方案对比;幂等键为什么不能用 UUID | 第 33 章 33.4
- 去重表 —— 与业务是否同事务;容量怎么治理 | 第 33 章 33.4.3
- 顺序消息 —— 三种保证层次;分区内乱序的两个来源;扩容为什么破坏顺序 | 第 33 章 33.5
- 消息积压 —— 第一步为什么是定性不是扩容;实例数上限是什么;分区能否减少 | 第 33 章 33.6
- 延迟消息 —— 固定档位的取舍;队列级 TTL 的队头阻塞;通用替代方案 | 第 33 章 33.7
- 时间轮 —— 槽位与
tickMs 的记账方式;多层设计的目的;复杂度优势 | 第 33 章 33.7.1 - 事务消息 —— 两阶段流程;为什么必须回查;三种跨系统写方案对比 | 第 33 章 33.8
- MQ 选型 —— 选型前要给什么约束;各主流 MQ 的能力边界 | 第 33 章 33.9
- CAP —— 准确表述;三选二错在哪;A 的定义与可用率的区别 | 第 34 章 34.1
- PACELC —— 它补上了哪半张账;延迟与一致性的取舍 | 第 34 章 34.1.3
- BASE —— 与 ACID 的关系;柔性事务的代价 | 第 34 章 34.1.4
- 一致性模型 —— 线性一致与顺序一致的差别;因果一致与最终一致的位置 | 第 34 章 34.2
- Raft —— 选举超时为什么要随机;日志复制的一致性检查;旧任期日志为什么不能提交 | 第 34 章 34.3
- Raft 成员变更 —— 为什么不能一次换掉一半;单步变更与联合共识 | 第 34 章 34.3.6
- Paxos —— 与 Raft 的关系;Basic Paxos 的活锁 | 第 34 章 34.4
- ZAB —— 它是主备复制还是共识;与 Raft 的差别 | 第 34 章 34.4
- 2PC / 3PC 与 TCC —— 阻塞与不确定态、协调者单点;TCC 的空回滚、幂等、悬挂与防悬挂标记 | 第 34 章 34.5
- Saga —— 它没有隔离性意味着什么;补偿的设计要点 | 第 34 章 34.5
- 本地消息表 —— 它把分布式问题转成了什么;可靠性与吞吐的取舍 | 第 34 章 34.5
- Snowflake —— 位段结构与容量;时钟回拨的四种处理策略 | 第 34 章 34.6
- 分布式锁与 fencing token —— Redis 与 ZooKeeper 的取舍;双持有与锁过期;
Redlock 的争议与为什么必须配 fencing token | 第 34 章 34.7 - 限流 —— 四种算法的差别;固定窗口的临界点问题;桶深与突发的关系 | 第 34 章 34.8.1
- 一致性哈希 —— 解决什么迁移量问题;虚拟节点为什么必要;与 Redis Cluster 槽的区别 | 第 34 章 34.8.2
- 微服务拆分 —— 三个可观测判据;收益与代价分别在谁身上 | 第 35 章 35.1
- 服务注册发现 —— 客户端发现与服务端发现的差别;AP 与 CP 的取舍 | 第 35 章 35.2
- 健康检查 —— 两种方式的误判;自我保护机制是否有意设计 | 第 35 章 35.2.3
- 负载均衡策略 —— 七种策略的判据;最少连接为什么常优于轮询 | 第 35 章 35.3
- 熔断 —— 三态与迁移条件;关键参数;与限流的区别 | 第 35 章 35.4
- 线程池隔离 —— 它买到的是什么;与信号量隔离的取舍 | 第 35 章 35.5.1
- 超时与重试放大 —— 逐跳递减的超时预算;重试次数为什么不能大 | 第 35 章 35.5.2
- 降级与兜底 —— 降级的层次;兜底数据从哪来 | 第 35 章 35.6
- 链路追踪 ——
TraceID/SpanID 怎么传播;头部采样与尾部采样的取舍 | 第 35 章 35.7 - 网关职责 —— 什么该放网关、什么不该;响应式网关的约束 | 第 35 章 35.8
- 灰度发布 —— 灰度维度有哪些;蓝绿与金丝雀的差别;回滚设计 | 第 35 章 35.9
- 接口幂等落地 —— 幂等键设计与唯一索引;幂等与并发控制的区别 | 第 35 章 35.10
- 容量估算 —— 四个量怎么算;均值到峰值到索引到副本的链条 | 第 36 章 36.3
- Feed 推拉 —— 推模式与拉模式的代价;混合模式的切分点 | 第 36 章 36.8
- 秒杀 —— 六层防线各挡什么;超卖与少卖的成因与补救 | 第 36 章 36.4
性能与排障
- 延迟数量级 —— 各级延迟的心算值;跨层访问的倍率;如何据此排优先级 | 第 3 章 3.1
- Little 定律 —— 公式与适用前提;
W 含不含排队;怎么用它反推线程数 | 第 3 章 3.2 - Amdahl 定律 —— 串行占比决定上限;算例;与弱扩展的关系 | 第 3 章 3.3
- USE 方法与 RED 方法 —— USE 三个维度分别查什么、查不出什么;RED 三个指标与为什么它先于 USE 使用 | 第 3 章 3.4
- P99 / P999 —— 均值怎么掩盖问题;报分位数要附什么;样本量要多大 | 第 3 章 3.5
- 尾延迟放大 —— 扇出下的概率计算;独立性假设不成立时如何;四类对策 | 第 3 章 3.5
- 基准测试陷阱 —— 预热、抖动、坐标系、均值掩盖长尾;怎么声明可信 | 第 3 章 3.6
- 先测量再优化 —— 闭环步骤;为什么不能直接改代码试 | 第 3 章 3.7
- 火焰图与四板斧排查 —— on-CPU 与 off-CPU 各回答什么、等锁为何不在 on-CPU 上;CPU、内存、磁盘、网络各先看哪个指标 | 第 3 章 3.4 / 第 11 章 11.8
- 高负载低 CPU —— 三种成因;每种用什么命令验证 | 第 11 章 11.2.3
- 内存可用量 ——
available 与 free 的区别;哪些 cache 可回收 | 第 11 章 11.5.1 - 磁盘延迟异常 —— 基线怎么建立;读写分开看;与队列深度的关系 | 第 11 章 11.6.3
- 接口慢的分层排查 —— DNS → 建连 → TLS → TTFB → 传输;每段用什么工具 | 第 19 章 19.6
- 跨机房调用代价 —— 与内存访问的倍率;由此得出的优化优先级 | 第 3 章 3.1
- 计时与时钟偏差 —— 跨机时序为什么不可信;NTP 偏差的容忍量级 | 第 35 章 35.11
- 超时设置 —— 分层预算怎么分;与重试的配合 | 第 35 章 35.5.2
- 资源池排队 —— 连接池与线程池的排队怎么体现;为什么不体现在利用率上 | 第 3 章 3.4
- 白板沟通节奏 —— 六步框架的时间分配;答不全时怎么补救 | 第 36 章 36.2
B.4 面试前的最后 30 分钟清单
警告
这份清单是检索练习,不是阅读材料。正确用法是盖住右列,只看左边的结论关键词,自己复述一遍完整推导;复述不出来再回正文。只读不测,30 分钟基本白花。
按「一旦答错会直接让面试官怀疑基本功」的优先级排序,前 20 条:
- MVCC 可见性判断:
m_ids/min_trx_id/max_trx_id 三个边界加沿 DB_ROLL_PTR 回溯;RC 与 RR 只差 ReadView 创建时机,且 RR 是首次快照读时建的 —— 第 22 章 22.4 / 22.5 - 间隙锁加锁范围:非唯一索引等值查询要向后扫到第一个不满足条件的记录才停,故锁范围比唯一索引多一整段区间 —— 第 24 章 24.4
- 两阶段提交崩溃恢复:决断依据是「redo 处于 prepare 且 binlog 完整」,先 binlog 后 commit 会让从库多数据 —— 第 21 章 21.5
epoll 数据结构:红黑树服务 epoll_ctl,就绪链表服务 epoll_wait,ep_poll_callback 负责挂入 —— 第 7 章 7.5- 零拷贝:零 CPU 拷贝、DMA 拷贝必然存在;
sendfile 是 2 次还是 3 次取决于网卡 scatter-gather DMA —— 第 7 章 7.8 TIME_WAIT 的两条理由:重发最后 ACK;让旧报文消失以免污染同四元组新连接。MSL 是单程故需 2 倍 —— 第 16 章 16.6.3- 拥塞窗口演化:超时则
cwnd = 1 MSS 重新慢启动,3 个重复 ACK 只砍半;ssthresh 折半 —— 第 17 章 17.6 - AQS:
state 是 volatile int 加 CAS、语义由子类定义;队列是双向的,为的是唤醒时能从尾往前找有效节点 —— 第 31 章 31.8 - 三色标记漏标两条件:黑指向白、且灰到白的引用被删除;增量更新与 SATB 各破坏其中一个 —— 第 32 章 32.7
- 写时复制:
fork 只复制页表并把 PTE 的 W 位清零,写入时缺页,按 _mapcount 判据决定复制还是原地恢复可写 —— 第 4 章 4.2 - CFS 的
vruntime:vruntime += 实际时间 × (1024/weight),两任务 vruntime 追平时时间比等于权重比 —— 第 5 章 5.3.1 volatile 屏障插入:写插 StoreStore + StoreLoad,读插 LoadLoad + LoadStore;x86 只剩 StoreLoad —— 第 30 章 30.4HashMap 扩容拆分:容量永远是 2 的幂,所以 hash & oldCap 一次位与就能决定留原位还是迁到 j + oldCap —— 第 29 章 29.4.5- 线程池四步流程:核心线程 → 队列 → 最大线程 → 拒绝;默认拒绝策略是
AbortPolicy 会抛异常 —— 第 31 章 31.1 / 31.3 - 平均负载:可运行 R 加不可中断 D 的任务数,指数衰减平均;高负载低 CPU 的三种成因 —— 第 11 章 11.2
%util 为什么不可靠:它只是「至少有一个在途请求的时长占比」,判据要看 await 与 aqu-sz —— 第 11 章 11.6.2overcommit 与 swap:malloc 成功不等于内存可得;vm.swappiness 是权重不是开关,设为 0 仍会换出 —— 第 12 章 12.5 / 12.6- 最左前缀:联合索引
(a,b,c) 中 a 全局有序、b 只在 a 固定时有序,故范围条件之后的列无法用于定位 —— 第 23 章 23.6 - Raft 提交规则:只有当前任期的日志能凭多数派提交,旧任期只能被连带提交,否则已提交条目会被覆盖 —— 第 34 章 34.3.4
- 消息不丢的三段防线:生产端同步确认加回调重试、Broker 端副本与刷盘、消费端「先处理再提交」 —— 第 33 章 33.2
提示
最后 3 分钟在纸上默写这三张图,能默出来就说明第 1、8、9 条真的记住了:MVCC 的 ReadView 与 undo 版本链、AQS 的 state 加双向队列加条件队列、三色标记的漏标时序。这三张图是全书被追问最深的三处,也是最容易在紧张时讲乱的三处。