Skip to content

面试题背诵手册 ​

基于「面试经验.md」整理的 12 大知识模块,每题配答案要点,便于背诵。


一、微服务 ​

Q1. 什么是微服务架构?它和单体架构有什么区别? ​

答:

  • 单体架构:所有功能模块打包在一个 WAR/JAR 中,共享一个数据库,部署在一个进程内。
  • 微服务架构:按业务能力拆分为多个独立服务,每个服务独立部署、独立数据库、独立技术栈,通过 HTTP/RPC 通信。
  • 核心区别:
    维度单体微服务
    部署一次部署整体独立部署
    数据库共享每服务独立
    技术栈统一可异构
    扩展整体扩展按需扩展单个服务
    故障影响全局局部隔离

Q2. 微服务核心组件有哪些?分别解决什么问题? ​

答:

组件作用代表实现
服务注册与发现服务上下线自动感知Nacos、Eureka、Consul
配置中心集中管理配置,动态刷新Nacos Config、Apollo
服务网关统一入口、路由、鉴权、限流Spring Cloud Gateway、Zuul
负载均衡请求分发Ribbon、LoadBalancer
服务调用声明式 RPCOpenFeign、Dubbo
熔断降级故障隔离、防止雪崩Sentinel、Hystrix
分布式事务跨服务数据一致性Seata
链路追踪全链路日志SkyWalking、Zipkin
消息总线配置广播Spring Cloud Bus

Q3. 微服务拆分原则有哪些? ​

答:

  1. 单一职责原则(SRP):每个服务只负责一个业务领域。
  2. 领域驱动设计(DDD):按限界上下文(Bounded Context)拆分。
  3. 高内聚低耦合:服务内部功能紧密相关,服务间依赖最少。
  4. 数据库独占:每个服务拥有独立数据库,禁止跨库 Join。
  5. 合适的粒度:不要过细(导致分布式事务复杂),不要过粗(失去微服务意义)。
  6. 团队自治:一个服务对应一个小团队(康威定律)。

Q4. 微服务之间如何通信?各有什么优缺点? ​

答:

  • HTTP/REST(OpenFeign):
    • 优点:通用、跨语言、调试方便。
    • 缺点:性能一般、序列化开销大。
  • RPC(Dubbo、gRPC):
    • 优点:性能高、二进制传输、长连接。
    • 缺点:跨语言支持差、调试复杂。
  • 消息队列(异步解耦):
    • 优点:异步解耦、削峰填谷。
    • 缺点:一致性要求高的场景不适用。

Q5. 微服务雪崩如何防止?熔断降级怎么实现? ​

答:

  • 雪崩原因:服务 A 调用 B,B 响应慢导致 A 线程池耗尽,A 又被 C 调用,最终整个链路垮掉。
  • 三大防线:
    1. 熔断(Circuit Breaker):错误率/慢调用达阈值后直接快速失败,类似保险丝。Sentinel 的熔断策略:慢调用比例、异常比例、异常数。
    2. 降级(Fallback):返回兜底数据(默认值、缓存、友好提示)。
    3. 限流:限制 QPS,超过阈值拒绝请求。Sentinel 的限流算法:快速失败、Warm Up、匀速排队。
  • 舱壁隔离:线程池/信号量隔离,防止一个服务拖垮整个 Tomcat 线程池。

Q6. 服务注册与发现原理?Nacos 和 Eureka 区别? ​

答:

  • 原理:服务启动时向注册中心注册(IP+端口),消费者从注册中心拉取服务列表并缓存本地,通过心跳维持。
  • Nacos vs Eureka:
    维度NacosEureka
    CAP支持 AP/CP 切换仅 AP
    健康检查TCP/HTTP/自定义心跳
    配置中心自带需 Config Server
    推送方式UDP+定时拉取定时拉取
    实例上下线推送通知客户端拉取

二、Redis 备份 ​

Q1. Redis 持久化有哪几种方式?区别是什么? ​

答:

维度RDBAOF
原理定时快照,存储数据二进制追加写命令日志
文件dump.rdb(紧凑)appendonly.aof(较大)
恢复速度快慢
数据安全可能丢失最后一次快照后数据最少丢 1 秒(everysec)
触发方式save/bgsave/自动实时追加
适用场景备份、容灾数据安全性要求高

Q2. RDB 的触发时机和 bgsave 原理? ​

答:

  • 触发时机:
    1. save:同步阻塞,生产禁用。
    2. bgsave:异步后台执行,主进程继续服务。
    3. 配置自动触发:save 900 1(900秒内1次修改)、save 300 10、save 60 10000。
    4. 主从全量同步时触发。
    5. shutdown 命令退出时触发。
  • bgsave 原理:
    1. 主进程 fork() 出子进程(利用 COW 写时复制技术)。
    2. 子进程遍历所有数据写入临时 RDB 文件。
    3. 写完后替换旧文件,通知主进程。
    4. fork 期间主进程阻塞,内存越大阻塞越久(这是 RDB 的缺点)。

Q3. AOF 的三种刷盘策略?怎么选? ​

答:

策略含义性能安全
always每条命令都刷盘最差最高(不丢数据)
everysec每秒刷盘一次折中较高(最多丢1秒)
no交给 OS 决定最好最差
  • 推荐:everysec,性能和安全均衡,生产默认。

Q4. AOF 重写(Rewrite)是什么?为什么需要? ​

答:

  • 问题:AOF 文件随命令追加不断变大,比如 incr key 执行 100 次,AOF 记录 100 条,但实际只存了 1 个值。
  • 重写:扫描当前内存数据,用最少的命令重新生成 AOF 文件,去除冗余。
  • 触发:
    • 自动:auto-aof-rewrite-percentage 100(文件比上次重写后大一倍)、auto-aof-rewrite-min-size 64mb。
    • 手动:bgrewriteaof 命令。
  • 原理:fork 子进程,遍历内存生成新 AOF,期间新命令同时写入旧 AOF 和重写缓冲区,完成后合并。

Q5. Redis 4.0 混合持久化是什么? ​

答:

  • 背景:RDB 恢复快但丢数据,AOF 数据全但恢复慢。
  • 混合持久化:AOF 重写时,先以 RDB 格式写入当前内存快照,再以 AOF 格式追加重写期间的增量命令。
  • 优点:兼顾恢复速度和数据完整性。
  • 开启:aof-use-rdb-preamble yes(4.0+ 默认开启)。

Q6. Redis 主从复制原理? ​

