重生——第五次面试2026.8.1一面

1、B 树 与 B + 树

(1)基础定义

二者都是多路平衡查找树,专为磁盘 IO 优化设计(磁盘按页加载数据,减少 IO 次数是核心目标)。

B 树
  1. 所有节点(根、中间、叶子)都存储索引键 + 完整数据记录
  2. 叶子节点无链表串联;
  3. 查找一条数据可能命中任意层级节点。
B + 树(MySQL InnoDB 索引默认结构)
  1. 非叶子节点只存索引键,不存真实数据,全部数据仅放在叶子节点;
  2. 叶子节点用双向链表有序串联;
  3. 主键、二级索引结构区分:
    • 主键索引:叶子存整行完整数据;
    • 二级索引:叶子只存储主键值。

(2)核心对比

特性 B 树 B + 树
数据存放位置 所有节点都存数据 仅叶子节点存数据
叶子节点关系 无链表相连 有序双向链表
单次查找 IO 次数 不稳定,最优根节点命中 高度固定,IO 次数稳定
范围查询 需要多次遍历树 遍历链表即可,效率极高
磁盘利用率 较低,非叶子占用大量空间 非叶子节点紧凑,一页能放下更多索引,树更低矮

适用场景

  • B 树:MongoDB 等数据库索引、文件系统索引;
  • B + 树:MySQL InnoDB,擅长等值查询 + 范围查询、排序、分页。

2、HTTP 常用状态码分类与作用

状态码由三位数字组成,分为 5 大类:

1xx 信息类(协商阶段)

  • 100 Continue:客户端可继续发送剩余请求体;
  • 101 Switching Protocols:协议切换(HTTP 升级为 WebSocket)。

2xx 请求成功

  • 200 OK:常规请求成功;
  • 201 Created:资源创建成功(POST 新增数据);
  • 204 No Content:执行成功无返回数据;
  • 206 Partial Content:断点续传分片下载。

3xx 重定向

  • 301 Moved Permanently:永久重定向,浏览器缓存跳转地址;
  • 302 Found:临时重定向,每次请求都跳转;
  • 304 Not Modified:协商缓存命中,资源未修改,直接用本地缓存。

4xx 客户端错误

  • 400 Bad Request:请求参数格式错误;
  • 401 Unauthorized:未登录、无身份认证;
  • 403 Forbidden:登录成功,但权限不足;
  • 404 Not Found:资源地址不存在;
  • 405 Method Not Allowed:请求方式不支持(GET 接口用 POST 调用);
  • 413 Payload Too Large:请求体超出服务器限制。

5xx 服务端内部错误

  • 500 Internal Server Error:服务器代码异常、空指针、SQL 报错;
  • 502 Bad Gateway:网关 / 反向代理后端服务挂了;
  • 503 Service Unavailable:服务过载、停机维护、限流拒绝;
  • 504 Gateway Timeout:网关等待下游服务超时。

3、INNER JOIN、LEFT JOIN(左连接)

数据表约定

A 表(左表)、B 表(右表),关联字段 id

  1. INNER JOIN 内连接 只返回两张表关联条件完全匹配的数据; 两边不匹配的数据全部丢弃。

    SELECT * FROM A INNER JOIN B ON A.id=B.id;

适用:需要两边数据都存在的关联查询。

  1. LEFT JOIN 左外连接左表 A 为全集 ,左表所有数据全部保留; 右表匹配上则拼接数据,没匹配上时右表字段全部填充 NULL

    SELECT * FROM A LEFT JOIN B ON A.id=B.id;

补充 RIGHT JOIN、FULL JOIN

  • RIGHT JOIN:右表全集,左表无匹配补 NULL;
  • FULL JOIN:两张表所有数据都保留,无匹配端补 NULL(MySQL 不原生支持)。

经典场景

LEFT JOIN:查询所有订单,连带可选的支付记录;没有支付的订单也要展示。

4、JVM 内存模型(运行时数据区)

JVM 规范定义的运行时五大内存分区,分为线程私有、线程共享两大类:

一、线程私有(每个线程独立,生命周期随线程)

  1. 程序计数器 PC Register 记录当前线程执行到哪一行字节码指令; 唯一不会发生 OOM 的区域。
  2. 虚拟机栈(Java 栈) 存放栈帧:局部变量表、操作数栈、动态链接、方法出口; 基本数据类型、对象引用存在此处; 栈深度过大:StackOverflowError;栈内存不足:OOM。
  3. 本地方法栈 执行 native 本地方法(C/C++),结构和虚拟机栈一致。

二、线程共享(全局唯一,JVM 启动创建)

  1. 堆 Heap(重中之重) 所有对象实例、数组分配在堆;GC 主要回收区域; 细分:新生代(Eden、S0、S1)、老年代;堆内存耗尽抛出 OOM。
  2. 方法区(元空间 Metaspace,JDK8 优化) JDK7 及以前:永久代; JDK8 改为本地内存的元空间:存储类结构、常量池、静态变量、注解、编译后的字节码; 元空间溢出也会 OOM。

