整理时间: 2026-03-17 来源: 牛客网面经抓取 + 面试题库整合 整理规则: 相同/相似知识点合并为一道,保留原始问题描述,全面详细回答


一、Java 基础

1.1 语言特性与概述

1. Java 之外还会其他语言吗?

作为Java后端开发工程师,我主要使用Java进行开发。在学习过程中也接触过其他语言:

  • C/C++: 学习过C语言基础,了解指针和内存管理的底层概念;C++学习过面向对象特性和STL容器

  • Python: 用于一些脚本开发和数据处理任务,了解其简洁的语法风格

  • JavaScript/TypeScript: 了解前端开发基础,能够进行全栈开发

  • Go: 了解其并发编程模型goroutine和channel机制

不同语言有其适用场景,Java适合企业级后端开发,其强类型、跨平台、丰富的生态等特性使其成为后端开发的主流选择。


2. 介绍一下 Spring AI 这个框架

Spring AI是Spring生态系统中专注于AI应用开发的框架,它提供了统一的标准接口来访问各种大语言模型(LLM)提供商。

核心特性包括:

  • 统一API: 提供了与OpenAI、Azure OpenAI、HuggingFace、Ollama等供应商的统一接口

  • Prompt模板: 支持动态构建提示词,使用模板引擎将参数注入

  • 输出转换: 自动将LLM输出转换为Java对象(POJO)

  • RAG支持: 提供了检索增强生成的完整支持,包括文档加载、分割、向量化、向量存储

  • 函数调用: 支持Function Calling,可以动态调用Java方法作为工具

典型使用流程:

  1. 添加依赖配置

  2. 创建ChatClient

  3. 构建Prompt

  4. 调用LLM并获取响应


3. 说说你对多态的理解

多态是面向对象三大特性之一(封装、继承、多态),指同一接口表现出不同行为的能力。

Java中多态的实现方式:

  • 方法重写(Override): 子类重写父类方法,运行 时根据实际对象类型决定调用哪个方法

  • 方法重载(Overload): 同一类中方法名相同但参数列表不同,编译时决定

多态的核心条件:

  1. 继承关系或接口实现

  2. 子类重写父类方法

  3. 父类引用指向子类对象(向上转型)

多态的好处:

  • 提高代码的可扩展性和可维护性

  • 符合开闭原则(对扩展开放,对修改关闭)

  • 降低类之间的耦合度


4. 什么是序列化和反序列化?

序列化是将Java对象转换为字节序列的过程,以便能够存储到磁盘文件、数据库或通过网络传输。

反序列化是将字节序列恢复为Java对象的过程。

Java中序列化的实现:

  • 实现Serializable接口(标记接口)

  • 使用ObjectInputStream/ObjectOutputStream进行读写

  • 使用transient关键字标记不需要序列化的字段

序列化的应用场景:

  • 对象持久化到文件或数据库

  • 网络传输对象(RMI、Socket通信)

  • 缓存对象到Redis等缓存中间件

  • Session序列化(分布式Session)


5. 序列化的原理分析

Java对象序列化原理:

  1. ObjectOutputStream:

    • 写入类元数据(类名、serialVersionUID)

    • 递归写入对象属性

    • 对于引用类型,写入完整的对象图

    • 使用引用机制避免重复序列化

  2. 序列化机制:

    • 每个对象关联一个序列化句柄

    • 首次遇到对象时写入完整数据并分配句柄

    • 后续遇到相同对象只写入句柄引用

  3. serialVersionUID:

    • 类的版本标识

    • 序列化时写入,反序列化时校验

    • 不一致会抛出InvalidClassException

  4. Externalizable接口:

    • 完全自定义序列化逻辑

    • 需要实现writeExternal和readExternal方法


6. 为什么需要序列化?

  1. 数据持久化: 将对象保存到磁盘、数据库,实现数据持久化存储

  2. 网络传输: 网络通信传输的是字节流,序列化将对象转换为字节序列才能传输

  3. 缓存: 将对象缓存到Redis等缓存中间件,需要将对象序列化

  4. 分布式计算: RMI、Hessian等远程调用技术依赖序列化

  5. Session复制: 分布式环境下Session需要序列化才能在节点间传递

  6. 跨语言通信: JSON、XML、ProtoBuf等序列化格式支持跨语言


7. 继承和接口相比,各自的局限性是什么?

继承的局限性:

  • Java单继承,一个类只能继承一个父类

  • 继承关系是"is-a"关系,可能导致类层次过深

  • 父类变更会影响所有子类,耦合度高

  • 可能导致"钻石问题"(多重继承的二义性)

接口的局限性:

  • 接口只能声明方法签名,不能包含实现(JDK 8之前的default方法)

  • 接口没有状态,只能定义常量

  • 接口实现类必须实现所有方法(default方法除外)

  • 接口变更是破坏性的,实现类需要适配

使用建议:

  • 使用接口定义行为规范,使用抽象类实现通用逻辑

  • 优先使用组合而非继承

  • 尽量使用接口解耦,抽象类用于代码复用


8. Java 中常见的集合类有哪些?分别有什么特点

Collection接口:

  • List: 有序、可重复

    • ArrayList: 数组实现,查询快O(1),增删慢O(n),线程不安全

    • LinkedList: 双向链表实现,增删快O(1),查询慢O(n)

    • Vector: 数组实现,线程安全,效率低

    • CopyOnWriteArrayList: 写时复制,读多写少场景

  • Set: 无序、不重复

    • HashSet: 基于HashMap,查找O(1),无序

    • LinkedHashSet: 维护插入顺序

    • TreeSet: 基于红黑树,有序O(logn)

    • CopyOnWriteArraySet: 线程安全读多写少

  • Queue: 队列

    • PriorityQueue: 优先级队列

    • ArrayDeque: 双端队列

Map接口: 键值对

  • HashMap: JDK 1.8数组+链表+红黑树,线程不安全

  • LinkedHashMap: 维护插入顺序

  • TreeMap: 红黑树实现,有序

  • Hashtable: 线程安全,效率低

  • ConcurrentHashMap: JDK 1.8 CAS+synchronized,线程安全高效

  • Properties: 继承Hashtable,配置文件


9. 简单介绍一下 Java 中常用的 HashMap,以及 1.8 之后有什么优化

HashMap是Java中最常用的Map接口实现类,基于哈希表实现,提供O(1)的增删改查性能。

JDK 1.8之前的实现:

  • 数组+链表

  • 头插法扩容

  • 链表中元素过多时查找效率降低O(n)

JDK 1.8之后的优化:

  1. 数组+链表+红黑树: 当链表长度超过8且数组长度≥64时,链表转换为红黑树,查询效率从O(n)优化到O(logn)

  2. 尾插法: 扩容时使用尾插法,避免多线程扩容时形成环形链表导致死循环

  3. 扩容优化: 扩容时元素移动只需要看原位置或原位置+旧容量位置,不需要重新计算hash

  4. 初始化优化: 使用扰动函数,将hashCode高低位异或,减少hash冲突

  5. 红黑树退化: 当红黑树节点数小于等于6时,退化为链表


10. HashMap 的底层实现?

JDK 1.8 HashMap底层数据结构是数组+链表+红黑树的组合。

主要组成部分:

  • Node[] table: 哈希桶数组,存储链表或红黑树的头节点

  • Node: 实现Map.Entry接口,包含hash、key、value、next

  • TreeNode: 继承Node,红黑树节点实现

关键参数:

  • initialCapacity: 初始容量,默认16

  • loadFactor: 负载因子,默认0.75

  • threshold: 扩容阈值 = capacity * loadFactor

  • TREEIFY_THRESHOLD: 树化阈值,8

  • UNTREEIFY_THRESHOLD: 退化阈值,6

put过程:

  1. 计算key的hash(扰动函数处理)

  2. 定位桶位置 (n-1) & hash

  3. 遍历链表/红黑树

  4. 找到key则更新value,未找到则插入

  5. 检查是否需要树化

  6. 检查是否需要扩容


11. 往 HashMap 里 put 一个值的过程是怎样的?

  1. hash计算: 调用hash()方法,对key的hashCode进行扰动处理

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  2. 定位索引: 使用(n-1) & hash计算数组下标位置

  3. 遍历桶: 如果桶不为空,遍历链表或红黑树

    • 如果找到相同的key,直接更新value

    • 如果未找到,执行插入

  4. 插入新节点:

    • 判断是否树化(链表长度≥8且数组长度≥64)

    • 使用尾插法插入链表尾部

  5. modCount++: 修改计数+1

  6. 检查扩容: size++后如果大于threshold,执行resize()

JDK 1.7头插法 vs 1.8尾插法:

  • 1.7: 新节点插入头部(头插法)

  • 1.8: 新节点插入尾部(尾插法)


12. HashMap 达到临界值时,扩容是怎么扩容的?

  1. 触发条件: 当size > threshold时触发扩容

  2. 扩容过程:

    • 新容量 = oldCapacity * 2(翻倍)

    • 新建Node数组

    • 将原数组元素迁移到新数组

  3. 元素迁移:

    • 遍历每个桶

    • 对链表/红黑树每个节点重新计算位置

    • 只需判断hash & oldCapacity是否为0

    • 为0保持原位置,为1移动到原位置+oldCapacity位置

  4. 性能优化:

    • 迁移过程不需要重新计算hash,利用容量特性判断位置

    • 头节点直接移动

    • 链表拆分为两个链表


13. HashMap 在 JDK1.7 升级到 1.8 做过哪些优化?

  1. 数据结构优化: 数组+链表 → 数组+链表+红黑树

    • 链表过长时查找效率O(n) → 红黑树O(logn)

  2. 插入方式优化: 头插法 → 尾插法

    • 避免多线程扩容时形成环形链表导致死循环

  3. hash函数优化:

    • 扰动函数处理,使hash分布更均匀

    • 高位参与运算,减少碰撞

  4. 扩容优化:

    • 不需要重新计算hash

    • 直接判断hash & oldCapacity决定新位置

  5. 初始化优化:

    • 延迟初始化,首次put时才创建数组

  6. 树化/退化条件:

    • 树化: 链表长度≥8且数组长度≥64

    • 退化: 红黑树节点数≤6


14. HashMap 在 JDK 8 下为什么要引入红黑树,树化与退化的条件是什么?

引入红黑树的原因:

  • 当hash冲突严重时,链表会很长

  • 链表查找是O(n),影响性能

  • 红黑树是自平衡二叉查找树,查找O(logn)

  • 在链表长度较长时,显著提升查找效率

树化条件:

  1. 链表长度 >= 8

  2. 数组长度 >= 64

    • 两个条件必须同时满足才会树化

    • 如果数组长度 < 64,会先扩容而非树化

退化条件:

  • 红黑树节点数 <= 6

  • 退化为链表,避免频繁树化/退化带来的开销

为什么选择8作为阈值:

  • 红黑树适合动态插入删除,链表适合静态查询

  • 8是一个经验值,链表长度超过8时,红黑树的log(8)=3 < 链表遍历平均次数

  • 退化阈值设为6,避免频繁转换


15. 红黑树相对于链表有什么优点

  1. 查询效率高:

    • 链表: O(n)

    • 红黑树: O(logn)

    • 当数据量大时,差异明显

  2. 自平衡:

    • 红黑树通过着色和旋转保持近似平衡

    • 保证最长路径不超过最短路径的2倍

    • 查找、插入、删除在最坏情况下都是O(logn)

  3. 适合范围查询:

    • 红黑树是有序的,支持范围查询

    • 链表不支持

  4. 空间换时间:

    • 红黑树节点需要存储颜色、左右子树指针

    • 但换取了更高的查询效率

适用场景:

  • 数据量大、查询频繁

  • 需要有序遍历

  • 插入删除不特别频繁


16. 红黑树和AVL树有什么区别?适用场景有什么不同?

AVL树:

  • 严格平衡,任意节点左右子树高度差不超过1

  • 查找效率更稳定O(logn)

  • 插入/删除可能需要多次旋转

  • 适合查找密集型场景

红黑树:

  • 近似平衡,最长路径不超过最短路径2倍

  • 插入/删除旋转次数少

  • 适合插入删除频繁场景

区别对比:

特性

AVL树

红黑树

平衡度

严格平衡

近似平衡

查找复杂度

O(logn)

O(logn)

插入/删除

可能多次旋转

最多2次旋转

高度

更低

略高

实现复杂度

较高

较低

适用场景:

  • AVL: 数据库索引(查找多,修改少)

  • 红黑树: 内存数据结构(Java HashMap、TreeMap、Linux进程调度)


17. JDK1.7 头插法为什么会出现死循环?

在JDK 1.7中,HashMap扩容采用头插法,多线程场景下会导致死循环。

原因分析:

  1. 线程A和B同时触发扩容

  2. 假设链表为 A → B → null(头节点A,尾节点B)

  3. 线程A执行transfer(),将链表反转:B → A → null

  4. 线程B也执行transfer()

  5. 由于头插法,线程B会创建新链表

  6. 由于Entry的next指针已被修改,形成环形链表

死循环过程:

原链表: A -> B -> C
线程A处理: C -> B -> A (反转)
线程B处理时,遍历到A,发现A.next=B,B.next=A,形成环

后果:

  • get()操作可能死循环CPU 100%

  • 内存泄漏

  • 数据丢失


18. 1.8 为什么改成尾插法?

  1. 避免死循环:

    • 尾插法保证插入顺序

    • 多线程扩容时不会形成环形链表

  2. 数据一致性:

    • 头插法会反转链表,导致数据丢失

    • 尾插法保持原有顺序

  3. 但仍非线程安全:

    • 尾插法解决了死循环问题

    • 但仍存在数据覆盖等问题

    • 多线程场景应使用ConcurrentHashMap


19. const int p和int const p有什么区别?

这是C/C++的指针语法问题:

  • const int p (或 int const p):

    • p是一个指向常量整数的指针

    • 不能通过p修改所指向的值

    • 可以重新赋值p指向其他地址

    • "const修饰的是int"

  • int * const p:

    • p是一个常量指针,指向整数

    • 可以通过p修改所指向的值

    • 不能重新赋值p指向其他地址

    • "const修饰的是p"

  • const int * const p:

    • 两者都不能修改

    • p的指向固定,内容也不能修改


20. Java 中 int 的取值范围的最小值是多少?

Java中int是32位有符号整数:

  • 最大值: 2^31 - 1 = 2147483647 (Integer.MAX_VALUE)

  • 最小值: -2^31 = -2147483648 (Integer.MIN_VALUE)

特殊说明:

  • Integer.MIN_VALUE = -2147483648

  • 但-2147483648这个字面量在代码中实际上是编译器特殊处理的

  • Integer.MIN_VALUE - 1 会发生溢出,结果是 Integer.MAX_VALUE

  • 这是因为补码表示中,-2147483648没有对应的正数表示


21. 数组长度扩容两倍,里面的数据会怎么变化?

这个问题需要区分不同上下文:

Java中数组:

  • 数组长度一旦创建不可改变

  • "扩容"实际上是创建新数组,将原数据复制过去

  • Arrays.copyOf() 内部使用System.arraycopy()

  • 所有元素都会被复制到新数组

HashMap扩容:

  • 新容量 = oldCapacity * 2

  • 元素重新计算位置:(n-1) & hash

  • 每个元素要么在原位置,要么在原位置+oldCapacity位置

  • 不需要重新计算hash

ArrayList扩容:

  • 新容量 = oldCapacity + oldCapacity/2 (1.5倍)

  • 使用Arrays.copyOf复制元素


22. 链表中删除指定节点时,时间复杂度是多少(仅说单链表场景)?

单链表删除节点:

  • 已知节点引用(不带头节点):

    • 删除当前节点需要找到前驱节点

    • 最坏O(n),因为需要遍历找到前驱

    • 最好O(1),如果删除的是第一个节点

  • 已知前驱节点:

    • 直接通过前驱节点的next指针删除

    • O(1)

  • 双向链表:

    • 无论是否知道前驱,直接操作指针

    • O(1)

