Skip to content

1. 说说缓存击穿、缓存穿透、缓存雪崩是什么?怎么解决?

缓存击穿

对于设置了过期时间的 key,缓存在某个时间点过期时,恰好有大量并发请求访问该 Key。这些请求发现缓存过期后会从后端 DB 加载数据并回设到缓存,大并发请求可能瞬间压垮 DB。

解决方案:

  • 互斥锁:缓存失效时,先用 Redis 的 setnx 设置互斥锁,操作成功后再 load DB 并回设缓存,否则重试 get 缓存。

  • 逻辑过期:

    ① 设置 key 时,存入过期时间字段,不给 key 设置实际过期时间;

    ② 查询时,从 redis 取数据后判断时间是否过期;

    ③ 若过期,开启新线程同步数据,当前线程返回旧数据。

缓存穿透

查询一个一定不存在的数据,若从存储层查不到数据则不写入缓存,导致该不存在的数据每次请求都要到 DB 查询,可能导致 DB 挂掉(大概率是遭攻击)。

解决方案一:通常使用布隆过滤器解决。

解决方案二:把数据库的空值也缓存起来

缓存雪崩

设置缓存时采用相同过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB,DB 瞬时压力过重雪崩。(与击穿的区别:雪崩是多 key,击穿是单个 key)

解决方案:将缓存失效时间分散开,在原有失效时间基础上增加 1-5 分钟随机值,降低过期时间重复率。

2. Redis 的持久化是怎么做的?

  1. RDB:快照文件,将 Redis 内存数据写入磁盘,实例宕机恢复时从 RDB 快照恢复数据。
  2. AOF:追加文件,Redis 执行写命令时记录到该文件,实例宕机恢复时重新执行命令恢复数据。

3. Redis 的数据过期策略有哪些?

  • 惰性删除:设置 key 过期时间后不主动处理,访问该 key 时检查是否过期,过期则删除,否则返回。

  • 定期删除:每隔一段时间检查部分 key,删除过期的 key。

    Redis 实际采用:

    惰性删除 + 定期删除

    配合使用。

4. 说说 Redisson 分布式锁的底层原理?

基于 Redis 的 setnx(key 未过期则无法设置)和 lua 脚本,支持手动加锁,可控制锁失效时间(业务执行时间)和等待时间(等待锁的时间);默认不主动设置,因业务执行时间不确定。

核心特性:

  • 看门狗机制:默认锁失效时间 30s,业务未执行完且锁快到期时自动续期(续期时间为失效时间的 1/3),业务完成自动释放锁;
  • 高并发下支持自旋重试获取锁,提升性能。

5. Redis 有哪些数据类型?应用场景分别是什么?

  • string:token、验证码;
  • hash:用户信息、购物车数据;
  • List:用户操作轨迹(如最近浏览商品、搜索历史);
  • Set:App 首页随机商品 / 内容推荐;
  • ZSet(权和值):销量统计、排行榜;
  • bitmap:签到;
  • geo:地理位置(附近公里的人 / 充电桩)。

6. 说说 MySQL 优化的思路?(从定位慢 SQL、分析 sql、优化几个维度来详细的讲)

定位慢 SQL

  1. 开启慢查询日志,记录慢 SQL;
  2. 使用 skywalking 链路追踪,定位慢接口,进而查看最终执行的 SQL。

分析 SQL

使用 MySQL 执行计划explain

  • 通过keykey_len检查是否命中索引、索引是否失效;
  • 通过type字段查看是否存在全索引 / 全表扫描,判断优化空间;
  • 通过extra判断是否回表,若回表可尝试加索引或修改返回字段。

优化

  • 建表:遵循三范式、合理冗余字段、选择合适存储引擎;
  • 索引:创建合适索引(类型、复合索引);
  • SQL 层面:优化 SQL 语句、分页优化(游标分页 / 限制显示页数);
  • 架构层面:ES 搜索、分库分表、主从复制、读写分离、三级缓存(Redis、Caffeine、MySQL)。

7. MySQL 的超大分页怎么优化?

超大分页(数据量大 + limit 分页 + 排序)效率低,采用覆盖索引 + 子查询解决:

先分页查询数据的 id 字段(走覆盖索引,效率高),再通过子查询根据 id 过滤查询目标数据。

8. 事务的特性是什么?可以详细说一下吗?