答:

  • 全量同步(首次或断线太久):
    1. Slave 连接 Master,发送 PSYNC ? -1。
    2. Master 执行 bgsave 生成 RDB,发送给 Slave。
    3. Master 同时把生成 RDB 期间的新命令写入复制缓冲区,发送给 Slave。
    4. Slave 加载 RDB 并执行缓冲区命令,完成同步。
  • 增量同步(断线后重连):
    1. Slave 发送 PSYNC <replicationId> <offset>。
    2. Master 检查 offset 是否在复制积压缓冲区(默认 1MB)内。
    3. 在则只发送缺失部分;不在则全量同步。
  • 命令传播:同步完成后,Master 每条写命令实时传播给 Slave。

Q7. Redis 哨兵(Sentinel)和集群(Cluster)区别? ​

答:

维度哨兵集群
架构一主多从 + 哨兵监控节点多主多从,分片存储
数据分片不分片,全量复制16384 槽位分片
故障转移哨兵自动选主节点间 Gossip 协议自动选主
容量瓶颈单机内存上限水平扩展
适用场景数据量小、读多写少数据量大、高并发

三、HashMap 加入/删除/扩容原理 ​

Q1. HashMap 的底层数据结构? ​

答:

  • JDK 1.7:数组 + 链表(头插法,并发可能形成环导致死循环)。
  • JDK 1.8:数组 + 链表 + 红黑树(尾插法)。
    • 链表长度 ≥ 8 且数组长度 ≥ 64:转红黑树。
    • 红黑树节点 ≤ 6:退化为链表。
  • 默认参数:
    • 初始容量 16,负载因子 0.75,扩容阈值 = 容量 × 0.75。
    • 树化阈值 8,退化阈值 6,树化最小数组容量 64。

Q2. HashMap 的 put 流程? ​

答:

  1. 计算 key 的 hash:hash = (h = key.hashCode()) ^ (h >>> 16)(扰动,让高位也参与运算,减少冲突)。
  2. 定位桶下标:index = (n - 1) & hash(n 是数组长度,必为 2 的幂次,位运算等价取模但更快)。
  3. 桶为空:直接放入。
  4. 桶非空:
    • 第一个节点 key 相等:覆盖 value。
    • 是树节点:调用红黑树插入。
    • 是链表:尾插法遍历,找到则覆盖,找不到则尾插。插入后长度 ≥ 8 尝试树化。
  5. ++size > threshold:扩容。

Q3. HashMap 的扩容机制? ​

答:

  • 触发条件:size > capacity × loadFactor(默认 16 × 0.75 = 12)。
  • 扩容过程:
    1. 创建新数组,容量翻倍(newCap = oldCap << 1)。
    2. 遍历旧数组每个桶:
      • 单节点:重新计算下标 newIndex = (newCap - 1) & hash,由于容量翻倍,新位置要么是原位置,要么是「原位置 + 旧容量」。
      • 链表:JDK 1.8 拆分为「低位链」和「高位链」两条,分别放到新数组的原位置和原位置+旧容量。
      • 红黑树:拆分后若节点 ≤ 6 退化为链表。
  • 为什么容量是 2 的幂次:
    1. index = (n-1) & hash 位运算代替取模,性能高。
    2. 扩容时只需判断 hash 的新增高位是 0 还是 1,决定是否迁移。

Q4. HashMap 的 get 流程? ​

答:

  1. 计算 key 的 hash。
  2. 定位桶 index = (n - 1) & hash。
  3. 桶为空:返回 null。
  4. 第一个节点 key 相等:返回 value。
  5. 是树节点:红黑树查找 O(logN)。
  6. 是链表:遍历查找 O(N)。

Q5. HashMap 为什么线程不安全? ​

答:

  • JDK 1.7:并发 put 头插法导致链表成环,get 时死循环。
  • JDK 1.8:
    • 并发 put 可能数据覆盖(两个线程同时判断桶为空,一个写入被覆盖)。
    • size 计数不准。
    • 扩容时数据丢失。
  • 替代方案:
    • ConcurrentHashMap(推荐):JDK 1.8 用 CAS + synchronized 锁单桶,并发安全且性能好。
    • Collections.synchronizedMap:全表锁,性能差。

Q6. HashMap 的 hash 为什么要扰动? ​

答:

  • hashCode() 是 int(32位),如果直接 index = hash % n,只有低位参与运算,冲突率高。
  • 扰动 (h = hashCode) ^ (h >>> 16) 让高 16 位和低 16 位异或,使高位特性也影响下标,分布更均匀,减少冲突。

四、JVM ​

Q1. JVM 内存区域划分?哪些是线程私有?哪些共享? ​

答:

区域线程私有作用是否会 OOM
程序计数器是记录当前执行字节码行号不会
虚拟机栈是方法栈帧(局部变量、操作数栈)是(StackOverflowError / OOM)
本地方法栈是Native 方法栈帧是
堆否对象实例、数组是
方法区否类信息、常量、静态变量是
运行时常量池否方法区内,字面量、符号引用是
直接内存否NIO 的 DirectByteBuffer是

Q2. 对象创建过程? ​

答:

  1. 类加载检查:常量池定位符号引用,检查是否已加载。
  2. 分配内存:
    • 指针碰撞:内存规整(Serial、ParNew)。
    • 空闲列表:内存不规整(CMS)。
    • 并发安全:CAS + 失败重试,或 TLAB(每个线程预分配一块)。
  3. 初始化零值:成员变量赋默认值(0、null)。
  4. 设置对象头:Mark Word(hash、年龄、锁状态)、类型指针、数组长度。
  5. 执行 <init>:构造函数赋初值。

Q3. 对象的内存布局? ​

答:

  • 对象头:
    • Mark Word:hash、GC 年龄、锁标志位、偏向线程 ID。
    • 类型指针:指向类元数据。
    • 数组长度(数组对象才有)。
  • 实例数据:字段值(相同宽度的字段放一起,父类在前)。
  • 对齐填充:8 字节对齐。

Q4. 类加载过程?双亲委派模型? ​

