面向中高级 Java 后端开发岗位。本文不是只罗列结论,而是按“结论、原理、工程边界”组织答案。
回答面试题时建议采用:先给结论,再讲原理,最后结合项目说明取舍。

使用建议

  • 第一轮:快速阅读所有标题,建立知识图谱;
  • 第二轮:脱离答案口述每道题;
  • 第三轮:给每个重点问题补充一个项目案例;
  • 第四轮:重点练习线上故障、系统设计和方案取舍。

目录


一、Java 基础

1. JDK、JRE 和 JVM 有什么区别?

  • JVM 负责加载和执行字节码,并提供运行时内存管理、垃圾回收和即时编译等能力。
  • JRE 可以理解为 JVM 加上运行 Java 程序所需的标准类库。
  • JDK 在运行环境之外,还包含 javacjavadocjcmdjstack 等开发和诊断工具。

可以概括为:JDK = 运行环境 + 开发工具。现代 JDK 的发布方式已经弱化了独立 JRE 的概念,但这个层次关系仍是理解 Java 平台的基础。

2. Java 为什么能够跨平台?

Java 源代码先被编译为与操作系统无关的字节码,再由不同平台上的 JVM 转换为本地机器指令。

.java 源文件 → javac → .class 字节码 → JVM → 本地机器指令

真正实现跨平台的是统一的字节码规范和各平台对应的 JVM,而不是同一份机器码在所有系统上直接执行。

3. Java 是编译型语言还是解释型语言?

Java 同时包含编译和解释过程。源代码先由 javac 编译成字节码;JVM 可以解释执行字节码,也会将热点代码通过 JIT 编译为本地机器码。

因此,Java 不是纯解释型语言,也不是传统意义上的纯静态编译语言。

4. 面向对象的三大特性是什么?

  • 封装:隐藏内部实现,通过稳定接口暴露能力。
  • 继承:子类复用父类的行为,但应避免过深继承体系。
  • 多态:同一接口可以对应不同实现,运行时根据实际对象类型调用方法。

工程中通常优先使用“接口 + 组合”,而不是为了复用代码滥用继承。

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

对比项 重载 Overload 重写 Override
位置 同一个类 父子类
参数列表 必须不同 必须相同
绑定时期 编译期 运行期
目的 提供不同调用形式 实现多态

重写方法的访问权限不能比父类更严格,final 方法不能被重写,静态方法也不属于真正的运行时重写。

6. ==equals() 有什么区别?

对基本类型,== 比较值;对引用类型,== 比较是否指向同一个对象。equals() 默认行为与 == 类似,但很多类会重写它,用于比较逻辑内容。

String a = new String("java");
String b = new String("java");

a == b;       // false
a.equals(b);  // true

7. 为什么重写 equals() 时必须重写 hashCode()

哈希容器会先通过 hashCode() 定位桶,再使用 equals() 判断对象是否相等。

Java 约定:两个对象如果通过 equals() 判断相等,它们的 hashCode() 必须相等。否则逻辑上相等的对象可能进入不同桶,导致 HashMap#get()HashSet#contains() 无法找到对象。

8. String、StringBuilder 和 StringBuffer 有什么区别?

  • String 不可变,适合少量拼接和共享。
  • StringBuilder 可变、非线程安全,单线程拼接优先使用。
  • StringBuffer 可变,主要方法带同步控制,但通常开销更高。

循环中大量拼接字符串时,应优先使用 StringBuilder

9. String 为什么设计为不可变?

主要原因包括:

  1. 可以安全地在线程之间共享;
  2. 支持字符串常量池;
  3. 哈希值稳定,适合作为 Map 的键;
  4. 文件路径、类名、网络地址等安全敏感字符串不会被调用方修改;
  5. 便于缓存派生结果。

10. Java 是值传递还是引用传递?

Java 只有值传递。传递对象时,复制的是“引用这个值”。

因此,方法内部可以通过复制后的引用修改原对象字段,但不能改变调用方引用变量本身所指向的对象。

11. 抽象类和接口如何选择?

抽象类适合表达“同一类事物的公共基类”,可以保存实例状态;接口更适合表达能力和契约,并允许一个类实现多个接口。

强调“是什么”时可考虑抽象类;强调“具有什么能力”和解耦实现时优先接口。

12. 受检异常和非受检异常有什么区别?

受检异常要求调用方捕获或声明抛出;RuntimeException 及其子类属于非受检异常,通常表示参数错误、非法状态或程序缺陷。

业务系统应建立明确异常体系,不要使用一个通用 Exception 吞掉参数错误、业务错误和系统错误。

13. try-with-resources 有什么作用?

它会自动关闭实现 AutoCloseable 的资源,并能正确保留主异常和资源关闭时的附加异常。

try (BufferedReader reader = Files.newBufferedReader(path)) {
    return reader.readLine();
}

现代 Java 应优先使用该结构管理文件、流、数据库资源等。

14. 泛型中的 ? extends T? super T 有什么区别?

记住 PECS:

Producer Extends,Consumer Super
  • ? extends T 适合读取,无法安全写入具体子类型。
  • ? super T 适合写入 T 及其子类,读取时通常只能按 Object 处理。

15. 反射有哪些使用场景和缺点?

常见场景包括 Spring IoC、ORM、JSON 序列化、RPC、测试框架和插件系统。

缺点包括运行时错误、封装被绕过、可读性下降、一定性能成本,以及在 AOT 或原生镜像中需要额外元数据配置。

16. Record 适合什么场景?

Record 适合表示不可变数据载体,如 DTO、事件和查询结果。

public record UserSummary(long id, String name) {}

它会自动生成构造方法、访问器、equals()hashCode()toString()。需要可变状态、复杂继承或框架代理的实体不一定适合使用 Record。

二、集合框架

17. Java 集合框架的主要接口有哪些?

主要结构如下:

Collection
├── List
├── Set
└── Queue / Deque

Map

Map 不继承 Collection。常用实现有 ArrayListHashSetHashMapTreeMapArrayDequePriorityQueue 和并发集合。

18. ArrayList 的底层原理是什么?

ArrayList 底层是动态数组。按下标访问平均 O(1),尾部追加均摊 O(1),中间插入和删除通常需要移动元素,复杂度 O(n)。

如果可以预估元素数量,设置初始容量可以减少扩容和数组复制。

19. ArrayList 和 LinkedList 如何选择?

大多数业务场景默认优先 ArrayList。它内存连续、缓存局部性好,实际遍历性能通常更高。

LinkedList 的节点对象有额外引用开销,插入前往往仍需要 O(n) 查找。实现栈或队列时通常优先 ArrayDeque

20. HashMap 的底层结构是什么?

现代 Java 中主要是:

数组 + 链表 + 红黑树

发生哈希冲突时,元素进入同一桶。冲突链较长且数组容量达到要求时,链表可转为红黑树。容量超过阈值时触发扩容。