ACID 特性

  • 原子性 (A):事务是不可分割的最小单元,要么全执行,要么全不执行;
  • 一致性 (C):事务执行前后,数据完整性约束保持一致;
  • 隔离性 (I):多个事务并发执行时,彼此隔离,互不干扰;
  • 持久性 (D):事务提交后,数据修改永久生效,即使系统故障也不会丢失。

底层原理

  • redo log:保证事务持久性,记录数据页的物理变化,服务宕机时用于同步数据;
  • undo log:保证原子性和一致性,记录逻辑日志,事务回滚时通过逆操作恢复数据(如删除数据时,undo log 新增 delete 语句,回滚时执行逆操作)。

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

MySQL 支持四种隔离级别:

  1. 未提交读(read uncommitted):无任何解决能力,项目中不使用;
  2. 读已提交(read committed):解决脏读,无法解决不可重复读、幻读;
  3. 可重复读(repeatable read):解决脏读、不可重复读,无法解决幻读(MySQL 默认级别);
  4. 串行化(serializable):解决所有问题,但事务串行执行,性能低。

10. 说说你项目中哪个业务使用过分库分表,怎么设计的呢?

以充电桩订单表 / 充电实时日志表为例(微服务架构,单库 + 水平分表,数据量 1000W+):

充电订单分表

  • 方案 1(用户端):4 张表,以用户 ID 为分片键,用户id和4取模,将订单数据分配到不同索引的表中(我们采用的是这种);
  • 方案 2(管理端):12 张表,以充电日期月份为分片键,月份和12取模,将订单数据分配到不同索引的表中

充电实时日志分表

按时间按月分表。

分片中间件

使用 ShardingSphere 组件,无需额外部署,引入组件并配置即可:存储时按分片键规则存入对应表,查询时自动从对应表读取。

11. B + 树和 B 树的区别?

  • B + 树:所有数据仅存于叶子节点,非叶子节点只存储索引;
  • B 树:每个节点同时存储「索引 + 数据」。

12. 什么是 AOP?项目中哪些地方使用了 AOP?AOP 的底层原理是什么?

定义

AOP(面向切面编程):不改动原有业务代码,为方法统一添加通用功能。

应用场景

统一日志记录、接口耗时监控、权限拦截 / 登录校验、统一事务管理等。

底层原理

JDK 动态代理 + CGLIB 动态代理

  • JDK 动态代理:基于接口,为实现接口的类生成代理;
  • CGLIB 动态代理:基于继承,为非接口类生成代理(子类)。

13. Spring 中事务失效的场景有哪些?

  1. 方法捕获异常但未抛出:异常被自行处理,事务无法感知,导致失效;
  2. 方法抛出检查异常:未配置rollbackFor时,事务不回滚(需在@Transactional配置rollbackFor = Exception.class);
  3. 方法非 public 修饰:Spring 事务仅对 public 方法生效。

14. Spring 的 bean 的生命周期?

  1. 读取BeanDefinition:获取 bean 的定义信息(类全路径、懒加载、单例等);
  2. 实例化:调用构造函数创建 bean 实例;
  3. 依赖注入:执行 set 方法、@Autowired等注入依赖;
  4. Aware 接口处理:若 bean 实现 Aware 接口(如 BeanNameAware),执行对应方法;
  5. BeanPostProcessor 前置处理:对 bean 进行增强;
  6. 初始化:执行InitializingBean接口方法、init-method@PostConstruct注解方法;
  7. BeanPostProcessor 后置处理:最终增强(可能生成代理对象);
  8. 销毁:容器关闭时执行销毁方法(DisposableBeandestroy-method)。

15. Springboot 自动配置原理?

@SpringBootApplication封装三个核心注解:

  • @SpringBootConfiguration:标识配置类;

  • @ComponentScan:扫描组件;

  • @EnableAutoConfiguration:自动配置核心,通过@Import导入配置选择器,读取classpath:META-INF/spring.factories中配置的类全类名;

    配置类根据条件注解(如

    @ConditionalOnClass

    )判断是否加载,满足条件则将 Bean 注入 Spring 容器。

16. Spring 的常见注解有哪些?

  1. 声明 Bean:@Component@Service@Repository@Controller
  2. 依赖注入:@Autowired@Qualifier@Resource
  3. 作用域:@Scope
  4. 配置相关:@Configuration@ComponentScan@Bean
  5. AOP 增强:@Aspect@Before@After@Around@Pointcut

