JVM与性能调优
1. 线上服务CPU飙升到100%,你如何排查?
考察点:Linux命令、JDK工具链、实战排查思路。
参考答案 :
这是经典的线上故障排查题,核心思路是"定位高负载线程 -> 分析堆栈"。
- 定位进程 :使用
top命令找到占用CPU最高的Java进程ID(PID)。 - 定位线程 :使用
top -Hp PID查看该进程下所有线程的CPU占用情况,找到占用最高的线程ID(TID)。 - 进制转换 :将TID转换为16进制(
printf "%x\n" TID),因为jstack日志中线程ID是十六进制的。 - 分析堆栈 :使用
jstack PID | grep 16进制TID -A 20查看该线程的堆栈信息。- 情况A :如果是
RUNNABLE状态且在执行业务代码,说明是算法复杂度高或死循环。 - 情况B :如果是
VM Thread或GC task thread,说明是频繁Full GC导致的(此时需结合jstat -gcutil验证)。
- 情况A :如果是
2. 什么是双亲委派模型?为什么要破坏它?(如Tomcat/SPI)
考察点:类加载机制、框架设计原理。
参考答案:
- 核心原理 :当一个类加载器收到加载请求时,它首先不会自己去尝试加载,而是把这个请求委派给父类加载器去完成。只有当父加载器反馈自己无法完成时(抛出
ClassNotFoundException),子类才会尝试自己加载。 - 好处 :保证Java核心类库的类型安全(例如用户无法自定义
java.lang.String来替换核心类)。 - 为什么要破坏(打破) :
- SPI机制(如JDBC) :JDBC接口在
rt.jar(启动类加载器加载),而MySQL驱动在classpath(应用类加载器加载)。启动类加载器无法感知应用类加载器,因此使用线程上下文类加载器(ThreadContextClassLoader),让父类加载器请求子类加载器去加载驱动。 - Tomcat :为了实现Web应用之间的隔离(不同应用加载不同版本的Spring),Tomcat自定义了
WebAppClassLoader,优先加载WEB-INF下的类,打破了双亲委派。
- SPI机制(如JDBC) :JDBC接口在
数据库(MySQL)深度优化
3. 什么是"回表"?如何避免?(覆盖索引)
考察点:索引底层结构、SQL优化实战。
参考答案:
- 回表 :InnoDB的普通索引(二级索引)叶子节点存储的是主键ID 。如果查询的字段不包含在索引中,数据库需要先查普通索引拿到主键ID,再拿着主键ID去**聚簇索引(主键索引)**中查找完整的行数据。这个过程叫回表。
- 避免回表(覆盖索引) :
- 如果查询的字段恰好是索引列(例如
select id, name from user where name = 'Alice',且name上有索引),则不需要回表。 - 优化手段 :建立联合索引 。例如经常查询
select id, age, name,可以建立(name, age)的联合索引,这样查询时直接从索引树获取数据,无需回表。
- 如果查询的字段恰好是索引列(例如
4. 事务隔离级别与MVCC(多版本并发控制)原理
考察点:事务特性、并发一致性、Undo Log。
参考答案:
- 隔离级别 :
- 读未提交:存在脏读。
- 读已提交(RC):解决脏读,存在不可重复读。
- 可重复读(RR,MySQL默认):解决脏读和不可重复读,通过MVCC很大程度上解决了幻读。
- MVCC原理 :
- 核心是隐藏字段 (DB_TRX_ID记录事务ID、DB_ROLL_PTR指向Undo Log)、Undo Log (版本链)和Read View(读视图)。
- RC级别:每次Select都生成一个新的Read View,所以能读到最新提交的数据。
- RR级别:第一次Select生成Read View,后续复用,保证同一事务内看到的数据一致。
Redis与分布式缓存
5. 缓存穿透、击穿、雪崩的区别及解决方案
考察点:高并发缓存场景、系统稳定性。
参考答案:
- 缓存穿透 :
- 现象 :查询根本不存在的数据,请求直接打到数据库。
- 解决 :
- 布隆过滤器:在缓存前加一层过滤。
- 缓存空对象:即使数据库没查到,也存一个null值到Redis(设置短过期时间)。
- 缓存击穿 :
- 现象 :某个热点Key突然过期,大量并发瞬间击穿到数据库。
- 解决 :
- 互斥锁(Mutex):只让一个线程查库重建缓存,其他等待。
- 逻辑过期:不设置TTL,在Value里存过期时间,异步更新。
- 缓存雪崩 :
- 现象 :大量Key同时过期,或Redis宕机。
- 解决 :
- 随机过期时间:在原有过期时间上加一个随机值。
- 高可用:Redis哨兵或集群模式。
6. 分布式锁的实现方案(Redis vs Zookeeper)
考察点:分布式协调、CAP权衡。
参考答案:
- Redis实现(Redlock) :
- 原理 :
setnx key value+expire(原子性)。 - 优点:性能极高(AP模型)。
- 缺点:极端情况下(主从切换)可能丢失锁,导致安全性问题。
- 原理 :
- Zookeeper实现 :
- 原理 :创建临时顺序节点。客户端监听前一个节点,前一个节点删除后,当前节点获得锁。
- 优点:强一致性(CP模型),无死锁风险。
- 缺点:频繁创建/删除节点,性能不如Redis。
微服务与架构设计
7. 分布式事务解决方案(CAP与BASE理论)
考察点:数据一致性、微服务架构。
参考答案 :
在微服务拆分后,本地事务失效,需要分布式事务。
- 2PC(两阶段提交):强一致性,但性能差,阻塞资源(如XA协议)。
- TCC(Try-Confirm-Cancel) :
- 原理:应用层的两阶段。Try阶段预留资源,Confirm提交,Cancel回滚。
- 场景:对一致性要求高且并发高的核心业务(如支付)。
- 最终一致性(BASE理论) :
- 本地消息表/MQ事务消息:先执行本地事务,发送消息,下游消费。如果失败则重试。
- Seata(AT模式):阿里开源,基于Undo Log自动回滚,对代码无侵入,适合大多数业务。
8. 消息队列(MQ)如何保证消息不丢失?
考察点:消息可靠性、系统设计。
参考答案 :
需要从三个阶段保证:
- 生产者阶段 :
- 开启Confirm机制 (RabbitMQ)或ACK机制(Kafka/RocketMQ)。只有Broker确认收到消息,生产者才认为发送成功。
- MQ服务端(Broker)阶段 :
- 持久化:开启消息持久化(同步刷盘或异步刷盘),防止宕机丢失。
- 多副本:主从同步,防止单点故障。
- 消费者阶段 :
- 手动ACK:关闭自动ACK。业务逻辑执行成功后,再手动发送ACK给Broker。如果处理失败,返回NACK触发重试。
算法与数据结构(实战类)
9. 如何设计一个限流算法?(令牌桶 vs 漏桶)
考察点:高并发防护、算法应用。
参考答案:
- 令牌桶算法 :
- 原理:固定速率向桶中放入令牌,桶有容量上限。请求处理前需获取令牌,无令牌则拒绝或等待。
- 特点 :允许突发流量(只要桶里有令牌)。
- 应用:Guava RateLimiter,适合保护下游服务同时允许短时间突发。
- 漏桶算法 :
- 原理 :水(请求)流入桶,桶底以固定速率漏水(处理)。桶满则溢出(拒绝)。
- 特点 :强行限制流出速率,平滑流量。
- 应用:适合对流出流量有严格限制的场景(如短信发送接口)。
10. 常见的排序算法中,哪些是稳定的?快排的时间复杂度?
考察点:基础算法功底。
参考答案:
- 稳定性 :指排序后,相同元素的相对位置不发生改变。
- 稳定:冒泡排序、插入排序、归并排序。
- 不稳定:快速排序、选择排序、堆排序。
- 快速排序 :
- 平均时间复杂度: O(nlogn)O(nlogn) 。
- 最坏情况: O(n2)O(n2) (当数组已有序,且每次选第一个为基准时)。
- 优化:随机选取基准值,或使用"三数取中"法。