21. HashMap 为什么通常使用 2 的幂作为容量?

桶下标可以通过:

(capacity - 1) & hash

快速计算。当容量为 2 的幂时,低位掩码分布更合理,扩容为两倍后,元素位置要么不变,要么移动一个旧容量的偏移,迁移更高效。

22. HashMap 的 put 流程是什么?

  1. 初始化底层数组;
  2. 计算哈希值和桶下标;
  3. 桶为空时直接插入;
  4. 桶非空时判断是否已有同键;
  5. 根据节点类型执行链表或红黑树插入;
  6. 链表过长时尝试树化;
  7. 新增键后更新大小;
  8. 超过阈值时扩容。

更新已有键的值不会增加 size

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

并发读写可能导致更新丢失、读取不一致、扩容期间结构变化以及可见性问题。

并发场景应使用 ConcurrentHashMap、外部同步或不可变 Map,不能因为写操作较少就假设 HashMap 安全。

24. ConcurrentHashMap 的核心原理是什么?

现代 ConcurrentHashMap 基于数组、CAS、桶级同步、链表/红黑树以及协作扩容。

读取大多无锁;空桶插入可使用 CAS;冲突桶写入通常只同步当前桶;扩容时多个线程可以共同迁移数据。

复合逻辑仍应使用 putIfAbsent()compute()merge() 等原子 API。

25. 为什么 ConcurrentHashMap 不允许 null 键和值?

并发环境中,如果 get() 返回 null,无法可靠区分“键不存在”和“键存在但值为 null”。即使再调用 containsKey(),两次调用之间映射也可能变化。

禁止 null 可以让 null 明确表示当前没有映射。

26. HashSet 是如何实现去重的?

HashSet 通常以 HashMap 为底层结构,把 Set 元素作为 Map 的键,值使用共享占位对象。

去重依赖 hashCode()equals(),因此自定义对象放入 Set 时必须正确实现这两个方法。

27. TreeMap 和 HashMap 有什么区别?

HashMap 基于哈希表,平均查询 O(1),不保证排序;TreeMap 基于红黑树,查询 O(log n),可以按键排序并支持范围查询。

需要快速键值访问时优先 HashMap;需要有序键、范围查找时考虑 TreeMap。

28. CopyOnWriteArrayList 适合什么场景?

它在写入时复制底层数组,读操作无需加锁,迭代器基于快照。

适合读多写少、数据量不大、遍历稳定性要求较高的场景,如监听器列表。不适合频繁写入或大集合。

29. fail-fast 是什么?

普通集合的迭代器发现结构被非预期修改时,会尽快抛出 ConcurrentModificationException

它不是线程安全保证。单线程中错误地边遍历边修改也会触发。并发集合通常提供弱一致迭代,而不是严格快照。

三、Java 并发

30. 并发和并行有什么区别?

并发表示多个任务在同一时间段内交替推进;并行表示多个任务在同一时刻真正执行。

单核 CPU 可以并发,但不能让多个普通计算任务真正并行。

31. 线程有哪些状态?

Java 线程主要状态:

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

Java 的 RUNNABLE 同时包含操作系统层面的运行中和等待 CPU 调度。

32. 什么是线程安全?

多个线程并发访问时,无论调度顺序如何,程序都能保持正确结果和状态一致性。

线程安全通常涉及:

  • 原子性;
  • 可见性;
  • 有序性。

33. Java 内存模型解决什么问题?

JMM 规定共享变量的读写、允许的重排序、同步操作的内存语义以及 happens-before 规则。

它关注并发可见性和顺序,不是堆、栈、方法区这些 JVM 运行时内存区域的同义词。

34. 什么是 happens-before?

如果操作 A happens-before 操作 B,那么 A 的结果对 B 可见,并且 A 在内存语义上先于 B。

常见规则包括:程序顺序、解锁到后续加锁、volatile 写到后续读、线程启动、线程终止和传递性。

35. volatile 有什么作用?

volatile 提供可见性,并限制特定重排序,但不能保证复合操作原子性。

volatile int count;
count++; // 仍不是原子操作

适合状态标志、单次写多次读和不依赖旧值的状态发布。

36. synchronized 的原理和特点是什么?

synchronized 基于对象监视器提供互斥、可见性、有序性和可重入性。

  • 实例同步方法锁当前对象;
  • 静态同步方法锁对应 Class
  • 同步代码块锁指定对象。

现代 JVM 会根据竞争情况进行多种优化,不应简单把它等同于“重量级锁”。

37. synchronized 和 ReentrantLock 有什么区别?

两者都支持互斥和可重入。ReentrantLock 还支持:

  • 可中断获取锁;
  • 超时尝试;
  • 公平锁;
  • 多个 Condition
  • 更灵活的加锁范围。

显式锁必须在 finally 中释放。

38. CAS 是什么?有什么问题?

CAS 的含义是:内存中的当前值等于预期值时才更新为新值,否则失败。

问题包括:

  • ABA;
  • 高竞争下自旋消耗 CPU;
  • 难以原子更新多个变量;
  • 复杂无锁逻辑可维护性差。

ABA 可通过版本号或 AtomicStampedReference 等方式处理。

39. AtomicInteger 为什么可以原子自增?

它通常使用 CAS 循环:

for (;;) {
    int current = get();
    int next = current + 1;
    if (compareAndSet(current, next)) {
        return next;
    }
}

如果值被其他线程修改,CAS 失败并重新读取、重试。

40. LongAdder 为什么适合高并发计数?

AtomicLong 的所有线程竞争一个变量;LongAdder 将热点拆分到多个内部单元,更新时分散竞争,读取总值时再求和。

它适合指标统计,不适合要求严格瞬时一致的余额等业务值。

41. AQS 是什么?

AQS 是构建锁和同步器的基础框架。它通过一个同步状态、CAS 和等待队列组织获取失败的线程。

ReentrantLockSemaphoreCountDownLatch 等都建立在类似机制之上。

42. CountDownLatch、CyclicBarrier 和 Semaphore 有什么区别?

  • CountDownLatch:等待一组任务完成,计数归零后不可重置。
  • CyclicBarrier:一组线程在屏障处相互等待,可循环使用。
  • Semaphore:通过许可证数量限制并发访问数。

43. ThreadPoolExecutor 的核心参数有哪些?

核心参数:

  • corePoolSize
  • maximumPoolSize
  • keepAliveTime
  • workQueue
  • threadFactory
  • handler

典型流程是:先创建核心线程;达到核心数后任务入队;队列满后再创建非核心线程;达到最大线程数后执行拒绝策略。

44. 为什么不建议直接使用 Executors 创建线程池?

某些快捷方法会隐藏无界队列或几乎无限创建线程的默认配置,可能导致内存溢出或线程耗尽。

生产系统应显式设置线程数、队列容量、线程名、拒绝策略和监控指标。

45. 线程池大小如何设置?