17. Spring、Spring boot、Spring cloud 的区别?

  • Spring:一站式 Java 开发核心框架,提供 IOC、AOP 等核心能力;
  • Spring Boot:简化 Spring 开发的脚手架,通过自动装配、依赖传递快速开发单体项目;
  • Spring Cloud:基于 Spring Boot 的微服务全家桶,用于搭建分布式微服务架构。

18. 你们项目中有没有做过限流?怎么做的?

项目背景

微服务架构,突发 QPS 达 2000,服务仅支撑 1200QPS。

实现方案

  • 版本 1:Nginx 限流(漏桶算法),按 IP 限流,每秒 20 请求;
  • 版本 2:Spring Cloud Gateway 的RequestRateLimiter过滤器(令牌桶算法),按 IP / 路径限流,设置令牌填充速率和桶容量。

核心算法

漏桶算法、令牌桶算法。

19. 有哪些常用的限流算法?他们有什么区别呢?

  • 固定窗口算法:将时间划分为固定的区间(如1分钟),在区间内维护一个计数器。每来一个请求计数器加1,达到阈值后拒绝请求;当进入下一个时间窗口时,计数器清零。适用场景:对精度要求不高、低频接口的简单防护。
  • 滑动窗口算法:为了解决固定窗口的突刺问题,它将大时间窗口细分为多个小格子(例如把1分钟分成6个10秒的小格)。随着时间推移,窗口向右平滑移动,丢弃过期的格子,仅统计当前窗口内的总请求数。
  • 漏桶算法:请求入桶,固定速率匀速处理,适合强制削峰(无突发流量支持),比如nginx限流;
  • 令牌桶算法:按速率生成令牌,请求需获取令牌才能执行,既能平稳限流,又支持短时突发流量(项目网关限流采用)。

20. 接口幂等性如何设计?

https://gitee.com/lau112233/elegent-idem

方案 1:基于 Redis 的 SETNX

  1. 客户端获取代表请求业务的唯一字段;
  2. 将该字段以 SETNX 存入 Redis,设置超时时间;
  3. SETNX 成功 → 首次请求,执行业务;失败 → 重复请求,直接返回。

方案 2:基于 Token 检查

  1. 客户端先请求获取 Token(服务端生成全局唯一 ID,存入 Redis 并返回);

  2. 业务请求携带 Token,服务端校验:

    • 校验成功 → 执行业务,删除 Redis 中的 Token;
    • 校验失败 → 重复操作,直接返回。

重复下单 / 提交补充

  1. 前端:防抖、按钮置灰(一定时间内仅允许一次请求);
  2. 服务端:幂等性处理 + 数据库唯一约束 / 乐观锁。

21. RabbitMq 如何保证消息的可靠性?

  1. 生产者:重试机制、确认机制(return callback + confirm callback);
  2. 存储层:交换机、队列、消息的持久化;
  3. 消费者:确认机制、重试机制、消费幂等性;
  4. 兜底:死信交换机处理消息堆积 / 丢失。

22. RabbitMQ 消息的重复消费问题如何解决的?

核心:幂等性处理(同接口幂等性方案)。

23. Kafka 是如何保证消费的顺序性?

Kafka 默认不保证顺序(一个 Topic 的多分区独立存储 / 消费),需保证顺序时,将消息发送到同一个分区

  1. 发送消息时指定分区号;
  2. 按相同业务标识设置消息 Key(默认按 Key 的 Hash 值分配分区)。

24. Java 的数据类型有哪些?

  • 基本数据类型(栈):byte、short、int、long、float、double、char、boolean;
  • 引用数据类型(堆):String、对象、数组、集合。

25. Java 中的集合有哪些?有什么区别?

两大体系

  • Collection(单列):

    • List(有序可重复):ArrayList(查询快)、LinkedList(增删快);

    • Set(无序不可重复):HashSet(无序)、LinkedHashSet(保插入顺序)、TreeSet(可排序);

  • Map(双列,键值对):HashMap(线程不安全、高性能)、ConcurrentHashMap(并发安全)、TreeMap(按键排序)。

26. Hashmap 的底层原理的是什么?

数据结构

  • JDK1.7:数组 + 链表;
  • JDK1.8:数组 + 链表 / 红黑树(链表长度≥8 且数组长度 > 64 时转红黑树,≤6 时退化为链表)。