易混区分

  • JVM 内存模型 ≠ Java 内存模型(JMM,规定多线程可见性、有序性);
  • JMM:主内存 + 工作内存,volatile、synchronized 依托 JMM 保证并发安全。

5、MVCC 多版本并发控制(InnoDB 核心)

核心作用

不加锁实现读不加锁、写加锁,解决读写阻塞,支撑 RC、RR 隔离级别,基于快照读实现无锁并发。

底层三大核心组件

  1. 隐藏字段 每行数据内置三个隐藏列:
  • DB_TRX_ID:最后修改该行的事务 ID;
  • DB_ROLL_PTR:回滚指针,指向 undo log 里的历史版本;
  • DB_ROW_ID:无主键时自动生成主键。
  1. undo log 回滚日志 记录数据修改前的旧版本数据,通过回滚指针串联成版本链; 事务回滚依靠 undo log,同时提供历史快照数据。
  2. read view 读视图 执行快照读时生成,用来判断当前版本数据对当前事务是否可见。

RR(可重复读)实现逻辑

事务第一次快照读时生成 ReadView,整个事务复用同一个视图; 只能看到视图创建前已提交的数据,期间其他事务修改不可见,实现可重复读。

两种读取模式

  1. 当前读(加锁读):select ... for update、update、delete,加行锁读取最新数据;
  2. 快照读(普通 select):走 MVCC 读取历史版本,无锁。

解决问题

解决 RC/RR 下脏读、不可重复读;InnoDB 依靠 MVCC + 间隙锁解决幻读。

6、双线程循环 count++ 100 次,最终结果分析

结论先行

  1. 几乎不可能等于 200
  2. 理论最小值:100;最大值理想 200;中间区间随机。

前置原理:count++ 非原子操作

count++ 编译后拆解为三步独立 CPU 指令:

  1. load:从主内存读取 count 值到线程本地工作内存;
  2. add:本地内存执行 + 1 运算;
  3. save:将计算结果写回主内存。 三步无锁、无同步,线程之间会互相覆盖。

场景拆解

设初始 count=0,线程 A、B 各循环 100 次:

1)出现数据覆盖(典型导致结果偏小)

示例:

  1. A 读取 count=0,还没执行 + 1 和写回;
  2. B 同时读取 count=0;
  3. A 本地 + 1=1,写回主内存 count=1;
  4. B 本地 + 1=1,写回主内存 count=1; 两次自增只生效一次,计数被覆盖,少 1 次。 大量并发覆盖叠加,最终总值小于 200。
2)最小值为什么是 100?

极端最坏场景: 线程 A 完整执行全部 100 次自增写入完毕; 线程 B 每一次读取的都是 A 最终写完后的最新值,B 的 100 次修改全部被无效覆盖,最终只有 A 的 100 次有效计数,count=100

如何保证最终一定 200

保证count++原子性:

  1. synchronized包裹自增代码块;
  2. 使用 AtomicInteger 原子类(CAS 无锁);
  3. ReentrantLock 加锁。

7、项目中遇到的问题 + 标准回答思路(通用面试话术)

答题固定结构:背景 → 问题现象 → 定位过程 → 解决方案 → 复盘优化

示例 1:线上接口慢,SQL 慢查询
  1. 背景:订单列表分页接口,数据量上万后接口响应 5s+;
  2. 现象:用户加载页面卡顿,监控大量慢 SQL 告警;
  3. 定位:开启慢查询日志,EXPLAIN 分析发现无索引、全表扫描、limit 大偏移量;
  4. 解决:
    • 给查询条件建立联合 B + 索引;
    • 大偏移分页改造为主键游标分页;
    • 热点数据接入 Redis 缓存;
  5. 复盘:上线前强制 SQL 审核,压测验证大数据量性能。
示例 2:Redis 缓存击穿
  1. 问题:热点商品 key 过期瞬间大量请求击穿缓存打垮 DB;
  2. 方案:互斥锁、逻辑过期时间、热点 key 永不过期。
示例 3:OOM 内存溢出
  1. 定位:dump 堆快照,MAT 分析发现大集合未释放、线程局部变量堆积;
  2. 优化:手动清空大集合、合理设置 JVM 参数、优化代码引用。

答题要点:不说空话,带上监控、日志、工具(Arthas、SkyWalking、EXPLAIN、MAT)。

8、分布式锁如何保证事务提交前锁不提前释放

痛点

Redisson 等分布式锁如果在事务未提交时,锁租期到期自动释放,其他线程抢占锁会引发脏数据。

主流解决方案

方案 1:Redisson 自带看门狗(自动续期,最优)
  1. 加锁成功后启动后台守护线程(看门狗);
  2. 锁有效期默认 30s,每隔 10s 检测持有锁的业务线程是否还在运行;
  3. 业务未执行完毕、事务未提交,自动延长锁过期时间;
  4. 业务正常结束主动解锁,看门狗停止;业务宕机,锁到期自动释放,不会死锁。

注意:必须使用 Redisson 的RLock,不要手动设置较短过期时间关闭看门狗。