CPU 密集任务的线程数通常接近 CPU 核数;I/O 密集任务可以更大,但必须受数据库连接池、下游限流、内存和超时预算约束。

最终应通过压测和监控确定,不能机械套一个公式。

46. CompletableFuture 的常见用法是什么?

常用方法:

  • supplyAsync()runAsync()
  • thenApply():转换结果
  • thenCompose():串联异步任务
  • thenCombine():组合独立任务
  • allOf():等待多个任务
  • exceptionally():异常恢复

生产中应显式传入业务线程池,避免所有任务共享公共池。

47. 什么是死锁?如何避免和排查?

死锁通常需要互斥、占有并等待、不可剥夺和循环等待四个条件。

避免方式包括统一锁顺序、缩小临界区、减少嵌套锁、使用超时锁,以及避免锁内调用外部接口。

排查工具:

jstack <pid>
jcmd <pid> Thread.print

48. ThreadLocal 有什么风险?

线程池会复用线程,如果 ThreadLocal 使用后不清理,可能导致数据串请求、内存泄漏或安全问题。

try {
    CONTEXT.set(value);
    // 业务处理
} finally {
    CONTEXT.remove();
}

49. 虚拟线程适合什么场景?

虚拟线程由 JVM 调度,适合大量阻塞式 I/O,让应用继续使用同步代码风格处理高并发。

它不会让 CPU 密集计算突破 CPU 核数限制。使用时仍需限制数据库连接、外部接口并发和其他稀缺资源。

四、JVM

50. JVM 运行时内存区域有哪些?

线程私有区域包括程序计数器、Java 虚拟机栈和本地方法栈;线程共享区域包括堆、方法区的实现区域以及运行时常量池。

直接内存不属于传统运行时数据区,但会影响进程总内存。

51. 堆和栈有什么区别?

堆主要保存对象,线程共享,由 GC 管理;栈是线程私有的,每次方法调用创建栈帧,保存局部变量、操作数栈和返回信息。

JIT 可能通过逃逸分析和标量替换消除对象的实际堆分配。

52. 对象创建过程是什么?

大致过程:

  1. 检查类是否已加载;
  2. 分配内存;
  3. 初始化零值;
  4. 设置对象头;
  5. 执行构造初始化;
  6. 返回引用。

多线程快速分配通常还会使用 TLAB。

53. 如何判断对象是否可以回收?

主流 JVM 使用可达性分析。从 GC Roots 出发遍历引用图,不可达对象可以成为回收候选。

GC Roots 常包括线程栈引用、静态字段、JNI 引用、活跃线程和 JVM 内部引用。

54. 强、软、弱、虚引用有什么区别?

  • 强引用:可达时不会回收。
  • 软引用:内存紧张时可能回收。
  • 弱引用:GC 时通常会回收。
  • 虚引用:不能取得对象,主要用于回收后的资源清理通知。

业务缓存更推荐使用支持容量、过期和统计的专业缓存。

55. 常见垃圾回收算法有哪些?

包括标记-清除、标记-复制、标记-整理、分代收集、区域化回收以及并发标记和转移。

收集器会组合这些思想,在吞吐量、停顿、CPU 和内存占用之间权衡。

56. Minor GC、Major GC 和 Full GC 如何理解?

通常:

  • Minor GC 主要回收年轻代;
  • Major GC 主要涉及老年代;
  • Full GC 涉及更大范围,可能包含整个堆和类元数据。

不同 JVM 和工具术语不完全统一,分析时应明确收集器、回收区域和是否发生 STW。

57. G1 的核心思想是什么?

G1 把堆划分为多个 Region,通过并发标记估算各区域回收收益,并在停顿预算内优先回收收益较高的区域。

它使用 Remembered Set 跟踪跨 Region 引用,并对大对象使用 Humongous Region。

58. 什么是 Stop-The-World?

STW 表示 JVM 暂停应用线程以完成某些全局操作。GC 的部分阶段、类重定义和某些安全点操作都会发生 STW。

低延迟 GC 也不是完全没有 STW,而是尽量缩短暂停并把更多工作并发化。

59. 类加载过程是什么?

类生命周期通常包括:

  1. 加载;
  2. 验证;
  3. 准备;
  4. 解析;
  5. 初始化;
  6. 使用;
  7. 卸载。

初始化阶段会执行静态字段赋值和静态代码块。

60. 双亲委派模型有什么作用?

类加载器通常先把请求交给父加载器,父加载器无法完成时,子加载器再尝试。

它有助于保护核心类、减少重复加载并维持类加载层次。容器、插件和 SPI 场景可能调整这种委派顺序。

61. JIT 是什么?

JIT 会根据运行时信息,把热点字节码编译为本地机器码。

常见优化包括方法内联、逃逸分析、标量替换、锁消除和循环优化。运行时假设失效后,JVM 也可能反优化。

62. 常见 OOM 类型有哪些?

常见类型:

  • Java heap space;
  • Metaspace;
  • Direct buffer memory;
  • Unable to create new native thread;
  • GC overhead limit exceeded;
  • 本地内存耗尽。

排查必须考虑堆、元空间、线程栈、直接内存、Code Cache 和本地库,而不能只看 -Xmx

63. 如何排查内存泄漏?

  1. 判断是堆、直接内存、线程还是本地内存;
  2. 获取堆转储或 Native Memory 信息;
  3. 使用 MAT、JProfiler 等分析大对象和支配树;
  4. 沿 GC Roots 查异常引用链;
  5. 结合业务流量和代码验证;
  6. 修复后压测和观察趋势。

常见原因包括静态集合、无界缓存、ThreadLocal、监听器、类加载器和无界队列。

64. 如何排查 CPU 飙高?

top -Hp <pid>
printf "%x
" <thread-id>
jstack <pid>

将线程 ID 转为十六进制,在线程转储中定位对应 nid。还可使用 JFR、Async Profiler 和火焰图定位热点。

常见根因有死循环、锁竞争、频繁 GC、正则回溯和序列化开销。

五、Spring 与 Spring Boot

65. IoC 和 DI 是什么?

IoC 表示对象创建和依赖管理由容器负责;DI 是容器把对象所需依赖注入进去的方式。

推荐构造器注入,因为依赖明确、易测试、可使用 final,并能更早暴露循环依赖。

66. Spring Bean 的生命周期是什么?

简化流程:

  1. 实例化;
  2. 属性填充;
  3. Aware 回调;
  4. 初始化前 BeanPostProcessor;
  5. 初始化方法;
  6. 初始化后 BeanPostProcessor;
  7. 对外使用;
  8. 容器关闭时执行销毁回调。

AOP 代理通常由后置处理器在初始化后阶段创建。

67. BeanFactory 和 ApplicationContext 有什么区别?

BeanFactory 是基础 IoC 容器接口;ApplicationContext 进一步提供事件、国际化、资源加载、环境配置和企业集成能力。

