Reference
Java 面试速查参考文档。覆盖 JVM、并发、集合、设计模式、Spring、Redis、消息队列、高可用、SQL、数据库选型与 HTTP 协议等,按左侧导航浏览。
持续更新JVM 总览
JVM 是 Java 程序的运行环境,负责把字节码加载到内存中,通过解释执行或 JIT 编译运行,同时管理内存、线程、垃圾回收和运行时安全。
核心组成
类加载子系统
加载、验证、准备、解析、初始化 class 文件,把字节码变成 JVM 可以使用的运行时结构。
运行时数据区
包括堆、方法区、虚拟机栈、本地方法栈、程序计数器,是对象、线程栈帧和类元数据的主要存放区域。
执行引擎
负责执行字节码,包含解释器、JIT 编译器和垃圾回收器。
本地接口
通过 JNI 调用 C/C++ 等本地方法,常见于底层系统能力或高性能库。
内存模型
运行时内存结构
| 区域 | 线程共享 | 存放内容 | 常见异常 |
|---|---|---|---|
| 堆 | 是 | 对象实例、数组,是 GC 管理的主要区域 | OutOfMemoryError: Java heap space |
| 方法区 / 元空间 | 是 | 类元数据、常量池、静态变量、JIT 编译后的代码等 | OutOfMemoryError: Metaspace |
| 虚拟机栈 | 否 | 方法调用的栈帧、局部变量表、操作数栈、返回地址 | StackOverflowError |
| 本地方法栈 | 否 | Native 方法调用信息 | StackOverflowError、OutOfMemoryError |
| 程序计数器 | 否 | 当前线程执行的字节码行号指示器 | 通常不会 OOM |
堆内存分代
- 新生代:大多数对象朝生夕死,通常分为 Eden、Survivor From、Survivor To。
- 老年代:长期存活对象、大对象或经历多次 Minor GC 后晋升的对象。
- 元空间:JDK 8 后替代永久代,使用本地内存,主要存储类元数据。
Java 内存模型 JMM
JMM 不是 JVM 运行时内存区域,而是 Java 并发语义规范,定义线程如何通过主内存和工作内存交互,解决可见性、有序性和原子性问题。
- 原子性:基本读写通常具备原子性,复合操作如
i++不具备原子性。 - 可见性:一个线程修改共享变量后,其他线程能否及时看到。
volatile、锁、final可提供可见性保障。 - 有序性:编译器和 CPU 可能重排序,JMM 通过 happens-before 规则约束可观察结果。
垃圾回收与调优
对象是否存活
- 引用计数法:实现简单,但无法解决循环引用,JVM 主流 GC 不采用它作为核心判断方式。
- 可达性分析:从 GC Roots 出发,能被引用链到达的对象是存活对象,否则可回收。
常见 GC Roots
- 虚拟机栈中引用的对象,例如局部变量。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中 JNI 引用的对象。
- 被同步锁持有的对象。
垃圾回收算法
| 算法 | 特点 | 适用场景 |
|---|---|---|
| 标记-清除 | 先标记可回收对象再清除,会产生内存碎片 | 基础算法,老年代早期方案 |
| 复制算法 | 把存活对象复制到另一块区域,清理整块旧区域,效率高但浪费空间 | 新生代 |
| 标记-整理 | 标记后把存活对象向一端移动,解决碎片问题 | 老年代 |
| 分代收集 | 根据对象生命周期把堆分代,不同区域使用不同算法 | 现代 JVM 常见整体策略 |
常见垃圾回收器
- Serial:单线程,简单稳定,适合客户端或小内存场景。
- Parallel:吞吐量优先,适合批处理、计算任务。
- CMS:低停顿老年代回收器,存在内存碎片问题,已逐步淘汰。
- G1:面向服务端的低停顿收集器,把堆划分为多个 Region,可预测停顿时间。
- ZGC / Shenandoah:面向超低停顿和大堆场景,停顿时间通常非常短。
调优思路
- 先明确目标:降低停顿、提升吞吐、减少 OOM,还是稳定响应时间。
- 收集证据:开启 GC 日志,结合监控、堆转储、线程栈分析。
- 判断问题类型:内存泄漏、对象创建过快、晋升失败、元空间不足、Full GC 频繁。
- 再调参数:堆大小、年轻代比例、GC 器选择、停顿目标、元空间上限。
- 压测验证:调优必须用真实流量模型或压测结果闭环。
常用参数
-Xms2g -Xmx2g
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-Xlog:gc*:file=gc.log:time,uptime,level,tags
类加载机制
类加载过程
- 加载:通过类的全限定名获取二进制字节流,转成方法区运行时数据结构,并在堆中生成
Class对象。 - 验证:确保字节码符合 JVM 规范,不危害虚拟机安全。
- 准备:为类变量分配内存并设置默认初始值。
- 解析:把常量池中的符号引用转换为直接引用。
- 初始化:执行类构造器
<clinit>(),给静态变量赋代码中定义的值,执行静态代码块。 - 使用:程序正常访问类和对象。
- 卸载:类加载器可回收、类无实例、Class 对象无引用时,类才可能被卸载。
双亲委派模型
类加载器收到加载请求后,先委托父加载器尝试加载,父加载器无法加载时,子加载器才自己加载。
- Bootstrap ClassLoader:加载 Java 核心类库。
- Extension / Platform ClassLoader:加载扩展类库,JDK 9 后称 Platform ClassLoader。
- Application ClassLoader:加载应用 classpath 下的类。
- Custom ClassLoader:用户自定义类加载器,常见于热部署、插件化、隔离加载。
双亲委派的好处
- 避免核心类被重复加载。
- 保护 Java 核心 API,防止用户自定义
java.lang.String等类替换核心类。 - 保证类加载的一致性和安全性。
什么时候会打破双亲委派
- JDBC、JNDI、SPI 等需要父加载器反向使用子加载器加载的实现类。
- Tomcat 等容器需要隔离不同 Web 应用的类。
- OSGi、插件化、热部署场景需要更灵活的类加载策略。
面试回答模板
问题:请你讲一下 JVM 内存结构
问题:线上频繁 Full GC 怎么排查
问题:双亲委派模型是什么,为什么需要它
常见追问
- JMM 和 JVM 内存结构有什么区别?JMM 是并发语义规范,关注可见性、有序性、原子性;JVM 内存结构是运行时数据区划分。
- 为什么新生代适合复制算法?因为新生代对象大多很快死亡,存活对象少,复制成本低且能快速清理整块区域。
- G1 为什么适合低停顿?G1 把堆拆成多个 Region,优先回收收益高的 Region,并可通过停顿目标控制回收节奏。
- 类什么时候初始化?主动使用类时初始化,例如创建实例、访问静态变量、调用静态方法、反射调用、初始化子类前初始化父类。
多线程并发总览
并发编程要解决三类问题:如何创建和管理线程、如何保证共享数据安全、如何提升吞吐并控制资源消耗。生产环境通常用线程池而不是裸建线程,用锁或 CAS 保证一致性,用 JUC 工具类简化协作。
线程状态
- NEW → RUNNABLE → RUNNING / BLOCKED / WAITING / TIMED_WAITING → TERMINATED
sleep()不释放锁;wait()释放锁并进入等待队列。yield()让出 CPU,不保证立刻切换;join()等待目标线程结束。
线程池
为什么用线程池
- 降低频繁创建/销毁线程的开销。
- 控制并发度,避免线程过多导致上下文切换和 OOM。
- 统一管理任务提交、执行、拒绝和监控。
ThreadPoolExecutor 七大参数
| 参数 | 含义 |
|---|---|
| corePoolSize | 核心线程数,即使空闲也保留(除非 allowCoreThreadTimeOut) |
| maximumPoolSize | 最大线程数 |
| keepAliveTime | 非核心线程空闲存活时间 |
| unit | 存活时间单位 |
| workQueue | 任务队列:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue |
| threadFactory | 创建线程的工厂,可定制线程名便于排查 |
| handler | 拒绝策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy |
任务提交流程
- 当前线程数 < corePoolSize → 创建核心线程执行。
- 否则尝试入队;队列未满则排队。
- 队列满且线程数 < maximumPoolSize → 创建非核心线程。
- 否则执行拒绝策略。
常见线程池(Executors 工厂方法)
newFixedThreadPool:固定大小 + 无界队列,任务堆积时可能 OOM。newCachedThreadPool:可扩容 + SynchronousQueue,高并发下线程数可能暴涨。newSingleThreadExecutor:单线程顺序执行。newScheduledThreadPool:定时/周期任务。
ForkJoinPool
分治任务专用,工作窃取(work-stealing)减少空闲;适合 CPU 密集型、可拆分的大任务,与 Stream 并行流底层相关。
锁机制
synchronized
- 可修饰实例方法、静态方法、代码块;自动加锁解锁,不可中断、不可设置超时。
- JDK 6 后锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(减少不必要的 OS 互斥)。
- 保证原子性、可见性、有序性(通过 monitor 与内存屏障)。
ReentrantLock
- 基于 AQS 实现,可重入;支持
lockInterruptibly()、tryLock(timeout)。 - 可选公平锁(按申请顺序)或非公平锁(吞吐更高,默认)。
- 需配合
Condition实现多条件等待,类似多个 wait/notify 队列。
读写锁 ReadWriteLock
读多写少场景:读锁共享、写锁独占,提升读并发。StampedLock 支持乐观读,适合读远多于写。
volatile
- 保证可见性、禁止部分重排序;不保证复合操作原子性(如 i++)。
- 典型场景:状态标志位、双重检查锁中的实例引用发布。
锁对比
| 机制 | 优点 | 注意点 |
|---|---|---|
| synchronized | 语法简单、JVM 优化成熟 | 功能相对固定 |
| ReentrantLock | 可中断、可超时、多 Condition | 必须 finally 中 unlock |
| volatile | 轻量、无阻塞 | 不能替代锁做复合更新 |
死锁四要素与避免
- 互斥、占有且等待、不可抢占、循环等待。
- 避免:固定加锁顺序、tryLock 超时、缩小锁粒度、使用无锁结构。
CAS 与 AQS
CAS(Compare-And-Swap)
比较内存中的值与期望值,相等则更新为新值,是 CPU 原子指令实现的无锁乐观更新。
- 优点:无阻塞、适合低竞争下的高性能计数、栈/队列头尾更新。
- ABA 问题:值从 A→B→A,CAS 仍成功;可用 AtomicStampedReference 带版本号解决。
- 自旋开销:竞争激烈时 CAS 反复失败,CPU 空转,此时锁可能更合适。
常用原子类
AtomicInteger/AtomicLong:基本类型原子操作。AtomicReference:引用原子替换。LongAdder:高并发计数,分段累加,比 AtomicLong 竞争小时更快。
AQS(AbstractQueuedSynchronizer)
JUC 锁与同步器的基础框架:state 表示同步状态,CLH 队列管理阻塞线程。
- ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock 等均基于 AQS。
- 独占模式 vs 共享模式;tryAcquire / tryRelease 由子类实现。
并发面试回答模板
问题:线程池参数怎么设
问题:synchronized 和 ReentrantLock 区别
常见追问
- ThreadLocal 内存泄漏?线程池复用线程时,若未 remove,Entry 的 key 弱引用被回收后 value 仍可能被强引用持有。
- CAS 一定比锁快吗?不一定,高竞争下自旋浪费 CPU,应选锁或 LongAdder 等分段结构。
Lambda 表达式
函数式接口(仅一个抽象方法)的简洁实现,配合方法引用简化代码。
语法要点
(参数) -> 表达式或(参数) -> { 语句; }- 类型可推断时可省略参数类型;单参数可省略括号。
- 方法引用:
类::静态方法、实例::方法、类::实例方法、构造器::new
// 函数式接口
@FunctionalInterface
interface Calculator {
int calc(int a, int b);
}
Calculator add = (a, b) -> a + b;
List<String> names = Arrays.asList("Tom", "Jerry");
names.forEach(System.out::println);
内置函数式接口
| 接口 | 方法 | 用途 |
|---|---|---|
| Predicate<T> | test | 断言/过滤 |
| Function<T,R> | apply | 映射转换 |
| Consumer<T> | accept | 消费副作用 |
| Supplier<T> | get | 延迟供给 |
接口默认方法与静态方法
Java 8 允许接口有 default 和 static 方法,便于 API 演进而不破坏实现类。
Stream API
对集合进行声明式、可链式、可并行的数据处理流水线,不存储元素,不修改源(除非明确可变操作)。
操作分类
- 中间操作(惰性):filter、map、flatMap、distinct、sorted、peek、limit、skip
- 终端操作(触发执行):forEach、collect、reduce、count、anyMatch、findFirst、min/max
List<User> result = users.stream()
.filter(u -> u.getAge() >= 18)
.sorted(Comparator.comparing(User::getName))
.map(User::getName)
.distinct()
.collect(Collectors.toList());
// 分组与统计
Map<String, Long> byDept = users.stream()
.collect(Collectors.groupingBy(User::getDept, Collectors.counting()));
并行流 parallelStream
- 底层 ForkJoinPool.commonPool(),适合 CPU 密集型、无共享可变状态的任务。
- 避免:IO 密集、小数据量、依赖顺序、涉及 synchronized 或非线程安全集合。
CompletableFuture
Java 8 增强的异步编程模型,支持组合、链式回调和异常处理,弥补 Future 只能阻塞 get 的不足。
常用 API
supplyAsync/runAsync:异步有返回值 / 无返回值thenApply/thenAccept/thenRun:串行依赖上一步结果thenCombine/allOf/anyOf:多任务组合exceptionally/handle:异常恢复orTimeout/completeOnTimeout(Java 9+):超时控制
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> fetchOrder(), executor)
.thenApply(order -> enrich(order))
.exceptionally(ex -> { log.error("fail", ex); return "DEFAULT"; });
// 等待多个任务
CompletableFuture.allOf(f1, f2, f3).join();
与 Future / 回调对比
| 方式 | 特点 |
|---|---|
| Future | get 阻塞,难以链式组合 |
| CompletableFuture | 非阻塞组合、显式线程池、异常传播可控 |
JDK 17 特性(LTS)
JDK 17 是长期支持版本,生产升级常选 11 → 17。以下为面试高频点。
语言与 API
- 密封类 Sealed Classes:限制谁可以继承/实现,配合模式匹配更清晰建模。
- 模式匹配 instanceof:判断类型同时绑定变量,减少强转样板代码。
- Records:不可变数据载体,自动生成构造器、equals、hashCode、toString。
- 文本块 Text Blocks:多行字符串
""" ... """,保留格式。 - Switch 表达式增强:
->语法、yield、模式匹配 switch(预览演进中)。
public sealed interface Shape permits Circle, Rect {}
public record Point(int x, int y) {}
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}
JVM 与库
- 默认封装更强(强封装 JDK 内部 API),反射访问需
--add-opens。 - 移除 AOT/JIT 实验特性、Applet API、Security Manager 逐步废弃。
- Foreign Function & Memory API(孵化/预览阶段演进)、Vector API 持续迭代。
- macOS/AArch64 等平台支持成熟,ZGC、G1 等 GC 持续优化。
JDK 21 特性(LTS)
JDK 21 是继 17 之后的 LTS,虚拟线程与若干预览特性进入主流面试题。
虚拟线程 Virtual Threads(Project Loom)
- 轻量级线程,由 JVM 调度,适合高并发 IO 密集型(大量阻塞等待)。
Thread.startVirtualThread(runnable)或Executors.newVirtualThreadPerTaskExecutor()- 不要池化虚拟线程;避免在虚拟线程中执行长时间 CPU 计算或 pin 住载体线程(如 synchronized 内阻塞,JDK 版本持续优化中)。
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i ->
executor.submit(() -> fetchFromRemote(i)));
}
语言与其它重要特性
- Record Patterns:解构 record,与 switch 模式匹配配合。
- Pattern Matching for switch:正式版,类型模式 + null 处理更完整。
- Sequenced Collections:统一首尾访问 API(getFirst、getLast、reversed)。
- String Templates(预览):更安全、可扩展的字符串插值。
- Generational ZGC:分代 ZGC 降低开销,改善吞吐与停顿。
- Key Encapsulation Mechanism API:后量子密码相关标准 API。
17 → 21 选型简述
| 场景 | 建议 |
|---|---|
| 稳定存量、框架未就绪 | 优先 JDK 17 LTS |
| 高并发 IO、微服务、新项目 | 评估 JDK 21 + 虚拟线程 |
| 强依赖反射 hack 内部 API | 先解决模块化与 --add-opens 再升级 |
现代 Java 面试回答模板
问题:Stream 和 for 循环怎么选
问题:CompletableFuture 如何避免线程池耗尽
问题:虚拟线程适合什么场景
常见追问
- Lambda 捕获的变量为什么必须是 effectively final?避免并发语义下闭包内外状态不一致,简化实现。
- Record 和 Lombok @Data 区别?Record 是语言级不可变语义,语义更清晰;Lombok 是编译期生成样板代码的普通类。
- JDK 17 和 21 为什么都标 LTS?Oracle 每两年一个 LTS,17 和 21 都是长期支持节点,企业可按生态成熟度选择。
集合框架体系总览
java.util 集合框架以接口为核心,常用实现类在底层用数组、链表、哈希表或红黑树组织数据。选型时看:是否有序、是否允许重复/null、读写比例、是否并发。
继承关系(面试常画)
Iterable→Collection→List/Set/QueueMap独立接口,不继承 Collection;entrySet()可当作 Set 视图操作
选型速查
| 需求 | 推荐 | 底层 |
|---|---|---|
| 随机访问、尾部追加多 | ArrayList | 动态数组 |
| 头尾插入删除多 | LinkedList | 双向链表 |
| 去重、无序、查快 | HashSet | HashMap 的 key |
| 去重且保持插入顺序 | LinkedHashSet | 哈希表 + 双向链表 |
| 去重且排序 | TreeSet | 红黑树 |
| K-V、查改快 | HashMap | 数组 + 链表/红黑树 |
| 高并发读多写少 | ConcurrentHashMap | 分段/CAS + 桶锁 |
| 高并发读多、写极少 | CopyOnWriteArrayList | 写时复制数组 |
List 底层实现
ArrayList
- 底层
Object[] elementData,默认初始容量 10(首次 add 时才真正分配)。 - 扩容:容量不足时
grow(),新容量约为old + (old >> 1),即约 1.5 倍,再Arrays.copyOf拷贝。 - 随机访问
get(i)O(1);尾部add均摊 O(1);中间插入/删除需数组搬移 O(n)。 - 非线程安全;
fail-fast:迭代中结构修改抛ConcurrentModificationException(modCount 检测)。
LinkedList
- 双向链表,节点含
item、prev、next;同时实现Deque,可作栈/队列。 - 头尾插入删除 O(1);按索引访问需遍历 O(n)。
- 内存开销大于 ArrayList(每个元素额外两个引用)。
Vector / Stack
- Vector 方法
synchronized,线程安全但粒度粗、性能差,已被淘汰思路。 - 需要线程安全 List 用
Collections.synchronizedList或并发集合,而非 Vector。
Set 底层实现
Set 不允许重复元素,HashSet、LinkedHashSet 内部本质是 HashMap(或 LinkedHashMap),元素存在 key 上,value 是固定占位对象 PRESENT。
HashSet
- 依赖
hashCode()定位桶,equals()判断相等;只重写 equals 不重写 hashCode 会破坏契约。 - 无序、允许一个 null(JDK 8+ HashMap 树化后仍支持 null key 的单 null 限制在 HashMap 层)。
LinkedHashSet
- 在 HashSet 基础上维护双向链表记录插入顺序(或访问顺序,构造参数
accessOrder)。 - 略增内存,适合需要稳定遍历顺序的去重场景。
TreeSet
- 底层
TreeMap,红黑树保证有序,增删查 O(log n)。 - 元素需实现
Comparable或构造时传入Comparator;不允许 null(比较时会 NPE)。
Map 底层实现
HashMap(JDK 8+)
- 结构:数组(桶)+ 链表;链表长度 ≥ 8 且数组长度 ≥ 64 时链表转红黑树;节点 ≤ 6 时树退化为链表。
- 默认负载因子 0.75:平衡空间与时间,过大则链表/树变长,过小则频繁扩容。
- 容量为 2 的幂:索引
(n - 1) & hash,等价取模且更快。 - hash 扰动:高 16 位与低 16 位异或,减少低位相同导致的冲突。
- put 流程:算索引 → 桶空则放头节点 → 否则比 key(先比 hash 再 equals)→ 冲突则尾插/树插 → 超阈值则 resize。
- resize:容量翻倍,节点要么在原索引,要么在「原索引 + 旧容量」(利用 e.hash & oldCap 分流,JDK 8 优化)。
- 非线程安全;多线程 put 可能死循环(JDK 7 头插扩容)或数据丢失,JDK 8 改为尾插但仍不安全。
// 扰动与寻址(概念)
hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
index = (table.length - 1) & hash;
LinkedHashMap
- 继承 HashMap,额外维护双向链表:
after指针串联顺序。 accessOrder=false:插入顺序;true:访问顺序,可用于 LRU 缓存(配合removeEldestEntry)。
TreeMap
- 红黑树按 key 排序;
Comparator或Comparable决定顺序。 - 有序、O(log n),不适合 hash 均匀分布的高吞吐纯查找(HashMap 均摊 O(1) 更优)。
Hashtable
- 方法级 synchronized,不允许 null key/value;遗留类,用 ConcurrentHashMap 替代。
HashMap vs Hashtable vs ConcurrentHashMap
| HashMap | Hashtable | ConcurrentHashMap | |
|---|---|---|---|
| 线程安全 | 否 | 是(粗粒度锁) | 是 |
| null | 允许 1 个 null key,多 null value | 不允许 | 不允许 |
| JDK 8 结构 | 数组+链表+红黑树 | 数组+链表 | 数组+链表+红黑树 |
| 锁粒度 | — | 整表 | 桶头 synchronized / CAS |
并发集合
ConcurrentHashMap(JDK 8+)
- 取消 Segment 分段锁(JDK 7),改为对每个桶头节点 synchronized + CAS 插入空桶。
- size 用
baseCount+CounterCell[]分散计数,减少竞争。 - 扩容支持多线程协助转移(ForwardingNode),复杂度高于 HashMap 单线程 resize。
get一般无锁(volatile 读可见性);迭代弱一致性,不抛 CME。
CopyOnWriteArrayList / CopyOnWriteArraySet
- 写时复制:修改时复制新数组再替换引用,读无锁、写成本高。
- 适合读多写极少(白名单、监听器列表);写频繁或数据量大时内存与复制开销大。
其它
ConcurrentLinkedQueue:CAS 无界非阻塞队列。BlockingQueue家族:ArrayBlockingQueue(有界数组+一把锁)、LinkedBlockingQueue(可选有界链表)、PriorityBlockingQueue、DelayQueue、SynchronousQueue 等,用于线程池与生产者消费者。
集合面试回答模板
问题:HashMap 底层原理,为什么用红黑树
问题:HashMap 线程不安全体现在哪
问题:ArrayList 和 LinkedList 区别
常见追问
- hashCode 和 equals 契约?相等对象 hash 必须相同;hash 相同对象可不等,故还要 equals。
- 为什么容量是 2 的幂?位运算取模、扩容时 rehash 可优化(JDK 8 高低位分流)。
- Comparable 和 Comparator?前者内置于类,自然排序;后者外部策略,可多种排序规则。
- fail-fast 和 fail-safe?前者迭代中检测 modCount 快速失败;后者(如 COW、CHM 迭代器)弱一致,不保证看到最新且一般不抛 CME。
- 如何用 LinkedHashMap 做 LRU?构造
accessOrder=true,重写removeEldestEntry在超过容量时删除最久未访问条目。
SOLID 设计原则
面向对象设计的五条基本原则,目标是降低耦合、提高可维护性与可扩展性。
| 原则 | 英文 | 含义 | 违反时的症状 |
|---|---|---|---|
| S | Single Responsibility | 一个类只应有一个引起变化的原因(一个职责) | 类臃肿、改一处牵动多处、难测试 |
| O | Open/Closed | 对扩展开放,对修改关闭 | 新需求只能改老代码、if-else 膨胀 |
| L | Liskov Substitution | 子类必须能替换父类且不改变程序正确性 | 子类重写后破坏父类契约、需 instanceof 特判 |
| I | Interface Segregation | 客户端不应依赖它不需要的接口 | 「胖接口」、空实现、被迫实现无用方法 |
| D | Dependency Inversion | 高层模块不依赖低层实现,都依赖抽象 | 业务层直接 new 具体类、难以替换与单测 |
面试记忆与示例
- S:订单服务只处理订单逻辑,邮件通知拆到
NotificationService。 - O:促销规则用策略接口扩展,而不是在结算里不断加
if (双11)。 - L:正方形不应继承长方形(改一边会破坏另一边语义);子类不能加强前置、削弱后置条件。
- I:把
Worker拆成Workable与Eatable,机器人不必实现eat()。 - D:Service 依赖
UserRepository接口,由 Spring 注入UserRepositoryImpl。
创建型模式
关注对象的创建过程,将「如何 new」与业务逻辑解耦。
单例 Singleton
- 保证全局唯一实例:配置中心、连接池管理器、Spring 单例 Bean(容器级)。
- 写法:饿汉、懒汉、静态内部类、枚举(推荐,防反射与序列化破坏)。
- 注意:多 ClassLoader、集群环境下「单例」语义不同;过度使用难测试。
工厂方法 Factory Method
- 定义创建对象的接口,由子类决定实例化哪个具体类。
- 示例:
LoggerFactory.getLogger()按配置返回不同 Logger 实现。
抽象工厂 Abstract Factory
- 创建一族相关/依赖的产品(UI 主题:Button + TextField 成套)。
- 与工厂方法:抽象工厂是「多产品族」,工厂方法是「单产品多实现」。
建造者 Builder
- 分步构建复杂对象,链式调用,参数可选多。
- 示例:Lombok
@Builder、StringBuilder、HTTP Request 构建、MyBatisSqlSessionFactoryBuilder。
原型 Prototype
- 通过克隆已有对象创建新对象,避免重复昂贵初始化。
- Java:
Cloneable浅拷贝;深拷贝需手动或序列化。Springprototypescope 类似思想。
结构型模式
关注类与对象的组合,形成更大结构并保持灵活。
适配器 Adapter
- 将不兼容接口转为客户端期望的接口。
- 类适配器(继承)vs 对象适配器(组合,更常用)。
- 示例:
InputStreamReader把字节流适配为字符流;对接第三方 SDK。
装饰器 Decorator
- 动态给对象添加职责,比继承更灵活,可层层包装。
- 示例:
BufferedInputStream包装FileInputStream;Collections.synchronizedList。 - 与代理:装饰器侧重「增强功能」,代理侧重「控制访问」。
代理 Proxy
- 为对象提供代理以控制访问、延迟加载、权限、日志、事务。
- 静态代理:手写代理类;JDK 动态代理:基于接口;CGLIB:子类继承,可代理类。
- Spring AOP、MyBatis Mapper、RPC 客户端均为典型应用。
外观 Facade
- 为子系统提供统一简化入口,降低调用复杂度。
- 示例:SLF4J 门面、封装多个微服务调用的聚合 Service。
其它(了解)
- 桥接 Bridge:抽象与实现分离,如消息发送(邮件/短信)× 平台(Windows/Mac)。
- 组合 Composite:树形结构统一对待叶子与容器,如菜单/文件夹。
- 享元 Flyweight:共享细粒度对象,如字符串常量池、棋类游戏棋子状态共享。
行为型模式
关注对象之间的通信、职责分配与算法封装。
策略 Strategy
- 封装一族算法,使它们可互换;消除条件分支。
- 示例:Comparator、支付渠道(微信/支付宝)、路由规则、促销计算。
模板方法 Template Method
- 父类定义算法骨架,子类重写某些步骤(钩子方法)。
- 示例:
AbstractList、Servletservice()、JUnit 测试生命周期、SpringJdbcTemplate。
观察者 Observer
- 一对多依赖:主题状态变化时通知所有观察者。
- Java:
Observable(已过时);Spring 事件ApplicationEvent、MQ 发布订阅、Listener。
责任链 Chain of Responsibility
- 请求沿处理者链传递,直到有人处理。
- 示例:Servlet Filter 链、Netty Pipeline、审批流、日志级别链。
状态 State
- 对象内部状态改变时改变其行为,用类代替大段 switch。
- 示例:订单状态机(待支付→已支付→已发货)、TCP 连接状态。
命令 Command
- 将请求封装为对象,支持撤销、排队、日志。
- 示例:Runnable、事务命令、GUI 宏命令。
常用模式速查表
| 模式 | 一句话 | JDK / 框架中的影子 |
|---|---|---|
| 单例 | 全局唯一 | Runtime、Spring 单例 Bean |
| 工厂 | 封装创建 | Calendar.getInstance、LoggerFactory |
| 建造者 | 分步构建 | StringBuilder、Lombok Builder |
| 适配器 | 接口转换 | InputStreamReader |
| 装饰器 | 动态增强 | IO 包装流 |
| 代理 | 控制访问 | Spring AOP、MyBatis Mapper |
| 外观 | 统一入口 | SLF4J |
| 策略 | 算法可换 | Comparator |
| 模板方法 | 固定流程 | JdbcTemplate |
| 观察者 | 发布订阅 | Spring Event、MQ |
| 责任链 | 链式处理 | Filter、Interceptor |
设计面试回答模板
问题:说说你理解的 SOLID
问题:代理模式在 Spring 里怎么体现
问题:策略模式和 if-else 怎么选
常见追问
- 单例模式线程安全写法?推荐静态内部类或枚举;双重检查锁需 volatile 防指令重排。
- 装饰器和代理区别?意图不同:装饰器扩展功能、可叠加;代理控制访问、常代表真实对象。
- 工厂方法和抽象工厂区别?前者创建一个产品的一种实现;后者创建多个相关产品的一组实现。
- 设计模式是否越多越好?否,过度设计增加复杂度;在变化点使用合适模式即可。
Spring Boot / Cloud 体系总览
Spring Boot 简化单体/微服务应用的启动与配置(自动配置、Starter、内嵌容器)。Spring Cloud 在 Boot 之上提供分布式能力:注册发现、配置中心、负载均衡、熔断限流、网关、链路追踪等。
常见组件对照(面试版)
| 能力 | 常见实现 |
|---|---|
| 注册与发现 | Nacos、Eureka、Consul |
| 配置中心 | Nacos Config、Spring Cloud Config |
| 负载均衡 | Spring Cloud LoadBalancer(替代 Ribbon) |
| 服务调用 | OpenFeign、RestTemplate + LB |
| 熔断限流 | Sentinel、Resilience4j(Hystrix 已停更) |
| API 网关 | Spring Cloud Gateway、Kong |
| 链路追踪 | Micrometer Tracing、SkyWalking、Zipkin |
自动配置原理
启动流程(精简)
@SpringBootApplication=@Configuration+@EnableAutoConfiguration+@ComponentScan- 运行
SpringApplication.run()→ 创建ApplicationContext(Servlet 环境多为 AnnotationConfigServletWebServerApplicationContext) - 加载
application.yml/properties、环境 Profile、Bean 定义 - 执行各类
*AutoConfiguration,按条件注册 Bean - 内嵌 Tomcat/Jetty/Undertow 启动 Web 容器
@EnableAutoConfiguration 做了什么
- 通过
@Import(AutoConfigurationImportSelector.class)读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Boot 2.7+ / 3.x;旧版为spring.factories) - 候选配置类经
@ConditionalOnXxx过滤后生效
常用条件注解
| 注解 | 含义 |
|---|---|
| @ConditionalOnClass | classpath 存在某类 |
| @ConditionalOnMissingBean | 容器中尚无该 Bean 时才注册默认实现 |
| @ConditionalOnProperty | 配置项开关,如 xxx.enabled=true |
| @ConditionalOnWebApplication | Web 应用环境 |
Starter 机制
- Starter = 依赖聚合 + 自动配置模块,如
spring-boot-starter-web、spring-boot-starter-data-redis - 自定义 Starter:autoconfigure 模块 +
AutoConfiguration.imports+ 配置属性类@ConfigurationProperties
@Configuration
@ConditionalOnClass(RedisTemplate.class)
@EnableConfigurationProperties(RedisProperties.class)
public class MyRedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RedisTemplate<String, Object> redisTemplate(...) { ... }
}
循环依赖
什么是循环依赖
Bean A 依赖 B,B 又依赖 A(或更长环 A→B→C→A)。Spring 通过三级缓存解决单例 + 字段/Setter 注入场景下的循环依赖。
三级缓存(Singleton)
| 缓存 | 名称 | 内容 |
|---|---|---|
| 一级 | singletonObjects | 完全初始化好的单例 Bean |
| 二级 | earlySingletonObjects | 早期暴露的 Bean(已实例化,可能未填完属性) |
| 三级 | singletonFactories | ObjectFactory,用于生成早期引用(AOP 代理对象在此介入) |
解决流程(A 依赖 B,B 依赖 A)
- 创建 A:实例化后放入三级缓存,继续属性注入发现需要 B
- 创建 B:实例化后放入三级缓存,注入 A 时从三级拿到 A 的早期引用(可能是代理)
- B 完成初始化,进入一级缓存
- A 拿到 B,完成初始化,进入一级缓存
无法解决的情况
- 构造器注入循环依赖:实例化前就要 B,无法提前暴露半成品 → 启动失败
- prototype 作用域:每次 new,不参与单例三级缓存
- 多例 + 单例混合环、非 Spring 管理的 new 对象
实践建议
- 优先重构:拆服务、事件驱动、
@Lazy延迟注入(治标) - 避免构造器环;必要时改用 Setter/字段注入并明确文档
- Spring Boot 2.6+ 默认禁止部分循环依赖行为可配置:
spring.main.allow-circular-references=true(不推荐长期依赖)
服务治理
微服务治理关注:服务如何被发现、如何可靠调用、如何限流熔断、如何统一配置与可观测。
注册与发现
- 服务启动时向注册中心(Nacos/Eureka)注册 IP、端口、元数据、健康状态
- 消费者从注册中心拉取/订阅服务列表,结合负载均衡选择实例
- Nacos 同时支持 CP(Raft 持久实例)与 AP(临时实例)模式;Eureka 偏 AP(自我保护)
配置中心
- 配置与代码分离,支持动态刷新(
@RefreshScope+ Spring Cloud Context) - 多环境 Namespace/Group、灰度发布、加密配置(Jasypt)
负载均衡与调用
@LoadBalanced RestTemplate或WebClient+ LoadBalancer 拦截- OpenFeign:声明式 HTTP 客户端,集成 LB、熔断、请求压缩;底层常为 HttpClient / OkHttp
- 负载策略:轮询、随机、权重(Nacos 权重)、同 zone 优先等
熔断、限流、降级
- Sentinel:QPS/并发线程数限流、熔断(慢调用比例、异常比例、异常数)、热点参数、系统自适应保护
- 熔断状态:关闭 → 打开(快速失败)→ 半开(探测恢复)
- 降级:返回默认值、缓存、友好提示,保证核心链路可用
其它治理能力
- 分布式事务:Seata(AT/TCC/Saga)、最终一致性(消息表、可靠消息)
- 链路追踪:TraceId 透传,分析慢调用与依赖拓扑
- 服务契约:API 文档(OpenAPI)、版本兼容、消费者驱动契约(可选)
Spring Cloud Gateway
基于 Spring WebFlux + Netty 的响应式 API 网关,统一入口:路由、过滤、限流、鉴权、跨域、灰度。
核心概念
- Route:路由 ID + 目标 URI + Predicate 集合 + Filter 集合
- Predicate:断言匹配请求(Path、Method、Header、Cookie、时间窗口等)
- Filter:Gateway Filter(路由级)与 Global Filter(全局),可改请求/响应
请求链路(简化)
- 客户端 → Gateway
- Global Filter 链(如负载均衡、限流、鉴权)
- 匹配 Route → 执行 Gateway Filter
- 通过 LoadBalancer 选择下游服务实例 → 转发
- 响应回传,可统一加 Header、日志、熔断
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
Gateway vs Zuul
| Gateway | Zuul 1.x | |
|---|---|---|
| 模型 | WebFlux 非阻塞 | Servlet 阻塞 |
| 性能 | 高并发下更易扩展 | 线程池瓶颈明显 |
| 维护 | Spring 官方主推 | 已进入维护态 |
典型能力实现
- 统一鉴权:Global Filter 校验 JWT,下游信任网关或二次校验
- 灰度发布:根据 Header/用户标签路由到金丝雀实例(结合 Nacos 元数据)
- 限流:RequestRateLimiter + Redis;或与 Sentinel Gateway 适配器集成
- 跨域:Cors WebFilter 或 Gateway 全局 CORS 配置
Spring 面试回答模板
问题:Spring Boot 自动配置原理
问题:Spring 如何解决循环依赖
问题:Gateway 在架构中的作用
常见追问
- @Bean 和 @Component 区别?@Component 是类路径扫描的通用组件;@Bean 是在 @Configuration 里用方法显式注册,适合第三方类。
- Feign 和 RestTemplate?Feign 声明式、可集成熔断与契约;RestTemplate 更底层,需手写 URL 与 LB。
- Nacos 和 Eureka?Nacos 兼具注册与配置;Eureka 仅注册发现,2.x 停更后新项目多选 Nacos/Consul。
- 为什么 Gateway 用 WebFlux?少量线程处理大量 IO 等待,适合网关转发;注意阻塞调用需调度到 boundedElastic 避免阻塞事件循环。
Redis 总览
Redis 是基于内存的 KV 数据库,单线程执行命令(6.0+ 多线程主要处理网络 IO),支持丰富数据结构、持久化、主从复制、哨兵与 Cluster。常用于缓存、会话、排行榜、分布式锁、消息队列等。
为什么快
- 内存读写 + 高效数据结构
- 单线程避免锁竞争(命令仍原子)
- IO 多路复用(epoll)处理大量连接
- 纯内存操作,持久化异步进行
数据结构
五大基础类型
| 类型 | 底层(常见) | 典型场景 | 常用命令 |
|---|---|---|---|
| String | SDS(简单动态字符串) | 缓存对象、计数器、分布式锁、Session | GET/SET、INCR、SETNX、EXPIRE |
| List | quicklist(链表 + ziplist 压缩) | 消息队列、最新列表(注意双端操作复杂度) | LPUSH/RPOP、BLPOP、LRANGE |
| Hash | ziplist 或 hashtable | 对象字段存储(用户信息),小 Hash 省内存 | HGET/HSET、HMGET、HINCRBY |
| Set | intset 或 hashtable | 标签、共同好友、去重、抽奖 | SADD、SINTER、SUNION、SRANDMEMBER |
| ZSet(Sorted Set) | skiplist + hashtable | 排行榜、延迟队列、范围查询 | ZADD、ZRANGE、ZRANK、ZINCRBY |
高级 / 扩展类型
- Bitmap:位操作,签到、活跃用户统计(BITCOUNT 需注意大数据耗时)
- HyperLogLog:基数估算(UV),省内存,有误差
- GEO:地理位置,底层 ZSet,附近的人
- Stream:消息流,消费者组,类似轻量 MQ(持久化、ACK、Pending)
- Pub/Sub:发布订阅,不持久化,订阅方离线会丢消息
键与过期
- 过期策略:定期删除 + 惰性删除(访问时检查是否过期)
- 内存淘汰(maxmemory-policy):noeviction、allkeys-lru、volatile-lru、allkeys-lfu 等
- 生产建议:缓存必须设 TTL;热点 key 注意过期时间打散
持久化
| 方式 | 特点 |
|---|---|
| RDB | 快照,恢复快,可能丢最后一次快照后的数据 |
| AOF | 记录写命令,数据更完整,文件大需 rewrite |
| 混合 | AOF 重写时嵌入 RDB 前缀,兼顾恢复速度与完整性 |
集群与高可用
三种部署形态
| 模式 | 作用 | 说明 |
|---|---|---|
| 主从复制 | 读写分离、数据备份 | 主写从读;异步复制有延迟;从节点可做冷备 |
| 哨兵 Sentinel | 自动故障转移 | 监控主从,主宕机选举新主;客户端连哨兵获取当前主地址 |
| Cluster | 水平扩展、分片 | 16384 个 hash slot,多主多从;无中心架构,Gossip 通信 |
Cluster 核心机制
- key →
CRC16(key) % 16384→ slot → 负责该 slot 的 master 节点 - MOVED:slot 不在当前节点,客户端应更新槽位映射并重试
- ASK:迁移中的临时重定向
- Hash Tag:
{userId}:profile与{userId}:order同 slot,保证事务/Multi 可用 - 最少 3 主 3 从(官方建议),主宕机从节点晋升
复制与一致性
- 主从异步复制:主成功写后返回客户端,再同步从库 → 主宕机可能丢少量已写未同步数据
- wait 命令:可等待 N 个从库确认(慎用,影响延迟)
- 脑裂:网络分区导致多主,需合理配置
min-slaves-to-write等(版本与方案各异)
缓存问题与解决方案
三大经典问题
| 问题 | 现象 | 方案 |
|---|---|---|
| 缓存穿透 | 查询不存在的数据,缓存和 DB 都没有,请求打到 DB | 布隆过滤器;缓存空值(短 TTL);接口校验与限流 |
| 缓存击穿 | 热点 key 过期瞬间,大量请求同时查 DB | 互斥锁(Redis SETNX)只放一个线程回源;逻辑过期(值带过期时间,异步刷新);热点 key 永不过期 + 后台更新 |
| 缓存雪崩 | 大量 key 同时过期或 Redis 宕机,DB 压力激增 | TTL 加随机抖动;多级缓存;集群高可用;限流降级;Redis 持久化与备份 |
缓存与数据库一致性
- Cache Aside(常用):读先缓存未命中再 DB 并回写;写先更新 DB 再删缓存(推荐删而非立刻更新,避免并发写乱序)
- 延迟双删:删缓存 → 写 DB → 延迟再删一次,降低脏读窗口
- 强一致场景:分布式锁、 Canal/MQ 订阅 binlog 异步删缓存、读主库
- 接受最终一致性时,短 TTL + 主动失效是性价比最高的方案
热点 Key(Hot Key)
- 本地缓存(Caffeine)挡一层;key 拆分(分片到多个 Redis key)
- 读写分离、多副本;大促前预热;监控 QPS 异常 key
大 Key(Big Key)
- 危害:阻塞单线程、迁移慢、内存不均、网卡打满
- 治理:
redis-cli --bigkeys、拆分 Hash/List、压缩、异步 UNLINK 删除
分布式锁(常考)
- 基础:
SET key uuid NX EX 30,解锁用 Lua 校验 uuid 再 DEL,防误删他人锁 - 问题:主从切换可能导致锁丢失 → Redisson 看门狗续期、Redlock(有争议,生产需评估)
- 强一致锁可考虑 ZooKeeper / etcd;Redis 锁适合允许极低概率重复的业务
// 互斥回源(击穿)伪代码
String v = redis.get(key);
if (v == null) {
if (redis.setnx(lockKey, "1", "EX", 10)) {
try {
v = db.load(key);
redis.setex(key, ttl, v);
} finally {
redis.del(lockKey);
}
} else {
Thread.sleep(50);
return get(key); // 重试或短暂降级
}
}
Redis 面试回答模板
问题:Redis 为什么单线程还快
问题:缓存穿透、击穿、雪崩区别与解决
问题:Redis Cluster 如何分片
常见追问
- String 底层 SDS 好处?O(1) 取长度、二进制安全、减少扩容频率。
- ZSet 为什么用跳表?实现简单,范围查询 O(log n),并发下比红黑树更易实现。
- 先删缓存还是先写库?先写库再删缓存更常见;先删缓存再写库并发下易出现脏缓存。
- Redis 和 Memcached?Redis 多数据结构、持久化、主从集群;Memcached 简单纯缓存、多线程。
消息队列总览
消息队列解耦、异步、削峰填谷。选型常看:吞吐、延迟、顺序、事务消息、运维生态、与现有技术栈契合度。
核心概念对照
| 概念 | RocketMQ | Kafka |
|---|---|---|
| 逻辑主题 | Topic | Topic |
| 分片单元 | Message Queue(Queue) | Partition |
| 消费组 | Consumer Group | Consumer Group |
| 消费进度 | Broker 端 Offset(可集群消费) | Broker 端 __consumer_offsets |
| 顺序消息 | 同一 Queue 有序 | 同一 Partition 有序 |
RocketMQ vs Kafka 对比
| 维度 | RocketMQ | Kafka |
|---|---|---|
| 定位 | 业务消息中间件,阿里系电商/金融场景成熟 | 分布式流平台,日志采集、大数据管道、流计算首选 |
| 吞吐 | 高(万级~十万级 TPS 常见),略低于 Kafka 极限 | 极高(百万级 TPS 集群),顺序写磁盘 + Page Cache |
| 延迟 | 毫秒级,支持定时/延迟消息级别丰富 | 毫秒级;延迟消息需自研或 Kafka 时间轮(版本特性) |
| 消息模型 | Topic + Queue;支持 Tag 过滤、SQL92 过滤 | Topic + Partition;依赖 Consumer 或 Streams 处理 |
| 事务消息 | 原生半消息 + 回查(本地事务与消息一致性) | 事务 API(0.11+),主要用于流式 exactly-once 生态 |
| 顺序消息 | 全局/分区顺序,Sharding Key 绑定 Queue | 分区内有序,key 决定 partition |
| 堆积能力 | 亿级堆积,依赖磁盘与消费速度 | 日志型存储,超长堆积与回溯是强项 |
| 消费模式 | 集群消费、广播消费 | Consumer Group 负载均衡;独立 Consumer 可重复读 |
| 运维与生态 | NameServer 轻量;国内文档与 Spring 集成多 | ZK/KRaft、Connect、Streams、Flink/Spark 生态强 |
| 典型场景 | 订单、支付、库存、分布式事务、定时任务 | 日志、埋点、CDC、实时数仓、流处理 |
架构差异(简图理解)
- RocketMQ:Producer → Broker(Master/Slave)← NameServer 路由;Consumer 拉取或长轮询 Push 效果
- Kafka:Producer → Broker(Leader/Follower ISR)← Controller/KRaft;Consumer 拉取(poll),消费位点提交
消息可靠性
可靠性 = 消息不丢、不重复(或重复可接受)、可追踪。需从生产 → 存储 → 消费全链路设计。
生产者不丢
| MQ | 手段 |
|---|---|
| RocketMQ | 同步发送 + 重试;事务消息;发送失败落库补偿任务 |
| Kafka | acks=all(ISR 全部确认);retries;enable.idempotence=true 防重 |
acks=0:不等待确认,可能丢acks=1:Leader 写入即返回,Leader 宕机未同步可能丢acks=all/-1:ISR 副本都确认,最可靠(需min.insync.replicas配合)
Broker 不丢
- RocketMQ:同步刷盘 / 异步刷盘;同步复制 / 异步复制;Master-Slave 故障切换
- Kafka:Partition 多副本 + ISR;
replication.factor;unclean.leader.election 关闭可降低丢数据风险 - 磁盘 RAID、监控磁盘使用率与副本滞后(Lag)
消费者不丢
- 先执行业务再提交 offset(或 RocketMQ CONSUME_SUCCESS 后再确认)
- 消费失败:重试队列 + 死信队列(DLQ)人工介入
- 自动提交 offset 可能在处理中途崩溃导致「以为已消费」→ 手动提交更可控
RocketMQ 事务消息流程
- 发送 Half 消息(对消费者不可见)
- 执行本地事务
- Commit → 消息可见;Rollback → 丢弃
- Broker 回查本地事务状态(超时或未决时)
至少一次 / 至多一次 / 精确一次
| 语义 | 含义 | 实现要点 |
|---|---|---|
| At most once | 可能丢,不重复 | 先提交 offset 再消费 |
| At least once | 不丢,可能重复 | 先消费再提交 offset + 消费端幂等 |
| Exactly once | 不丢不重 | Kafka 事务 + Streams;或业务层幂等表 |
幂等设计
MQ 普遍提供 至少一次(At Least Once) 投递,网络重试、Rebalance、消费超时会导致重复消费,业务必须幂等。
幂等层次
- 生产幂等:Kafka
enable.idempotence(PID + Sequence);业务侧用业务 ID 去重表 - 消费幂等:核心,靠唯一键、状态机、数据库唯一约束
常见消费幂等方案
| 方案 | 做法 | 适用 |
|---|---|---|
| 唯一键 + DB | 订单号/消息 ID 唯一索引,重复插入失败即已处理 | 创建类操作 |
| 幂等表 | 记录 messageId,处理前 INSERT,成功则标记;重复则跳过 | 通用 |
| Redis SETNX | messageId 作为 key,过期时间略大于重试窗口 | 高并发、允许极低 Redis 故障风险 |
| 状态机 | 仅允许 待支付→已支付,重复「已支付」消息直接返回成功 | 订单、工单流转 |
| 版本号/乐观锁 | UPDATE ... WHERE version = ?,影响行数 0 则已处理或冲突 | 更新类操作 |
| 业务 Token | 先申请一次性 Token,消费时校验并销毁 | 表单重复提交场景 |
设计要点
- 消息体携带全局唯一
messageId或bizId,与业务主键对齐 - 幂等记录与业务写放在同一本地事务,避免「记了幂等但业务失败」
- 消费逻辑本身要可重入:外部调用(支付、发短信)也需幂等或先查状态
- 重试次数与退避策略,最终进 DLQ,防止毒消息无限循环
// 幂等表伪代码
@Transactional
void onMessage(Message msg) {
if (idempotentRepo.exists(msg.getMessageId())) {
return; // 已处理
}
idempotentRepo.insert(msg.getMessageId()); // 唯一索引防并发
orderService.createOrder(msg.getBody());
}
MQ 面试回答模板
问题:RocketMQ 和 Kafka 怎么选
问题:如何保证消息不丢
问题:如何处理重复消费
常见追问
- 消息堆积怎么办?扩容 Consumer、增加 Partition/Queue、优化消费逻辑、临时降级非核心消费、排查慢 SQL。
- 顺序消息如何实现?同一 Sharding Key 进同一 Queue/Partition;消费单线程或分区锁。
- 死信队列作用?重试耗尽后隔离毒消息,人工修复或丢弃,保护主队列。
- Rebalance 影响?消费暂停、重复消费风险上升,应控制单次拉取量、优雅下线。
CAP / BASE 理论
CAP(分布式系统三选二)
- C(Consistency)一致性:所有节点同一时刻读到相同数据
- A(Availability)可用性:每个请求都能得到非错误响应(不保证最新)
- P(Partition tolerance)分区容错:网络分区时系统仍能继续工作
分布式环境下网络分区不可避免,因此实际在 CP 与 AP 之间权衡:
| 类型 | 选择 | 典型 |
|---|---|---|
| CP | 牺牲可用性,保证强一致 | ZooKeeper、etcd、HBase、Redis 集群故障时部分不可用 |
| AP | 牺牲强一致,保证可用 | Eureka、Cassandra、DNS |
BASE(互联网大规模实践)
- BA(Basically Available)基本可用:降级、限流、故障转移,核心功能可用
- S(Soft state)软状态:允许中间状态存在一段时间
- E(Eventually consistent)最终一致性:经过一段时间后达到一致
秒杀系统设计
核心矛盾:读多写少、瞬时超高并发、库存不能超卖。思路:流量层层递减,库存扣减原子化,异步削峰。
分层架构
- 前端:按钮防重复、验证码、活动未开始静态化、CDN 静态资源
- 接入层:Nginx/Gateway 限流、黑名单、IP 频控、页面缓存
- 应用层:活动页数据 Redis 预热;登录态校验;秒杀接口独立集群
- 服务层:Redis 预减库存(Lua 原子 DECR);成功则发 MQ 异步创建订单
- 数据层:DB 最终扣减 + 唯一约束防超卖;订单服务幂等
防超卖手段
- Redis Lua:
if stock > 0 then decr end原子判断扣减 - 数据库:
UPDATE stock SET n=n-1 WHERE id=? AND n>0影响行数为 0 则失败 - 一人一单:用户 ID + 活动 ID 唯一索引或 Redis Set
- 禁止:先查库存再 update 的非原子「读-改-写」
常见问题
| 问题 | 对策 |
|---|---|
| 缓存击穿 | 活动数据预热、逻辑过期 |
| DB 被打穿 | Redis 挡量 + MQ 异步写库 |
| 恶意刷单 | 风控、验证码、设备指纹、限流 |
| 库存预热不一致 | 以 DB 为准定时校准;活动开始锁定库存 |
分布式事务
跨服务、跨库、跨 MQ 的一致性。强一致成本高,互联网多采用最终一致性。
方案对比
| 方案 | 一致性 | 特点 |
|---|---|---|
| 2PC / XA | 强一致 | 协调者两阶段提交;阻塞、性能差,微服务少用 |
| TCC | 最终/准强 | Try-Confirm-Cancel,业务侵入大,需空回滚、防悬挂 |
| 本地消息表 | 最终一致 | 业务与消息同库事务,定时扫描发 MQ |
| 事务消息(RocketMQ) | 最终一致 | 半消息 + 本地事务 + 回查 |
| Saga | 最终一致 | 长事务拆本地事务 + 补偿;编排式或 choreography |
| Seata AT | 弱强一致 | 自动生成回滚 SQL,对业务透明,依赖 undo_log |
选型建议
- 短链路、强一致要求:Seata AT / TCC(量不大时)
- 订单+库存+支付:Saga 补偿或可靠消息 + 幂等
- 原则:能避免分布式事务就避免(合并服务、单库);必须用时优先异步最终一致
分布式锁
在分布式环境下互斥执行:库存扣减、定时任务单实例、防重复提交。
实现对比
| 实现 | 优点 | 缺点 |
|---|---|---|
| Redis(SET NX EX) | 性能高、实现简单 | 主从切换可能丢锁;需 Lua 安全解锁;Redisson 看门狗续期 |
| ZooKeeper / etcd | 强一致、临时顺序节点公平 | 性能低于 Redis;运维成本高 |
| 数据库唯一索引 | 无额外组件 | 性能差、锁表风险 |
| Redisson | 可重入、看门狗、读写锁、联锁 | 依赖 Redis 集群可用性 |
正确使用要点
- 锁 value 用 UUID,解锁 Lua 脚本校验「只删自己的锁」
- 过期时间 > 业务最大耗时,或看门狗自动续期
- 锁粒度尽量小;能不用锁则用 DB 乐观锁 / 原子 SQL
- Redlock 有争议,金融强一致场景慎选 Redis 锁
限流
保护系统不被突发流量压垮,保证核心链路可用。可在网关、应用、Redis 多层实施。
常见算法
| 算法 | 特点 | 场景 |
|---|---|---|
| 固定窗口 | 实现简单,边界处可能双倍突发 | 粗粒度限流 |
| 滑动窗口 | 更平滑,统计更准 | 接口 QPS |
| 漏桶(Leaky Bucket) | 恒定流出速率,削峰 | 下游处理能力固定 |
| 令牌桶(Token Bucket) | 允许一定突发,平均速率受限 | Guava RateLimiter、Sentinel |
实现层级
- 网关:Spring Cloud Gateway RequestRateLimiter、Nginx limit_req
- 框架:Sentinel 按资源名 QPS/线程数、热点参数限流
- 分布式:Redis + Lua 全局限流;按用户/IP/接口维度
与降级区别
- 限流:超过阈值直接拒绝或排队,保护系统
- 降级:关闭非核心功能,返回兜底数据,保证主流程
熔断与降级
防止故障依赖拖垮整个系统(雪崩)。熔断快速失败;降级提供备选方案。
熔断器状态机(Hystrix / Sentinel / Resilience4j)
- Closed:正常调用,统计错误率/慢调用比例
- Open:超过阈值则熔断,直接失败或走 fallback,经过休眠时间后进入半开
- Half-Open:放行少量探测请求,成功则关闭熔断,失败则继续打开
触发条件(常见)
- 错误比例 > 50%(滑动窗口内)
- 慢调用比例 > 阈值(RT 超过设定 ms)
- 异常数、并发线程数超限(Sentinel)
实践组合
| 手段 | 作用 |
|---|---|
| 超时 | 避免线程阻塞堆积 |
| 重试 | 幂等接口有限次重试 + 退避 |
| 熔断 | 依赖故障时快速失败 |
| 降级 | 返回缓存/默认值,关闭推荐位 |
| 隔离 | 线程池/信号量隔离,Bulkhead 舱壁模式 |
| 限流 | 入口削峰 |
高可用面试回答模板
问题:如何设计一个秒杀系统
问题:CAP 怎么理解,和 BASE 关系
问题:分布式事务怎么做
问题:限流和熔断区别
常见追问
- 雪崩效应?依赖故障 → 线程阻塞 → 资源耗尽 → 级联失败;用超时、熔断、隔离、限流打破链条。
- TCC 空回滚?Try 未执行却收到 Cancel,需识别并直接成功;悬挂是 Cancel 先于 Try,需防重。
- 令牌桶 vs 漏桶?令牌桶允许突发;漏桶输出恒定,更适合平滑写下游。
SQL 调优总览
调优目标:更快返回(降低 RT)或更少资源(CPU/IO/扫描行数/Shuffle 量)。先定位瓶颈,再改 SQL、索引或架构。
通用排查步骤
- 看慢查询日志 / APM / 执行计划(
EXPLAIN/EXPLAIN ANALYZE) - 确认是否全表扫描、错误索引、回表过多、Join 顺序差
- 检查数据量、统计信息是否过期(
ANALYZE TABLE) - 改写 SQL、加/改索引、拆查询、预聚合、缓存
- 压测验证,对比 rows examined、执行时间、成本
MySQL EXPLAIN 关键列
| 列 | 关注点 |
|---|---|
| type | system > const > eq_ref > ref > range > index > ALL(尽量避免 ALL) |
| key | 实际使用的索引 |
| rows | 预估扫描行数,越小越好 |
| Extra | Using filesort、Using temporary 需警惕 |
索引基本原则
- 最左前缀:联合索引 (a,b,c) 可用于 a、ab、abc 条件
- 区分度高的列放前面;避免对索引列函数/隐式类型转换(
WHERE YEAR(dt)=2024坏,范围改区间好) - 覆盖索引减少回表;写多读少表慎加索引
谓词下推(Predicate Pushdown)
将 WHERE、HAVING 等过滤条件尽可能下推到数据源附近先执行,减少向上层传递的数据量,降低网络、内存与 CPU 成本。
关系型数据库(MySQL / PostgreSQL)
- 优化器自动下推:能走索引的条件在存储引擎层过滤
- 子查询改写:将过滤写在子查询内部,避免先全量再过滤
- 视图/派生表:外层 WHERE 可能合并进内层(MySQL 5.7+ 条件下推优化)
-- 不推荐:外层才过滤
SELECT * FROM (
SELECT * FROM orders WHERE status IN (1,2,3)
) t WHERE t.created_at >= '2024-01-01';
-- 推荐:条件下推至最内层
SELECT id, amount, created_at
FROM orders
WHERE status IN (1,2,3)
AND created_at >= '2024-01-01'
AND user_id = ?;
数仓 / 大数据(Hive、Spark SQL、Presto)
- 分区裁剪:WHERE 带分区列(
dt='2024-05-01')只读对应目录 - 列裁剪:只 SELECT 需要的列,减少 ORC/Parquet 读取
- 谓词下推至存储:文件级跳过 Stripe/Row Group(谓词 + 列存统计信息 min/max)
- 避免在最外层才过滤几亿行明细;ETL 尽早 Filter
无法在 SQL 层下推时注意
- 对计算列过滤:
WHERE UPPER(name)='ABC'→ 考虑生成列 + 索引或冗余列 - 跨库 Join:先在各库内过滤再联邦 Join,减少中间结果
- ORM 生成 SQL 丢失条件:检查 N+1、全表 load 再内存 filter
Join 优化
Join 类型与代价
| 类型 | 说明 | 注意 |
|---|---|---|
| INNER JOIN | 只保留匹配行 | 最常用的优化对象 |
| LEFT JOIN | 保留左表全部 | 右表条件放 ON 还是 WHERE 结果不同 |
| STRAIGHT_JOIN | 强制 MySQL 连接顺序 | 仅在确认优化器选错时用 |
MySQL Join 算法(NLJ / BNL / Hash)
- Nested Loop Join:驱动表逐行嵌套被驱动表;被驱动表需有索引(eq_ref/ref)
- Block Nested Loop:无索引时缓存驱动表块,仍较慢
- Hash Join(8.0.18+):等值连接大表无合适索引时,内存建哈希表
优化要点
- 小表驱动大表(传统 NLJ):减少外层循环次数;8.0 优化器常自动选驱动表
- 被驱动表 Join 列建索引:避免对被驱动表全表扫描
- 先过滤再 Join:各表 WHERE 后再 Join,缩小中间结果(谓词下推)
- 避免 Join 列类型/字符集不一致:导致无法走索引
- 控制 Join 表数量:超 3~4 张考虑宽表、物化中间表或业务拆分
- 数仓大表 Join:Map Join(广播小表)、Bucket Join(分桶对齐)、Skew Join 处理数据倾斜
-- LEFT JOIN 条件位置
-- 过滤右表且不要保留右表无匹配:条件放 WHERE
SELECT a.* FROM users a
LEFT JOIN orders b ON a.id = b.user_id
WHERE b.created_at >= '2024-01-01'; -- 无订单用户会被滤掉,语义等同 INNER
-- 保留左表全部:右表过滤放 ON
SELECT a.* FROM users a
LEFT JOIN orders b ON a.id = b.user_id AND b.created_at >= '2024-01-01';
子查询 vs Join
IN (SELECT ...)有时可改EXISTS或 Join,便于走索引- 避免相关子查询逐行执行;改 Join 或窗口函数(MySQL 8.0)
报表查询提效与降本
报表多为读多写少、聚合重、时间范围大,目标:少扫明细、少重复计算、可缓存。
架构层手段
| 手段 | 作用 |
|---|---|
| 读写分离 | 报表走从库,隔离 OLTP |
| 汇总表 / 宽表 | 按天/店铺预聚合,查询只扫汇总行 |
| 物化视图 | 定时刷新中间结果(MySQL 可定时任务写汇总表) |
| OLAP 引擎 | ClickHouse、Doris、StarRocks 做多维分析 |
| 缓存 | Redis 缓存热点报表;结果带版本与 TTL |
| 异步导出 | 超大报表走 MQ + 离线生成,避免拖垮在线库 |
SQL 层降本
- 分区表:按日/月分区,查询必带分区键,避免扫描全部分区
- 只查必要列:禁止
SELECT *扫宽表 - 分页优化:深分页
LIMIT 100000,20慢 → 用上次最大 ID 游标:WHERE id > ? ORDER BY id LIMIT 20 - 聚合下推:在数仓层 GROUP BY,应用层不再拉明细聚合
- 近似计算:UV 用 HyperLogLog、采样统计降低精确度换速度
- 冷热分离:历史数据归档到对象存储 + 外表查询
降低计算成本(云/集群)
- 减少全表 COUNT(*):用统计表或估算(
SHOW TABLE STATUS) - 合并重复报表任务:同一汇总表多报表复用
- 调度错峰:T+1 凌晨批处理,避免与高峰 OLTP 抢资源
- Spark/Hive:控制 Shuffle 分区数、广播小维表、过滤前置、缓存复用中间 Dataset
- 监控:扫描字节数、Spill 磁盘、任务时长,设资源配额
反模式(报表常见)
- 在线库直接跑大范围 JOIN + GROUP BY + 排序
- 实时库做复杂多维下钻无索引维度
- 每次请求全量重算,无增量更新
SQL 面试回答模板
问题:慢 SQL 怎么排查和优化
问题:什么是谓词下推
问题:Join 很慢怎么优化
问题:报表查询如何不影响线上又降成本
常见追问
- 覆盖索引?索引包含查询所需全部列,避免回表。
- 索引失效场景?对列运算、不等于、前导模糊 %abc、OR 未全覆盖、类型转换。
- filesort 一定慢?内存 sort buffer 够用可接受;数据量大考虑索引有序或降序需求改设计。
- COUNT(1) vs COUNT(*)?InnoDB 下优化器处理相近,性能差异可忽略,看语义即可。
数据库选型总览
先分清 workload:OLTP(高并发短事务、强一致)vs OLAP(大批量扫描、聚合分析)vs HTAP(两者都要,有取舍)。
| 数据库 | 定位 | 一句话 |
|---|---|---|
| MySQL | OLTP | 互联网业务库首选,生态成熟 |
| PostgreSQL | OLTP + 复杂 SQL | 功能最全的开源关系库,扩展强 |
| ClickHouse | OLAP | 列存分析引擎,聚合极速 |
| Hologres | 实时数仓 / HTAP | 云原生实时分析,兼容 PG 协议,离线与实时一体 |
OLTP vs OLAP
| 维度 | OLTP | OLAP |
|---|---|---|
| 典型操作 | INSERT/UPDATE 单行、点查 | 大范围扫描、GROUP BY、JOIN 大表 |
| 数据模型 | 行存、范式设计 | 列存、宽表/星型模型 |
| 一致性 | 强一致 ACID | 常最终一致,批量导入 |
| 扩展 | 主从、分库分表 | 分布式分片、MPP |
MySQL / PostgreSQL / ClickHouse / Hologres 对比
| 维度 | MySQL | PostgreSQL | ClickHouse | Hologres |
|---|---|---|---|---|
| 类型 | 关系型 OLTP | 关系型 OLTP(可轻量 OLAP) | 列存 OLAP | 实时数仓(行列混合,HTAP 倾向) |
| 存储引擎 | InnoDB 行存为主 | Heap 行存,扩展列存(cstore 等) | MergeTree 系列列存 | 行列共存,Segment 存储,支持实时写入 |
| 事务 ACID | 完整支持(InnoDB) | 完整支持,MVCC | 弱/无传统事务,批量导入为主 | 支持主键表事务语义;分析表偏批量 |
| 并发写入 | 高,行级锁 | 高,MVCC 读写不阻塞 | 批量写入优,高频单行更新弱 | 高吞吐实时写入(Flink 导入等) |
| 点查 / 主键 | 极强,B+ 树索引 | 强,多种索引(B-tree、GIN、GiST) | 主键查询可用但非强项 | 主键表支持高 QPS 点查 + 分析 |
| 复杂分析 | 弱,大表聚合慢 | 中等,窗口函数、CTE 强 | 极强,向量化执行 | 强,MPP 并行,面向 PB 级 |
| JOIN 大表 | 吃力 | 中等 | 看引擎与内存,宽表更好 | 优化器强,适合数仓星型模型 |
| 更新删除 | 频繁 UPDATE/DELETE | 同左 | 不推荐频繁改;Collapsing/Replacing 引擎 | 支持 UPSERT、增量更新,仍偏分析场景 |
| SQL 兼容 | MySQL 方言 | 标准 SQL 程度高 | 类 SQL,函数/语法有差异 | PostgreSQL 协议兼容 |
| 扩展性 | 主从、中间件分片(ShardingSphere) | 流复制、Citus 分布式 | 分片集群,本地表 + Distributed 表 | 云原生弹性扩缩容,存算分离 |
| 生态 | Java 最熟、云 RDS 普及 | GIS、JSON、时序(Timescale)插件 | 日志/监控/BI 集成广 | 阿里云、Flink/MaxCompute/DataWorks 一体 |
| 部署 | 自建/RDS 成熟 | 自建/RDS/云托管 | 自建/云 CK 服务 | 主要为阿里云托管 |
| 成本模型 | 按实例,OLTP 性价比好 | 同左,复杂查询需调优 | 存储压缩高,分析 QPS 成本低 | 按 CU/存储,实时数仓综合计费 |
各库核心特点(面试记忆)
MySQL
InnoDB B+ 树、redo/undo、主从复制;适合订单、用户、交易;分库分表成熟。
PostgreSQL
MVCC 无回滚段读阻塞、丰富索引与类型;适合复杂查询、地理、JSON、金融合规场景。
ClickHouse
列存 + 向量化 + 数据压缩;MergeTree 后台合并;适合日志分析、大屏、用户行为聚合。
Hologres
实时写入 + 交互式查询;与 Flink 双流、MaxCompute 离线打通;Serving 层点查 + 分析一体。
场景与典型架构
选型建议
| 场景 | 推荐 | 说明 |
|---|---|---|
| 电商订单、支付、账户 | MySQL / PG | 强事务、行级锁、成熟 ORM |
| 复杂报表、存储过程、GIS | PostgreSQL | SQL 能力强、扩展多 |
| APP 埋点、日志、监控指标 | ClickHouse | 高压缩、聚合快、成本低 |
| 实时大屏、即席分析、湖仓一体(阿里云) | Hologres | Flink 实时入仓 + 多维分析 + 在线 Serving |
| 既要交易又要实时分析 | MySQL + CK/Hologres | CDC(Canal/Debezium)同步到分析库,勿硬扛 OLTP |
Lambda / 湖仓数据流(常见)
- 业务库 MySQL → Binlog → Flink/Canal
- 实时层写入 Hologres 或 Kafka → ClickHouse
- 离线层 MaxCompute/Hive T+1 汇总,再回灌 Hologres 做融合查询
- BI / 报表 / API 查询走 OLAP,保护 OLTP
ClickHouse vs Hologres(常对比)
| 维度 | ClickHouse | Hologres |
|---|---|---|
| 优势 | 极致单表聚合、开源可控、压缩比高 | 实时写入低延迟、PG 生态、阿里云数据栈集成、主键点查 |
| 劣势 | UPDATE 弱、分布式 Join 需建模 | 厂商绑定、成本依赖云定价 |
| 更适合 | 日志/指标、自助分析、成本敏感海量扫描 | 实时数仓、Flink 实时链路、需要 PG 兼容的 Serving |
MySQL vs PostgreSQL(常对比)
- MySQL:简单易用、主从普及、互联网案例多;复杂 SQL、CTE 历史较弱(8.0 已改善)
- PG:标准 SQL、窗口函数、CTE、扩展生态;同样规格下复杂查询往往更稳
- 选型:团队熟悉度 + 云厂商支持 + 是否需要 PG 特有扩展
数据库面试回答模板
问题:MySQL 和 ClickHouse 怎么分工
问题:Hologres 和 ClickHouse 区别
问题:为什么报表不用 MySQL 直接查
常见追问
- ClickHouse 为何快?列存、压缩、向量化执行、少随机 IO、MPP。
- PG 的 MVCC?每行多版本,读不阻塞写,需 VACUUM 回收死元组。
- HTAP 是什么?一套系统同时扛 OLTP+OLAP,如 TiDB、Hologres 部分场景;一般要接受架构或性能权衡。
- CDC 是什么?捕获数据库变更日志同步到数仓,保证分析侧近实时。
HTTP 协议总览
HTTP(HyperText Transfer Protocol)是应用层协议,基于 TCP(HTTP/3 基于 QUIC/UDP),采用请求-响应模型,默认无状态(状态靠 Cookie/Session/Token 维护)。
报文结构
请求:
请求行 GET /api/user HTTP/1.1
首部行 Host: example.com
Content-Type: application/json
空行
消息体 {"id":1}
响应:
状态行 HTTP/1.1 200 OK
首部行 Content-Type: application/json
Content-Length: 15
空行
消息体 {"name":"Tom"}
一次完整交互(简化)
- DNS 解析域名 → IP
- TCP 三次握手建立连接(HTTPS 再 TLS 握手)
- 客户端发送 HTTP 请求
- 服务端处理并返回响应
- 连接关闭(HTTP/1.0)或复用(Keep-Alive / HTTP/2 多路复用)
核心特性
- 无状态:服务器不保存客户端上下文,利于水平扩展
- 灵活可扩展:首部可扩展;REST 用 URI + 方法表达资源
- 明文/加密:HTTP 明文;HTTPS = HTTP + TLS
方法与状态码
常用方法(REST 语义)
| 方法 | 语义 | 幂等 | 安全 | 典型用途 |
|---|---|---|---|---|
| GET | 获取资源 | 是 | 是 | 查询,不应有副作用 |
| POST | 提交/创建 | 否 | 否 | 创建资源、表单提交 |
| PUT | 全量更新/替换 | 是 | 否 | 整体替换资源 |
| PATCH | 部分更新 | 否* | 否 | 局部字段更新 |
| DELETE | 删除 | 是 | 否 | 删除资源 |
| HEAD | 同 GET 无 body | 是 | 是 | 检查资源是否存在、元数据 |
| OPTIONS | 查询支持的方法 | 是 | 是 | CORS 预检 |
* PATCH 幂等性视实现而定,面试可说「通常不保证」。
常见状态码
| 类别 | 码 | 含义 |
|---|---|---|
| 1xx | 100 | Continue,继续发送 body |
| 2xx 成功 | 200 | OK |
| 201 | Created,POST 创建成功 | |
| 204 | No Content,成功无 body(DELETE 常见) | |
| 3xx 重定向 | 301 | 永久重定向,SEO 权重转移 |
| 302/307 | 临时重定向(307 保持方法) | |
| 304 | Not Modified,协商缓存命中 | |
| 4xx 客户端 | 400 | Bad Request,参数错误 |
| 401 | Unauthorized,未认证 | |
| 403 | Forbidden,无权限 | |
| 404 | Not Found | |
| 409 | Conflict,资源冲突 | |
| 429 | Too Many Requests,限流 | |
| 5xx 服务端 | 500 | Internal Server Error |
| 502 | Bad Gateway,网关上游异常 | |
| 503 | Service Unavailable,过载/维护 | |
| 504 | Gateway Timeout,上游超时 |
HTTP 版本演进
| 版本 | 关键特性 | 问题/改进 |
|---|---|---|
| HTTP/1.0 | 每次请求新建 TCP 连接 | 连接开销大 |
| HTTP/1.1 | 默认 Keep-Alive 持久连接;管道化(pipelining,少用了);分块传输 Chunked | 队头阻塞(同一连接上请求串行应答) |
| HTTP/2 | 二进制分帧、多路复用、头部压缩 HPACK、服务端推送(Push,现较少用) | TCP 层仍可能队头阻塞;单连接多 Stream |
| HTTP/3 | 基于 QUIC(UDP),独立 Stream,连接迁移 | 解决 TCP 队头阻塞,弱网更稳 |
队头阻塞(HOL Blocking)
- HTTP/1.1:同一连接多个请求排队等待响应
- HTTP/2:应用层多路复用,但 TCP 丢包会阻塞整个连接所有 Stream
- HTTP/3:QUIC 在传输层按 Stream 独立重传,减轻 HOL
连接管理
Connection: keep-alive(1.1 默认持久)- 浏览器对同一域名并发连接数有限(HTTP/1.1 常 6 个),故雪碧图、域名分片;HTTP/2 单连接多路复用缓解
首部与缓存
常见请求首部
Host:虚拟主机必选(1.1)User-Agent、Accept、Accept-Encoding: gzipAuthorization: Bearer <token>/ Basic 认证Cookie:携带会话Content-Type/Content-LengthIf-None-Match、If-Modified-Since:协商缓存验证
常见响应首部
Set-Cookie:HttpOnly、Secure、SameSite 防 XSS/CSRFCache-Control:缓存策略核心ETag/Last-Modified:协商缓存标识Location:重定向 URL
缓存机制
| 类型 | 机制 | 首部 |
|---|---|---|
| 强缓存 | 未过期直接用本地,不请求服务器 | Cache-Control: max-age=3600;过期看 Expires(优先级低) |
| 协商缓存 | 过期后问服务器是否变化,未变则 304 | ETag + If-None-Match;或 Last-Modified + If-Modified-Since |
优先级:Cache-Control > Expires;ETag 比时间戳更准。
Content-Type 常考
application/json:REST APIapplication/x-www-form-urlencoded:表单默认multipart/form-data:文件上传text/html、application/octet-stream
CORS(跨域)
- 浏览器同源策略:协议+域名+端口相同
- 简单请求:直接发,响应带
Access-Control-Allow-Origin - 预检请求:OPTIONS 先发,带
Access-Control-Request-Method等
HTTPS 与 TLS
HTTPS = HTTP + TLS(SSL 已淘汰),提供机密性、完整性、身份认证。
TLS 握手(1.2 简化)
- Client Hello:支持的 TLS 版本、加密套件、随机数
- Server Hello:选定参数、证书(含公钥)、随机数
- 客户端验证证书(CA 链、域名、有效期)
- 双方用非对称加密协商出对称会话密钥(premaster secret)
- 后续 HTTP 数据用对称加密(AES 等)传输
TLS 1.3 改进
- 握手往返更少(1-RTT,甚至 0-RTT 恢复会话)
- 禁用弱加密套件,更安全更快
相关概念
- 对称加密:加解密同一密钥,速度快,传数据用
- 非对称加密:公钥加密、私钥解密,握手用
- 证书:CA 对公钥+主体签名,防中间人
- HSTS:强制浏览器 HTTPS 访问,防降级攻击
HTTP 面试回答模板
问题:HTTP 和 HTTPS 区别
问题:GET 和 POST 区别
问题:HTTP/1.1 和 HTTP/2 区别
问题:强缓存和协商缓存
常见追问
- TCP 三次握手?SYN → SYN+ACK → ACK,确认双方收发能力。
- Cookie 和 Session?Cookie 存客户端;Session 存服务端,Cookie 带 SessionId。
- JWT 和 Session?JWT 无状态、服务端不存;需注意过期与吊销。
- 502 和 504?502 上游无效响应;504 上游超时。