存储机制

  1. Key 计算哈希值,与数组长度取模得到索引;
  2. 索引位置无数据 → 直接存入;
  3. 哈希冲突:
    • JDK1.7:新数据挂载到链表尾部;
    • JDK1.8:新数据插入链表头部,满足条件则转红黑树。

扩容

  • 默认初始容量 16,负载因子 0.75;
  • 容量不足时,2 倍扩容,重新计算哈希值(rehash)。

27. 说说 ConcurrentHashMap 的原理?

JDK1.7

  • 结构:分段数组(Segment) + 链表;
  • 线程安全:Segment 继承 ReentrantLock,修改数据时需获取对应 Segment 的锁(分段锁)。

JDK1.8

  • 结构:与 HashMap 一致(数组 + 链表 / 红黑树);
  • 线程安全:放弃 Segment,采用 Node + CAS + Synchronized(仅锁定冲突链表 / 红黑树的首节点,哈希不冲突则无并发)。

28. 单链表和双链表有什么区别?

  • 单链表:仅存后继指针,只能从头遍历到尾,内存占用小,增删需遍历找前驱;
  • 双链表:存前驱 + 后继指针,可双向遍历,增删更高效,内存占用更大。

29. ArrayList 底层是如何实现的?

  1. 检查容量:确保 size+1 能容纳新元素;
  2. 扩容判断:size+1 > 当前数组长度 → 调用grow扩容(1.5 倍);
  3. 存储元素:将新元素放入 size 位置,size+1;
  4. 返回添加结果。

30. 创建线程的方式有哪些?他们有什么区别?

方式区别
继承 Thread单继承限制、耦合高、无返回值
实现 Runnable避免单继承、任务与线程分离、可复用、无返回值
实现 Callable有返回值、可抛异常、能获取执行结果
线程池复用线程、控制并发、减少创建销毁开销(项目开发首选)

31. 线程池的运行原理是什么?

  1. 新任务到达 → 检查核心线程数是否已满;
  2. 核心线程未满 → 新建核心线程执行;
  3. 核心线程已满 → 任务入阻塞队列排队;
  4. 队列已满 → 新建非核心线程执行;
  5. 所有线程已满 → 触发拒绝策略。

32. 线程包括哪些状态,状态之间是如何变化的?

状态定义(Thread.State)

新建(NEW)、可运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、有时限等待(TIMED_WAITING)、终结(TERMINATED)。

状态切换

  • 新建 → 可运行:调用start()
  • 可运行 → 终结:任务执行完毕;
  • 可运行 → 阻塞:获取锁失败(进入 Monitor 阻塞队列);
  • 阻塞 → 可运行:持锁线程释放锁,唤醒阻塞线程;
  • 可运行 → 等待:获取锁后调用wait()(释放锁);
  • 等待 → 可运行:其他线程调用notify()/notifyAll()
  • 可运行 → 有时限等待:调用sleep(long)/wait(long)
  • 有时限等待 → 可运行:超时自动唤醒。

33. 新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?

使用join()方法:T3 中调用 T2.join (),T2 中调用 T1.join (),确保 T1 先执行,T2 次之,T3 最后。

34. Wait、sleep、notify 分别是什么?有什么区别?

方法核心特性
sleep()主动休眠指定时间,不释放锁,时间到自动唤醒
wait()进入等待阻塞,释放锁,需等待notify()/notifyAll()唤醒
notify()唤醒一个正在 wait 的线程
notifyAll()唤醒所有正在 wait 的线程

35. 如何确定核心线程数?

IO 密集型任务(文件 / DB / 网络读写)

推荐:核心线程数 = 2N + 1(N 为 CPU 核数)。

CPU 密集型任务(计算、转换)

推荐:核心线程数 = N + 1(N 为 CPU 核数)。

36. 并发编程中的锁有哪些?他们有什么区别?

基础锁(单体项目)

  • synchronized:JVM 级锁,重量级,悲观锁;
  • ReentrantLock:JDK 级锁,可手动控制加锁 / 释放,支持悲观 / 乐观锁。

分布式锁(分布式项目)

  • Redisson:读写锁、排他锁;
  • Zookeeper:强一致性锁。

悲观锁 vs 乐观锁

  • 悲观锁:认为并发冲突必然发生,先加锁再操作(synchronized、ReentrantLock);
  • 乐观锁:认为冲突概率低,操作时不加锁,通过版本号 / CAS 判断冲突(如 MySQL 乐观锁、CAS)。