注意:

  • 单链表删除操作本身是O(1)

  • 关键是能否直接找到要删除的节点或前驱节点


23. 归并排序的空间复杂度是多少,核心优势是什么?

空间复杂度: O(n)

  • 需要一个与原数组等大的临时数组

  • 递归调用栈深度: O(log n)

  • 总空间: O(n + log n) = O(n)

核心优势:

  1. 稳定性: 归并排序是稳定的排序算法,相等元素的相对顺序不变

  2. 时间复杂度稳定: 最好、最坏、平均都是O(n log n)

    • 不受数据分布影响

    • 适合大规模数据排序

  3. 适合外排序:

    • 适合处理大规模文件的排序

    • 可以逐段读入内存处理

  4. 适合链表:

    • 链表可以通过修改指针实现O(1)空间的归并排序

  5. 并行友好:

    • 归并过程可以并行处理

    • 适合多核并行计算


24. 冒泡排序的时间复杂度是多少,什么场景下效率最低?

时间复杂度:

  • 最好: O(n) (优化后,加了提前终止标志)

  • 平均: O(n²)

  • 最坏: O(n²)

效率最低的场景:

  1. 完全逆序数组: 每一轮都要进行完整比较,交换次数最多

  2. 基本有序但非升序: 如 [2,1,3,4,5,6,7,8,9],需要多轮冒泡

  3. 数据量大的随机数组: O(n²)复杂度在数据量大时性能很差

冒泡排序特点:

  • 简单直观,稳定排序

  • 空间O(1)

  • 适合数据量小、基本有序的场景

  • 实际生产中很少使用


25. 快速排序的最坏时间复杂度是多少,什么场景会触发?

最坏时间复杂度: O(n²)

触发场景:

  1. 数组基本有序:

    • 每次划分极度不平衡

    • 如 [1,2,3,4,5,6,7,8] 升序数组

    • 每次pivot都是最大/最小值

  2. 数组完全逆序:

    • 同理,每次划分一边为空

  3. 大量重复元素:

    • 所有元素相等

    • 每次划分都极度不平衡

优化方案:

  1. 随机选择pivot: 避免特定数据模式的最坏情况

  2. 三数取中: 选择left、mid、right三个数的中值作为pivot

  3. 双pivot快排: JDK 1.7之后对基本类型使用双pivot快排

  4. 小数组使用插入排序: 数据量小时,插入排序更快


26. 什么是内存泄漏?如何检测和避免内存泄漏?

内存泄漏: 程序分配内存后,无法释放已不再使用的内存,导致内存占用持续增长。

常见原因:

  1. 静态集合类: 静态Map/Set持有对象引用

  2. 单例模式: 生命周期与应用一致,持有大量对象

  3. 未关闭的资源: 数据库连接、网络连接、文件流未关闭

  4. 监听器/回调: 注册监听器但未取消注册

  5. ThreadLocal: 未调用remove()导致内存泄漏

  6. 内部类引用外部类: 非静态内部类持有外部类引用

  7. 缓存: 缓存数据无限增长

  8. 字符串常量池: 大量String.intern()调用

检测方法:

  1. VisualVM: 监控内存使用趋势

  2. MAT (Memory Analyzer Tool): 分析堆转储文件

  3. JProfiler: 专业Java性能分析工具

  4. JConsole: JDK自带监控工具

  5. GC日志: 分析GC频率和内存回收情况

避免策略:

  1. 使用完资源及时释放(finally/try-with-resources)

  2. 合理使用WeakHashMap、SoftReference

  3. 避免long-lived对象持有大量引用

  4. 使用ThreadLocal后调用remove()

  5. 定期进行内存分析


27. vector和list的底层数据结构有什么不同?分别适用于什么场景?

vector:

  • 底层是动态数组

  • 连续内存存储,支持随机访问

  • 插入/删除在末尾O(1),在中间O(n)

  • 适合: 随机访问频繁,尾部插入删除为主

list:

  • 底层是双向链表

  • 非连续内存,通过指针连接

  • 任意位置插入删除O(1)

  • 不支持随机访问,访问需要遍历

  • 适合: 频繁的中间插入删除,顺序遍历

C++ STL中的选择:

  • vector: 默认首选,性能更好,内存连续,缓存友好

  • list: 需要频繁在中间插入删除,或迭代器稳定性要求高

Java对应:

  • vector对应ArrayList(但vector是线程安全的)

  • list对应LinkedList


28. 写时拷贝(Copy-On-Write)的应用场景和好处是什么?

Copy-On-Write (COW):

  • 写入时不直接修改原数据

  • 先复制一份,在副本上修改

  • 修改完成后,用副本替换原数据

应用场景:

  1. 操作系统: fork()进程时,父子进程共享内存,写时复制

  2. Java集合: CopyOnWriteArrayList、CopyOnWriteArraySet

  3. 数据库: MVCC实现

  4. Git: 文件系统快照

  5. 函数式编程: 不可变数据结构

好处:

  1. 读多写少场景性能好: 读操作无需加锁,可以并发读

  2. 实现简单: 无需复杂的锁机制

  3. 数据一致性: 写操作不会影响读取

  4. 无锁编程: 提高并发性能

缺点:

  1. 每次写都需要复制,内存开销大

  2. 写操作频繁时性能差

  3. 内存占用高,可能引发GC

适用场景:

  • 读操作远多于写操作

  • 对数据一致性要求不高

  • 缓存、配置等场景


29. 介绍一下你做过的最有挑战性的项目,遇到了什么技术难点,如何解决的?

我做过最有挑战性的项目是一个高并发抽奖与发奖系统,峰值流量集中在活动开始后的前3分钟,核心难点是“高并发下不超卖 + 发奖链路不重复 + 异常可恢复”。

我的处理方法:

  1. 库存一致性: Redis + Lua 原子扣减,避免并发超卖

  2. 发奖异步化: 下单与发奖解耦,通过MQ削峰

  3. 消费幂等: 业务唯一键 + 去重表,防止重复发奖

  4. 补偿机制: 失败消息重试 + 定时对账修复

  5. 可观测性: 增加全链路traceId、失败率告警

结果:

  • 峰值期间接口成功率稳定在99.9%+

  • 核心接口P99由约800ms降至200ms内

  • 重复发奖问题基本消除(通过幂等保障)


30. 请用一句话概括你参与过的核心项目的核心业务场景。

我参与的是一个高并发活动平台,负责“用户实时请求 -> 风控校验 -> 库存扣减 -> 异步发奖 -> 最终一致落库”的核心链路建设与稳定性保障。


31. 项目中你负责的模块如何保证接口的幂等性?

接口幂等性指同一请求多次执行与执行一次的效果相同。

保证幂等性的方案:

  1. 数据库唯一约束:

    • 使用唯一索引防止重复插入

    • 业务单号+唯一索引

  2. 分布式锁:

    • Redis SETNX实现

    • 锁的key=接口+业务参数

    • 锁过期时间防止死锁

  3. 状态机:

    • 订单状态流转:创建→支付→发货→完成

    • 只有正确状态才能执行对应操作

  4. 去重表:

    • 记录已处理的请求ID

    • 处理前先查询是否已处理

  5. Token机制:

    • 前端获取Token,后端验证

    • 验证成功后删除Token


32. 你遇到印象最深的问题是什么?怎么解决的?

我印象最深的问题是一次线上高峰期出现“缓存击穿 + 慢SQL叠加”导致接口雪崩。

处理过程:

  1. 问题现象: 接口RT从毫秒级上升到秒级,超时率持续上升

  2. 排查过程: 先看监控发现DB连接池打满,再查慢日志和Redis命中率

  3. 根因分析: 热点key过期后大量请求直接回源,叠加一条缺索引SQL

  4. 解决方案: 热点key互斥重建 + 过期时间随机化 + SQL加索引 + 限流降级

  5. 复盘结果: 建立上线前压测门禁与缓存预热流程


33. 分享一个你印象最深的 Bug 吧~当时现象是啥?怎么一步步定位到根因的?最后怎么解决的?从中学到了啥?

示例(可直接复述):

  • 现象: 活动开始后,用户偶发看到“重复中奖”

  • 定位步骤:

    1. 查业务日志,发现同一用户同一活动短时间内出现两条成功记录

    2. 查MQ消费日志,发现存在消息重投递

    3. 查数据库,确认缺少业务唯一约束

  • 根因: 消费端“至少一次投递”语义下未做严格幂等

  • 修复:

    1. 增加 (activity_id, user_id, biz_no) 唯一索引

    2. 消费逻辑改为“先幂等校验再执行业务”

    3. 异常重试采用指数退避,避免瞬时放大

  • 经验: 任何异步链路都要按“会重复、会乱序、会失败”设计


34. 你编程主要在什么操作系统下进行的?

主要在Windows和Linux下进行开发:

  • Windows: 开发环境,IDE使用

  • Linux: 服务器环境,测试环境,Docker容器

常用工具:

  • IDE: IntelliJ IDEA, VS Code

  • 终端: Git Bash, WSL, Xshell

  • 版本控制: Git


35. Windos和Linux操作系统的区别是什么?

特性

Windows

Linux

界面

图形化友好

命令行为主

授权

收费闭源

开源免费

稳定性

相对较低

极高

安全性

需要额外防护

相对安全

软件支持

商业软件多

开源软件多

服务器市场

较少

主导

文件系统

NTFS

ext4, xfs

终端

CMD/PowerShell

Bash/Zsh

权限管理

用户组

用户/组+sudo


1.2 并发编程

53. 进程 线程的区别 深入讲一下

进程:

  • 操作系统资源分配的基本单位

  • 拥有独立的地址空间

  • 包含代码、数据、文件、内存等资源

  • 进程间通信需要IPC机制

  • 切换开销大(需要切换页表等)

线程:

  • CPU调度的基本单位

  • 共享进程的地址空间

  • 拥有独立的栈和寄存器

  • 线程间通信简单(共享内存)

  • 切换开销小

进程与线程的关系:

  • 一个进程可以包含多个线程

  • 线程是进程内的执行流

  • 线程共享进程的资源

Java中的线程:

  • Java线程与OS线程一一映射

  • 线程是抢占式调度

  • 线程安全需要自己保证


54. 说说进程和线程的区别,以及它们的通信方式

进程间通信方式:

  1. 管道/命名管道: 父子/兄弟进程间通信

  2. 消息队列: 消息链表,异步通信

  3. 共享内存: 最高效的进程间通信方式

  4. 信号量: 计数器,控制资源访问

  5. 信号: 异步通知机制

  6. 套接字(Socket): 网络通信,也可用于本地

  7. 文件映射: 共享文件区域

线程间通信方式:

  1. 共享变量: 共享进程的内存

  2. wait/notify: Object方法

  3. Lock/Condition: JUC包

  4. volatile: 保证可见性

  5. ThreadLocal: 线程本地变量

  6. 阻塞队列: 生产者-消费者模式

  7. CountDownLatch/CyclicBarrier: 同步工具


55. 说说进程和线程的区别,以及线程间通信的方式有哪些?

(与上题类似,补充线程间通信方式)

线程间通信方式详解:

  1. volatile变量:

    • 保证可见性

    • 适合简单状态标志

  2. synchronized + wait/notify:

    • 最基础的线程通信方式

    • wait()释放锁,notify()唤醒

  3. ReentrantLock + Condition:

    • 更灵活的等待/通知机制

    • 可以精确唤醒指定线程

  4. CountDownLatch:

    • 倒计时器

    • 等待一组线程完成

  5. CyclicBarrier:

    • 栅栏

    • 一组线程相互等待

  6. Semaphore:

    • 信号量

    • 控制资源访问数量

  7. 阻塞队列:

    • BlockingQueue

    • 生产者-消费者模式

  8. CompletableFuture:

    • 异步编程

    • 任务编排


56. 线程之间哪些内存是共享的?

线程共享的内存区域:

  1. : 所有线程共享,存放new的对象

  2. 方法区/元空间: 所有线程共享,存放类信息、静态变量、常量

  3. 字符串常量池: 堆中的一部分

线程私有的内存区域:

  1. 虚拟机栈: 每个线程私有,存放局部变量、操作数栈

  2. 本地方法栈: 与虚拟机栈类似,服务于native方法

  3. 程序计数器: 记录当前线程执行的字节码行号

注意:

  • 栈是线程私有的,不存在线程安全问题

  • 堆是共享的,需要考虑线程安全

  • volatile保证可见性,但不能保证原子性


57. 多线程共享数据出现线程安全问题的核心原因是什么?

核心原因: 多个线程同时访问共享资源,且至少有一个线程在修改资源。

具体分析:

  1. 原子性问题:

    • 复合操作不是原子操作

    • 如 i++ 需要:读→改→写 三步

    • 可能被其他线程打断

  2. 可见性问题:

    • 线程修改后未立即刷新到主内存

    • 其他线程读取到的是旧值

  3. 有序性问题:

    • 指令重排序导致执行顺序改变

    • 破坏多线程协作

产生条件:

  • 多个线程并发

  • 访问同一共享资源

  • 至少有一个线程在修改

解决方案:

  • 原子性: synchronized, Lock, CAS

  • 可见性: volatile, synchronized, Lock

  • 有序性: volatile, synchronized


58. 多线程共享数据时,使用 synchronized 关键字的核心作用是什么?

synchronized的核心作用:

  1. 原子性保证: 保证同一时刻只有一个线程执行同步代码块

  2. 可见性保证: 释放锁前会将工作内存刷新到主内存

  3. 有序性保证: 防止指令重排序

用法:

// 1. 修饰实例方法
public synchronized void method() {}

// 2. 修饰静态方法
public synchronized static void method() {}

// 3. 修饰代码块
synchronized(obj) {}

锁的对象:

  • 实例方法: this对象

  • 静态方法: Class对象

  • 代码块: 指定的对象

底层原理:

  • JVM层面实现

  • 通过monitorenter/monitorexit指令

  • 自动获取和释放锁


59. CAS 是什么?

CAS (Compare And Swap):

  • 比较并交换,是一种无锁算法

  • 包含三个操作数:内存位置V、预期值A、新值B

  • 只有当V的值等于A时,才将V更新为B

Java中的CAS:

  • 位于java.util.concurrent.atomic包

  • 基于Unsafe类的native方法实现

实现原理:

// 伪代码
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

CAS的应用:

  • AtomicInteger、AtomicLong等原子类

  • ConcurrentHashMap的CAS操作

  • AQS中的CAS操作

CAS的优缺点:

  • 优点: 非阻塞,性能高

  • 缺点: ABA问题、自旋开销、只能保证单变量


60. ThreadLocal的原理及在项目中的作用

ThreadLocal原理:

  • 为每个线程提供独立的变量副本

  • 内部通过ThreadLocalMap实现

  • key是ThreadLocal实例,value是存储的值

  • 每个Thread对象都有一个ThreadLocalMap

ThreadLocalMap:

  • 类似HashMap的数据结构

  • Entry继承WeakReference<ThreadLocal<?>>

  • key是弱引用,value是强引用

项目中的作用:

  1. 线程隔离: 每个线程有自己的用户信息

  2. 事务管理: Spring的事务同步器

  3. 日期格式化: SimpleDateFormat线程不安全,用ThreadLocal解决

  4. 链路追踪: MDC日志追踪

使用示例:

// 存储用户信息
ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
userThreadLocal.set(currentUser);
User user = userThreadLocal.get();
userThreadLocal.remove(); // 使用完必须移除

61. ThreadLocalMap 的底层数据结构是什么,为什么要采用这种结构?

底层数据结构:

  • 类似于HashMap的数组+链表/红黑树

  • 数组: Entry[] table

  • 初始化容量: 16

  • 负载因子: 2/3

Entry结构:

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