方案 2:手动控制锁生命周期
  1. 先开分布式锁 → 再开启数据库事务
  2. 事务执行完毕、commit 提交成功后,主动释放锁
  3. 禁止事务未提交就解锁;
  4. 兜底:设置合理锁超时,超时时间必须大于业务事务最大执行耗时。
方案 3:锁粒度后置

把解锁逻辑放到事务提交之后的 finally 代码块,保证只有事务落地才释放锁。

禁忌操作

不要在事务中途主动解锁;不要锁过期时间设置比业务执行时间短。

9、AI 框架 ChatMemory 对话记忆实现原理

保存历史上下文对话,让大模型具备连续对话记忆,不用每次重复输入历史对话。

主流实现方案

1、内存型 Memory(临时,服务重启丢失)
  1. 以会话 ID 为 key,内存 Map 存储用户 + AI 历史消息列表;
  2. 每次提问前拼接全部历史消息 + 当前用户问题,一起送入 LLM;
  3. 缺点:进程重启丢失数据,只适合测试。
2、持久化 Memory(生产常用)

存储介质:Redis、MySQL、MongoDB

  • sessionId 划分独立会话;
  • 每条消息记录角色(user/assistant/system)、时间、内容;
  • 策略 1:全量上下文拼接,会话越长 Prompt 越长,token 飙升;
  • 策略 2:滑动窗口截断,只保留最近 N 轮对话,控制 token 消耗;
  • 策略 3:摘要压缩,定时把久远对话总结为简短摘要,减少上下文长度。
3、向量型记忆(长期记忆 RAG)

历史对话向量化存入向量库; 用户新问题向量化检索相似度最高的历史对话,注入上下文,实现超长周期记忆。

核心流程

用户提问 → 查询当前 session 历史记忆 → 组装 prompt 上下文 → 调用大模型 → 模型回复后把本轮问答存入 Memory。

10、EasyExcel 五万条数据稳定导入 + 弱网下断点续传落地方案

一、五万条稳定导入优化(EasyExcel 原生优势 + 业务优化)

1、EasyExcel 本身优势

对比 POI:POI 加载全表到内存极易 OOM;EasyExcel 基于 SAX 逐行流式读取,一行读取一行处理,内存占用极低,五万条轻松无压力。

稳定落地优化手段
  1. 分批读取 + 分批入库 设置每次读取 100~500 条为一批,攒批后执行批量 INSERT;避免单条循环插入数据库,降低 IO 开销;
  2. 异步解耦(大数据量) 前端上传文件→后端接收文件存临时存储(OSS / 本地)→返回任务 ID; MQ 发送导入任务,消费者异步读取 EasyExcel 解析入库,前端轮询任务进度;
  3. 前置数据校验 解析阶段校验格式、手机号、重复主键、必填项,非法数据记录行号与错误原因,导出错误 Excel,不阻塞整体导入;
  4. 内存防护 关闭不必要对象缓存,解析时及时回收临时对象,禁止一次性加载全表数据;
  5. 事务控制:分批批次内开启事务,失败回滚当前批次,已成功批次不回滚。

二、弱网环境断点续传实现

整体思路:前端分片上传 + 后端断点标记 + EasyExcel 分片解析
  1. 前端分片切割 前端将 Excel 按固定大小分片(5MB / 片),携带文件唯一 md5、分片序号、总分片数; 弱网断连后,前端只重新上传未完成分片; 后端记录每个文件分片上传状态,全部分片上传完成后合并为完整 Excel。

  2. 解析阶段断点续导(解析中途失败续跑)

  • 数据库记录表:导入任务表(taskId,已解析行数、状态、文件地址、失败标记)
  • EasyExcel 自定义监听器,每解析 N 行更新数据库已处理行数;
  • 程序崩溃、网络中断、服务重启后,用户发起续导请求;
  • 后端读取任务已解析行数,使用 EasyExcel跳过前面已处理行,从断点行继续解析入库。
  1. 弱网兜底 文件先持久化到云存储,不占用服务器临时内存;网络波动只重传分片,无需从头上传整个 Excel; 超时重传机制,前端指数退避重试分片上传。

整体链路

前端分片上传→后端合并完整文件→创建导入任务→EasyExcel 流式分批解析 + 定时记录断点→异常后根据 taskId 从断点续解析→导入完成生成结果报告。

相关推荐
DarLing丶张皇1 小时前
【源码】JeecgBoot导出Excel模板
java·spring boot
ocean'1 小时前
防火墙策略路由
linux·服务器·数据库
SkyStream1 小时前
无人机系列-篇三-AI视频识别接入架构与优化
后端
223糖1 小时前
Idea,pycharm2026激活保姆级教程
java·ide·intellij-idea
SkyStream1 小时前
无人机系列-篇二-ZLMediaKit多路流管理与分发
后端
纪念 2291 小时前
数据库基础
数据库·笔记·学习方法
songgz2 小时前
zVM统一OS与DB
jvm·数据库·vm
brave_zhao2 小时前
mysql的安装
数据库·mysql
小马9262 小时前
GPT-6 Swarm 架构深度解析:从单体模型到 Agent 集群协同的范式跃迁
gpt·架构·大模型·openai·agent·分布式ai·swarm架构