37. 什么是 CAP 理论?

CAP 代表:

  • 一致性(Consistency):所有节点数据实时一致;
  • 可用性(Availability):服务始终可用,响应请求;
  • 分区容错性(Partition tolerance):网络分区时,系统仍能运行。

分布式系统中 P 必须保证,因此只能在 C 和 A 中二选一。

38. 你在项目中哪里用了多线程?

  1. 数据同步:千万级数据从 MySQL 同步到 ES,用线程池 + CountDownLatch 避免 OOM;
  2. 大数据导出:Excel 导出大量数据,线程池分批处理;
  3. 聚合查询:用户下单后,线程池并行调用订单、商品、物流微服务接口,提升性能;
  4. 非核心任务:保存用户搜索记录,异步线程执行,不阻塞主流程。

39. 说说微服务中用户的登录态是怎么鉴权并进行流转的?

  1. 用户登录生成 JWT 令牌,前端请求携带 Token;
  2. 网关过滤器统一校验解析 Token,将用户信息通过请求头透传;
  3. 各微服务拦截器接收用户信息,存入 ThreadLocal;
  4. 统一封装公共模块,所有微服务引入该模块实现登录态全局流转。

40. 说说 jvm 的内存模型?

JVM 内存分为:

  • 元空间(Metaspace):存储类元信息、常量池等(替代永久代);
  • 堆(Heap):线程共享,存储对象实例;
  • 程序计数器:线程私有,记录当前线程执行位置;
  • 虚拟机栈:线程私有,存储方法栈帧(局部变量、操作数栈等);
  • 本地方法栈:线程私有,支持本地方法(Native)执行。

41. 说说什么是双亲委派机制?

类加载器收到加载请求时,先委派父类加载器执行,所有请求最终传至启动类加载器;仅当父类加载器无法加载(搜索范围无该类),子类加载器才自行加载。

核心目的:避免类重复加载、保证核心类(如 java.lang.String)的安全性。

42. 说说 java 堆的内存模型?

分区比例

  • 新生代:老年代 = 1:2;
  • 新生代内部:Eden:S0:S1 = 8:1:1。

对象生命周期

  1. 新对象优先分配到 Eden 区;
  2. Eden 不足 → 标记 Eden+S0 存活对象,复制到 S1,释放 Eden+S0;
  3. 再次 Eden 不足 → 标记 Eden+S1 存活对象,复制到 S0,释放 Eden+S1;
  4. 存活对象熬过 15 次 GC(默认),晋升到老年代;
  5. 大对象 / 幸存区不足 → 对象提前晋升老年代。

43. 如何定位垃圾?

  1. 引用计数法(辅助):对象被引用则计数 + 1,引用释放则 - 1,计数为 0 则标记为垃圾(无法解决循环引用);
  2. 可达性分析算法(核心):从 GC Roots(如虚拟机栈引用、类静态属性)向下遍历,不可达的对象判定为垃圾。

44. JVM 垃圾回收算法有哪些?

  1. 标记 - 清除:标记垃圾 → 清除垃圾(产生内存碎片);
  2. 复制算法:将内存分为两块,复制存活对象到另一块,清空原内存(无碎片,新生代采用);
  3. 标记 - 整理:标记垃圾 → 存活对象向一端移动 → 清除边界外垃圾(老年代采用);
  4. 分代回收:结合上述算法,新生代用复制、老年代用标记 - 整理 / 标记 - 清除。

45. 说一下 JVM 有哪些垃圾回收器?

  1. 串行收集器:单线程 GC,STW(Stop The World),适合小型应用;
  2. 并行收集器:多线程 GC,STW,追求吞吐量;
  3. CMS(并发标记清除):老年代收集器,低停顿,并发执行(标记 - 清除,产生碎片);
  4. G1(Garbage First):JDK9 默认,分区管理堆,响应时间 + 吞吐量兼顾,复制算法,支持混合收集。

46. 说说 JVM 调优的参数都有哪些?