实际 Spring 应用通常使用 ApplicationContext

68. Spring Bean 有哪些作用域?

常见作用域:

  • singleton
  • prototype
  • request
  • session
  • application
  • websocket

Spring 单例是容器级单例,不是 JVM 全局单例。单例 Bean 不应保存请求级可变状态。

69. Spring 如何处理循环依赖?

传统单例 Bean 的字段或 Setter 循环依赖,可通过提前暴露引用的机制解决。

构造器循环依赖、prototype 循环依赖以及部分复杂代理场景无法自动处理。更重要的是,循环依赖常说明职责划分需要重构。

70. Spring AOP 的原理是什么?

Spring AOP 主要通过运行时代理拦截方法调用,在目标方法前后执行事务、权限、日志和指标等横切逻辑。

可使用基于接口的 JDK 动态代理,也可以使用基于子类的类代理。

71. JDK 动态代理和类代理有什么区别?

JDK 动态代理基于接口;类代理通过创建目标类子类完成增强,不要求实现接口,但无法覆盖 final 类或 final 方法。

业务代码应面向接口和代理语义,而不是依赖具体代理实现。

72. 为什么 Spring AOP 自调用会失效?

同一个类中 this.inner() 不会再次经过代理,因此标在 inner() 上的事务或切面可能不生效。

常见解决方式是把方法拆到另一个 Bean,或重新设计代理和事务边界。

73. @Transactional 的原理是什么?

事务代理会:

  1. 读取事务属性;
  2. 获取或创建事务;
  3. 执行业务方法;
  4. 正常结束时提交;
  5. 满足回滚规则时回滚;
  6. 清理线程绑定资源。

事务是否生效取决于调用是否经过代理、事务管理器、数据源参与情况和异常规则。

74. Spring 事务传播行为有哪些?

高频传播行为:

  • REQUIRED:有事务加入,没有则创建;
  • REQUIRES_NEW:始终新建并挂起外部事务;
  • SUPPORTS:有则加入,无则非事务;
  • NOT_SUPPORTED:以非事务执行;
  • MANDATORY:必须已有事务;
  • NEVER:必须没有事务;
  • NESTED:基于保存点的嵌套语义。

REQUIRES_NEW 会额外占用连接,应关注连接池容量。

75. Spring 事务默认什么时候回滚?

默认对运行时异常和 Error 回滚,受检异常通常不自动回滚。

@Transactional(rollbackFor = Exception.class)

如果方法捕获异常后不再抛出,代理可能认为执行成功并提交事务。

76. 事务中调用远程接口有什么风险?

会导致数据库连接和锁长期占用,远程失败也无法由数据库事务回滚。

更常见的改进是缩短本地事务,使用 Outbox、本地消息表、事务提交后事件、幂等和补偿机制。

77. Spring MVC 请求处理流程是什么?

  1. 请求到达 DispatcherServlet
  2. HandlerMapping 查找处理器;
  3. HandlerAdapter 调用 Controller;
  4. 参数解析和校验;
  5. 执行业务逻辑;
  6. 消息转换器序列化响应;
  7. 异常解析器统一处理;
  8. 返回响应。

78. Filter、Interceptor 和 AOP 有什么区别?

  • Filter 位于 Servlet 层,适合请求响应处理;
  • Interceptor 围绕 Spring MVC Handler;
  • AOP 面向方法调用,不局限于 Web。

应根据拦截层级选择,而不是比较哪个工具更高级。

79. Spring Boot 自动配置原理是什么?

Spring Boot 根据类路径、已有 Bean、配置属性和应用类型等条件,决定是否导入和创建默认 Bean。

常见条件注解包括 @ConditionalOnClass@ConditionalOnMissingBean@ConditionalOnProperty

80. Starter 和自动配置有什么区别?

Starter 解决依赖聚合和版本约定;自动配置解决 Bean 和默认行为配置。

两者通常一起使用,但概念不同。

81. @ConfigurationProperties@Value 如何选择?

@Value 适合少量独立配置;@ConfigurationProperties 适合成组、层次化配置,并支持类型绑定、校验和更好的测试。

复杂业务配置优先使用 @ConfigurationProperties

82. 为什么推荐构造器注入?

构造器注入让依赖显式、对象初始化完整、字段可声明为 final,也更方便单元测试。

如果构造参数很多,往往说明类职责过重,应考虑拆分。

六、数据库访问框架

83. MyBatis 中 #{}${} 有什么区别?

#{} 使用参数占位和预编译,能正确处理类型并降低 SQL 注入风险;${} 是字符串直接替换,只适合无法参数化的表名、列名和排序字段,并必须做白名单校验。

84. MyBatis 一级缓存和二级缓存是什么?

一级缓存通常是 SqlSession 级别;二级缓存是 Mapper 命名空间级别,可跨会话。

分布式系统中要谨慎使用二级缓存,不能把它当作完整业务缓存方案。

85. 什么是 N+1 查询?如何解决?

先查询 N 条主记录,再对每条记录单独查关联数据,就会产生 1+N 次查询。

解决方式包括 JOIN、批量 IN 查询、DTO 投影、fetch join、EntityGraph 或在内存中批量组装。

86. JPA 实体有哪些状态?

常见状态:

  • Transient;
  • Managed;
  • Detached;
  • Removed。

Managed 实体字段变化可以通过脏检查在提交时生成更新 SQL。

87. JPA 懒加载有什么风险?

会话关闭后访问关联对象可能异常;JSON 序列化可能意外加载大量数据;还容易产生 N+1 查询。

建议使用明确查询、DTO 投影,不要直接把实体作为接口响应。

88. 数据库连接池有什么作用?

连接池通过复用数据库连接降低创建成本,并限制数据库并发连接数。

关键配置包括最大连接数、获取超时、空闲超时、最大生命周期、连接校验和泄漏检测。连接池不是越大越好。

七、MySQL

89. MySQL 的逻辑架构是什么?

可以简化为:

客户端 → 连接与认证 → SQL 解析与优化 → 执行器 → 存储引擎

Server 层负责连接、解析、优化和执行协调;InnoDB 等存储引擎负责数据、索引、事务和锁。

90. InnoDB 为什么使用 B+Tree 索引?

B+Tree 分支因子大、树高度低,能减少磁盘随机 I/O;叶节点有序并形成链式结构,适合范围查询。

非叶节点只保存索引信息,使每页容纳更多键。

91. 聚簇索引和二级索引有什么区别?

InnoDB 主键索引叶节点保存整行数据;二级索引叶节点通常保存二级索引列和主键值。

通过二级索引查询非覆盖列时,需要再访问聚簇索引,这称为回表。

92. 什么是覆盖索引?

查询所需列都能从某个索引直接取得时,不需要回表。

覆盖索引能减少 I/O,但不应为了覆盖所有查询建立过宽索引。

93. 联合索引的最左前缀原则是什么?