采用弱引用的原因:

  • key是ThreadLocal的弱引用

  • 当ThreadLocal对象不再被外部引用时,可以被GC回收

  • 避免ThreadLocal对象长期存活导致内存泄漏

  • 防止线程长期存活时ThreadLocalMap不断膨胀

为什么不用强引用:

  • 如果用强引用,ThreadLocal对象无法被GC回收

  • 即使ThreadLocal不再使用,也会占用内存

注意:

  • 虽然key是弱引用,但value是强引用

  • 使用完ThreadLocal后必须调用remove(),否则可能内存泄漏


62. TransmittableThreadLocal 相比 InheritableThreadLocal,核心解决了什么问题?

InheritableThreadLocal的问题:

  • 只能在父子线程间传递

  • 不能在线程池、异步任务等场景传递

  • 子线程创建时复制父线程的值,但之后修改不会同步

TransmittableThreadLocal (TTL):

  • Alibaba开源的库

  • 核心解决:线程池、异步任务场景下的ThreadLocal传递

解决的问题:

  1. 线程池场景: 主线程设置值,线程池中的线程能获取到

  2. CompletableFuture: 异步任务能获取主线程的ThreadLocal

  3. ForkJoinPool: 子任务能获取父任务的ThreadLocal

  4. 消息队列消费者: 消费线程能获取设置的值

原理:

  1. 任务提交时,捕获当前ThreadLocal快照

  2. 任务执行时,恢复快照到工作线程

  3. 任务结束后,清理恢复的ThreadLocal


63. synchronized 和 ReentrantLock 的区别?

特性

synchronized

ReentrantLock

实现

JVM层面

JDK API层面

用法

关键字

需要lock/unlock

公平锁

非公平

可配置

锁超时

不支持

支持tryLock()

中断响应

不支持

支持lockInterruptibly()

条件变量

不支持

多个Condition

释放方式

自动释放

必须手动释放

ReentrantLock优势:

  1. 可中断等待

  2. 可设置公平锁

  3. 可以尝试获取锁

  4. 多个条件变量

  5. 锁投票

synchronized优势:

  1. 语法简洁

  2. JVM自动管理

  3. 性能已优化

选择建议:

  • 简单同步用synchronized

  • 复杂场景用ReentrantLock


64. volatile 的作用?

  1. 保证可见性:

    • 线程修改后立即刷新到主内存

    • 其他线程读取到最新值

  2. 禁止指令重排序:

    • 插入内存屏障

    • 防止重排序导致的问题

  3. 不保证原子性:

    • 如i++这种复合操作无法保证

    • 需要用synchronized或AtomicInteger

使用场景:

  • 状态标志

  • 双重检查锁定

  • 可见性要求高的变量


65. volatile 和 synchronized 的区别?

特性

volatile

synchronized

作用

修饰变量

修饰方法/代码块

原子性

不保证

保证

可见性

保证

保证

有序性

保证(禁重排)

保证

性能

轻量

重量

无锁

有锁

volatile适用场景:

  • 状态标志

  • 双重检查锁定

  • 读写分离场景的写操作

synchronized适用场景:

  • 需要保证原子性的场景

  • 复杂同步逻辑


66. 线程WAITING状态和TIMED_WAITING状态最核心的区别是什么?

WAITING状态:

  • 无限期等待,必须被其他线程唤醒

  • 调用wait()、join()(不带超时)

  • LockSupport.park()

TIMED_WAITING状态:

  • 限时等待,时间到自动返回

  • Thread.sleep()

  • wait(timeout)

  • join(timeout)

  • LockSupport.parkNanos()

  • Condition.await(timeout)

核心区别:

  • WAITING: 等待时间不确定,依赖其他线程通知

  • TIMED_WAITING: 等待时间确定,到时间自动唤醒

实际影响:

  • WAITING状态的线程不会被超时中断

  • TIMED_WAITING可以通过超时自动唤醒


67. 线程池在任务堆积、队列满、拒绝策略触发时的底层执行流程是什么?

线程池执行流程:

提交任务
    ↓
判断核心线程数是否已满?
    ↓ 是 ↓ 否
    ↓   创建核心线程执行任务
    ↓
判断阻塞队列是否已满?
    ↓ 是 ↓ 否
    ↓   进入队列等待
    ↓
判断最大线程数是否已满?
    ↓ 是 ↓ 否
    ↓   创建非核心线程执行任务
    ↓
执行拒绝策略

拒绝策略:

  1. AbortPolicy (默认): 抛RejectedExecutionException

  2. CallerRunsPolicy: 由调用方线程执行

  3. DiscardPolicy: 静默丢弃

  4. DiscardOldestPolicy: 丢弃最老的任务

队列满的场景:

  • 任务提交速度 > 处理速度

  • 队列设置过小

  • 线程处理速度过慢


68. 100个进程,5个并发数的工作,应该怎么设计?

设计思路:

  • 使用线程池控制并发

  • 100个任务,5个并发线程

方案一:线程池+CountDownLatch

ExecutorService executor = Executors.newFixedThreadPool(5);
CountDownLatch latch = new CountDownLatch(100);

for (int i = 0; i < 100; i++) {
    final int taskId = i;
    executor.submit(() -> {
        try {
            process(taskId);
        } finally {
            latch.countDown();
        }
    });
}
latch.await();
executor.shutdown();

方案二:信号量

Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 100; i++) {
    semaphore.acquire();
    executor.submit(() -> {
        try {
            process(i);
        } finally {
            semaphore.release();
        }
    });
}

69. 详细说说父子进程,包括内存地址、共享页表、写时拷贝、子进程回收

父子进程:

  • fork()创建子进程

  • 父子进程地址空间独立

  • 共享父进程的资源

写时拷贝(Copy-On-Write):

  • fork()时不立即复制内存

  • 父子进程共享同一物理页

  • 只有当某进程写入时,才复制该页

  • 节省内存,提高效率

共享页表:

  • 父子进程共享父进程的页表

  • 页表项指向相同的物理页

  • 写入时触发缺页中断,分配新页

进程回收:

  • 父进程需要wait()子进程

  • 防止僵尸进程

  • 僵尸进程: 进程已退出但父进程未回收


70. 你是如何避免获奖数据重复插入的,核心解决思路是什么?

问题场景: 抽奖活动可能因为网络重试、并发导致重复插入

解决方案:

  1. 数据库唯一约束:

    • 对业务单号加唯一索引

    • 重复插入会报错

  2. 分布式锁:

    • Redis SET lock{活动ID}{用户ID} NX EX

    • 获取锁后才执行业务

  3. 前置查询:

    • 插入前先查询是否存在

    • 存在则返回已有结果

  4. 幂等设计:

    • 使用业务单号作为幂等标识

    • 相同单号只处理一次


71. 项目中处理抽奖异步逻辑时,使用的具体异步框架是什么?

常见选择:

  1. ThreadPoolExecutor:

    • 自定义线程池

    • 灵活配置参数

  2. CompletableFuture:

    • Java 8异步编程

    • 支持任务编排

  3. 消息队列:

    • RabbitMQ

    • RocketMQ

    • Kafka

  4. 分布式任务调度:

    • XXL-Job

    • ElasticJob

    • Spring Task

选择依据:

  • 实时性要求

  • 可靠性要求

  • 复杂度考量


1.3 Java 核心

72. Java 多态的实现方式中,方法重写和方法重载最核心的区别是什么?

特性

方法重写(Override)

方法重载(Overload)

发生位置

子类与父类

同一类

参数列表

必须相同

必须不同

返回类型

相同或子类型

无要求

访问修饰符

不能比父类更严格

无要求

编译时决定

核心区别:

  • 重写: 运行时多态,依赖继承关系

  • 重载: 编译时多态,参数列表区分


73. Java 封装特性在你项目中的具体体现是什么(仅说一个核心场景)?

示例:用户模块的封装

  1. 属性私有化: private修饰属性

  2. 提供getter/setter: 控制访问

  3. 业务逻辑封装: 在setter中加入校验逻辑

  4. 对外暴露服务: 通过Service层提供

public class User {
    private String name;
    private int age;

    public void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("年龄不合法");
        }
        this.age = age;
    }
}

74. 你用 Java 创建线程时,继承 Thread 类和实现 Runnable 接口最核心的区别是什么?

特性

Thread

Runnable

Java单继承

占用

不占用

资源共享

困难

容易

线程状态

继承后直接有

需要包装

启动方式

start()

new Thread(Runnable)

核心区别:

  • Runnable是任务,Thread是执行任务的载体

  • Runnable可以实现资源共享

  • Java单继承的限制

最佳实践:

  • 实现Runnable接口

  • 使用线程池


75. Java 中运行时异常(RuntimeException)和受检异常(Checked Exception)最核心的区别是什么?

特性

RuntimeException

CheckedException

处理要求

不强制捕获

必须捕获或声明

继承关系

RuntimeException及其子类

Exception排除RuntimeException

发生时机

运行时

编译时

例子

NullPointerException

IOException

核心区别:

  • RuntimeException: 编程错误,应该通过代码避免

  • CheckedException: 需要外部处理,IO、网络等

最佳实践:

  • 不要过度使用CheckedException

  • 优先使用RuntimeException

  • 在边界进行统一异常处理


76. 方法不是 public

(问题不完整,可能指方法访问修饰符相关)

Java访问修饰符:

  • public: 公开的

  • protected: 同包或子类

  • default(无): 同包可见

  • private: 私有


77. 你是用 workflow 方式搭建的 agent 吗?

(根据项目实际情况回答)

  • 如果使用工作流方式:LangChain、Spring AI等

  • 如果使用纯LLM调用:直接API调用

工作流方式的优点:

  • 流程可控

  • 易于调试

  • 可嵌入业务逻辑


78. 用 Spring AI 写一个 agent 的过程大概是什么样的?

  1. 添加依赖:

    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter</artifactId>
    </dependency>
  2. 配置:

    • 配置API Key

    • 选择模型

  3. 创建ChatClient:

    ChatClient chatClient = ChatClient.builder(chatModel).build();
  4. 构建Prompt:

    • 使用PromptTemplate

    • 定义输入参数

  5. 调用:

    String response = chatClient.prompt()
        .user("问题")
        .call()
        .content();

79. 整个过程完全是大模型自己决策吗?

不是完全自动,通常需要:

  1. Prompt工程: 人工设计提示词

  2. Function Calling: 接入外部工具

  3. 工作流编排: 控制执行流程

  4. 结果校验: 验证输出正确性

典型架构:

  • LLM负责理解和生成

  • 人类负责设计流程和校验


80. 还接触过其他 Agent 开发框架吗?

  1. LangChain:

    • Python为主

    • 丰富的组件生态

  2. LangChain4j:

    • Java版的LangChain

    • 简化LLM集成

  3. LlamaIndex:

    • 专注于RAG

  4. AutoGen:

    • Microsoft开源

    • 多Agent协作


81. JDK 动态代理与 CGLIB 动态代理的底层实现差异是什么?

JDK动态代理:

  • 要求目标类实现接口

  • 基于Java反射机制

  • 运行时生成$Proxy0类

  • 实现相同接口

  • 调用InvocationHandler

CGLIB动态代理:

  • 不需要接口

  • 基于ASM字节码修改

  • 运行时生成目标类的子类

  • 重写方法添加逻辑

  • 使用MethodInterceptor

对比:

特性

JDK代理

CGLIB代理

需要接口

性能

较慢

较快

依赖

JDK自带

第三方库

继承

实现接口

继承类


82. 什么是模板元编程?有什么实际应用?

模板元编程(Template Metaprogramming):

  • 使用模板在编译时生成代码

  • C++的模板特化、偏特化

  • 可以在编译时计算值

Java中的应用:

  • 泛型: 编译时类型擦除

  • 注解处理器: 编译时生成代码

  • Lombok: 使用注解处理器生成代码

C++模板示例:

template<int n>
struct Factorial {
    static const int value = n * Factorial<n-1>::value;
};

template<>
struct Factorial<0> {
    static const int value = 1;
};

83. 说说C++的模板编程,模板的优缺点是什么?

C++模板:

  • 泛型编程的基础

  • 函数模板和类模板

  • 模板特化和偏特化

优点:

  1. 代码复用

  2. 类型安全

  3. 性能高(编译时生成代码)

  4. 灵活性高

缺点:

  1. 编译时间长

  2. 代码膨胀

  3. 编译错误信息难懂

  4. 学习曲线陡峭


二、MySQL 数据库

2.1 索引原理

84. MySQL中的外连接和内连接

内连接(INNER JOIN):

  • 只返回两个表中匹配的行

  • 不匹配的行不返回

外连接(OUTER JOIN):

  • 返回匹配的行,还返回不匹配的行

  • LEFT JOIN: 左表所有行 + 右表匹配行

  • RIGHT JOIN: 右表所有行 + 左表匹配行

  • FULL JOIN: 左右表所有行(MySQL不支持)

语法示例:

-- 内连接
SELECT * FROM A INNER JOIN B ON A.id = B.id;

-- 左外连接
SELECT * FROM A LEFT JOIN B ON A.id = B.id;

85. MySQL中索引的作用和类型有哪些?

索引的作用:

  • 加快数据检索速度

  • 减少IO操作

  • 加速表连接

  • 加速排序和分组

索引类型:

  1. 按数据结构:

    • B+树索引(默认)

    • 哈希索引(Memory引擎)

    • 全文索引(Full-text)

  2. 按物理存储:

    • 聚簇索引(Clustered)

    • 非聚簇索引(Secondary)

  3. 按字段个数:

    • 单列索引

    • 联合索引

  4. 按特性:

    • 主键索引(唯一且非空)

    • 唯一索引

    • 普通索引


86. 数据库有哪些索引类型?B+树索引的原理是什么?

索引类型:

  1. B+树索引: MySQL默认

  2. 哈希索引: 等值查询,范围查询不支持

  3. 全文索引: 文本搜索

  4. 空间索引: GIS数据

B+树原理:

  • 是一种多路平衡查找树

  • 只有叶子节点存储数据

  • 叶子节点之间通过双向链表连接

  • 所有数据都存在叶子节点

为什么是B+树:

  • 减少IO次数(矮胖)

  • 范围查询友好(链表)

  • 查询稳定(所有数据在叶子)


87. MySQL 索引底层是什么结构?

InnoDB引擎:

  • 使用B+树作为索引结构

  • 聚簇索引: 主键索引,叶子节点存储完整数据

  • 非聚簇索引: 二级索引,叶子节点存储主键值

B+树特点:

  • 多路平衡查找树

  • 非叶子节点不存储数据

  • 叶子节点存储数据

  • 叶子节点双向链表


88. B+树的特点是什么?

  1. 多路平衡:

    • 每个节点可以有多个子节点

    • 路径长度相同

  2. 只叶子存储数据:

    • 非叶子节点只存储索引

    • 叶子节点存储完整数据

  3. 叶子节点链表:

    • 叶子节点通过双向链表连接

    • 范围查询高效

  4. 查询稳定:

    • 无论查询哪条数据,路径长度相同

    • IO次数可预测


89. 为什么用 B+ 树而不是 B 树?

B树 vs B+树:

特性

B树

B+树

数据存储

叶子+非叶子

仅叶子

查询稳定性

不稳定

稳定

范围查询

需要遍历树

链表遍历

IO次数

更多

更少

空间利用率

选择B+树的原因:

  • 查询性能稳定

  • 范围查询高效

  • 空间利用率高

  • 更适合磁盘存储


90. SQL 索引的核心作用是解决什么问题?

核心作用: 加快数据检索速度

解决的问题:

  1. 全表扫描: 从O(n)降低到O(log n)

  2. 排序: 避免filesort

  3. 分组: 避免临时表

  4. 连接: 加速多表关联

代价:

  • 占用额外空间

  • 降低写操作性能

  • 需要维护成本


91. 如何检查SQL是否使用了覆盖索引?