答:

  • 加载→验证→准备→解析→初始化→使用→卸载:
    1. 加载:通过类全限定名获取二进制字节流,生成 Class 对象。
    2. 验证:文件格式、元数据、字节码、符号引用。
    3. 准备:静态变量赋默认值(int=0)。
    4. 解析:符号引用替换为直接引用。
    5. 初始化:执行 <clinit>,静态变量赋初值、静态代码块。
  • 双亲委派:类加载时先委托父加载器加载,父加载不了再自己加载。
    • 启动类加载器(Bootstrap):rt.jar。
    • 扩展类加载器(Extension):ext/*.jar。
    • 应用类加载器(App):classpath。
    • 好处:防止核心类被篡改(自定义 java.lang.String 会被 Bootstrap 加载),避免重复加载。
    • 打破场景:SPI(JDBC)、Tomcat(每个 Web 应用独立 ClassLoader)、OSGi。

Q5. GC 判断对象存活的方法? ​

答:

  • 引用计数法:每被引用+1,引用失效-1。问题:循环引用(A 引 B、B 引 A)导致永远不死。早期 Python 用,JVM 不用。
  • 可达性分析:从 GC Roots 出发,能到达的对象存活,否则回收。
    • GC Roots:
      1. 虚拟机栈中的局部变量。
      2. 方法区的静态变量。
      3. 方法区的常量。
      4. 本地方法栈 JNI 引用。
      5. 活跃线程。

Q6. 四种引用类型? ​

答:

引用特性应用
强引用Object o = new Object(),永不回收普通引用
软引用内存不足时回收缓存(SoftReference)
弱引用下次 GC 必回收ThreadLocal、WeakHashMap
虚引用形同虚设,仅跟踪回收事件跟踪对象被 GC 的时机

Q7. 垃圾收集算法? ​

答:

  1. 标记-清除:标记存活对象,清除其他。缺点:内存碎片。
  2. 标记-复制:将存活对象复制到另一半,清空原区。用于新生代(Eden:S0:S1 = 8:1:1)。
  3. 标记-整理:标记存活对象,向一端移动整理。用于老年代。
  4. 分代收集:
    • 新生代:复制算法(对象朝生夕死,存活少)。
    • 老年代:标记-清除/整理(存活多,不适合复制)。

Q8. 常见垃圾收集器? ​

答:

收集器作用域算法特点
Serial新生代复制单线程,STW,客户端
ParNew新生代复制Serial 多线程版,配合 CMS
Parallel Scavenge新生代复制吞吐量优先
Serial Old老年代标记整理单线程
Parallel Old老年代标记整理多线程,吞吐量
CMS老年代标记清除低延迟,4阶段:初始标记→并发标记→重新标记→并发清除
G1整堆Region化标记整理可预测停顿,大堆
ZGC整堆Region + 染色指针<10ms 停顿,TB 级堆

Q9. JVM 调优常见参数? ​

答:

  • 堆:-Xms(初始堆)、-Xmx(最大堆)、-Xmn(新生代)。
  • 元空间:-XX:MetaspaceSize、-XX:MaxMetaspaceSize。
  • 栈:-Xss(线程栈大小,默认 512K~1M)。
  • GC:-XX:+UseG1GC、-XX:MaxGCPauseMillis=200。
  • GC 日志:-Xloggc:、-XX:+PrintGCDetails。
  • OOM 时 dump:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=。

五、xxl-job 多实例启动问题 ​

Q1. xxl-job 是什么?核心架构? ​

答:

  • 分布式任务调度平台:调度中心(Admin)+ 执行器(业务系统集成 SDK)。
  • 架构:
    • 调度中心:负责任务调度、日志管理、配置管理。
    • 执行器:业务系统嵌入 SDK,接收调度并执行任务。
    • 通信:HTTP。
  • 特点:动态路由、失败重试、告警、任务分片、日志可视化。

Q2. 多个 jar 包部署会有多个 xxl-job 执行器同时启动,如何解决? ​

答:

  • 问题场景:微服务多实例部署时,每个实例都注册成执行器,同一个任务可能被多个实例同时执行,导致重复执行。
  • 解决方案:

方案一:保证执行器 appname + 注册地址唯一 ​

  • 每个实例注册时使用唯一地址(IP + 不同端口):
    yaml
    xxl:
      job:
        executor:
          appname: xxx-job
          address:        # 留空则自动获取
          ip:             # 留空则自动获取
          port: 9999      # 每个实例不同端口
  • 调度中心看到的是多个执行器实例,任务按路由策略分发。

方案二:合理配置路由策略 ​

路由策略说明
FIRST / LAST第一个/最后一个,单机执行
ROUND轮询,多机分担
RANDOM随机
CONSISTENT_HASH一致性哈希
LEAST_FREQUENTLY_USED最不经常使用
LEAST_RECENTLY_USED最近最久未使用
FAILOVER故障转移,顺序心跳检测,跳过故障机
BUSYOVER忙碌转移
SHARDING_BROADCAST分片广播,所有实例都执行(适合大数据分片)
  • 单实例执行:用 FIRST 或 FAILOVER。
  • 分布式处理:用 SHARDING_BROADCAST,每个实例处理一部分分片。

方案三:分布式锁保证幂等 ​

  • 任务逻辑入口加 Redis 分布式锁:
    java
    @XxlJob("demoJobHandler")
    public void execute() {
        String lockKey = "job:demoJob:lock";
        String requestId = UUID.randomUUID().toString();
        try {
            // 尝试加锁,过期时间略大于任务执行时间
            Boolean locked = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, requestId, 30, TimeUnit.MINUTES);
            if (!Boolean.TRUE.equals(locked)) {
                XxlJobHelper.log("未获取到锁,跳过执行");
                return;
            }
            // 执行业务逻辑
            doBusiness();
        } finally {
            // 释放锁需校验 requestId,防止误删
            releaseLock(lockKey, requestId);
        }
    }

方案四:保证业务幂等 ​

  • 即使重复执行也无影响,例如:状态机判断、唯一索引、insert ignore、update ... where status=xxx。

Q3. xxl-job 路由策略「分片广播」怎么用? ​

答:

  • 所有执行器实例都会被调度,每个实例通过 XxlJobHelper.getShardIndex() 和 getShardTotal() 获取自己处理的分片。
  • 示例:
    java
    @XxlJob("shardingJob")
    public void shardingJob() {
        int index = XxlJobHelper.getShardIndex();  // 当前实例编号
        int total = XxlJobHelper.getShardTotal();  // 总实例数
        // 处理 id % total == index 的数据
        List<Long> ids = mapper.selectByShard(index, total);
        process(ids);
    }
  • 适合大数据量处理(如百万用户推送、对账)。

六、MQ 消息消费 ​

Q1. MQ 消费消息的两种模式? ​

答:

  1. Pull(拉)模式:消费者主动从 Broker 拉取消息。
    • 优点:消费速率可控。
    • 缺点:实时性差,需轮询。
    • RocketMQ 默认 Pull。
  2. Push(推)模式:Broker 主动推给消费者。
    • 优点:实时性好。
    • 缺点:消费者处理慢会堆积。
    • RabbitMQ 是 Push(长轮询实现)。

Q2. 如何保证消息不丢失? ​

答: 消息从生产到消费经过三环节:生产者→Broker→消费者,每环都可能丢。

环节丢失原因解决方案
生产者→Broker网络异常、Broker 宕机1. 同步发送 + 重试
2. Confirm 机制(RabbitMQ)/ ACK(RocketMQ)
3. 本地消息表 + 定时补偿
Broker 存储宕机内存丢失1. 持久化(磁盘)
2. 主从复制
3. 镜像队列(RabbitMQ)/ 同步刷盘(RocketMQ)
Broker→消费者消费者处理失败但已 ACK1. 手动 ACK,处理成功后才确认
2. 失败重试 + 死信队列

Q3. 如何保证消息顺序性? ​

答:

  • 全局有序:单队列单消费者,性能差,极少用。
  • 局部有序(推荐):同一业务 ID 的消息发到同一队列,由同一消费者消费。
  • 实现:
    • RabbitMQ:单队列单消费者,或一致性哈希交换器。
    • RocketMQ:MessageQueueSelector 按 orderId % 队列数 选择队列,消费端 MessageListenerOrderly 单线程消费同一队列。
    • Kafka:同一 key 的消息进入同一分区,分区内有序。

Q4. 如何保证消息幂等(避免重复消费)? ​

答:

  • 原因:网络抖动导致 ACK 丢失,Broker 重发;消费者重启。
  • 方案:
    1. 唯一 ID + 去重表:业务消息表,消费前检查是否已处理。
    2. Redis setnx:setnx(msgId, 1, expire),成功才处理。
    3. 状态机:业务状态只能前进,update ... where status='待支付'。
    4. 数据库唯一索引:业务表唯一键约束。
    5. 乐观锁:version 字段。

Q5. 如何处理消息积压? ​

答:

  • 临时扩容:
    1. 增加消费者数量(注意 Kafka 消费者数不能超过分区数)。
    2. 临时 Topic 转发:消费后不处理,直接转发到一个新 Topic(分区数扩大 10 倍),再用更多消费者处理。
  • 优化消费逻辑:
    1. 批量处理(一次拉多条)。
    2. 异步化:消费后丢线程池异步处理,快速 ACK。
    3. 跳过堆积消息:先丢弃非关键消息(埋点日志)。
  • 根因排查:消费者是否慢 SQL?外部依赖超时?线程池满?

Q6. 死信队列(DLX/DLQ)是什么?什么时候用? ​

答:

  • 死信:被拒绝(nack/reject 且 requeue=false)、TTL 过期、队列满的消息。
  • 应用场景:
    1. 消费失败重试 N 次仍失败,转入 DLQ 人工处理。
    2. 延迟队列:发到 TTL 队列,过期后进入死信目标队列,实现延迟消费。
  • RabbitMQ 配置:
    java
    Map<String, Object> args = new HashMap<>();
    args.put("x-dead-letter-exchange", "dlx.exchange");
    args.put("x-dead-letter-routing-key", "dlx.routing.key");
    args.put("x-message-ttl", 60000);
    channel.queueDeclare("biz.queue", true, false, false, args);

Q7. RocketMQ 和 Kafka 和 RabbitMQ 区别?怎么选型? ​

答:

维度KafkaRocketMQRabbitMQ
语言Scala/JavaJavaErlang
吞吐量百万级十万级万级
延迟msmsμs
顺序分区有序分区有序队列有序
事务弱强(事务消息)弱
延迟消息不支持支持(18个级别)插件/TTL+DLX
应用场景日志、大数据金融、电商订单业务消息、复杂路由

七、负载均衡算法 ​

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

答:

  1. 轮询(Round Robin):1→2→3→1→2→3,按顺序循环。
  2. 加权轮询(Weighted Round Robin):按权重比例分配,性能差异大时使用。如 A:B:C = 5:2:1。
  3. 随机(Random):随机选一个,简单。
  4. 加权随机(Weighted Random):按权重概率随机。
  5. 源地址哈希(IP Hash):相同 IP 固定到同一服务器,可保持 Session。
  6. 最小连接数(Least Connections):选当前连接数最少的,适合长连接。
  7. 一致性哈希(Consistent Hash):环状哈希空间,节点增减只影响相邻段,减少迁移。
  8. 最快响应(Fastest Response):选响应时间最短的。

Q2. 一致性哈希原理?为什么需要? ​

答:

  • 问题:普通哈希 hash % N,节点数 N 变化时几乎全部数据要迁移。
  • 一致性哈希:
    1. 将整个哈希空间组织成环(0 ~ 2^32 - 1)。
    2. 节点哈希后落到环上某点。
    3. 数据顺时针找到的第一个节点就是它的归属。
    4. 节点增减只影响环上相邻段的数据。
  • 虚拟节点:解决数据倾斜(节点少时分布不均),每个物理节点对应 150+ 虚拟节点。
  • 应用:Redis Cluster 的槽位、Nginx 一致性哈希、Dubbo 路由。

Q3. 轮询和加权轮询的实现?Nginx 加权轮询算法? ​

答:

  • Nginx 加权轮询算法(平滑加权轮询):
    • 每个节点有 当前权重 currentWeight 和 有效权重 effectiveWeight。
    • 每次选择时:所有 currentWeight += effectiveWeight,选最大 currentWeight 的节点,将其 currentWeight -= 总权重。
    • 效果:权重高的节点被分散选择,不会集中命中。
    • 示例(A=5, B=1, C=1):
      初始 [0,0,0]
      1. [5,1,1] → 选A → [-2,1,1]
      2. [3,2,2] → 选A → [-4,2,2]
      3. [1,3,3] → 选B → [1,-4,3]
      ...

Q4. Ribbon 的负载均衡策略有哪些? ​

答:

策略类说明
轮询(默认)RoundRobinRule默认策略
随机RandomRule随机选
重试RetryRule轮询+失败重试
最佳可用BestAvailableRule选并发最小的
可用过滤AvailabilityFilteringRule过滤故障,再选
响应时间加权WeightedResponseTimeRule按响应时间分配权重
区域感知ZoneAvoidanceRuleAWS 场景,避开故障区域
一致性哈希—需自定义

Q5. Dubbo 的负载均衡策略? ​

答:

策略说明
Random(默认)按权重随机
RoundRobin加权轮询
LeastActive最少活跃调用数,慢的少分配
ConsistentHash一致性哈希,相同参数请求同一提供者
ShortestResponse最短响应时间(2.7+)

八、Docker 与 K8s ​

Q1. Docker 是什么?核心概念? ​

答:

  • Docker:基于 Linux 内核 namespace(隔离)和 cgroup(资源限制)的容器化技术,相比虚拟机更轻量。
  • 核心概念:
    概念说明
    镜像(Image)只读模板,分层存储
    容器(Container)镜像运行实例,可读写层
    仓库(Registry)镜像存储分发,Docker Hub、Harbor
    Dockerfile镜像构建脚本
    数据卷(Volume)持久化数据
    网络(Network)bridge/host/overlay

Q2. Docker 和虚拟机区别? ​

答:

维度虚拟机Docker
隔离级别操作系统级进程级
资源占用GB 级MB 级
启动分钟秒
镜像大小GBMB
性能弱于原生接近原生
系统支持每个VM独立OS共享宿主机内核

Q3. Dockerfile 常用指令? ​

答:

dockerfile
FROM openjdk:17              # 基础镜像
WORKDIR /app                # 工作目录
COPY target/app.jar app.jar # 拷贝
EXPOSE 8080                 # 声明端口
ENV TZ=Asia/Shanghai        # 环境变量
ENTRYPOINT ["java","-jar","app.jar"]  # 启动命令
  • CMD vs ENTRYPOINT:
    • CMD:容器启动默认命令,可被 docker run 后参数覆盖。
    • ENTRYPOINT:启动命令,参数追加到后面,不易覆盖。
    • 推荐组合:ENTRYPOINT + CMD(CMD 作为 ENTRYPOINT 的默认参数)。

Q4. Docker 网络模式有哪些? ​

答:

模式说明应用
bridge(默认)桥接 docker0,每容器独立 IP大多数场景
host共享宿主机网络栈,无独立 IP性能敏感
none无网络安全隔离
container共享另一容器网络sidecar
overlay跨主机通信K8s、Swarm

Q5. K8s 是什么?核心组件? ​

答:

  • Kubernetes:容器编排系统,负责容器部署、扩缩容、故障恢复、负载均衡。
  • Master 节点:
    组件作用
    kube-apiserverAPI 入口,所有操作都经过它
    etcd分布式 KV 存储,保存集群状态
    kube-scheduler调度 Pod 到合适 Node
    kube-controller-manager控制器集合(副本、节点、端点等)
  • Worker 节点:
    组件作用
    kubelet管理 Pod 生命周期,向 Master 汇报
    kube-proxy维护网络规则,实现 Service 负载均衡
    容器运行时Docker / containerd

Q6. Pod 是什么?为什么 K8s 用 Pod 而不直接用容器? ​

答:

  • Pod:K8s 最小调度单位,包含一个或多个共享网络和存储的容器。
  • 为何用 Pod:
    1. 容器间共享网络(同 localhost)、共享 Volume。
    2. 适合 sidecar 模式(如主业务容器 + 日志收集容器)。
    3. 解耦:Pod 内多容器同生命周期。
  • 一个 Pod 一个 IP,Pod 内容器共享。

Q7. K8s 常见工作负载? ​

答:

类型说明场景
Deployment无状态应用,管理 ReplicaSetWeb 服务
StatefulSet有状态,稳定网络标识 + 持久存储数据库、MQ
DaemonSet每个 Node 跑一个日志采集、监控
Job / CronJob一次性 / 定时任务批处理、xxl-job
Service服务抽象 + 负载均衡对外暴露

Q8. Service 类型有哪些? ​

答:

类型说明
ClusterIP集群内访问(默认)
NodePort节点端口暴露,范围 30000-32767
LoadBalancer云厂商 LB
ExternalNameCNAME 到外部域名

Q9. 滚动更新和回滚原理? ​

答:

  • 滚动更新:Deployment 默认策略,新建 RS,逐步增加新版本 Pod,减少旧版本 Pod,零停机。
  • 回滚:kubectl rollout undo deployment/xxx --to-revision=N,恢复到历史 ReplicaSet。
  • 控制参数:
    • maxSurge:滚动时最多超出期望副本数(默认 25%)。
    • maxUnavailable:滚动时最多不可用副本数(默认 25%)。

九、MySQL 索引与三大日志 ​

Q1. 索引数据结构?为什么用 B+ 树? ​

答:

  • InnoDB 索引:B+ 树。
  • 为什么 B+ 树:
    1. 对比二叉树:数据量大时树太高,磁盘 IO 多。
    2. 对比 B 树:B+ 树非叶子节点不存数据,扇出更大,树更矮;叶子节点用链表连接,范围查询高效。
    3. 对比红黑树:同样树高问题。
    4. 对比 Hash:Hash 不支持范围查询、排序。
  • 特性:
    • 叶子节点存数据,非叶子节点存索引。
    • 叶子节点用双向链表连接。
    • 一个节点 = 一页(16KB),3~4 层可存千万级数据。

Q2. 聚簇索引和非聚簇索引区别? ​

答:

维度聚簇索引非聚簇索引(二级索引)
数据存储叶子节点存整行数据叶子节点存主键值
数量每表一个(主键)多个
查询直接返回数据需回表查询主键索引
InnoDB主键即聚簇唯一/普通索引为二级
  • 回表:二级索引查到主键,再到聚簇索引查整行数据。
  • 覆盖索引:查询字段都在二级索引中,无需回表。explain 中 Extra 显示 Using index。

Q3. 索引失效场景有哪些? ​

答:

  1. 不满足最左前缀:联合索引 (a,b,c),查询条件无 a 或跳过 a。
  2. 索引列做运算/函数:WHERE age + 1 = 20、WHERE YEAR(create_time)。
  3. 类型隐式转换:字符串列 WHERE phone = 13800000000(数字转字符串)。
  4. !=、<>、NOT IN、NOT LIKE:可能全表扫描。
  5. LIKE 以 % 开头:LIKE '%abc',LIKE 'abc%' 可用索引。
  6. OR 两边不都有索引:一边无索引则全表。
  7. ORDER BY 字段不在索引中或顺序不一致。
  8. 优化器认为全表扫描更快:数据量小或区分度低(如性别)。
  9. IS NULL / IS NOT NULL:可能不走索引。

Q4. redo log 是什么? ​

答:

  • 作用:保证事务的持久性(D),崩溃恢复后数据不丢。
  • 原理:InnoDB 引擎层日志,记录「数据页的物理修改」。事务提交时先写 redo log(顺序写,性能高),再异步刷数据页到磁盘(随机写)。
  • WAL(Write-Ahead Log):先写日志,再写数据。崩溃后通过 redo log 恢复。
  • 刷盘策略(innodb_flush_log_at_trx_commit):
    • 0:每秒刷盘,崩溃丢 1 秒数据。
    • 1:每次提交都刷盘(默认,最安全)。
    • 2:提交时写到 OS Buffer,每秒 fsync。
  • 大小:固定大小循环写,写满后触发刷盘。

Q5. bin log 是什么? ​

答:

  • 作用:MySQL Server 层日志,用于复制和数据恢复。
  • 格式:
    格式内容优缺点
    StatementSQL 语句日志小,但函数/now() 不一致
    Row行变更(前像+后像)准确,但日志大
    Mixed混合折中
  • 与 redo log 区别:
    维度redo logbin log
    层级InnoDB 引擎Server 层
    内容物理日志(页修改)逻辑日志(SQL/行变更)
    写入循环写追加写
    用途崩溃恢复复制、备份恢复
    事务提交时已写提交后才写

Q6. undo log 是什么? ​

答:

  • 作用:保证事务的原子性(A)和实现 MVCC。
  • 原理:记录数据修改前的旧值(前像)。
    • 事务回滚:用 undo log 恢复数据。
    • MVCC:旧版本数据通过 undo log 链找到,供快照读。
  • 类型:
    • insert undo log:insert 产生的,事务提交后即删除。
    • update undo log:update/delete 产生的,用于 MVCC,无事务依赖后由 Purge 线程清理。

Q7. 两阶段提交(redo log + bin log)? ​

答:

  • 目的:保证 redo log 和 bin log 一致,避免主从数据不一致。
  • 流程:
    1. Prepare:写 redo log,标记为 prepare 状态。
    2. 写 bin log。
    3. Commit:写 redo log,标记为 commit 状态。
  • 崩溃恢复:
    • redo log 已 commit:直接提交。
    • redo log 是 prepare:
      • 检查 bin log 是否完整:完整则提交,不完整则回滚。

Q8. MVCC 原理? ​

答:

  • 目的:多版本并发控制,提升并发性能,解决读写冲突。
  • 组成:
    1. 隐藏字段:DB_TRX_ID(事务 ID)、DB_ROLL_PTR(指向 undo log 的指针)。
    2. undo log 版本链:每次修改通过 undo log 形成历史版本链。
    3. ReadView:记录「活跃事务列表」,决定哪个版本可见。
  • 可见性判断(RC 每次快照读生成新 ReadView,RR 只在第一次生成):
    • 版本的 trx_id < min_trx_id:可见。
    • 版本的 trx_id > max_trx_id:不可见。
    • 在活跃列表中:不可见。
    • 否则可见。
  • RC vs RR:
    • RC(读已提交):每次 SELECT 都生成新 ReadView,能看到最新提交。
    • RR(可重复读):事务第一次 SELECT 时生成 ReadView,后续复用,保证可重复读。

十、设计模式 ​

Q1. 单例模式(重点) ​

答:

  • 定义:确保一个类只有一个实例,并提供全局访问点。
  • 应用场景:配置管理、线程池、日志对象、Spring Bean(默认单例)。

饿汉式(线程安全) ​

java
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() { return INSTANCE; }
}
  • 类加载时创建,线程安全,但可能浪费内存。

懒汉式(双重检查,推荐) ​

java
public class Singleton {
    // volatile 防止指令重排序(new 不是原子操作)
    private static volatile Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {                 // 第一次检查,避免无谓加锁
            synchronized (Singleton.class) {
                if (instance == null) {         // 第二次检查,防止重复创建
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 为什么要 volatile:new Singleton() 实际是三步:分配内存→初始化→赋值引用。指令重排序可能变为「分配→赋值→初始化」,其他线程拿到未初始化对象。

静态内部类(推荐) ​

java
public class Singleton {
    private Singleton() {}
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() { return Holder.INSTANCE; }
}
  • 利用类加载机制保证线程安全,懒加载。

枚举(最佳,防反射破坏) ​

java
public enum Singleton {
    INSTANCE;
    public void method() {}
}
  • 天然防反射、防序列化破坏。

Q2. 单例模式被反射破坏怎么办?序列化破坏怎么办? ​

答:

  • 反射破坏:通过 setAccessible(true) 调用私有构造器创建新实例。
    • 防御:构造器中判断实例是否已存在,存在则抛异常。
    • 枚举单例:JVM 保证枚举单例,反射无法创建。
  • 序列化破坏:反序列化时创建新对象。
    • 防御:实现 readResolve() 方法返回实例。
    • 枚举单例:序列化机制保证枚举单例。

Q3. 工厂模式 ​

答:

  • 定义:定义创建对象的接口,让子类决定实例化哪个类。
  • 应用场景:
    • 对象创建逻辑复杂。
    • 客户端不关心创建细节。
    • 需要解耦创建和使用。
  • 三种形态:
    1. 简单工厂:一个工厂类通过 if/else 创建不同产品。违反开闭原则。
    2. 工厂方法:每个产品对应一个工厂类,扩展新产品时新增工厂。类多。
    3. 抽象工厂:工厂创建一系列相关产品(产品族)。
  • 实战案例:
    java
    // 支付方式工厂
    public interface PayFactory {
        Pay createPay();
    }
    public class AliPayFactory implements PayFactory {
        public Pay createPay() { return new AliPay(); }
    }
    public class WeChatPayFactory implements PayFactory {
        public Pay createPay() { return new WeChatPay(); }
    }
    // 使用
    PayFactory factory = new AliPayFactory();
    Pay pay = factory.createPay();
    pay.pay(100);
  • Spring 中的工厂:BeanFactory 创建 Bean,FactoryBean 自定义创建逻辑。

Q4. 策略模式 ​

答:

  • 定义:定义一系列算法,封装起来,可互相替换。
  • 应用场景:
    • 多种支付方式、多种折扣算法、多种排序方式。
    • 消除大量 if/else。
  • 实战案例:
    java
    // 策略接口
    public interface DiscountStrategy {
        BigDecimal discount(BigDecimal price);
    }
    // 满减
    public class FullReductionStrategy implements DiscountStrategy {
        public BigDecimal discount(BigDecimal price) {
            return price.compareTo(new BigDecimal("100")) >= 0
                ? price.subtract(new BigDecimal("20")) : price;
        }
    }
    // 打折
    public class PercentageStrategy implements DiscountStrategy {
        private BigDecimal rate;
        public PercentageStrategy(BigDecimal rate) { this.rate = rate; }
        public BigDecimal discount(BigDecimal price) { return price.multiply(rate); }
    }
    // 上下文
    public class DiscountContext {
        private DiscountStrategy strategy;
        public void setStrategy(DiscountStrategy strategy) { this.strategy = strategy; }
        public BigDecimal calc(BigDecimal price) { return strategy.discount(price); }
    }
  • 结合 Spring:将策略 Bean 注入 Map,按 key 选择:
    java
    @Component
    public class StrategyFactory {
        @Autowired
        private Map<String, DiscountStrategy> strategyMap;
        public DiscountStrategy get(String type) { return strategyMap.get(type); }
    }

Q5. 策略模式和工厂模式区别? ​

答:

维度策略模式工厂模式
关注点行为/算法对象创建
客户端知道有多种策略,主动选择不关心创建细节
目的运行时切换行为解耦创建和使用
应用折扣、支付、排序实例化对象

Q6. 开闭原则(OCP)?设计模式如何体现? ​

答:

  • 开闭原则:对扩展开放,对修改关闭。新增功能时添加新代码,不改老代码。
  • 体现:
    • 工厂模式:新增产品 → 新增工厂类,不改老工厂。
    • 策略模式:新增策略 → 新增策略类,不改上下文。
    • 单例模式:本身不体现,但单例避免重复创建。

十一、MySQL 事务与 Spring 事务 ​

Q1. 事务的 ACID 特性? ​

答:

特性含义由谁保证
原子性 Atomicity全部成功或全部回滚undo log
一致性 Consistency数据合法状态业务 + AID
隔离性 Isolation并发事务互不干扰锁 + MVCC
持久性 Durability提交后永久保存redo log

Q2. MySQL 事务隔离级别?各级别解决什么问题? ​

答:

隔离级别脏读不可重复读幻读
读未提交(RU)✓ 可能✓ 可能✓ 可能
读已提交(RC)×✓ 可能✓ 可能
可重复读(RR,默认)××✓ 可能(InnoDB 用间隙锁解决)
串行化(Serializable)×××
  • 脏读:读到未提交的数据。
  • 不可重复读:同一事务两次读同一行,结果不同(被 update)。
  • 幻读:同一事务两次查询,行数不同(被 insert/delete)。
  • InnoDB 在 RR 级别:通过 MVCC(快照读)+ 间隙锁(当前读)解决幻读。

Q3. MySQL 的锁有哪些? ​

答:

  • 按粒度:
    锁粒度冲突
    表锁表级锁整表,并发差
    行锁行级锁索引项,并发好
    页锁页级介于两者
  • 按类型:
    锁含义
    共享锁(S锁,读锁)LOCK IN SHARE MODE,可读不可写
    排他锁(X锁,写锁)FOR UPDATE,独占
  • InnoDB 行锁:
    • 记录锁(Record Lock):锁单条索引记录。
    • 间隙锁(Gap Lock):锁索引区间,防止插入,解决幻读。
    • 临键锁(Next-Key Lock):记录锁 + 间隙锁,锁前开后区间。
  • 意向锁:表级,标识表内有行锁,加速判断是否有冲突。

Q4. Spring 事务的传播行为? ​

答:

传播行为说明
REQUIRED(默认)有事务加入,没有则新建
REQUIRES_NEW总是新建,挂起当前事务
NESTED嵌套事务,外层回滚则内层也回滚,内层回滚不影响外层
SUPPORTS有事务加入,没有则非事务执行
NOT_SUPPORTED非事务执行,挂起当前事务
NEVER非事务执行,有事务则抛异常
MANDATORY必须在事务中,否则抛异常
  • 常用:REQUIRED(默认)、REQUIRES_NEW(独立事务,如日志记录)、NESTED(部分回滚)。

Q5. Spring 事务失效的场景? ​

答:

  1. 方法非 public:Spring AOP 默认只代理 public 方法。
  2. 自调用:同类中 A 方法调用 B 方法,B 的 @Transactional 失效(未走代理)。
    • 解决:从容器获取代理对象调用,或 AopContext.currentProxy()。
  3. 异常被 try-catch 吞掉:@Transactional 默认只对 RuntimeException 回滚,需 rollbackFor = Exception.class。
  4. 抛出非 RuntimeException:默认不回滚,需配置 rollbackFor。
  5. 数据库不支持事务:如 MyISAM 引擎。
  6. 传播行为配置错误:如 NOT_SUPPORTED、NEVER。
  7. Bean 未被 Spring 管理:new 出来的对象无代理。
  8. 多线程:事务绑定当前线程,新线程无事务上下文。

Q6. @Transactional 原理? ​

答:

  • 基于 AOP 代理:Spring 为 @Transactional 标注的 Bean 创建代理对象。
  • 执行流程:
    1. 调用代理方法。
    2. 代理拦截,开启事务(TransactionManager)。
    3. 执行目标方法。
    4. 正常返回 → 提交事务;抛异常 → 回滚(按 rollbackFor 判断)。
    5. 释放资源。
  • 核心接口:
    • PlatformTransactionManager:事务管理器。
    • TransactionDefinition:事务定义(传播、隔离、超时、只读)。
    • TransactionStatus:事务状态。

Q7. 分布式事务怎么处理? ​

答:

  • 2PC(两阶段提交):协调者 prepare → commit。问题:单点、阻塞、数据不一致。
  • 3PC:CanCommit → PreCommit → DoCommit。引入超时,缓解阻塞。
  • TCC(Try-Confirm-Cancel):业务层面两阶段,需写补偿代码。高性能但侵入。
  • Saga:长事务拆分为多个本地事务 + 补偿事务。适合长流程。
  • 本地消息表:业务表 + 消息表同事务,定时扫描消息表发 MQ。最终一致。
  • 事务消息(RocketMQ):半消息 → 本地事务 → commit/rollback。最终一致。
  • Seata:
    • AT 模式:自动生成回滚 SQL,无侵入。
    • TCC 模式:业务补偿。
    • Saga 模式:长事务编排。
    • XA 模式:强一致性。

十二、排序算法与查找 ​

Q1. 常见排序算法的时间/空间复杂度? ​

答:

算法平均时间最坏时间空间稳定
冒泡O(n²)O(n²)O(1)✓
选择O(n²)O(n²)O(1)×
直接插入O(n²)O(n²)O(1)✓
希尔O(n^1.3)O(n²)O(1)×
快排O(nlogn)O(n²)O(logn)×
归并O(nlogn)O(nlogn)O(n)✓
堆排序O(nlogn)O(nlogn)O(1)×
基数O(d·n)O(d·n)O(n)✓

Q2. 冒泡排序代码? ​

答:

java
public void bubbleSort(int[] arr) {
    int n = arr.length;
    for (int i = 0; i < n - 1; i++) {
        boolean swapped = false;  // 优化:无交换说明已有序
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
                swapped = true;
            }
        }
        if (!swapped) break;
    }
}
  • 每轮把最大元素「冒泡」到末尾,每轮比较次数减 1。

Q3. 直接插入排序代码? ​

答:

java
public void insertionSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        int key = arr[i];   // 待插入元素
        int j = i - 1;
        // 向左找第一个比 key 小的位置,沿途元素后移
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;   // 插入
    }
}
  • 类似扑克牌整理:把第 i 个元素插入前 i-1 个有序序列中。
  • 小数据量优势:近乎有序时效率高,常作为快排/归并的子过程。

Q4. 二分查找代码?前提和注意事项? ​

答:

  • 前提:数组有序、随机访问(链表不行)。
java
// 标准版(非递归)
public int binarySearch(int[] arr, int target) {
    int left = 0, right = arr.length - 1;
    while (left <= right) {              // 注意 <=
        int mid = left + (right - left) / 2;  // 防溢出
        if (arr[mid] == target) return mid;
        else if (arr[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}
  • 变体:
    • 查找第一个等于 target:arr[mid] >= target 时 right = mid - 1,最后检查 left。
    • 查找最后一个等于 target:arr[mid] <= target 时 left = mid + 1。
  • 注意:
    • mid = (left + right) / 2 在 left、right 大时溢出,应用 left + (right - left) / 2。
    • 循环条件 <= 还是 < 取决于区间定义。

Q5. 快速排序原理和代码? ​

答:

  • 思想:分治。选一个 pivot,小于放左,大于放右,递归处理左右子数组。
  • 平均 O(nlogn),最坏 O(n²)(已有序且选首尾为 pivot)。优化:三数取中、随机 pivot。
java
public void quickSort(int[] arr, int l, int r) {
    if (l >= r) return;
    int pivot = arr[l], i = l, j = r;
    while (i < j) {
        while (i < j && arr[j] >= pivot) j--;
        while (i < j && arr[i] <= pivot) i++;
        if (i < j) { int t = arr[i]; arr[i] = arr[j]; arr[j] = t; }
    }
    arr[l] = arr[i]; arr[i] = pivot;
    quickSort(arr, l, i - 1);
    quickSort(arr, i + 1, r);
}

Q6. 归并排序原理? ​

答:

  • 分治:递归拆半 → 合并有序子数组。
  • 稳定,O(nlogn),但需 O(n) 额外空间。
  • 应用:外部排序(大数据分块排序后归并)、链表排序、求逆序对。

Q7. 堆排序原理? ​

答:

  • 堆:完全二叉树,父节点 ≥/≤ 子节点(大/小顶堆)。
  • 步骤:
    1. 建堆:从最后一个非叶子节点向上调整,O(n)。
    2. 排序:堆顶(最大)与末尾交换,堆大小减 1,下沉调整,重复。
  • 特点:原地排序,O(nlogn),不稳定。

面试技巧 ​

  1. 不会就说不会,别硬编。
  2. 答到点上:先说结论,再展开原理,最后结合项目场景。
  3. 主动引导:往自己熟悉的方向引(如被问 MySQL,主动提一下项目里的优化案例)。
  4. 代码题:先讲思路再写代码,边写边解释。
  5. 面完问面试官反馈:厚脸皮问表现和建议,下次改进。

高频追问预演 ​

微服务 ​

  • Q:你们微服务怎么划分的?依据什么?
  • Q:服务间调用超时怎么处理?重试会不会导致重复扣款?
  • Q:网关怎么做限流?令牌桶和漏桶区别?

Redis ​

  • Q:缓存雪崩、缓存击穿、缓存穿透怎么解决?
  • Q:缓存和数据库一致性怎么保证?(延迟双删、Canal、消息)
  • Q:Redis 为什么这么快?(内存、单线程避免上下文切换、IO 多路复用、高效数据结构)

HashMap ​

  • Q:ConcurrentHashMap 在 1.7 和 1.8 的实现区别?
  • Q:为什么树化阈值是 8?(泊松分布,概率极低)

JVM ​

  • Q:线上 OOM 怎么排查?(jmap dump、MAT 分析)
  • Q:CPU 100% 怎么排查?(top 找进程、jstack 找线程、找死循环或 GC 频繁)
  • Q:怎么选择 GC 收集器?(堆大小、停顿要求、吞吐量)

xxl-job ​

  • Q:xxl-job 调度中心高可用怎么做?(多节点 + DB 一致性锁)
  • Q:任务执行失败怎么重试?(配置重试次数 + 失败告警)

MQ ​

  • Q:怎么实现延迟消息?
  • Q:消息积压 100 万条怎么办?
  • Q:如何保证消费者处理顺序又不影响吞吐?

负载均衡 ​

  • Q:Nginx 的负载均衡策略有哪些?(轮询、加权、ip_hash、fair、url_hash)
  • Q:服务网格(Istio)的负载均衡在哪一层?

Docker/K8s ​

  • Q:Docker 镜像分层原理?怎么减小镜像大小?(多阶段构建、Alpine 基础镜像)
  • Q:K8s 的 Service 怎么找到 Pod?(kube-proxy + iptables/ipvs + Endpoints)
  • Q:Pod 一直在 Pending 怎么排查?(资源不足、调度约束、镜像拉取失败)

MySQL ​

  • Q:联合索引 (a,b,c),哪些查询能走索引?
  • Q:explain 中的 type、Extra、key_len 各代表什么?
  • Q:慢 SQL 怎么优化?

设计模式 ​

  • Q:项目里用了什么设计模式?解决了什么问题?
  • Q:JDK 里有哪些设计模式?(IO 流装饰者、集合迭代器、Runnable 适配器)

事务 ​

  • Q:Spring 事务和数据库事务什么关系?
  • Q:什么时候用 REQUIRES_NEW?(日志、审计,独立提交不影响主流程)
  • Q:分布式事务你们怎么做的?(Seata AT / 本地消息表 / RocketMQ 事务消息)

算法 ​

  • Q:为什么快排平均比归并快?(常数因子小、原地、缓存友好)
  • Q:百万数据找 TopK?(小顶堆,O(nlogk))

背诵建议:

  1. 先背框架(每个问题的要点 1-2-3)。
  2. 再填充细节(数字、对比表)。
  3. 最后结合项目场景讲一遍,加深印象。
  4. 每天复习 2-3 个模块,循环 3-4 遍即可烂熟。