索引 (a, b, c) 通常可支持从最左列开始的匹配:aa,ba,b,c

索引设计还要结合等值、范围、排序和选择性,不能只机械套最左原则。

94. 哪些情况可能导致索引利用不足?

常见情况:

  • 对索引列做函数或运算;
  • 隐式类型转换;
  • 前导模糊查询;
  • 未满足联合索引前导列;
  • 选择性太低;
  • 返回数据比例过高;
  • 排序规则不一致。

最终应通过 EXPLAIN 和实际执行统计验证。

95. 如何分析 EXPLAIN?

重点关注访问类型、选择的索引、估算扫描行数、过滤比例、连接顺序、额外排序、临时表和回表情况。

“使用了索引”不等于 SQL 一定快,扫描大量索引记录同样可能很慢。

96. 慢 SQL 如何排查?

  1. 从慢查询日志或 APM 定位;
  2. 查看调用频率和总耗时;
  3. 检查执行计划和实际执行统计;
  4. 分析扫描、回表、排序、临时表和锁等待;
  5. 优化 SQL、索引或数据模型;
  6. 在接近生产的数据量下验证。

97. ACID 分别表示什么?

  • Atomicity:原子性;
  • Consistency:一致性;
  • Isolation:隔离性;
  • Durability:持久性。

业务一致性是目标,数据库通过原子、隔离和持久等机制提供保障。

98. 事务隔离级别有哪些?

标准隔离级别:

  • Read Uncommitted;
  • Read Committed;
  • Repeatable Read;
  • Serializable。

它们在并发性与脏读、不可重复读、幻读风险之间做权衡。

99. MVCC 是什么?

MVCC 通过保存数据历史版本和事务视图,使普通读取可以选择可见版本,减少读写互相阻塞。

InnoDB 通过事务信息、Undo Log 和 Read View 等机制实现。显式锁定读仍需锁机制。

100. 快照读和当前读有什么区别?

普通 SELECT 通常是快照读,读取符合事务视图的版本。

SELECT ... FOR UPDATEUPDATEDELETE 等属于当前读,读取最新记录并根据语句加锁。

101. InnoDB 常见锁有哪些?

包括共享锁、排他锁、记录锁、间隙锁、Next-Key Lock、意向锁和元数据锁。

锁范围取决于隔离级别、索引、查询条件和执行计划。缺少合适索引时,锁范围可能远大于预期。

102. 什么是间隙锁和 Next-Key Lock?

间隙锁锁住索引记录之间的范围;Next-Key Lock 可理解为记录锁与相邻间隙锁的组合。

它们可限制范围内并发插入,帮助处理幻读。分析时必须结合实际索引。

103. MySQL 死锁如何处理?

InnoDB 能检测死锁,并选择一个事务回滚。

应用层应统一更新顺序、缩短事务、使用合适索引,并对死锁异常进行有限且幂等的重试。

104. redo log、undo log 和 binlog 有什么区别?

  • redo log:InnoDB 层,用于崩溃恢复和持久性;
  • undo log:用于事务回滚和 MVCC 历史版本;
  • binlog:Server 层,用于复制、审计和时间点恢复。

三者职责不同,不能互相替代。

105. 为什么需要两阶段提交?

事务提交时需要保证 redo log 和 binlog 一致。

简化流程是 redo prepare、写 binlog、redo commit。崩溃恢复时结合两类日志状态判断事务是否应提交。

106. MySQL 主从复制原理是什么?

主库提交事务并写 binlog;从库获取日志、写入中继日志并重放。

复制通常存在延迟,因此读写分离需要处理写后读、故障切换和一致性路由。

107. 深分页为什么慢?如何优化?

LIMIT 1000000, 20 可能扫描并丢弃大量前置记录。

优化方式包括游标分页、覆盖索引先查主键、限制最大页深度和使用搜索引擎处理复杂检索。

SELECT *
FROM orders
WHERE id > ?
ORDER BY id
LIMIT 20;

108. 什么时候考虑分库分表?

只有单库单表出现明确容量或吞吐瓶颈,且索引、SQL、缓存、归档和垂直扩展仍不足时才考虑。

分片会带来跨分片查询、分布式事务、全局 ID、扩容迁移和分页排序等复杂度。

八、Redis

109. Redis 为什么快?

主要原因:

  • 数据主要在内存;
  • 数据结构高效;
  • 命令路径短;
  • 事件驱动 I/O;
  • 核心命令执行避免大量共享锁竞争;
  • 支持 Pipeline、批量和 Lua 减少网络往返。

不能简单归结为“单线程所以快”。

110. Redis 常见数据类型和场景有哪些?

  • String:缓存、计数;
  • Hash:对象字段;
  • List:队列;
  • Set:去重、关系;
  • Sorted Set:排行榜、延迟任务;
  • Bitmap:签到;
  • HyperLogLog:近似基数;
  • Stream:消息流;
  • GEO:位置数据。

111. RDB 和 AOF 有什么区别?

RDB 是时间点快照,文件紧凑、恢复通常较快,但快照间可能丢数据;AOF 记录写操作,丢失窗口较小,但文件更大且需要重写。

应根据 RPO、RTO、I/O 和数据重要性选择或组合。

112. Redis 如何删除过期键?

通常组合使用惰性删除和定期抽样删除。

只使用惰性删除会让长期不访问的过期键占用内存;全量持续扫描又会消耗过多 CPU。

113. Redis 有哪些淘汰策略?

可以按以下维度选择:

  • 不淘汰;
  • 只淘汰带过期时间的键;
  • 在全部键中淘汰;
  • 使用 LRU、LFU、TTL 或随机策略。

必须结合 Redis 是缓存还是数据存储,以及核心数据是否允许被淘汰。

114. 缓存穿透是什么?

查询的数据在缓存和数据库都不存在,每次都访问数据库。

解决方式:缓存空值、布隆过滤器、参数校验、鉴权和限流。

115. 缓存击穿是什么?

热点键失效时,大量并发同时访问数据库。

解决方式:互斥重建、逻辑过期、提前刷新、请求合并、热点不过期和多级缓存。

116. 缓存雪崩是什么?

大量键同时失效或 Redis 整体不可用,导致数据库瞬时过载。

解决方式:过期时间随机化、高可用、多级缓存、限流熔断、热点预热和数据库保护。

117. Cache Aside 模式是什么?

读取先查缓存,未命中查数据库并回填。更新通常先更新数据库,再删除缓存。

删除缓存比直接更新更简单,但仍存在短暂不一致,需要根据业务使用延迟双删、消息通知、binlog 订阅或版本号。

118. 更新数据库后删除缓存为什么仍可能不一致?

读请求可能先查到旧数据库值,写请求更新并删除缓存后,读请求又把旧值写回缓存。

可使用延迟再删、消息驱动失效、版本号、同键串行或订阅 binlog 降低不一致窗口。