核心调优参数:

  • 堆大小:-Xms(初始堆)、-Xmx(最大堆);
  • 新生代比例:-XX:NewRatio(新生代 / 老年代)、-XX:SurvivorRatio(Eden/S0);
  • 元空间:-XX:MetaspaceSize-XX:MaxMetaspaceSize
  • 垃圾回收器:-XX:+UseG1GC-XX:+UseCMSGC
  • GC 日志:-XX:+PrintGCDetails-XX:+PrintGCTimeStamps
  • OOM 堆转储:-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath
  • 线程栈大小:-Xss

47. 说说什么是工厂 + 策略模式?

工厂模式

  • 核心:统一工厂根据类型创建不同对象,屏蔽new过程;
  • 落地:订单类型、充电设备类型、告警消息类型的对象创建。

策略模式

  • 核心:同一行为的不同算法,通过接口多实现,运行时动态切换;
  • 落地:订单计费策略(尖峰平谷电价)、故障处理策略、支付方式策略。

48. 说说 oom 异常、CPU 飙升、线程死锁的排查思路?

OOM

  1. 导出堆快照:

    • 方案 1:程序未退出 → jmap -dump:format=b,file=dump.hprof <进程ID>
    • 方案 2:自动导出 → 启动参数添加-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/xxx/dump.hprof
  2. 分析快照:使用 Eclipse MAT/IDEA Profiler 定位内存泄漏代码。

CPU 飙升

方案 1:JDK 工具

  1. top → 找到高 CPU 进程 ID;
  2. ps H -eo pid,tid,%cpu | grep <进程ID> → 找到高 CPU 线程 ID;
  3. printf '0x%x\n' <线程ID> → 转换为 16 进制;
  4. jstack <进程ID> | grep <16进制线程ID> -A 20 → 定位问题代码。

方案 2:Arthas

  1. thread → 查看 CPU 占比最高线程;
  2. thread <线程ID> → 查看线程堆栈。

线程死锁

方案 1:jps+jstack

  1. jps → 找进程 ID;
  2. jstack <进程ID> → 查看死锁信息。

方案 2:jvisualvm

命令行执行jvisualvm,可视化查看死锁。

方案 3:Arthas

thread -b → 定位阻塞其他线程的死锁线程。

49. 接口慢的排查思路?

使用 Arthas 的trace命令,监控接口各方法的耗时,定位慢执行环节:

运行

bash
trace 全类名 方法名 --cost > 500

50. 接口报错的排查思路?

  1. 生成 TraceID:服务端入口生成唯一 TraceID;
  2. 日志埋点:所有日志输出 TraceID;
  3. 接口返回:返回值携带 TraceID;
  4. 日志排查:通过前端提供的 TraceID 快速检索日志。

51. 说说 xxl-job 和 schedule 的区别?

表格

特性Spring ScheduleXXL-Job
部署方式本地单机分布式集群
重复执行集群部署会重复执行支持负载均衡,避免重复执行
管控能力无管理页面、无日志、无重试可视化后台、日志、失败重试、告警
动态配置需重启服务无需重启,动态修改
适用场景小型单机项目微服务、分布式中大型项目

52. 说说分布式事务是什么?Seata 的不同模式的两阶段分别是什么样子的?

分布式事务

跨服务的事务(如订单创建:扣库存 + 创订单 + 扣余额),需保证多服务操作的一致性。

Seata 核心角色

  • TC:事务协调者,维护全局 / 分支事务状态;
  • TM:事务管理器,定义全局事务范围;
  • RM:资源管理器,管理分支事务。

XA 模式

一阶段(RM)

  1. 注册分支事务到 TC;
  2. 执行业务 SQL 但不提交;
  3. 报告执行状态到 TC。

二阶段(TC+RM)

  • TC:检测分支状态,全成功则通知提交,否则通知回滚;
  • RM:执行提交 / 回滚。

AT 模式

一阶段(RM)

  1. 注册分支事务;
  2. 记录 undo-log(数据快照);
  3. 执行 SQL 并提交;
  4. 报告状态到 TC。

二阶段

  • 提交:RM 删除 undo-log;
  • 回滚:RM 根据 undo-log 恢复数据。

AT vs XA

  • XA:一阶段不提交,锁定资源,强一致;
  • AT:一阶段提交,不锁定资源,最终一致,性能更高(企业主流)。

TCC模式

一阶段(RM)

  1. 注册分支事务到 TC;
  2. 执行 Try 操作:资源检查、预留 / 锁定业务资源;
  3. 报告 Try 执行状态到 TC。