覆盖索引:

  • 查询的所有列都在索引中

  • 无需回表查询

检查方法:

EXPLAIN SELECT id, name, age FROM user WHERE name = 'Tom';

关键字段:

  • type: const, ref, range等(不是ALL)

  • Extra: Using index(表示使用了覆盖索引)

  • Extra: Using index condition(可能回表)

示例:

-- 覆盖索引
CREATE INDEX idx_name_age ON user(name, age);
SELECT name, age FROM user WHERE name = 'Tom';

-- 非覆盖索引
SELECT * FROM user WHERE name = 'Tom';

92. MySQL 联合索引的生效规则中,最关键的一点是什么?

最关键点: 最左前缀原则

含义:

  • 联合索引(a, b, c)

  • 可以使用索引的情况: a | a,b | a,b,c

  • 不能使用索引: b | b,c | c

示例:

CREATE INDEX idx_a_b_c ON table(a, b, c);

-- 走索引
WHERE a = 1
WHERE a = 1 AND b = 2
WHERE a = 1 AND b = 2 AND c = 3

-- 不走索引
WHERE b = 2
WHERE b = 2 AND c = 3
WHERE c = 3

注意:

  • 顺序必须正确

  • 遇到范围查询(w>, <, like)会停止匹配


93. MySQL 中聚簇索引和非聚簇索引的核心区别是什么?

聚簇索引:

  • 叶子节点存储完整数据行

  • 每张表只能有一个

  • 通常是主键索引

  • 数据与索引存储在一起

非聚簇索引:

  • 叶子节点存储主键值

  • 每张表可以有多个

  • 二级索引都是非聚簇索引

  • 需要回表查询完整数据

区别对比:

特性

聚簇索引

非聚簇索引

数据存储

叶子节点

主键值

数量

1个

多个

查询

直接返回数据

可能回表

插入顺序

影响物理存储

不影响


94. InnoDB 引擎中,聚簇索引的底层存储结构及查询流程是什么?

存储结构:

  • B+树结构

  • 叶子节点存储完整数据行

  • 主键作为索引键

  • 主键值唯一标识每行

查询流程:

  1. 根据查询条件定位索引

  2. 如果是主键查询,直接从聚簇索引获取数据

  3. 如果是二级索引,先查二级索引获取主键,再用主键查聚簇索引(回表)

InnoDB行格式:

  • Compact行格式

  • 变长字段长度列表

  • NULL值列表

  • 事务ID和回滚指针(隐藏列)

  • 实际数据


95. MySQL 索引下推(ICP)的核心作用是什么?

索引下推(Index Condition Pushdown):

  • 在索引遍历过程中,对索引包含的字段进行过滤

  • 减少回表次数

示例:

CREATE INDEX idx_name_age ON user(name, age);

SELECT * FROM user WHERE name LIKE '张%' AND age = 20;

ICP作用:

  • 不使用ICP: 先按name找到所有记录,再过滤age

  • 使用ICP: 在遍历索引时同时检查age,减少回表

开启方式:

  • MySQL 5.6+默认开启

  • EXPLAIN中查看Using index condition


96. 索引什么时候会失效?

  1. 不满足最左前缀:

    -- 索引 idx(a,b,c)
    WHERE b = 1  -- 失效
  2. 使用函数或计算:

    WHERE LOWER(name) = 'tom'  -- 失效
    WHERE age + 1 = 20  -- 失效
  3. 类型转换:

    WHERE name = 123  -- 隐式转换,失效
  4. 使用LIKE通配符:

    WHERE name LIKE '%张'  -- 失效
    WHERE name LIKE '%张%'  -- 失效
  5. OR连接:

    WHERE a = 1 OR b = 2  -- 如果没有组合索引,失效
  6. 不等于:

    WHERE age != 20  -- 可能失效
  7. IS NULL:

    WHERE name IS NULL  -- 可能失效

97. 你写 SQL 时,如何避免 JOIN 多表后出现笛卡尔积的问题?

笛卡尔积: 两表每行都匹配

避免方法:

  1. 明确连接条件:

    -- 正确
    SELECT * FROM A JOIN B ON A.id = B.a_id;
    
    -- 错误,会产生笛卡尔积
    SELECT * FROM A, B;
  2. 使用INNER JOIN而非CROSS JOIN:

    -- 避免
    SELECT * FROM A CROSS JOIN B;
  3. 先过滤再连接:

    SELECT * FROM
        (SELECT * FROM A WHERE ...) a
    JOIN
        (SELECT * FROM B WHERE ...) b
    ON a.id = b.a_id;
  4. 检查数据关联:

    • 确认关联字段是否正确

    • 确认是否有一对多的关系


98. MySQL 中左连接(LEFT JOIN)查询时,ON 和 WHERE 条件的核心区别是什么?

ON条件:

  • 控制如何连接两个表

  • 在生成连接结果时起作用

  • 左连接会保留左表所有行

WHERE条件:

  • 对连接后的结果进行过滤

  • 在连接完成后起作用

  • 会影响最终结果集

示例:

-- ON: 连接条件
SELECT * FROM A LEFT JOIN B ON A.id = B.id AND B.status = 1;
-- 即使B.status != 1,A的数据也会保留

-- WHERE: 过滤条件
SELECT * FROM A LEFT JOIN B ON A.id = B.id WHERE B.status = 1;
-- B.status != 1的行会被过滤掉

2.2 事务与隔离级别

99. 详细说说MySQL的事务机制

事务特性(ACID):

  1. 原子性(Atomicity):

    • 事务是最小执行单位

    • 要么全部成功,要么全部失败

    • 通过undo log实现

  2. 一致性(Consistency):

    • 事务执行前后,数据库状态一致

    • 通过其他特性保证

  3. 隔离性(Isolation):

    • 并发事务互不干扰

    • 通过锁和MVCC实现

  4. 持久性(Durability):

    • 事务提交后,修改永久保存

    • 通过redo log实现

事务控制:

START TRANSACTION;
-- SQL语句
COMMIT; -- 提交
ROLLBACK; -- 回滚

100. 数据库事务的隔离级别有哪些?分别解决什么问题?

四个隔离级别:

  1. READ UNCOMMITTED (读未提交):

    • 问题: 脏读、不可重复读、幻读

    • 解决: 无

  2. READ COMMITTED (读已提交):

    • 解决脏读

    • 问题: 不可重复读、幻读

  3. REPEATABLE READ (可重复读):

    • 解决脏读、不可重复读

    • 问题: 幻读(InnoDB通过MVCC解决)

  4. SERIALIZABLE (串行化):

    • 解决所有问题

    • 性能最差

解决的问题:

  • 脏读: 读取到其他事务未提交的数据

  • 不可重复读: 同一事务两次读取结果不同

  • 幻读: 同一事务两次查询结果数量不同


101. 事务的隔离级别有哪些?

MySQL常见四种隔离级别:

  1. READ UNCOMMITTED(读未提交)

    • 可能出现:脏读、不可重复读、幻读

    • 并发最高,一般不用于生产核心交易

  2. READ COMMITTED(读已提交)

    • 解决脏读

    • 仍可能出现不可重复读、幻读

    • Oracle默认级别

  3. REPEATABLE READ(可重复读)

    • 解决脏读、不可重复读

    • InnoDB默认级别,配合MVCC与间隙锁可抑制幻读

  4. SERIALIZABLE(串行化)

    • 最强隔离,基本避免并发异常

    • 吞吐最低,适合极少数强一致关键场景

生产建议:

  • 大多数OLTP业务使用 REPEATABLE READREAD COMMITTED

  • 不要盲目升到 SERIALIZABLE,会显著降低吞吐


102. 幻读是什么?如何解决?

幻读:

  • 事务A读取到了事务B提交的新增数据

  • 两次查询结果集数量不同

示例:

-- T1: SELECT * FROM user WHERE age > 10; -- 0条
-- T2: INSERT INTO user VALUES(1, 'Tom', 20); COMMIT;
-- T1: SELECT * FROM user WHERE age > 10; -- 1条(幻读)

解决方案:

  1. SERIALIZABLE:

    • 串行化,最严格

  2. 可重复读 + Gap锁:

    • InnoDB的Next-Key锁

    • 锁住记录之间的间隙

    • 防止插入新记录

  3. MVCC:

    • 读使用快照

    • 解决读幻读

    • 写仍需锁


103. MySQL 中 MVCC 机制的底层实现原理是什么?

MVCC (Multi-Version Concurrency Control):

  • 多版本并发控制

  • 同一时刻不同事务看到不同版本的数据

核心组成:

  1. 隐藏列:

    • trx_id: 事务ID

    • roll_pointer: 回滚指针

    • row_id: 行ID(非必须)

  2. undo log:

    • 记录数据的历史版本

    • 形成版本链

  3. read view:

    • 活跃事务ID列表

    • min_trx_id、max_trx_id

查询流程:

  1. 获取read view

  2. 查看数据trx_id

  3. 根据可见性规则判断是否可见

  4. 不可见则通过undo log查找历史版本

快照读 vs 当前读:

  • 快照读: SELECT(MVCC)

  • 当前读: SELECT...FOR UPDATE, INSERT, UPDATE, DELETE(加锁)


104. InnoDB 事务原子性是依靠什么机制保证的?

undo log:

  • 记录修改前的数据

  • 事务回滚时恢复数据

工作流程:

  1. 事务执行时,先记录undo log

  2. 修改数据

  3. 如果需要回滚,根据undo log恢复

  4. 事务提交后,undo log可能用于其他事务的MVCC

undo log类型:

  • Insert undo log: 插入操作

  • Update undo log: 更新删除操作


2.3 分布式事务

105. 分布式场景下,如何解决 MySQL 事务的分布式一致性问题?

方案对比:

  1. 2PC (两阶段提交):

    • 准备阶段

    • 提交阶段

    • 缺点: 协调者单点、同步阻塞

  2. 3PC (三阶段提交):

    • CanCommit

    • PreCommit

    • DoCommit

    • 改善: 添加超时、预提交

  3. TCC (Try-Confirm-Cancel):

    • Try: 预留资源

    • Confirm: 确认执行

    • Cancel: 取消预留

    • 优点: 性能好

    • 缺点: 业务侵入性大

  4. Seata AT模式:

    • 自动生成回滚SQL

    • 对业务侵入小

  5. 消息事务:

    • 本地消息表

    • 消息队列


106. 2PC 分布式协议最大的缺陷是什么?

2PC (Two-Phase Commit):

阶段一:准备阶段

  • 协调者向所有参与者发送Prepare请求

  • 参与者执行事务但不提交

  • 返回成功或失败

阶段二:提交阶段

  • 全部成功:发送Commit

  • 有失败:发送Rollback

最大缺陷:

  1. 同步阻塞:

    • 所有参与者在等待期间阻塞

    • 其他事务无法执行

  2. 协调者单点:

    • 协调者故障会导致阻塞

    • 可能导致数据不一致

  3. 数据不一致:

    • 部分参与者收到Commit,部分没收到

    • 导致数据不一致

  4. 无法解决:

    • 协调者发出Commit后宕机

    • 参与者无法得知最终结果


107. 2PC 的原理是什么?有什么缺点?

2PC(两阶段提交)原理:

  1. Prepare阶段

    • 协调者向参与者发送 prepare

    • 参与者执行本地事务并写预提交日志,不真正提交

    • 返回“可提交/不可提交”

  2. Commit/Rollback阶段

    • 若全部参与者返回可提交,协调者广播 commit

    • 若任一失败,广播 rollback

缺点:

  1. 同步阻塞:参与者在等待决议期间锁资源

  2. 协调者单点:协调者故障会导致参与者长时间不确定状态

  3. 网络分区风险:可能出现部分提交、部分回滚

  4. 恢复复杂:异常场景下需要额外日志与人工干预


108. 3PC 相比 2PC 有什么改进?

3PC (Three-Phase Commit):

阶段:

  1. CanCommit: 协调者询问是否可以提交

  2. PreCommit: 协调者发送预提交,参与者执行

  3. DoCommit: 协调者发送真正提交

改进点:

  1. 增加超时机制:

    • 参与者等待PreCommit超时,自动提交

    • 减少阻塞时间

  2. 两阶段拆分:

    • 将提交过程拆分为预提交和提交

    • 减少数据不一致风险

  3. 更好的故障恢复:

    • 参与者可以自行判断状态

仍存在的问题:

  • 协调者单点未完全解决

  • 网络分区问题仍存在

  • 性能开销大


109. TCC 模式的原理是什么?

TCC (Try-Confirm-Cancel):

三个阶段:

  1. Try (预留):

    • 资源预留

    • 冻结资源

    • 记录预操作

  2. Confirm (确认):

    • 确认执行

    • 真正消耗资源

    • 预留转为实际

  3. Cancel (取消):

    • 取消预留

    • 释放资源

示例:转账:

  • Try: 冻结源账户金额

  • Confirm: 从源账户扣款,加载目标账户

  • Cancel: 解冻源账户金额

特点:

  • 需要业务代码实现

  • 性能好

  • 侵入性大


110. Seata AT 模式是什么?

Seata AT (Automatic Transaction):

工作原理:

  1. 解析SQL: 分析SQL类型和表结构

  2. 生成回滚SQL: 自动生成反向SQL

  3. 执行业务SQL: 执行正向SQL

  4. 保存回滚日志: 保存到undo_log表

  5. 提交事务: 提交本地事务

特点:

  • 对业务侵入小

  • 自动生成回滚SQL

  • 支持高并发

  • 需要undo_log表

适用场景:

  • 微服务分布式事务

  • 数据一致性要求高


2.4 SQL 优化

111. 慢SQL如何定位?

  1. 开启慢查询日志:

    slow_query_log = 1
    long_query_time = 1
    slow_query_log_file = /var/log/mysql/slow.log
  2. 查看慢查询日志:

    • 使用mysqldumpslow分析

    • mysqldumpslow -t 10 slow.log

  3. EXPLAIN分析:

    EXPLAIN SELECT * FROM user WHERE id = 1;
  4. SHOW PROCESSLIST:

    • 查看当前执行的SQL

  5. Performance Schema:

    • MySQL 5.6+性能分析

  6. 监控工具:

    • Prometheus + Grafana

    • MySQL Exporter


112. MySQL 慢 SQL 排查处理优化

排查步骤:

  1. 开启慢查询

  2. EXPLAIN分析

    • type: 访问类型

    • key: 使用索引

    • rows: 扫描行数

    • Extra: 额外信息

  3. 检查索引

    • 是否命中索引

    • 是否覆盖索引

  4. 检查数据量

    • 是否大表查询

  5. 检查执行计划

    • 是否全表扫描

优化方案:

  • 加索引

  • 优化SQL结构

  • 减少返回字段

  • 分页优化


113. MySQL 执行一条 SQL 的过程?

  1. 连接器:

    • 连接管理

    • 权限验证

  2. 查询缓存:

    • 命中直接返回(MySQL 8.0移除)

  3. 解析器:

    • 词法分析

    • 语法分析

    • 生成AST

  4. 预处理器:

    • 检查表、字段存在性

    • 权限检查

  5. 优化器:

    • 生成执行计划

    • 选择索引

    • 确定连接顺序

  6. 执行器:

    • 调用存储引擎

    • 返回结果

  7. 存储引擎:

    • InnoDB执行具体操作


114. 那么多索引 MySQL 怎么选?

优化器选择索引的逻辑:

  1. 统计信息:

    • 表的cardinality

    • 索引基数

  2. 成本分析:

    • IO成本

    • CPU成本

  3. 规则判断:

    • 最左前缀

    • 索引下推

  4. 强制索引:

    • FORCE INDEX

影响因素:

  • 索引区分度

  • 索引长度

  • 统计信息准确性

  • 查询条件