119. Redis 分布式锁如何实现?

加锁应使用原子 SET key value NX PX timeout,锁值必须唯一。解锁时用 Lua 原子校验锁值并删除。

还要考虑业务超时、续期、主从切换、网络分区、可重入和 fencing token。强一致协调场景未必适合仅使用 Redis。

120. 主从、Sentinel 和 Cluster 有什么区别?

主从用于副本和读扩展;Sentinel 提供监控和主从切换;Cluster 同时提供分片、副本和故障转移。

Cluster 能扩展容量,但会增加多键操作和运维复杂度。

121. 大 Key 和热 Key 有什么风险?

大 Key 会造成网络、复制、删除和延迟问题;热 Key 会导致单节点 CPU 或网络过载。

可通过拆分、压缩、异步删除、本地缓存、多副本、热点分片和限流处理。

九、消息队列与 Kafka

122. 为什么使用消息队列?

消息队列可以异步、解耦、削峰和驱动事件处理。

代价是引入消息丢失、重复、顺序、一致性、积压和运维复杂度。必须有明确收益再引入。

123. 如何保证消息不丢失?

端到端考虑:

  • 生产端确认、重试、Outbox;
  • Broker 持久化和副本;
  • 消费成功后再提交位点;
  • 失败重试和死信;
  • 消费幂等;
  • 监控积压和异常。

不能只依赖一个 Broker 参数声称绝对不丢。

124. 如何处理重复消息?

消费端需要幂等。常见方式:

  • 业务唯一键;
  • 数据库唯一约束;
  • 去重表;
  • 状态机;
  • 条件更新;
  • 幂等 Token。

优先让业务数据本身具备幂等约束。

125. 如何保证消息顺序?

通常只保证同一业务键的局部顺序。Kafka 中可把相同业务键路由到同一分区。

还要避免多线程处理、失败重试和异步下游导致乱序。

126. Kafka 的 Topic、Partition 和 Replica 是什么?

Topic 是逻辑类别;Partition 是有序追加日志和并行单位;Replica 是分区副本;Leader 负责读写,Follower 复制数据。

一个分区内有序,不同分区不保证全局顺序。

127. Consumer Group 的原理是什么?

同一消费组内,分区会在消费者之间分配;一个分区同一时刻通常只由组内一个消费者处理。

不同消费组可以独立消费同一消息。消费者数量超过分区数时,多余消费者会空闲。

128. Kafka Offset 是什么?

Offset 是消息在分区中的位置。消费者通过维护位点实现断点续消费、重放和独立进度。

自动提交简单但可能过早提交,生产系统应把提交边界与业务成功边界对齐。

129. Kafka 为什么吞吐量高?

原因包括顺序追加、批量发送和拉取、页缓存、零拷贝相关优化、分区并行、压缩和异步处理。

高吞吐通常需要在批量大小与等待延迟之间权衡。

130. at-most-once、at-least-once 和 exactly-once 是什么?

  • at-most-once:最多一次,可能丢失;
  • at-least-once:至少一次,可能重复;
  • exactly-once:在定义的处理边界内结果只生效一次。

如果流程涉及外部数据库,仍需要幂等、Outbox 或事务协调,不能把 Kafka 内部语义直接等同于端到端严格一次。

131. 消息积压如何排查?

检查生产速率、消费者数量、单条处理时间、下游依赖、毒消息、分区倾斜和再均衡。

可临时扩容消费者、批处理、隔离失败消息、优化下游、限流生产端,并补充积压告警。

十、网络与 HTTP

132. TCP 和 UDP 有什么区别?

TCP 面向连接,提供可靠、有序字节流以及重传、流控和拥塞控制;UDP 面向数据报,不保证可靠和顺序,但开销较小。

HTTP/3 使用基于 UDP 的 QUIC,在用户态实现可靠传输等能力。

133. TCP 为什么三次握手?

三次握手用于确认双向收发能力并同步初始序列号:

  1. 客户端 SYN;
  2. 服务端 SYN + ACK;
  3. 客户端 ACK。

第三次确认让服务端知道客户端已经收到服务端的序列信息。

134. TCP 为什么通常四次挥手?

TCP 是全双工连接,两个发送方向需要分别关闭。一方发送 FIN 只表示自己不再发送,对方仍可继续发送,因此关闭通常分成两个方向完成。

ACK 和 FIN 有时可以合并,所以抓包中不一定总是四个独立报文。

135. TIME_WAIT 有什么作用?

它让最后 ACK 丢失时可以重发,并使旧连接的延迟报文在网络中消失,避免污染后续同四元组连接。

大量 TIME_WAIT 应优先优化 Keep-Alive 和连接复用,而不是粗暴关闭协议保护。

136. HTTP/1.1、HTTP/2 和 HTTP/3 有什么区别?

HTTP/1.1 支持持久连接,但连接级并发能力有限;HTTP/2 使用二进制分帧、多路复用和头部压缩;HTTP/3 基于 QUIC,减少传输层队头阻塞并支持连接迁移。

137. HTTPS 的工作原理是什么?

TLS 会协商协议和加密套件,验证服务端证书,协商会话密钥,随后使用高效的对称加密传输,并保证完整性。

公钥体系主要用于身份认证和密钥协商。

138. Cookie、Session 和 Token 有什么区别?

Cookie 是浏览器存储并自动携带的数据;Session 是服务端保存会话状态;Token 是客户端携带的访问凭证,可以有状态或自包含。

设计时需考虑注销、权限变更、泄漏、过期、刷新、CSRF 和 XSS。

139. GET 和 POST 的区别是什么?

语义上 GET 用于获取资源,应安全且幂等;POST 用于提交处理,不要求幂等。

POST 并不天然比 GET 安全,真正的安全依赖 HTTPS、鉴权、输入校验和服务端实现。

140. 常见 HTTP 状态码有哪些?

  • 200:成功;
  • 201:创建成功;
  • 204:成功无响应体;
  • 400:参数错误;
  • 401:未认证;
  • 403:无权限;
  • 404:不存在;
  • 409:冲突;
  • 429:请求过多;
  • 500:服务内部错误;
  • 502/504:网关或上游问题;
  • 503:暂时不可用。

十一、Linux 与线上排障

141. 常用 Linux 排查命令有哪些?

top / htop / pidstat       # CPU、线程
free -h / vmstat           # 内存
df -h / du -sh / iostat    # 磁盘
ss -lntp / tcpdump / curl  # 网络
ps / lsof / strace         # 进程和文件

关键不是背命令,而是先判断瓶颈属于 CPU、内存、I/O、网络、锁还是下游依赖。

142. Load Average 是什么?

它表示一定时间窗口内处于可运行状态或不可中断等待状态的任务数量平均值。

需要结合 CPU 核数、I/O wait、容器配额和历史基线判断,高负载不一定等于 CPU 满。

143. CPU 不高但系统很慢,可能是什么原因?