二阶段

  • 提交:RM 执行 Confirm 操作:确认并正式使用预留资源,完成业务提交;
  • 回滚:RM 执行 Cancel 操作:释放预留资源,取消业务操作。

53. 说说 threadLocal 的底层原理?

  1. ThreadLocal 内部维护ThreadLocalMap
  2. set():以 ThreadLocal 为 Key,资源对象为 Value,存入当前线程的ThreadLocalMap
  3. get():以 ThreadLocal 为 Key,从当前线程的ThreadLocalMap获取 Value;
  4. remove():移除当前线程ThreadLocalMap中对应 Key 的 Value(避免内存泄漏)。

54. 说说 es 的倒排索引的原理?

  1. 分词:对文档字段分词,生成词条;
  2. 建表:创建 “词条 → 文档 ID 列表” 的映射表;
  3. 搜索:先查词条索引得到文档 ID,再根据 ID 查询文档(查询速度远快于正排索引)。

55. 如何优化 es 的查询性能?

索引设计

  1. 字段精准设计:检索用 text,过滤 / 排序用 keyword / 数值,无需检索的字段关闭 index;
  2. 分片规划:单分片 20-50G,单节点分片数≤20;
  3. 冷热分离:按时间拆分索引,仅查询热数据。

查询优化

  1. 避免深分页:用 search_after/scroll 替代 from+size;
  2. 优先用 filter 上下文(缓存、无相关性评分);
  3. 过滤返回字段(_source),减少数据传输。

56. 如何去调节 es 的 score?

核心方式:

  1. boost 加权:对字段 / 查询条件加权(如field:keyword^2);
  2. function_score:自定义函数(如权重、衰减、随机)干预评分;
  3. 衰减函数:对距离 / 时间等维度衰减评分;
  4. 负向降级:对不符合条件的文档降低评分。

57. 说出 10 个常用的 Linux 命令

  1. ls:查看目录文件;
  2. cd:切换工作目录;
  3. pwd:查看当前路径;
  4. mkdir:创建文件夹;
  5. rm:删除文件 / 目录;
  6. cp:复制文件 / 目录;
  7. mv:移动 / 重命名文件;
  8. tail:查看文件末尾(tail -f 实时日志);
  9. grep:过滤文本内容;
  10. ps:查看进程(ps -ef 查 Java 进程)。

58. 说出 5 个以上常用的 docker 命令

  1. docker pull:拉取镜像;
  2. docker images:查看本地镜像;
  3. docker run:创建并启动容器;
  4. docker exec -it:进入容器终端;
  5. docker ps:查看运行中容器(ps -a 查看所有);
  6. docker start/stop/restart:启停 / 重启容器;
  7. docker rm/rmi:删除容器 / 镜像;
  8. docker logs:查看容器日志。

59. AI 工具的使用?

使用 Claude Code:

  1. 需求设计:辅助分析需求,生成需求说明书、数据库设计等文档;
  2. 原型图:Claude Code + Pencil 生成原型;
  3. 开发流程:生成开发规范文档,按默认 /plan/openspec/harness engineering 模式开发,需求审查迭代;
  4. 测试:编写测试用例,执行测试。

60. 说说Redis的主从同步流程

主从同步分为了两个阶段,一个是全量同步,一个是增量同步

全量同步是指从节点第一次与主节点建立连接的时候使用全量同步,流程是这样的:

第一:从节点请求主节点同步数据,其中从节点会携带自己的replication id和offset偏移量。

第二:主节点判断是否是第一次请求,主要判断的依据就是,主节点与从节点是否是同一个replication id,如果不是,就说明是第一次同步,那主节点就会把自己的replication id和offset发送给从节点,让从节点与主节点的信息保持一致。

第三:在同时主节点会执行bgsave,生成rdb文件后,发送给从节点去执行,从节点先把自己的数据清空,然后执行主节点发送过来的rdb文件,这样就保持了一致

当然,如果在rdb生成执行期间,依然有请求到了主节点,而主节点会以命令的方式记录到缓冲区,缓冲区是一个日志文件,最后把这个日志文件发送给从节点,这样就能保证主节点与从节点完全一致了,后期再同步数据的时候,都是依赖于这个日志文件,这个就是全量同步

增量同步指的是,当从节点服务重启之后,数据就不一致了,所以这个时候,从节点会请求主节点同步数据,主节点还是判断不是第一次请求,不是第一次就获取从节点的offset值,然后主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步