115. 如何分析一条慢 SQL?

  1. EXPLAIN分析:

    EXPLAIN FORMAT=JSON SELECT ...
  2. 关键字段:

    • type: 访问类型(最好const-ref-range)

    • possible_keys: 可用索引

    • key: 实际使用索引

    • rows: 扫描行数

    • Extra: Using filesort/Using temporary

  3. SHOW PROFILE:

    SET profiling = 1;
    SELECT ...;
    SHOW PROFILES;
    SHOW PROFILE FOR QUERY 1;
  4. 慢查询日志:

    • 找到执行时间长的SQL


116. EXPLAIN 主要看哪些字段?

字段

含义

id

查询序号

select_type

查询类型

table

表名

type

访问类型

possible_keys

可用索引

key

实际使用索引

key_len

索引长度

ref

索引列比较的值

rows

估算扫描行数

filtered

过滤比例

Extra

额外信息

重点关注:

  • type: 最好const/ref/range,避免ALL

  • key: 是否使用索引

  • rows: 扫描行数,越少越好

  • Extra: 避免Using filesort/temporary


117. MySQL 深度分页优化的核心方案是什么?

问题: OFFSET很大时性能差

方案:

  1. 子查询优化:

    SELECT * FROM orders
    WHERE id > (SELECT id FROM orders LIMIT 100000, 1)
    LIMIT 10;
  2. 延迟关联:

    SELECT o.* FROM orders o
    INNER JOIN (SELECT id FROM orders LIMIT 100000, 10) t
    ON o.id = t.id;
  3. 游标分页:

    • 使用上一页最后一条的ID

    • WHERE id > lastId LIMIT 10

  4. 分库分表:

    • 按时间或ID分表

    • 减少单表数据量

  5. ES搜索:

    • 大数据量使用ElasticSearch


118. 慢 SQL 的底层成因(除索引问题外)有哪些,如何从数据库内核层面优化?

其他原因:

  1. 锁等待:

    • 行锁、表锁

    • 解决:减少锁范围,优化事务

  2. 连接数:

    • 连接数过多

    • 解决:使用连接池

  3. 服务器资源:

    • CPU、IO瓶颈

    • 解决:升级硬件

  4. 配置问题:

    • 缓冲区大小

    • 解决:调优参数

  5. 数据量:

    • 单表数据量太大

    • 解决:分库分表

内核优化:

  • 调整buffer pool

  • 优化redo log配置

  • 调整thread pool


119. 你实际做过SQL优化的落地实现吗?具体怎么做的?

做过,典型案例是订单查询接口在大促时P99超过2秒。

落地步骤:

  1. 定位: 慢查询日志 + APM定位到一条多条件分页SQL

  2. 分析: EXPLAIN 发现 type=ALL,并出现 Using filesort

  3. 优化:

    • 新增联合索引(按过滤条件与排序字段顺序设计)

    • SELECT * 改为按需字段

    • 深分页改成“游标分页”

  4. 验证: 线上影子流量回放,确认执行计划稳定命中索引

  5. 结果: P99从2s+降到200ms左右,DB CPU明显下降

  6. 固化: 增加SQL评审规范(禁止无索引大表分页)


120. 多表联合查询(比如 LEFT JOIN)变慢了,你一般会从哪些地方入手排查和优化?有实际处理过的例子吗?

排查方向:

  1. 检查索引:

    • 关联字段是否都有索引

    • 是否有覆盖索引

  2. 检查数据量:

    • 是否有大表JOIN

    • 数据分布情况

  3. 检查SQL写法:

    • 避免笛卡尔积

    • 筛选条件提前

  4. 检查执行计划:

    • 是否有嵌套循环

    • 是否需要小表驱动

优化方案:

  • 增加索引

  • 优化JOIN顺序

  • 分页优化

  • 减少返回字段


2.5 数据库锁与运维

121. 行锁和表锁的区别?

行锁:

  • 锁定行记录

  • InnoDB支持

  • 粒度细,并发高

  • 开销大

表锁:

  • 锁定整张表

  • MyISAM/InnoDB支持

  • 粒度粗,并发低

  • 开销小

加锁方式:

-- 行锁
SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE;
SELECT * FROM user WHERE id = 1 FOR UPDATE;

-- 表锁
LOCK TABLES user READ;
LOCK TABLES user WRITE;

122. 乐观锁和悲观锁的区别?

悲观锁:

  • 假设会发生冲突

  • 先获取锁再操作

  • 实现:SELECT...FOR UPDATE

  • 适合写多场景

乐观锁:

  • 假设不会冲突

  • 先操作,验证冲突再重试

  • 实现:版本号/CAS

  • 适合读多写少场景

实现示例:

-- 悲观锁
SELECT * FROM user WHERE id = 1 FOR UPDATE;

-- 乐观锁(版本号)
UPDATE user SET balance = balance - 10, version = version + 1
WHERE id = 1 AND version = 1;

123. 给几十万条数据的表新增字段时,如何避免锁表影响业务?

方案:

  1. 在线DDL:

    • MySQL 5.6+支持

    • ALGORITHM=INSTANT

  2. pt-online-schema-change:

    • Percona工具

    • 创建新表,复制数据,替换

  3. ghost:

    • GitHub工具

    • 类似pt-osc

  4. 业务低峰期:

    • 选择业务低峰期执行

  5. 分批处理:

    • 小批量添加

最佳实践:

  • 评估影响范围

  • 提前备份

  • 监控执行过程


124. MySQL中如何创建、查询、修改和删除表?

-- 创建表
CREATE TABLE user (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    age INT
);

-- 查询表结构
DESC user;
SHOW CREATE TABLE user;

-- 修改表
ALTER TABLE user ADD COLUMN email VARCHAR(100);
ALTER TABLE user MODIFY COLUMN name VARCHAR(100);
ALTER TABLE user DROP COLUMN email;

-- 删除表
DROP TABLE user;

三、Redis

3.1 数据结构与基础

126. Redis的内存淘汰机制包含哪些策略?

Redis内存淘汰策略:

  1. noeviction (默认):

    • 不删除,返回错误

  2. volatile-lru:

    • 删除设置了过期时间的键,LRU算法

  3. allkeys-lru:

    • 所有键,LRU算法

  4. volatile-ttl:

    • 删除TTL最小的键

  5. volatile-random:

    • 随机删除设置了过期时间的键

  6. allkeys-random:

    • 随机删除所有键

  7. volatile-lfu:

    • 删除使用频率最低的键(Redis 4.0+)

  8. allkeys-lfu:

    • 删除所有键中使用频率最低的(Redis 4.0+)


127. Redis 有哪些数据结构?

基本数据类型:

  1. String: 字符串,最大512MB

  2. List: 双向链表

  3. Set: 无序集合

  4. ZSet: 有序集合

  5. Hash: 哈希表

高级数据类型:

  1. Bitmap: 位图

  2. HyperLogLog: 基数统计

  3. Stream: 消息队列

  4. Geospatial: 地理位置


128. String 类型的底层实现是什么?

RedisObject:

  • type: string (0)

  • encoding: 编码类型

  • ptr: 指向实际数据

编码类型:

  1. int: 整数,用long类型存储

  2. embstr: 短字符串,一次分配

  3. raw: 长字符串,多次分配

embstr vs raw:

  • embstr: ≤44字节

  • raw: >44字节


129. Hash 底层是怎么实现的?

两种编码:

  1. ziplist (压缩列表):

    • 元素少时使用

    • 节省内存

    • 元素: <512个,value<64字节

  2. hashtable (哈希表):

    • 元素多时转换

    • O(1)复杂度

转换条件:

  • hash-max-ziplist-entries: 512

  • hash-max-ziplist-value: 64字节


130. Redis 的 String 类型在业务中最常用的一个场景是什么?

  1. 缓存:

    • 缓存数据库查询结果

    • 序列化JSON存储

  2. 分布式锁:

    • SETNX命令

    • SET key value NX EX seconds

  3. Session存储:

    • 用户Session信息

  4. 计数器:

    • INCR、DECR原子操作

  5. 限流:

    • 滑动窗口


3.2 持久化机制

131. Redis的持久化机制有哪些?分别怎么实现?

RDB (Redis Database):

  • 定时生成数据快照

  • save/bgsave命令

  • 触发机制:时间/命令/配置

AOF (Append Only File):

  • 记录所有写命令

  • appendonly yes开启

  • 三种刷盘策略

混合持久化:

  • Redis 4.0+

  • RDB+AOF结合


132. RDB(快照持久化)

实现方式:

  1. save: 阻塞主线程

  2. bgsave: fork子进程

触发机制:

  • 手动: save/bgsave

  • 自动: 配置save m n

优点:

  • 文件紧凑,适合备份

  • 恢复快

缺点:

  • 可能有数据丢失

  • fork子进程开销大


133. AOF(追加日志持久化)

三种刷盘策略:

  1. always: 每次写都刷盘

  2. everysec: 每秒刷盘(默认)

  3. no: 操作系统决定

重写机制:

  • 压缩AOF文件

  • bgrewriteaof命令

优点:

  • 数据丢失少

  • 可读性好

缺点:

  • 文件比RDB大

  • 恢复慢


134. RDB 和 AOF 的区别?

特性

RDB

AOF

数据完整性

可能丢失数据

相对完整

文件大小

恢复速度

IO类型

一次性

持续IO

性能

fork开销大

建议:

  • 同时开启

  • 混合持久化


135. 混合持久化是什么意思?

Redis 4.0+支持:

  1. AOF重写时,使用RDB格式写入开头

  2. 后续命令用AOF格式追加

  3. 恢复时先加载RDB部分

优势:

  • 结合RDB和AOF优点

  • 恢复更快

  • 数据更完整


3.3 缓存问题

136. 缓存穿透是什么?如何解决?

定义:

  • 查询一个不存在的数据

  • 数据库查不到,缓存也没有

  • 大量请求直接打到数据库

解决方案:

  1. 布隆过滤器:

    • 判断key是否存在

    • 存在才查缓存和DB

  2. 空值缓存:

    • 查询结果为空也缓存

    • 设置较短过期时间

  3. 参数校验:

    • 过滤非法请求

  4. 限流:

    • 防止恶意攻击


137. 缓存击穿是什么?如何解决?

定义:

  • 热点key过期瞬间

  • 大量请求同时穿透到数据库

解决方案:

  1. 互斥锁:

    • 只允许一个请求查DB

    • 其他等待缓存结果

  2. 逻辑过期:

    • 不设置过期时间

    • 用后台异步刷新

  3. 永不过期:

    • 热点数据不设过期


138. 缓存雪崩是什么?如何解决?

定义:

  • 大量缓存同时失效

  • 请求全部打到数据库

解决方案:

  1. 随机过期时间:

    • 过期时间加随机值

  2. 多级缓存:

    • 本地缓存 + Redis

  3. 高可用:

    • Redis集群

  4. 限流降级:

    • 保护数据库

  5. 预热:

    • 提前加载数据


3.4 分布式锁

139. 分布式锁的可重入性如何实现

可重入锁:

  • 同一线程可以多次获取

  • 内部维护计数器

Redis实现:

  • value存储线程标识+计数器

  • 判断是否是当前线程

  • Lua脚本保证原子性

示例:

if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('incr', KEYS[1])
else
    return redis.call('set', KEYS[1], 1)
end

140. 分布式锁原理大概是什么?

核心思想:

  • 多个客户端竞争同一把锁

  • 只有获取到锁的才能执行

SET命令:

SET lock_key unique_value NX PX 30000
  • NX: 不存在才设置

  • PX: 过期时间

释放锁:

  • 判断value后删除

  • 用Lua脚本保证原子性


141. SETNX 的功能是什么?

SETNX (SET if Not eXists):

  • 当key不存在时设置值

  • 当key存在时不做操作

  • 返回1表示成功,0表示失败

用途:

  • 分布式锁

  • 防止重复


142. 加锁后进程异常退出,锁泄露怎么办?

问题:

  • 获取锁后进程崩溃

  • 锁未释放,其他进程无法获取

解决方案:

  1. 设置过期时间:

    • PX参数设置过期时间

    • 即使未主动释放,过期自动释放

  2. Watchdog:

    • Redisson实现

    • 自动续期

  3. 死锁检测:

    • 监控锁状态

    • 定期清理


143. Redisson 分布式锁的实现原理?

核心机制:

  1. SETNX + Lua脚本:

    • 获取锁

    • 释放锁

  2. Watchdog (看门狗):

    • 自动续期

    • 默认10秒检查一次

    • 延长锁过期时间

  3. 可重入:

    • 维护重入计数

  4. 公平锁:

    • 等待队列

  5. RedLock:

    • 多个Redis实例


144. Redis 连接池的作用?

  1. 复用连接:

    • 避免频繁创建销毁连接

  2. 提高性能:

    • 减少TCP握手开销

  3. 资源控制:

    • 控制最大连接数

  4. 故障恢复:

    • 连接异常自动重连


145. 分布式锁相关:set nx ex、watch dog、redlock

  1. set nx ex:

    • 原子性加锁

    • 带过期时间

  2. Watchdog:

    • 自动续期机制

    • Redisson实现

  3. RedLock:

    • 多个Redis节点

    • 多数节点成功才算获取成功

    • 提高可靠性


146. 如何设计一个分布式锁?需要考虑哪些问题?

需要考虑:

  1. 互斥性:

    • 同一时间只能一个客户端获取

  2. 死锁:

    • 客户端异常,锁无法释放

    • 解决方案:过期时间

  3. 可重入:

    • 同一线程可多次获取

  4. 高性能:

    • 获取/释放锁快

  5. 高可用:

    • Redis集群部署

  6. 公平性:

    • 按请求顺序获取锁


147. 先执行set nx再执行set ex会存在什么问题

问题:

  • 两步操作不原子

  • 中间可能崩溃

  • 导致锁永不过期

正确做法:

SET lock_key value NX PX 30000

或使用Lua脚本:

if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then
    redis.call('pexpire', KEYS[1], ARGV[2])
    return 1
else
    return 0
end

3.5 集群与高可用

148. Redis 主从复制原理?

流程:

  1. 从节点连接主节点

  2. 从节点发送SYNC/PSYNC

  3. 主节点fork子进程

  4. 子进程发送RDB

  5. 后续发送增量命令

复制类型:

  • 全量复制

  • 增量复制

原理:

  • 主节点写操作记录到repl_backlog

  • 从节点同步


149. 哨兵模式的作用?

作用:

  1. 监控:

    • 监控主从节点健康状态

  2. 自动故障转移:

    • 主节点下线后

    • 选举从节点为新主节点

  3. 通知:

    • 客户端通知

核心功能:

  • Sentinel本身是高可用的

  • 多个Sentinel组成集群


150. Redis Cluster 的原理?

槽位分配:

  • 16384个槽

  • 每个节点负责一部分

数据路由:

  • 计算key的槽位

  • 跳转到对应节点

故障转移:

  • 节点间PING/PONG

  • 故障节点下线后重新分配

特点:

  • 去中心化

  • 数据分片

  • 高可用


151. Redis 哨兵和 Cluster 集群,你觉得它们核心区别在哪?平时项目里怎么选?(比如数据量大小、高可用要求)

哨兵模式:

  • 主从复制

  • 一个主节点,多个从节点

  • 数据保存在主节点

  • 适合数据量较小

Cluster集群:

  • 数据分片

  • 多个主节点

  • 数据分布在不同节点

  • 适合大数据量

选择建议:

  • < 10万QPS: 哨兵

10万QPS: Cluster


3.6 实际应用

152. 缓存预热:Redis预减库存(DECR stock + Lua脚本原子操作)

预减库存方案:

-- Lua脚本
local stock = redis.call('GET', KEYS[1])
if stock == false or tonumber(stock) < tonumber(ARGV[1]) then
    return -1
end
return redis.call('DECRBY', KEYS[1], ARGV[1])

优势:

  • 原子操作

  • 避免超卖

  • 高并发性能好


153. Redis 怎么保证多扣/多增的可靠性?