可能是磁盘 I/O、网络等待、数据库锁、线程池排队、连接池耗尽、外部接口超时、GC 停顿、DNS 或容器限流。

应观察端到端 Trace 和线程状态,而不是只看 CPU。

144. 磁盘满了如何排查?

df -h
du -x -h /path | sort -h
lsof +L1

df 很高但 du 找不到文件时,可能是文件已删除但仍被进程打开。常见根因还有日志未轮转、堆转储和容器日志。

145. 如何排查接口响应慢?

从外到内分析:

  1. DNS 和客户端网络;
  2. 网关;
  3. 应用排队;
  4. 线程池;
  5. 方法 Trace;
  6. 数据库、缓存、MQ;
  7. 外部依赖;
  8. GC、CPU、磁盘和网络;
  9. 最近变更和异常参数。

重点观察 P95/P99,而不是只看平均值。

十二、分布式与微服务

146. CAP 定理是什么?

发生网络分区时,系统无法同时保证线性一致性和所有请求都可用。

网络分区是现实条件,因此实际是在分区期间权衡一致性和可用性。CAP 的一致性不等同于 ACID 中的一致性。

147. BASE 理论是什么?

BASE 强调基本可用、软状态和最终一致。

最终一致不代表可以无限期不一致,必须定义可接受窗口、重试和补偿方案。

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

包括两阶段提交、TCC、Saga、本地消息表、Transactional Outbox、事务消息、幂等重试和对账补偿。

选择依据是一致性要求、是否可补偿、参与方数量、时延和开发运维成本。

149. TCC 是什么?

TCC 把业务拆为 Try、Confirm、Cancel。

实现时必须处理幂等、空回滚、悬挂、超时和重试。它一致性较强,但业务侵入和实现成本也高。

150. Saga 是什么?

Saga 把长事务拆为多个本地事务,失败时按流程执行补偿。

适合长流程、跨服务且业务可补偿的场景。补偿不一定能恢复到物理上从未发生的状态。

151. 什么是幂等?接口如何实现幂等?

相同请求执行一次或多次,最终业务效果相同。

常见方式:业务唯一键、数据库唯一约束、状态机、条件更新、去重表、Token 和消费记录。

152. 分布式 ID 有哪些方案?

数据库自增、UUID、Redis 自增、号段、Snowflake 和专用 ID 服务。

Snowflake 类方案需要处理时钟回拨、节点 ID 冲突和峰值序列耗尽。

153. 服务注册与发现是什么?

服务实例向注册中心登记地址和元数据,调用方或代理获取可用实例并完成路由。

需要健康检查、租约、实例上下线、多机房、灰度标签和注册中心自身高可用。

154. 常见负载均衡算法有哪些?

轮询、加权轮询、随机、最少连接、一致性哈希、最短响应时间和标签路由。

选择要结合实例性能、请求长短、热点、可用区和灰度需求。

155. 常见限流算法有哪些?

固定窗口、滑动窗口、漏桶和令牌桶。

限流维度可以是系统、用户、IP、接口、租户或业务键。限流后还要设计错误响应和降级路径。

156. 熔断、降级和限流有什么区别?

限流控制进入系统的请求;熔断在下游持续失败时暂停调用;降级在资源不足或依赖异常时提供简化能力。

三者共同用于防止故障扩散,但解决的问题不同。

157. 为什么重试会放大故障?

下游过载时立即重试会进一步增加流量,形成重试风暴。

安全重试需要幂等、指数退避、随机抖动、总超时预算、最大次数和熔断,并避免多层重试叠加。

158. 一致性哈希是什么?

它把节点和键映射到哈希环,键由顺时针遇到的第一个节点负责。节点增减时只迁移部分数据。

虚拟节点可以改善分布不均并支持权重。

159. 为什么需要全链路追踪?

一次请求可能经过网关、多个服务、数据库、缓存和消息队列。

Trace 记录调用关系、耗时和错误位置,并与 Metrics、Logs 一起组成可观测性的核心能力。

160. 微服务一定比单体好吗?

不一定。微服务带来独立部署和团队自治,也引入网络故障、分布式事务、接口治理和运维复杂度。

系统和团队规模不足时,模块化单体通常更简单可靠。

十三、系统设计

161. 系统设计题应该如何回答?

推荐顺序:

  1. 明确需求和边界;
  2. 估算 QPS、用户量和数据量;
  3. 定义核心 API;
  4. 设计数据模型;
  5. 画主链路;
  6. 识别热点和瓶颈;
  7. 讨论缓存、异步和扩展;
  8. 讨论一致性和高可用;
  9. 讨论安全、监控和成本;
  10. 说明取舍。

不要一开始就堆 Redis、MQ 和分库分表。

162. 如何设计秒杀系统?

核心是控制进入核心链路的有效请求:

  1. CDN 和静态化;
  2. 资格校验和防刷;
  3. 网关限流;
  4. 库存预热;
  5. Redis Lua 原子预扣;
  6. 成功请求进入队列;
  7. 异步创建订单;
  8. 数据库条件更新和唯一约束兜底;
  9. 超时未支付回补;
  10. 对账和补偿。

163. 如何设计短链接系统?

需要短码生成、映射存储、重定向、缓存和异步统计。

还要考虑冲突、热点、恶意链接、过期、自定义短码、多地域和 301/302 选择。

164. 如何设计排行榜?

中小规模实时榜单可使用 Redis Sorted Set。

还需定义同分规则、时间窗口、历史榜单、分区聚合、防作弊和落库策略。

165. 如何设计延迟任务系统?

可选 Redis Sorted Set、时间轮、消息队列延迟功能、数据库扫描或专用调度平台。

必须考虑幂等、至少一次触发、任务取消、修改时间、失败重试、分片和到期洪峰。

166. 如何设计文件上传系统?

推荐客户端获取受限预签名地址后直传对象存储,服务端保存元数据并异步完成病毒扫描、转码和内容审核。

还要支持分片、断点续传、类型和大小限制、权限、临时文件清理和 CDN。

167. 如何设计高可用系统?

需要应用多实例、跨可用区、数据库副本、缓存和 MQ 高可用、超时重试、限流熔断、容量冗余、灰度发布、备份恢复和故障演练。

还要明确 RTO 和 RPO。高可用不是单纯堆机器。

十四、安全

168. 如何防止 SQL 注入?

使用预编译参数和 ORM 绑定;动态表名、列名、排序字段使用白名单;数据库账号遵循最小权限;避免字符串拼接 SQL。

169. XSS 是什么?如何防止?

XSS 让恶意脚本在其他用户浏览器中执行。

防护包括按输出上下文编码、HTML 白名单清洗、CSP、HttpOnly Cookie 和避免危险 DOM API。

170. CSRF 是什么?如何防止?

CSRF 利用浏览器自动携带 Cookie,诱导用户发送非预期请求。