方案:

  1. Lua脚本:

    • 原子操作

  2. 事务:

    • MULTI/EXEC

  3. 乐观锁:

    • WATCH

  4. 限流:

    • 控制并发


154. Redis 为什么这么快?

  1. 纯内存操作:

    • 内存访问纳秒级

  2. 单线程:

    • 避免上下文切换

    • 无锁开销

  3. IO多路复用:

    • epoll机制

    • 高并发处理

  4. 高效数据结构:

    • 简单动态字符串

    • 压缩列表

    • 跳跃表

  5. 协议简单:

    • RESP协议


155. Redis 淘汰策略?

当Redis内存达到 maxmemory 后,会按配置策略淘汰key:

  1. noeviction:不淘汰,写请求报错(默认)

  2. allkeys-lru:从所有key中淘汰最近最少使用

  3. allkeys-lfu:从所有key中淘汰访问频率最低

  4. allkeys-random:随机淘汰所有key

  5. volatile-lru:仅在设置过期时间的key里做LRU淘汰

  6. volatile-lfu:仅在过期key里做LFU淘汰

  7. volatile-random:仅在过期key里随机淘汰

  8. volatile-ttl:优先淘汰TTL更小(更快过期)的key

选型建议:

  • 通用缓存优先 allkeys-lruallkeys-lfu

  • 若必须保留持久key,可考虑 volatile-*


156. Redis 过期 key 删除策略?

三种策略:

  1. 惰性删除:

    • 访问时检查

    • CPU友好,内存不友好

  2. 定期删除:

    • 定时检查

    • 平衡方案

  3. 定时删除:

    • 设置过期时间同时删除

    • CPU不友好

Redis实现:

  • 惰性删除 + 定期删除


四、计算机网络

4.1 TCP/UDP

157. TCP是如何保证可靠传输的?

  1. 校验和:

    • 校验数据完整性

  2. 序列号:

    • 标识数据顺序

  3. 确认机制(ACK):

    • 确认收到数据

  4. 重传机制:

    • 超时重传

    • 快速重传

  5. 流量控制:

    • 滑动窗口

    • 避免发送过快

  6. 拥塞控制:

    • 慢启动

    • 拥塞避免

    • 快重传

    • 快恢复


158. TCP 与 UDP 的区别?

特性

TCP

UDP

连接性

面向连接

无连接

可靠性

可靠

不可靠

传输方式

字节流

数据报

拥塞控制

速度

头部

20-60字节

8字节

场景

文件、邮件

视频、语音


159. TCP 为什么是可靠的?

TCP可靠性来自“检测 + 纠错 + 流控 + 拥塞控制”的组合机制:

  1. 序列号机制:保证数据有序重组

  2. ACK确认:发送方确认接收方是否收到

  3. 超时重传:超时未确认则重传丢失报文

  4. 快速重传:收到重复ACK可提前重传

  5. 校验和:检测数据损坏

  6. 滑动窗口:按接收能力控制发送速率

  7. 拥塞控制:根据网络状态动态调整发送窗口

因此TCP不是“永不丢包”,而是“即使丢包也能恢复并保证应用层看到可靠字节流”。


160. 三次握手的过程?

流程:

客户端                  	服务端
  |                     	  |
  |------ SYN=1, seq=x ------>|
  |                      	  |
  |<--- SYN=1, ACK=1, seq=y, ack=x+1 ----|
  |                     	  |
  |------ ACK=1, seq=x+1, ack=y+1 ----->|
  |                    	      |
         (连接建立)

目的:

  1. 确认双方接收能力

  2. 同步序列号


161. TCP 三次握手和四次挥手为什么是这样设计的?

三次握手原因:

  • 确认双方序列号

  • 避免历史连接

  • 确保连接建立

四次挥手原因:

  • 双向连接需要双向关闭

  • 确认数据发送完成

  • 每方都需要FIN和ACK


162. 四次挥手的过程?

客户端                  服务端
  |                       |
  |------ FIN=1, seq=u ------>|
  |                       |
  |<--- ACK=1, ack=u+1 ----|
  |                       |
  |            (客户端到服务端关闭)
  |                       |
  |<--- FIN=1, seq=v ----|
  |                       |
  |------ ACK=1, ack=v+1 ----->|
  |                       |
         (连接关闭)

TIME_WAIT:

  • 等待2MSL

  • 确保最后ACK到达


163. TCP 拥塞控制流程是什么?

四个算法:

  1. 慢启动:

    • cwnd从1开始

    • 指数增长

  2. 拥塞避免:

    • cwnd达到阈值后

    • 线性增长

  3. 快重传:

    • 连续3个重复ACK

    • 立即重传

  4. 快恢复:

    • cwnd减半

    • 拥塞避免

状态:

  • 慢开始

  • 拥塞避免

  • 超时:cwnd=1

  • 快恢复:cwnd=threshold/2


4.2 HTTP/HTTPS

164. HTTP协议的工作原理是什么?常见状态码有哪些?

HTTP工作原理:

  1. 客户端建立TCP连接

  2. 发送HTTP请求

  3. 服务器处理请求

  4. 返回HTTP响应

  5. 关闭连接

状态码:

  • 1xx: 信息响应

  • 2xx: 成功(200, 201, 204)

  • 3xx: 重定向(301, 302, 304)

  • 4xx: 客户端错误(400, 401, 403, 404)

  • 5xx: 服务端错误(500, 502, 503)


165. HTTP1.0、HTTP1.1、HTTP2.0 的区别?

特性

1.0

1.1

2.0

长连接

不支持

支持

支持

管道化

不支持

支持

支持

多路复用

不支持

不支持

支持

头部压缩

不支持

不支持

HPACK

服务器推送

不支持

不支持

支持

二进制

文本

文本

二进制


166. HTTP 和 HTTPS 的区别?

特性

HTTP

HTTPS

安全性

明文

加密

端口

80

443

证书

不需要

需要SSL证书

性能

SEO

-

加分


167. HTTPS的握手过程是怎样的?为什么需要三个随机数?

握手过程:

  1. 客户端发送ClientHello

  2. 服务端发送ServerHello + 证书

  3. 客户端验证证书 + 发送预主密钥

  4. 服务端确认

  5. 生成会话密钥

三个随机数:

  1. client_random

  2. server_random

  3. pre_master_secret

原因:

  • 增强随机性

  • 防止重放攻击

  • 提高安全性


168. HTTPS 的原理?

HTTPS = HTTP + TLS/SSL。

核心原理:

  1. 身份认证:通过CA证书证明服务端身份

  2. 密钥协商:握手阶段协商会话密钥

  3. 对称加密传输:业务数据用会话密钥加密,性能高

  4. 完整性校验:防止传输中被篡改

握手关键步骤(简化):

  • ClientHello(算法套件、随机数)

  • ServerHello + 证书

  • 客户端校验证书并协商密钥

  • 双方生成会话密钥

  • 进入加密通信


169. 请介绍一下HTTP和HTTPS的区别,以及HTTPS的握手过程

HTTP vs HTTPS:

  1. HTTP明文传输,HTTPS加密传输

  2. HTTP默认80端口,HTTPS默认443端口

  3. HTTPS需要证书,HTTP不需要

  4. HTTPS可提供身份认证、机密性、完整性

HTTPS握手过程(面试版):

  1. 客户端发起 ClientHello

  2. 服务端返回 ServerHello 和证书

  3. 客户端验证证书合法性(CA链、域名、有效期)

  4. 双方协商出会话密钥

  5. 后续HTTP数据通过会话密钥加密传输


170. HTTP3 和 QUIC 了解吗?

HTTP3:

  • 基于QUIC协议

  • UDP实现

  • 0-RTT连接

  • 无队头阻塞

  • 连接迁移

QUIC:

  • Google提出

  • 混合TCP+UDP优点

  • TLS内置

  • 连接ID


171. 说说HTTP和HTTPS的区别,HTTPS的性能开销在哪里?

性能开销:

  1. SSL/TLS握手:

    • 额外的RTT

    • 密钥交换计算

  2. 加密/解密:

    • CPU计算资源

    • 对称加密影响小

  3. 证书验证:

    • 证书链验证

优化:

  • 证书预加载

  • Session复用

  • HTTP/2

  • OCSP stapling


172. 什么是中间人攻击?HTTPS如何防御?

中间人攻击:

  • 攻击者插入通信双方

  • 窃听/篡改数据

HTTPS防御:

  1. 证书验证:

    • 验证证书链

    • 验证域名

    • 检查有效期

  2. 公钥基础设施:

    • CA信任链

  3. 证书透明度:

    • CT日志

  4. TLS加密:

    • 数据加密传输


4.3 DNS与URL

173. URL请求过程中,DNS解析的核心作用是什么?

DNS作用:

  • 将域名解析为IP地址

  • 分布式数据库

  • 分层查询

解析过程:

  1. 浏览器缓存

  2. 本地DNS缓存

  3. Hosts文件

  4. DNS服务器递归查询


174. DNS解析只有浏览器才支持吗?详细说说DNS解析过程

解析流程:

  1. 浏览器缓存: 检查缓存

  2. 系统缓存: OS hosts文件

  3. 本地区域服务器: ISP DNS

  4. 根域名服务器: .

  5. 顶级域名服务器: .com

  6. 权威域名服务器: example.com

解析方式:

  • 递归查询

  • 迭代查询


4.4 网络编程

175. Socket 编程了解吗?

Socket概念:

  • 网络通信的抽象

  • 端到端的通信

基本流程:

服务端:

  1. socket() 创建socket

  2. bind() 绑定地址

  3. listen() 监听

  4. accept() 接受连接

  5. read()/write() 通信

  6. close() 关闭

客户端:

  1. socket() 创建socket

  2. connect() 连接

  3. write()/read() 通信

  4. close() 关闭


176. 长连接和短连接的区别?

短连接:

  • 每次请求都建立连接

  • 请求完成后关闭

  • 适合请求少

长连接:

  • 保持连接复用

  • 适合高并发

  • 需要心跳保活

HTTP中的区别:

  • HTTP/1.0短连接

  • HTTP/1.1默认长连接


177. 网络编程中的select、poll、epoll有什么区别?

特性

select

poll

epoll

最大连接

1024

无限制

无限制

效率

O(n)

O(n)

O(1)

内存拷贝

触发方式

水平触发

水平触发

边沿触发/水平

epoll优势:

  • 适合大量连接

  • 效率高

  • 无需遍历


178. Epoll的底层实现原理,为什么比select/poll快?ET和LT模式有什么区别?

epoll原理:

  • 监听红黑树

  • 就绪链表

  • 回调机制

为什么快:

  1. 不遍历所有fd

  2. 无频繁用户/内核切换

  3. 直接返回就绪的fd

ET vs LT:

  • LT (Level Trigger):

    • 水平触发

    • 持续通知

    • 简单,不易丢数据

  • ET (Edge Trigger):

    • 边沿触发

    • 只通知一次

    • 效率高,需要处理完


五、消息队列

5.1 消息队列作用

179. 消息队列的作用是什么?

  1. 异步处理:

    • 提高系统响应速度

  2. 削峰填谷:

    • 缓解高并发压力

  3. 解耦:

    • 上下游系统松耦合

  4. 消息通讯:

    • 点对点、发布订阅


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

使用消息队列的核心原因有四点:

  1. 异步解耦

    • 请求链路拆短,主流程先返回,非核心逻辑异步处理

  2. 削峰填谷

    • 高峰请求先入队,消费者按稳定速率处理,保护下游DB

  3. 系统解耦与扩展

    • 生产者和消费者独立演进,新增下游只需新增消费者

  4. 最终一致性与可靠交付

    • 配合重试、死信、补偿机制,提升分布式场景下可靠性

注意:

  • MQ会引入复杂度(幂等、顺序、延迟、可观测),要按场景使用


181. 削峰填谷是什么意思?

削峰:

  • 高并发时,消息积压在队列

  • 数据库压力减少

填谷:

  • 高峰过后,消息逐步处理

  • 系统平稳运行


5.2 Kafka

182. Kafka 分区的目的是什么?压力具体指什么?

分区目的:

  1. 并行消费:

    • 多分区多消费者

  2. 负载均衡:

    • 数据分布到多个分区

  3. 提高吞吐:

    • 水平扩展

压力:

  • 磁盘IO

  • 网络带宽

  • 内存使用


183. Kafka 怎么保证消息有序性?

  1. 单分区内有序:

    • 同一分区内有序

  2. 全局有序:

    • 只用一个分区

    • 牺牲并发

  3. 分区规则:

    • 同一业务数据发送到同一分区


184. 消费者组的原理?

消费者组:

  • 多个消费者组成

  • 共同消费主题

  • 分区数=消费者数(最理想)

特点:

  • 同一组内不会重复消费

  • 不同组可以重复消费


185. Kafka 和 RocketMQ 的区别?

特性

Kafka

RocketMQ

吞吐量

极高

延迟

毫秒级

可靠性

多副本

多种模式

顺序消息

分区内有序

支持全局有序

事务消息

支持

原生支持

死信队列

支持

适用场景

大数据

业务消息


5.3 RocketMQ

186. 为什么选用 RocketMQ 而不是 Kafka?

  1. 业务场景:

    • 事务消息

    • 顺序消息

    • 延迟消息

  2. 可靠性要求:

    • 金融级场景

    • 消息不丢失

  3. 功能丰富:

    • 死信队列

    • 消息重试


187. 延迟队列是怎么实现的?

RocketMQ:

  • 延迟等级

  • messageDelayLevel配置

  • 消息投递到SCHEDULE_TOPIC_XXXX

实现原理:

  • 按延迟等级存储

  • 定时器检查投递


188. 事务消息的原理?

两阶段提交:

  1. 发送半消息:

    • 先发送到MQ

    • 标记为prepared状态

  2. 执行本地事务:

    • 业务操作

  3. 提交/回滚:

    • 成功:提交消息

    • 失败:回滚消息

补偿机制:

  • 定时检查未决事务


189. RabbitMQ 的镜像队列机制是如何保证消息高可用的?

镜像队列:

  • 主从复制

  • 消息同步到多个节点

机制:

  1. master节点处理读写

  2. slave同步

  3. master失效,slave升级

模式:

  • classic

  • quorum(新版)


5.4 消息可靠性

190. 消息重复消费怎么办?

原因:

  • 网络抖动

  • 消费端未ACK

  • 生产者重试

解决方案:

  1. 幂等性:

    • 唯一ID

    • 去重表

  2. 业务层处理:

    • 状态机

    • 乐观锁


191. 消息丢失怎么办?

丢失环节:

  1. 生产端丢失

  2. MQ存储丢失

  3. 消费端丢失

解决方案:

  1. 生产端:

    • 确认机制

    • 重试

  2. MQ端:

    • 持久化

    • 多副本

  3. 消费端:

    • 手动ACK

    • 幂等处理


192. 项目中使用RabbitMQ时,如何解决消息丢失和重复消费问题?

完整链路方案:

  1. 生产端防丢:

    • 开启 publisher confirm

    • 发送失败重试 + 本地消息表兜底

  2. Broker防丢:

    • 队列/交换机持久化

    • 镜像/仲裁队列保证高可用

  3. 消费端防丢:

    • 业务处理成功后再ACK(手动ACK)

    • 消费失败N次后进入死信队列

  4. 防重复消费:

    • 消息唯一ID + 幂等表/唯一索引

    • 业务状态机防止重复执行副作用


193. RabbitMQ 中如何保证消费端不出现消息重复消费的问题?

RabbitMQ本身无法承诺“绝不重复”,工程上要做“消费幂等”。

常见做法:

  1. 每条消息携带全局唯一 messageId/bizNo

  2. 消费前先查幂等记录(或利用唯一索引原子判重)

  3. 已处理则直接ACK并返回

  4. 未处理则执行业务,成功后落幂等记录并ACK

注意:

  • 幂等记录与业务更新最好在同一事务内提交

  • 避免“业务成功但幂等记录失败”导致重复执行


194. 在项目中,使用消息队列有没有遇到什么问题?

遇到过,主要是三类:

  1. 消息积压

    • 原因:消费能力低于生产速度

    • 处理:增加消费者并发、按业务分队列、高峰期降级非核心消息

  2. 消费失败重试风暴

    • 原因:下游依赖异常导致大量重试

    • 处理:引入指数退避、死信队列、失败熔断

  3. 顺序错乱

    • 原因:并发消费导致同一业务Key乱序

    • 处理:同Key路由同队列,消费端串行化处理


六、JVM 虚拟机

6.1 内存模型

195. 简述 JVM 有哪些内存结构

  1. 程序计数器: 线程私有,记录字节码行号

  2. 虚拟机栈: 线程私有,方法栈帧

  3. 本地方法栈: 线程私有,native方法

  4. : 线程共享,对象实例

  5. 方法区/元空间: 线程共享,类信息


196. JVM 内存模型是怎样的?

这个问题要区分两层含义:

  1. JVM运行时数据区(内存结构)

    • 线程私有:程序计数器、虚拟机栈、本地方法栈

    • 线程共享:堆、方法区(元空间)

  2. JMM(Java Memory Model)并发内存模型

    • 规定线程如何通过主内存与工作内存交互

    • 核心是保证可见性、有序性、原子性语义

    • volatile/synchronized/Lock 都是在JMM规则下生效

面试建议:

  • 如果面试官问“内存模型”,先反问是“运行时数据区”还是“JMM并发模型”,体现专业性。


197. 堆和栈的区别?

特性

线程

共享

私有

存储

对象实例

局部变量

空间

GC

主要GC区域

无GC

分配方式

动态

静态

速度


198. 方法区存放什么数据?

  1. 类信息:

    • 类的元数据

    • 成员信息

  2. 静态变量:

    • static修饰的变量

  3. 常量:

    • 字符串常量池

  4. JIT代码:

    • 即时编译器生成的代码


199. 哪些区域比较容易发生 OOM

  1. 堆OOM:

    • 对象太多

    • 内存泄漏

  2. 栈OOM:

    • 递归太深

    • 线程太多

  3. 元空间OOM:

    • 类太多

    • 动态生成类

  4. 直接内存OOM:

    • NIO使用过多


201. 对象创建过程?

  1. 类加载检查:

    • 检查符号引用

  2. 分配内存:

    • 指针碰撞/空闲列表

  3. 初始化零值:

    • 默认值

  4. 设置对象头:

    • Mark Word

    • 元数据信息

  5. 执行init:

    • 构造函数


202. 对象如何判断可以回收?

算法:

  1. 引用计数:

    • 引用为0可回收

    • 循环引用问题

  2. 可达性分析:

    • GC Roots向下搜索

    • 不在引用链可回收

GC Roots:

  • 栈引用

  • 静态引用

  • 常量引用

  • 本地方法引用


203. 垃圾回收算法有哪些?

  1. 标记-清除:

    • 标记-清除

    • 效率低、碎片

  2. 复制:

    • 分为两块

    • 简单高效

    • 内存减半

  3. 标记-整理:

    • 标记-整理

    • 无碎片

    • 效率中等

  4. 分代收集:

    • 新生代复制

    • 老年代整理


204. Full GC 触发机制?

  1. 老年代空间不足

  2. System.gc()

  3. 元空间不足

  4. Minor GC前检查

  5. CMS并发失败


205. 内存分配策略?

  1. 对象优先Eden:

    • 新对象在Eden

  2. 大对象直接进老年代:

    • 大于阈值

  3. 长期存活对象进老年代:

    • 15次Minor GC

  4. 动态年龄判断:

    • Survivor相同年龄超过一半


206. JVM 内存分配与回收过程?

  1. Eden分配:

    • 新对象在Eden

  2. Minor GC:

    • Eden满触发

    • 存活进Survivor

  3. 对象晋升:

    • 达到年龄进老年代

  4. Full GC:

    • 老年代满触发


207. CMS和G1的区别

特性

CMS

G1

收集方式

标记-清除

标记-整理

内存划分

新生代/老年代

多个Region

碎片

有碎片

无碎片

停顿时间

不可控

可预测

适用场景

老年代

大堆


208. G1是如何实现可预测停顿时间的功能的?

G1原理:

  1. Region划分:

    • 将堆划分为多个Region

  2. 优先列表:

    • 优先回收垃圾最多的Region

  3. 停顿时间目标:

    • MaxGCPauseMillis参数

  4. Mixed GC:

    • 回收年轻代+部分老年代


209. G1 垃圾回收器中 Remembered Set 的作用是什么?

Remembered Set:

  • 记录Region间的引用关系

  • 避免全堆扫描

作用:

  • 年轻代引用老年代:记录

  • 老年代引用年轻代:记录

实现:

  • 每个Region有一个RSet

  • 记录其他Region对它的引用


210. ZGC 颜色指针的核心解决了什么问题?

颜色指针:

  • 指针本身存储GC状态

  • 42位对象指针

解决的问题:

  1. 并发标记:

    • 无需STW

  2. 对象移动:

    • 无需更新引用

  3. 读屏障:

    • 并发转移

  4. 可扩展性:

    • 支持TB级堆


6.2 类加载

211. 类加载机制?

  1. 加载:

    • 读取字节码

  2. 验证:

    • 字节码验证

  3. 准备:

    • 分配内存

  4. 解析:

    • 符号引用变直接引用

  5. 初始化:

    • 静态代码块


212. 双亲委派模型?

加载器:

  • Bootstrap ClassLoader

  • Extension ClassLoader

  • Application ClassLoader

委派流程:

  1. 向上委派给父加载器

  2. 父加载不了才自己加载

优点:

  • 避免类重复加载

  • 安全性(防止篡改)


七、分布式系统与微服务

7.1 分布式理论

213. CAP 定理是什么?

CAP:

  • Consistency (一致性): 所有节点数据一致

  • Availability (可用性): 每次请求都能获得响应

  • Partition Tolerance (分区容错): 系统在网络分区下仍能运行

三者不可兼得:

  • CA: 单点系统

  • CP: 优先一致性

  • AP: 优先可用性


214. BASE 理论是什么?

BASE:

  • Basically Available: 基本可用

  • Soft State: 软状态

  • Eventually Consistent: 最终一致性

是对CAP的补充:

  • 放弃强一致性

  • 保证最终一致性


7.2 分布式ID

215. 分布式 ID 生成器怎么实现?

  1. UUID:

    • 无序

    • 性能好

  2. 数据库自增:

    • 有序

    • 单点

  3. 雪花算法:

    • 有序

    • 高性能

  4. Redis INCR:

    • 有序

    • 依赖Redis


216. Snowflake 算法的原理?

结构:

  • 1位符号位

  • 41位时间戳

  • 10位机器ID

  • 12位序列号

特点:

  • 有序

  • 高性能

  • 依赖时钟


7.3 微服务架构

217. 说说你对微服务架构的理解,以及遇到的问题和解决方案

微服务:

  • 服务拆分

  • 独立部署

  • 独立数据库

问题:

  • 服务治理

  • 分布式事务

  • 服务通信

解决方案:

  • Spring Cloud

  • Dubbo


218. 微服务架构的优缺点?

优点:

  1. 独立部署

  2. 技术异构

  3. 弹性伸缩

  4. 快速迭代

缺点:

  1. 复杂度高

  2. 分布式事务

  3. 运维难度

  4. 服务治理


219. 服务注册与发现?

注册中心:

  • Eureka

  • Nacos

  • Consul

流程:

  1. 服务启动注册

  2. 心跳保活

  3. 服务发现

  4. 负载均衡


220. 配置中心的作用?

作用:

  1. 统一配置管理

  2. 动态配置更新

  3. 环境隔离

  4. 版本管理

常见:

  • Nacos

  • Apollo

  • Spring Cloud Config


221. API 网关的作用?

  1. 路由转发:

    • 请求路由

  2. 认证鉴权:

    • 身份验证

  3. 限流熔断:

    • 保护服务

  4. 协议转换:

    • HTTP/Dubbo

  5. 日志记录:

    • 访问日志


7.4 负载均衡

222. 负载均衡中,轮询算法的核心缺陷是什么?

轮询缺陷:

  1. 不考虑服务器性能

  2. 不考虑当前负载

  3. 不考虑网络状况

改进算法:

  • 加权轮询

  • 最少连接

  • IP哈希


7.5 高可用

223. 你们的系统是如何保证高可用的?如果服务器宕机了怎么办?

高可用方案:

  1. 负载均衡

  2. 服务冗余

  3. 健康检查

  4. 自动故障转移

  5. 限流熔断

宕机处理:

  • 流量切换

  • 告警通知


224. 线上转码服务挂了,你们有什么后续处理?

(根据项目回答)

  1. 告警通知

  2. 流量切换

  3. 问题定位

  4. 服务恢复

  5. 复盘分析


十、AI / 大模型

10.1 LLM 交互

235. 大模型调用数据库的全链路流程?

  1. 用户请求: 用户输入问题

  2. 意图识别: 判断是否需要查询数据库

  3. SQL生成: LLM生成SQL

  4. SQL校验: 验证SQL安全性

  5. 执行查询: 连接数据库执行

  6. 结果处理: 格式化结果

  7. 生成回答: 结合数据生成最终回复


236. 什么是 Function Calling?

Function Calling:

  • 大模型调用外部函数的能力

  • 让AI能执行具体操作

  • 扩展AI的能力边界

流程:

  1. 定义函数签名

  2. 用户请求

  3. 判断是否调用函数

  4. 执行函数获取结果

  5. 将结果返回给模型

  6. 生成最终回复


237. MCP 是什么?

MCP (Model Context Protocol):

  • Anthropic提出的模型上下文协议

  • 标准化的AI与外部工具交互方式

  • 统一接口访问各种资源

核心:

  • 工具调用

  • 资源访问

  • 提示词管理


238. Skills 是什么?

Skills:

  • 预定义的工具集

  • 封装好的函数调用

  • 解决特定问题

用途:

  • 文件操作

  • 代码执行

  • 命令运行


239. skills和tools和mcp之间的区别

概念

说明

Skills

高级能力封装

Tools

底层工具函数

MCP

标准化协议


10.2 RAG

240. RAG 和传统搜索有什么区别?

特性

传统搜索

RAG

原理

关键词匹配

向量语义匹配

理解力

字面

语义

准确性

可能不相关

更精准

上下文


241. 为什么不直接用关键词检索?

  1. 同义词问题:

    • 用户说"电脑"希望找到"计算机"

  2. 语义理解:

    • 理解真实意图

  3. 歧义消除:

    • "苹果"可能是水果或公司


242. RAG 效果优化有哪些方法?

  1. 文档分块:

    • 合理分块大小

  2. 向量化模型:

    • 选择更好的模型

  3. 混合检索:

    • 关键词+向量

  4. 重排序:

    • ReRanker优化

  5. 查询改写:

    • HyDE等


243. 利用PGVector的混合索引(稠密+稀疏)提升召回率;

实现:

  • 稠密向量: 语义相似度

  • 稀疏向量: BM25关键词

  • 结合两者优势


244. 使用HyDE算法,先让大模型生成初步答案再转为向量进行检索;

HyDE:

  1. 让LLM生成假设答案

  2. 将假设答案向量化

  3. 用假设答案检索

  4. 结合真实问题检索


245. 引入ReRanker对召回结果重排序,以平衡召回率与精确度。

ReRanker:

  • 对初检结果重排序

  • 更精准的模型

  • 平衡精确率和召回率


10.3 Agent

246. 智能体和大模型怎么交互、协同?工作流程是什么?

工作流程:

  1. 用户输入

  2. 理解意图

  3. 规划步骤

  4. 执行工具

  5. 评估结果

  6. 生成回复

交互方式:

  • 工具调用

  • 状态传递

  • 循环迭代


247. Agent 间通信和状态管理怎么设计?

通信方式:

  1. 共享状态:

    • 共享内存/数据库

  2. 消息队列:

    • 异步通信

  3. API调用:

    • 同步通信

状态管理:

  • 持久化存储

  • 上下文传递


248. Agent 效果评估怎么做?

  1. 自动化指标:

    • 准确率

    • 召回率

  2. 人工评估:

    • 质量评分

  3. 对比测试:

    • A/B测试

  4. 真实场景:

    • 生产环境监控


10.4 AI工具相关

249-257题:AI工具相关

(根据实际情况回答)


十一、算法题

11.1 排序算法

258. 写一段堆排序,不要用系统 API,15 分钟写可运行代码

public class HeapSort {
    public void sort(int[] arr) {
        int n = arr.length;

        // 构建大顶堆
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }

        // 排序
        for (int i = n - 1; i > 0; i--) {
            swap(arr, 0, i);
            heapify(arr, i, 0);
        }
    }

    private void heapify(int[] arr, int n, int i) {
        int largest = i;
        int left = 2 * i + 1;
        int right = 2 * i + 2;

        if (left < n && arr[left] > arr[largest]) {
            largest = left;
        }

        if (right < n && arr[right] > arr[largest]) {
            largest = right;
        }

        if (largest != i) {
            swap(arr, i, largest);
            heapify(arr, n, largest);
        }
    }

    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

259. 手写快排

public class QuickSort {
    public void sort(int[] arr, int low, int high) {
        if (low < high) {
            int pi = partition(arr, low, high);
            sort(arr, low, pi - 1);
            sort(arr, pi + 1, high);
        }
    }

    private int partition(int[] arr, int low, int high) {
        int pivot = arr[high];
        int i = low - 1;

        for (int j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                swap(arr, i, j);
            }
        }

        swap(arr, i + 1, high);
        return i + 1;
    }

    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

260. 手写归并排序

public class MergeSort {
    public void sort(int[] arr, int left, int right) {
        if (left < right) {
            int mid = (left + right) / 2;
            sort(arr, left, mid);
            sort(arr, mid + 1, right);
            merge(arr, left, mid, right);
        }
    }

    private void merge(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1];
        int i = left, j = mid + 1, k = 0;

        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }

        while (i <= mid) temp[k++] = arr[i++];
        while (j <= right) temp[k++] = arr[j++];

        System.arraycopy(temp, 0, arr, left, temp.length);
    }
}

11.2 树

261. 算法题:扁平数组化成树(二叉树)

class Node {
    int val;
    int parentId;
    // children...
}

public List<Node> buildTree(List<Node> nodes) {
    Map<Integer, Node> map = new HashMap<>();
    List<Node> roots = new ArrayList<>();

    for (Node node : nodes) {
        map.put(node.id, node);
    }

    for (Node node : nodes) {
        if (node.parentId == 0) {
            roots.add(node);
        } else {
            Node parent = map.get(node.parentId);
            if (parent != null) {
                parent.children.add(node);
            }
        }
    }

    return roots;
}

262. 算法题:层次遍历二叉树变体

public List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> result = new ArrayList<>();
    if (root == null) return result;

    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);

    while (!queue.isEmpty()) {
        int levelSize = queue.size();
        List<Integer> level = new ArrayList<>();

        for (int i = 0; i < levelSize; i++) {
            TreeNode node = queue.poll();
            level.add(node.val);

            if (node.left != null) queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }

        result.add(level);
    }

    return result;
}

11.3 链表

263. 反转链表

public ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode curr = head;

    while (curr != null) {
        ListNode next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }

    return prev;
}

264. 反转双向链表

public Node reverse(Node head) {
    Node curr = head;
    Node prev = null;

    while (curr != null) {
        Node next = curr.next;
        curr.next = prev;
        curr.prev = next;
        prev = curr;
        curr = next;
    }

    return prev;
}

11.4 设计题