防护方式:CSRF Token、SameSite Cookie、校验 Origin/Referer、敏感操作二次验证,以及不用 GET 修改状态。

171. JWT 有哪些优缺点?

优点是自包含、方便跨服务验证;缺点是签发后难以立即撤销、权限变更不易即时生效、泄漏后在过期前可使用。

常见设计是短期 Access Token、Refresh Token 轮换、最小 Claims 和严格验证签发方、受众与过期时间。

172. 密码应该如何存储?

使用 Argon2、bcrypt、scrypt 或 PBKDF2 等密码哈希算法,每个密码使用随机盐,并配置足够计算成本。

不能明文存储,也不应只做普通快速哈希。

173. 如何防止重放攻击?

组合使用 HTTPS、时间戳、Nonce、请求签名、短时间窗口和服务端去重。

签名保证来源和完整性,防重放还需要服务端验证请求是否已使用。

174. 什么是最小权限原则?

用户、服务、进程和账号只获得完成任务所需的最小权限。

应应用于数据库账号、服务调用、对象存储、管理接口、密钥和生产运维权限。

十五、项目与场景题

175. 如何介绍自己的项目?

按以下结构:

  1. 业务背景和目标;
  2. 你的角色和责任;
  3. 用户量、QPS、数据量;
  4. 核心架构;
  5. 最大难点;
  6. 你的具体方案和取舍;
  7. 量化效果;
  8. 问题和复盘。

不要只罗列技术名词,要说明你真正做了什么。

176. 如何回答“解决过最难的问题”?

使用:

现象 → 影响 → 排查 → 根因 → 方案 → 验证 → 复盘

可以选择 P99 延迟、死锁、消息积压、内存泄漏、热点缓存或数据不一致等真实案例。

177. 线上接口突然变慢怎么办?

先止损:限流、降级、熔断、扩容或回滚。

再定位:错误率、Trace、线程池、连接池、慢 SQL、锁、Redis、MQ、外部接口、GC、CPU、磁盘和网络。

最后修复根因、补监控、压测并复盘。

178. 数据库连接池满了怎么办?

检查活跃连接、等待线程、慢 SQL、长事务、连接泄漏、锁等待和线程池配置。

可临时限流或降级,但不能只增加连接数;还要缩短事务并让线程池并发与数据库容量匹配。

179. 重复消息导致重复扣款如何处理?

扣款请求携带唯一业务流水号,数据库建立唯一约束,并在同一事务中写流水和更新余额。

重复请求返回首次结果。不能只依赖 Redis 锁,数据库业务约束必须兜底。

180. 如何防止库存超卖?

数据库可以使用条件更新:

UPDATE sku_stock
SET stock = stock - 1
WHERE sku_id = ?
  AND stock > 0;

高并发时可先在缓存预扣并异步落库,但数据库仍应保留最终约束,并处理取消、回补和对账。

181. 服务调用超时应如何设置?

根据端到端预算分配。连接超时和读取超时分开;下游超时小于上游剩余预算;重试总时间不能超过总预算。

不同接口应有不同超时,不能全局统一一个大数。

182. 如何设计灰度发布?

可按用户、租户、地区、请求头或比例路由。

必须保证接口、数据库和消息格式兼容,建立新旧版本独立指标、自动回滚阈值和分阶段扩大流量计划。

183. 如何证明性能优化有效?

需要优化前基线、固定环境、明确 P95/P99、QPS、CPU、GC 和下游负载,对比压测并确认没有把压力转移到其他组件。

“感觉变快”或只看平均响应时间不能证明优化成功。

十六、编码与算法

184. Java 后端常见算法主题有哪些?

重点准备数组、字符串、哈希表、链表、栈、队列、二叉树、堆、二分、滑动窗口、双指针、回溯、动态规划、BFS/DFS、并查集、LRU 和 Top K。

回答时要说明时间、空间复杂度和边界条件。

185. 如何实现线程安全单例?

推荐枚举或静态内部类:

public final class Singleton {
    private Singleton() {}

    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

双重检查方案中的实例引用必须使用 volatile

186. 如何实现 LRU 缓存?

核心结构是 HashMap + 双向链表。Map 提供 O(1) 定位,链表维护最近使用顺序,访问时移动到头部,容量满时删除尾节点。

Java 也可基于 LinkedHashMap 的访问顺序模式实现,但默认不是线程安全的。

187. 如何找第 K 大元素?

方案包括排序 O(n log n)、大小为 K 的最小堆 O(n log k) 和 Quickselect 平均 O(n)。

应先询问数据是否流式、K 是否很小、是否允许修改原数组。

188. 如何判断链表是否有环?

使用快慢指针。慢指针每次一步,快指针每次两步;若有环最终相遇,无环则快指针到达 null。

相遇后一个指针回到头部,两者每次一步,再次相遇处是环入口。

189. 二分查找最容易错在哪里?

常见错误是区间定义不一致、循环条件错误、中点计算、边界更新和寻找首末位置时模板混乱。

应先明确使用闭区间 [left, right] 还是左闭右开 [left, right),全程保持一致。

190. 如何设计有界阻塞队列?

需要固定容量容器、一把锁、非空条件和非满条件。

入队在满时等待,成功后通知非空;出队在空时等待,成功后通知非满。条件检查必须使用 while,处理虚假唤醒和重新竞争。

十七、面试表达模板

原理题回答模板

先给一句结论。
再讲核心数据结构或执行流程。
然后说明复杂度、线程安全、一致性或失败模式。
最后补充适用场景和局限。

场景题回答模板

先确认业务目标和约束。
给出主方案和核心链路。
分析失败、重复、超时、一致性和容量问题。
最后说明监控、降级与方案取舍。

项目题回答模板

背景 → 规模 → 个人职责 → 难点 → 排查/设计 → 取舍 → 量化结果 → 复盘

不会的问题如何回答

可以坦诚说明没有在生产中直接使用,但继续展示推理能力:

这个特性我没有在生产中直接使用过。
按照我的理解,它主要解决……问题,核心机制可能包括……。
如果用于当前场景,我会重点验证……。

不要编造,也不要只回答“不知道”。

十八、面试前检查清单

  • 能否在 3 分钟内介绍一个核心项目?
  • 能否说明自己负责的边界和贡献?
  • 能否解释 HashMap、ConcurrentHashMap?
  • 能否解释 volatile、synchronized、CAS 和线程池?
  • 能否画出 JVM 内存结构并说明 GC?
  • 能否说明 Spring AOP 和事务失效场景?
  • 能否解释 MySQL 索引、MVCC、锁和日志?
  • 能否说明 Redis 缓存一致性和分布式锁风险?
  • 能否说明 Kafka 重复、丢失、顺序和积压?
  • 能否完成秒杀、高可用、延迟任务等系统设计?
  • 能否用 P95/P99、QPS、CPU 等数据证明优化效果?
  • 能否给出方案的局限和替代方案?

参考资料


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