265. 场景题:弱网环境下上传 1G 视频,应该怎么设计?

  1. 分片上传:

    • 将视频分片

    • 每片单独上传

  2. 断点续传:

    • 记录已上传分片

    • 失败后继续

  3. 压缩:

    • 视频压缩

  4. 重试机制:

    • 失败重试

  5. 秒传:

    • MD5校验


266. 场景题:100个进程,5个并发数的工作,应该怎么设计?

这个问题本质是“限并发执行 + 任务可观测 + 失败可恢复”。

设计目标:

  1. 同时最多执行5个任务

  2. 100个任务全部执行完成(成功/失败可统计)

  3. 单任务失败不影响整体

  4. 可重试、可超时、可监控

推荐实现(Java):

  • 使用 ThreadPoolExecutor,核心/最大线程数都设为5

  • 使用有界队列(防止内存无限增长)

  • 每个任务加超时控制(Future.get(timeout)

  • 结果统一汇总(成功数、失败数、重试数)

关键参数建议:

  • corePoolSize = 5

  • maximumPoolSize = 5(严格限并发)

  • workQueue = LinkedBlockingQueue(容量可控)

  • rejectedPolicy = CallerRunsPolicy(反压)

工程增强点:

  • 任务幂等(避免重试导致副作用)

  • 失败重试(指数退避)

  • 全链路日志(traceId)

  • 指标上报(任务耗时、成功率)


267. 算法题(全英文)给定一个二维数组:...

原题干信息不完整,但从描述可推断为“二维网格路径计数”类问题。

通用解题思路(面试可直接说):

  1. 明确状态定义:dp[i][j][k] 表示从起点到 (i,j) 恰好经过 k 个点的路径数

  2. 转移方程:由上、下、左、右(或题目允许方向)转移

  3. 边界条件:起点在 k=1 时路径数为1

  4. 复杂度评估:时间 O(R*C*K*D),空间可滚动优化

若是经典“从A到B只能向右/向下”的版本:

  • 状态:dp[i][j] = dp[i-1][j] + dp[i][j-1]

  • 若要求“经过C个点”,在状态中再加一维记录步数/点数

答题建议:

  • 先确认“可移动方向、障碍物、是否允许重复经过点、点数定义(含起终点)”

  • 再给状态转移,最后给代码


十二、项目经验

268-286题

这组题高频考察“你是否真的做过项目”。建议统一按 STAR(背景-任务-行动-结果)回答。

通用高分回答骨架:

  1. 背景(S): 业务规模、峰值流量、核心痛点

  2. 职责(T): 你负责的模块和边界

  3. 行动(A): 技术方案、关键决策、踩坑与取舍

  4. 结果(R): 用指标量化(QPS、RT、错误率、成本)

可直接套用模板:

  • 项目是一个XX业务平台,高峰QPS约X万,当时核心问题是延迟高/不稳定/一致性

  • 我负责订单与库存链路(或检索与推荐链路),重点做了三件事:

    1. 数据层:索引优化 + 慢SQL治理

    2. 缓存层:热点缓存 + 失效策略重构

    3. 异步层:MQ削峰 + 幂等消费

  • 最终指标:P99由Xms降到Yms,错误率由A%降到B%,机器成本下降C%

268. 项目架构介绍?

  • 分层:网关层 -> 业务服务层 -> 数据层 -> 基础设施层

  • 核心组件:Nginx/Spring Boot/MySQL/Redis/RocketMQ/ES

  • 非功能:监控、告警、限流、熔断、灰度、审计

269. 负责部分和业务接入成本?

  • 负责模块要说清“输入/输出/依赖/边界”

  • 接入成本用“开发天数 + 配置项 + 联调项 + 风险项”量化

270. 代码库检索?

  • 关键词检索(symbol)+ 调用链追踪 + 提交历史

  • 大仓库建议结合 grep + IDE references + blame

271. 你的项目使用了什么架构?包含哪些模块?数据库怎么用?

  • 架构:微服务 + 事件驱动

  • 模块:用户、订单、库存、支付、风控、报表

  • 数据库:OLTP 用 MySQL,缓存用 Redis,检索用 ES

272. 介绍一下你的RPC项目?实际应用还是练手?

  • 若实际应用:强调压测数据、故障演练、线上指标

  • 若练手项目:强调协议设计、序列化、服务发现、容错机制

273. 项目来源(博主/学校)如何回答?

  • 实话实说,重点转到“你做了哪些增强和原创优化”

  • 面试官看重的是“理解深度与改造能力”,不是项目来源

281. 日志系统怎么设计,如何处理海量日志?

  • 采集(Filebeat/Fluentd)-> 消息队列 -> 存储(ES/HDFS)-> 查询(Kibana)

  • 分级日志、采样策略、冷热分层、索引生命周期管理(ILM)

282. 抽奖结果保存的核心技术方案?

  • 预扣库存(Redis+Lua)-> 异步落库(MQ)-> 幂等落库(唯一键)-> 补偿对账

283. Excel转PDF不清晰如何解决?

  • 统一字体和渲染引擎

  • 调整DPI与页面缩放

  • 大表分页渲染,避免一次性内存峰值

284. 权限控制模块如何设计数据权限与功能权限隔离?

  • 功能权限:接口/菜单级别(RBAC)

  • 数据权限:行级过滤(部门、租户、数据范围)

  • 网关鉴权 + 服务内二次鉴权

285. 自定义异常如何保证定位准确?

  • 统一异常码体系(系统码+业务码)

  • 记录请求上下文(traceId、用户、参数摘要)

  • 错误信息可观测且可追踪到代码位点

286. catch 父异常还是子异常?

  • 一定“先子类后父类”,否则子类分支不可达

  • 最后一个 Exception 兜底,并上报监控


十三、场景题

289-300题

这组题核心是“架构能力 + 取舍能力 + 风险意识”。

289. 秒杀系统设计?

  • 流量入口:CDN + 限流(令牌桶)+ 验签 + 防重放

  • 核心链路:库存预扣(Redis+Lua)-> 下单异步化(MQ)-> 最终落库

  • 防超卖:库存扣减原子化 + 幂等订单 + 失败补偿

  • 稳定性:降级开关、熔断、隔离舱、热点隔离

290. 怎么保证积分可靠性,防止多扣/多增?

  • 账户流水表 + 唯一业务单号 + 幂等写入

  • 账户余额更新采用乐观锁版本号或悲观锁

  • 引入对账任务,发现差异自动补偿

291. 高并发下数据库读写瓶颈怎么解?

  • 读:缓存前置、读写分离、热点数据本地缓存

  • 写:异步化、批量写、分库分表

  • 查询:索引优化、SQL重写、避免深分页

292. 分布式存储方案?

  • 对象存储(S3/OSS)用于文件

  • KV存储用于元数据热点

  • OLAP/ES用于分析与检索

293. 分库分表策略?

  • 按用户ID或时间维度分片

  • 避免跨分片事务和跨分片排序

  • 提前规划扩容与数据迁移方案

294. 不用MQ如何实现延迟任务?

  • 时间轮(Hashed Wheel Timer)

  • Redis ZSet(score=执行时间)+ 轮询抢占

  • 数据库延迟表 + 定时扫描(低吞吐场景)

295-296. 检索效果优化案例?

  • 召回:关键词+向量混合检索

  • 排序:业务特征 + ReRank

  • 评估:离线指标(NDCG/Recall)+ 在线A/B

297. 微信红包系统怎么设计?

  • 发红包:余额冻结 -> 红包拆分 -> 入账队列

  • 抢红包:原子扣减 + 防重复领取

  • 资金安全:强一致账本 + 对账补偿 + 风控规则

298. RPC序列化模块优先考虑什么?

  • 性能、兼容性、可扩展性、安全性

  • 优先支持 schema 演进(向前/向后兼容)

299. docker代码沙箱流程?

  • 收代码 -> 构建镜像/复用镜像 -> 启动容器 -> 执行 -> 收集结果 -> 销毁

300. 代码沙箱如何防恶意攻击?

  • 资源限制:CPU/内存/磁盘/进程数

  • 安全限制:seccomp、只读文件系统、禁网

  • 时间限制:执行超时强杀

301. 判题高并发如何避免容器抖动?

  • 容器池预热 + 任务队列 + 分级调度

  • 冷热镜像分层缓存,减少拉镜像开销

  • 节点资源水位控制,避免雪崩


十四、Spring / 框架

14.1 Spring

302. Spring 的核心特性?

  1. IoC/DI:

    • 控制反转

    • 依赖注入

  2. AOP:

    • 面向切面编程

  3. 事务管理:

    • 声明式事务

  4. MVC:

    • Web框架


303. Spring Bean 的生命周期?

  1. 实例化:

    • new创建

  2. 属性填充:

    • 依赖注入

  3. 初始化:

    • @PostConstruct

    • InitializingBean

  4. 销毁:

    • @PreDestroy

    • DisposableBean


304. Spring 循环依赖如何解决?

三级缓存:

  • singletonObjects

  • earlySingletonObjects

  • singletonFactories

解决方式:

  • 构造器循环依赖无法解决

  • setter/字段循环依赖可以解决(三级缓存)


305. Spring 中 @Transactional 注解的 propagation 属性,最常用的传播行为是什么?

常用传播行为:

  • REQUIRED (默认): 有事务加入,无事务创建

  • REQUIRES_NEW: 总是创建新事务

  • SUPPORTS: 有事务加入,无则非事务


14.2 Spring Boot

306. Spring Boot 自动配置原理?

  1. @SpringBootApplication:

    • @EnableAutoConfiguration

  2. @EnableAutoConfiguration:

    • 导入AutoConfigurationImportSelector

  3. META-INF/spring.factories:

    • 配置类路径

  4. @Conditional:

    • 条件装配


307. 服务端处理请求时,Tomcat的Connector组件核心职责是什么?

Connector职责:

  1. 监听端口

  2. 接收请求

  3. 协议解析

  4. 交给Container处理


14.3 MyBatis

308. MyBatis #{} 和 ${} 的区别?

特性

#{}

${}

编译方式

预编译

直接拼接

SQL注入

安全

不安全

类型处理

自动


309. MyBatis 一级缓存和二级缓存?

一级缓存:

  • SqlSession级别

  • 默认开启

  • 同一会话内有效

二级缓存:

  • SqlSessionFactory级别

  • 跨会话有效

  • 需要手动开启


310. MyBatis 插件原理?

原理:

  • 拦截器链

  • JDK动态代理 -四大对象:

    • Executor

    • StatementHandler

    • ParameterHandler

    • ResultSetHandler


311. MyBatis 一级缓存的作用域是哪里?默认是否开启?

  • 作用域: SqlSession

  • 默认: 开启


14.4 其他框架

312. 基于 AOP 思想,如何自定义拦截器防止 SQL 注入?

  1. 定义注解

  2. 编写切面

  3. 拦截参数

  4. 过滤特殊字符


十五、设计模式

490-495题

单例模式:

  • 饿汉/懒汉/DCL/枚举

工厂模式:

  • 简单工厂/工厂方法/抽象工厂

代理模式:

  • JDK/CGLIB

观察者模式:

  • 事件监听

使用场景:

  • 根据业务选择

面试加分点(设计模式题建议这样答):

  • 先说“解决什么问题”,再说“为什么是这个模式而不是别的模式”。

  • 必须落到项目场景,例如:

    • 单例:配置中心客户端、线程池管理器

    • 工厂:多渠道支付对象创建

    • 代理:RPC客户端桩、AOP切面

    • 观察者:订单状态变更触发短信/积分/通知


十七、Linux 与 Git

17.1 Linux 命令

329-343题

常用命令:

  • ls, cd, pwd

  • grep, find

  • cat, head, tail

  • chmod, chown

  • ps, top

  • netstat

高频排障命令组合(可直接口述):

  1. 查CPU高占用进程:top -Hp <pid>

  2. 看Java线程栈:jstack <pid> | grep -n "BLOCKED"

  3. 查日志错误:grep -in "error" app.log | tail -n 100

  4. 查端口占用:netstat -tunlp | grep 8080

  5. 查磁盘/IO:df -hiostat -x 1 5

342题可执行答案:

  • find . -name "*.log" -print0 | xargs -0 grep -in "error" > error_log.txt


17.2 Git

344-346题

rebase vs merge:

  • rebase: 线性历史

  • merge: 保留分支历史

冲突解决:

  • 手动编辑

  • git add

标准冲突处理流程:

  1. git fetch 拉最新分支

  2. git rebase origin/main(或 merge)

  3. 解决冲突文件

  4. 本地测试通过后 git rebase --continue

  5. 推送并发起PR


十九、分布式定时任务与接口

359-362题

幂等性:

  • 唯一ID

  • 状态机

限流:

  • 计数器

  • 令牌桶

  • 滑动窗口

接口限流落地建议:

  • 网关层全局限流(保护系统)

  • 业务层细粒度限流(按用户/接口/租户)

  • 超限返回明确错误码,便于客户端退避重试


二十、认证与安全

20.1 JWT

363-370题

JWT结构:

  • Header

  • Payload

  • Signature

优点:

  • 无状态

  • 可验证

安全问题:

  • 过期时间

  • 密钥安全

JWT详细防护方案:

  1. AccessToken短期有效,RefreshToken中期有效

  2. 敏感操作强制二次校验(短信/OTP)

  3. token中包含 jti,服务端维护黑名单实现强制下线

  4. 密钥轮换与版本化(kid)

  5. 全程HTTPS,防止明文窃取


二十一、实习与职业发展

371-390题

这部分建议“真诚 + 结构化 + 与岗位匹配”。

常见问题回答模板:

  • 为什么不留在上一家公司?

    • 从“业务方向匹配度、成长空间、技术挑战”回答,不抱怨。

  • 你在实习学到了什么?

    • 讲工程化能力:需求拆解、联调、上线、复盘。

  • 你的职业规划?

    • 1年做深模块,3年能扛系统,5年形成领域专长。

376. 软件开发流程是什么样的?

  • 需求评审 -> 技术方案评审 -> 开发联调 -> 测试回归 -> 灰度发布 -> 复盘

379. 如何定位系统瓶颈并优化?

  • 先指标定位(RT/QPS/错误率)

  • 再链路拆解(网关/应用/DB/缓存/MQ)

  • 最后小步改造+AB验证


二十二、转码与数据处理

391-395题

392. 两种转码方法优缺点?

  • 本地FFmpeg:灵活、成本低;但扩容和隔离能力弱

  • 分布式转码服务:扩展性强、可观测好;但架构复杂度更高

393. 转码失败率如何监控?

  • 指标:任务成功率、平均耗时、超时率、重试率

  • 告警:按失败率阈值+连续时间窗口触发

394. 转码结果前端如何感知?

  • 轮询任务状态或WebSocket推送

  • 状态机:排队中/处理中/成功/失败

395. 同步还是异步?为什么?

  • 大文件转码必须异步,避免请求超时和线程阻塞

  • 同步仅用于小文件或预览场景

396. 为什么不直接存原始文件?

  • 终端兼容性、带宽成本、播放性能、审核与水印处理等都要求统一编码格式


二十三、其他问题

396-403题

397. 常见问题与注意事项

  • 避免“只背概念不落地”,每题最好配一个项目场景

398-399. 兼容性问题如何修复并提升成功率?

  • 先按失败类型分桶(编码、格式、字段)

  • 分别做兼容适配和兜底策略

  • 通过灰度验证,观察成功率提升

400. 项目里最大困难及解决?

  • 重点说“跨团队协作+技术方案落地+结果量化”

401. 并发导致数据不一致怎么处理?

  • 事务隔离级别、乐观锁/悲观锁、分布式锁、消息最终一致性

402. 死锁排查常用JDK命令?

  • jstack <pid>(最常用)

  • 可配合 jcmdjmap 做进一步分析

403. 什么是死锁,四个条件,如何避免?

  • 条件:互斥、占有且等待、不可剥夺、循环等待

  • 避免:固定加锁顺序、超时锁、减少锁嵌套、无锁化改造

两块二每分钟