1. 说说缓存击穿、缓存穿透、缓存雪崩是什么?怎么解决?
缓存击穿
对于设置了过期时间的 key,缓存在某个时间点过期时,恰好有大量并发请求访问该 Key。这些请求发现缓存过期后会从后端 DB 加载数据并回设到缓存,大并发请求可能瞬间压垮 DB。
解决方案:
互斥锁:缓存失效时,先用 Redis 的 setnx 设置互斥锁,操作成功后再 load DB 并回设缓存,否则重试 get 缓存。
逻辑过期:
① 设置 key 时,存入过期时间字段,不给 key 设置实际过期时间;
② 查询时,从 redis 取数据后判断时间是否过期;
③ 若过期,开启新线程同步数据,当前线程返回旧数据。
缓存穿透
查询一个一定不存在的数据,若从存储层查不到数据则不写入缓存,导致该不存在的数据每次请求都要到 DB 查询,可能导致 DB 挂掉(大概率是遭攻击)。
解决方案一:通常使用布隆过滤器解决。
解决方案二:把数据库的空值也缓存起来
缓存雪崩
设置缓存时采用相同过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB,DB 瞬时压力过重雪崩。(与击穿的区别:雪崩是多 key,击穿是单个 key)
解决方案:将缓存失效时间分散开,在原有失效时间基础上增加 1-5 分钟随机值,降低过期时间重复率。
2. Redis 的持久化是怎么做的?
- RDB:快照文件,将 Redis 内存数据写入磁盘,实例宕机恢复时从 RDB 快照恢复数据。
- 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
- 开启慢查询日志,记录慢 SQL;
- 使用 skywalking 链路追踪,定位慢接口,进而查看最终执行的 SQL。
分析 SQL
使用 MySQL 执行计划explain:
- 通过
key和key_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 支持四种隔离级别:
- 未提交读(read uncommitted):无任何解决能力,项目中不使用;
- 读已提交(read committed):解决脏读,无法解决不可重复读、幻读;
- 可重复读(repeatable read):解决脏读、不可重复读,无法解决幻读(MySQL 默认级别);
- 串行化(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 中事务失效的场景有哪些?
- 方法捕获异常但未抛出:异常被自行处理,事务无法感知,导致失效;
- 方法抛出检查异常:未配置
rollbackFor时,事务不回滚(需在@Transactional配置rollbackFor = Exception.class); - 方法非 public 修饰:Spring 事务仅对 public 方法生效。
14. Spring 的 bean 的生命周期?
- 读取
BeanDefinition:获取 bean 的定义信息(类全路径、懒加载、单例等); - 实例化:调用构造函数创建 bean 实例;
- 依赖注入:执行 set 方法、
@Autowired等注入依赖; - Aware 接口处理:若 bean 实现 Aware 接口(如 BeanNameAware),执行对应方法;
- BeanPostProcessor 前置处理:对 bean 进行增强;
- 初始化:执行
InitializingBean接口方法、init-method、@PostConstruct注解方法; - BeanPostProcessor 后置处理:最终增强(可能生成代理对象);
- 销毁:容器关闭时执行销毁方法(
DisposableBean、destroy-method)。
15. Springboot 自动配置原理?
@SpringBootApplication封装三个核心注解:
@SpringBootConfiguration:标识配置类;@ComponentScan:扫描组件;@EnableAutoConfiguration:自动配置核心,通过@Import导入配置选择器,读取classpath:META-INF/spring.factories中配置的类全类名;配置类根据条件注解(如
@ConditionalOnClass)判断是否加载,满足条件则将 Bean 注入 Spring 容器。
16. Spring 的常见注解有哪些?
- 声明 Bean:
@Component、@Service、@Repository、@Controller; - 依赖注入:
@Autowired、@Qualifier、@Resource; - 作用域:
@Scope; - 配置相关:
@Configuration、@ComponentScan、@Bean; - 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
- 客户端获取代表请求业务的唯一字段;
- 将该字段以 SETNX 存入 Redis,设置超时时间;
- SETNX 成功 → 首次请求,执行业务;失败 → 重复请求,直接返回。
方案 2:基于 Token 检查
客户端先请求获取 Token(服务端生成全局唯一 ID,存入 Redis 并返回);
业务请求携带 Token,服务端校验:
- 校验成功 → 执行业务,删除 Redis 中的 Token;
- 校验失败 → 重复操作,直接返回。
重复下单 / 提交补充
- 前端:防抖、按钮置灰(一定时间内仅允许一次请求);
- 服务端:幂等性处理 + 数据库唯一约束 / 乐观锁。
21. RabbitMq 如何保证消息的可靠性?
- 生产者:重试机制、确认机制(return callback + confirm callback);
- 存储层:交换机、队列、消息的持久化;
- 消费者:确认机制、重试机制、消费幂等性;
- 兜底:死信交换机处理消息堆积 / 丢失。
22. RabbitMQ 消息的重复消费问题如何解决的?
核心:幂等性处理(同接口幂等性方案)。
23. Kafka 是如何保证消费的顺序性?
Kafka 默认不保证顺序(一个 Topic 的多分区独立存储 / 消费),需保证顺序时,将消息发送到同一个分区:
- 发送消息时指定分区号;
- 按相同业务标识设置消息 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 时退化为链表)。
存储机制
- Key 计算哈希值,与数组长度取模得到索引;
- 索引位置无数据 → 直接存入;
- 哈希冲突:
- 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 底层是如何实现的?
- 检查容量:确保 size+1 能容纳新元素;
- 扩容判断:size+1 > 当前数组长度 → 调用
grow扩容(1.5 倍); - 存储元素:将新元素放入 size 位置,size+1;
- 返回添加结果。
30. 创建线程的方式有哪些?他们有什么区别?
| 方式 | 区别 |
|---|---|
| 继承 Thread | 单继承限制、耦合高、无返回值 |
| 实现 Runnable | 避免单继承、任务与线程分离、可复用、无返回值 |
| 实现 Callable | 有返回值、可抛异常、能获取执行结果 |
| 线程池 | 复用线程、控制并发、减少创建销毁开销(项目开发首选) |
31. 线程池的运行原理是什么?
- 新任务到达 → 检查核心线程数是否已满;
- 核心线程未满 → 新建核心线程执行;
- 核心线程已满 → 任务入阻塞队列排队;
- 队列已满 → 新建非核心线程执行;
- 所有线程已满 → 触发拒绝策略。
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. 你在项目中哪里用了多线程?
- 数据同步:千万级数据从 MySQL 同步到 ES,用线程池 + CountDownLatch 避免 OOM;
- 大数据导出:Excel 导出大量数据,线程池分批处理;
- 聚合查询:用户下单后,线程池并行调用订单、商品、物流微服务接口,提升性能;
- 非核心任务:保存用户搜索记录,异步线程执行,不阻塞主流程。
39. 说说微服务中用户的登录态是怎么鉴权并进行流转的?
- 用户登录生成 JWT 令牌,前端请求携带 Token;
- 网关过滤器统一校验解析 Token,将用户信息通过请求头透传;
- 各微服务拦截器接收用户信息,存入 ThreadLocal;
- 统一封装公共模块,所有微服务引入该模块实现登录态全局流转。
40. 说说 jvm 的内存模型?
JVM 内存分为:
- 元空间(Metaspace):存储类元信息、常量池等(替代永久代);
- 堆(Heap):线程共享,存储对象实例;
- 程序计数器:线程私有,记录当前线程执行位置;
- 虚拟机栈:线程私有,存储方法栈帧(局部变量、操作数栈等);
- 本地方法栈:线程私有,支持本地方法(Native)执行。
41. 说说什么是双亲委派机制?
类加载器收到加载请求时,先委派父类加载器执行,所有请求最终传至启动类加载器;仅当父类加载器无法加载(搜索范围无该类),子类加载器才自行加载。
核心目的:避免类重复加载、保证核心类(如 java.lang.String)的安全性。
42. 说说 java 堆的内存模型?
分区比例
- 新生代:老年代 = 1:2;
- 新生代内部:Eden:S0:S1 = 8:1:1。
对象生命周期
- 新对象优先分配到 Eden 区;
- Eden 不足 → 标记 Eden+S0 存活对象,复制到 S1,释放 Eden+S0;
- 再次 Eden 不足 → 标记 Eden+S1 存活对象,复制到 S0,释放 Eden+S1;
- 存活对象熬过 15 次 GC(默认),晋升到老年代;
- 大对象 / 幸存区不足 → 对象提前晋升老年代。
43. 如何定位垃圾?
- 引用计数法(辅助):对象被引用则计数 + 1,引用释放则 - 1,计数为 0 则标记为垃圾(无法解决循环引用);
- 可达性分析算法(核心):从 GC Roots(如虚拟机栈引用、类静态属性)向下遍历,不可达的对象判定为垃圾。
44. JVM 垃圾回收算法有哪些?
- 标记 - 清除:标记垃圾 → 清除垃圾(产生内存碎片);
- 复制算法:将内存分为两块,复制存活对象到另一块,清空原内存(无碎片,新生代采用);
- 标记 - 整理:标记垃圾 → 存活对象向一端移动 → 清除边界外垃圾(老年代采用);
- 分代回收:结合上述算法,新生代用复制、老年代用标记 - 整理 / 标记 - 清除。
45. 说一下 JVM 有哪些垃圾回收器?
- 串行收集器:单线程 GC,STW(Stop The World),适合小型应用;
- 并行收集器:多线程 GC,STW,追求吞吐量;
- CMS(并发标记清除):老年代收集器,低停顿,并发执行(标记 - 清除,产生碎片);
- 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:程序未退出 →
jmap -dump:format=b,file=dump.hprof <进程ID>; - 方案 2:自动导出 → 启动参数添加
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/xxx/dump.hprof;
- 方案 1:程序未退出 →
分析快照:使用 Eclipse MAT/IDEA Profiler 定位内存泄漏代码。
CPU 飙升
方案 1:JDK 工具
top→ 找到高 CPU 进程 ID;ps H -eo pid,tid,%cpu | grep <进程ID>→ 找到高 CPU 线程 ID;printf '0x%x\n' <线程ID>→ 转换为 16 进制;jstack <进程ID> | grep <16进制线程ID> -A 20→ 定位问题代码。
方案 2:Arthas
thread→ 查看 CPU 占比最高线程;thread <线程ID>→ 查看线程堆栈。
线程死锁
方案 1:jps+jstack
jps→ 找进程 ID;jstack <进程ID>→ 查看死锁信息。
方案 2:jvisualvm
命令行执行jvisualvm,可视化查看死锁。
方案 3:Arthas
thread -b → 定位阻塞其他线程的死锁线程。
49. 接口慢的排查思路?
使用 Arthas 的trace命令,监控接口各方法的耗时,定位慢执行环节:
运行
trace 全类名 方法名 --cost > 50050. 接口报错的排查思路?
- 生成 TraceID:服务端入口生成唯一 TraceID;
- 日志埋点:所有日志输出 TraceID;
- 接口返回:返回值携带 TraceID;
- 日志排查:通过前端提供的 TraceID 快速检索日志。
51. 说说 xxl-job 和 schedule 的区别?
表格
| 特性 | Spring Schedule | XXL-Job |
|---|---|---|
| 部署方式 | 本地单机 | 分布式集群 |
| 重复执行 | 集群部署会重复执行 | 支持负载均衡,避免重复执行 |
| 管控能力 | 无管理页面、无日志、无重试 | 可视化后台、日志、失败重试、告警 |
| 动态配置 | 需重启服务 | 无需重启,动态修改 |
| 适用场景 | 小型单机项目 | 微服务、分布式中大型项目 |
52. 说说分布式事务是什么?Seata 的不同模式的两阶段分别是什么样子的?
分布式事务
跨服务的事务(如订单创建:扣库存 + 创订单 + 扣余额),需保证多服务操作的一致性。
Seata 核心角色
- TC:事务协调者,维护全局 / 分支事务状态;
- TM:事务管理器,定义全局事务范围;
- RM:资源管理器,管理分支事务。
XA 模式
一阶段(RM)
- 注册分支事务到 TC;
- 执行业务 SQL 但不提交;
- 报告执行状态到 TC。
二阶段(TC+RM)
- TC:检测分支状态,全成功则通知提交,否则通知回滚;
- RM:执行提交 / 回滚。
AT 模式
一阶段(RM)
- 注册分支事务;
- 记录 undo-log(数据快照);
- 执行 SQL 并提交;
- 报告状态到 TC。
二阶段
- 提交:RM 删除 undo-log;
- 回滚:RM 根据 undo-log 恢复数据。
AT vs XA
- XA:一阶段不提交,锁定资源,强一致;
- AT:一阶段提交,不锁定资源,最终一致,性能更高(企业主流)。
TCC模式
一阶段(RM)
- 注册分支事务到 TC;
- 执行 Try 操作:资源检查、预留 / 锁定业务资源;
- 报告 Try 执行状态到 TC。
二阶段
- 提交:RM 执行 Confirm 操作:确认并正式使用预留资源,完成业务提交;
- 回滚:RM 执行 Cancel 操作:释放预留资源,取消业务操作。
53. 说说 threadLocal 的底层原理?
- ThreadLocal 内部维护
ThreadLocalMap; set():以 ThreadLocal 为 Key,资源对象为 Value,存入当前线程的ThreadLocalMap;get():以 ThreadLocal 为 Key,从当前线程的ThreadLocalMap获取 Value;remove():移除当前线程ThreadLocalMap中对应 Key 的 Value(避免内存泄漏)。
54. 说说 es 的倒排索引的原理?
- 分词:对文档字段分词,生成词条;
- 建表:创建 “词条 → 文档 ID 列表” 的映射表;
- 搜索:先查词条索引得到文档 ID,再根据 ID 查询文档(查询速度远快于正排索引)。
55. 如何优化 es 的查询性能?
索引设计
- 字段精准设计:检索用 text,过滤 / 排序用 keyword / 数值,无需检索的字段关闭 index;
- 分片规划:单分片 20-50G,单节点分片数≤20;
- 冷热分离:按时间拆分索引,仅查询热数据。
查询优化
- 避免深分页:用 search_after/scroll 替代 from+size;
- 优先用 filter 上下文(缓存、无相关性评分);
- 过滤返回字段(_source),减少数据传输。
56. 如何去调节 es 的 score?
核心方式:
- boost 加权:对字段 / 查询条件加权(如
field:keyword^2); - function_score:自定义函数(如权重、衰减、随机)干预评分;
- 衰减函数:对距离 / 时间等维度衰减评分;
- 负向降级:对不符合条件的文档降低评分。
57. 说出 10 个常用的 Linux 命令
ls:查看目录文件;cd:切换工作目录;pwd:查看当前路径;mkdir:创建文件夹;rm:删除文件 / 目录;cp:复制文件 / 目录;mv:移动 / 重命名文件;tail:查看文件末尾(tail -f实时日志);grep:过滤文本内容;ps:查看进程(ps -ef查 Java 进程)。
58. 说出 5 个以上常用的 docker 命令
docker pull:拉取镜像;docker images:查看本地镜像;docker run:创建并启动容器;docker exec -it:进入容器终端;docker ps:查看运行中容器(ps -a查看所有);docker start/stop/restart:启停 / 重启容器;docker rm/rmi:删除容器 / 镜像;docker logs:查看容器日志。
59. AI 工具的使用?
使用 Claude Code:
- 需求设计:辅助分析需求,生成需求说明书、数据库设计等文档;
- 原型图:Claude Code + Pencil 生成原型;
- 开发流程:生成开发规范文档,按默认 /plan/openspec/harness engineering 模式开发,需求审查迭代;
- 测试:编写测试用例,执行测试。
60. 说说Redis的主从同步流程
主从同步分为了两个阶段,一个是全量同步,一个是增量同步
全量同步是指从节点第一次与主节点建立连接的时候使用全量同步,流程是这样的:
第一:从节点请求主节点同步数据,其中从节点会携带自己的replication id和offset偏移量。
第二:主节点判断是否是第一次请求,主要判断的依据就是,主节点与从节点是否是同一个replication id,如果不是,就说明是第一次同步,那主节点就会把自己的replication id和offset发送给从节点,让从节点与主节点的信息保持一致。
第三:在同时主节点会执行bgsave,生成rdb文件后,发送给从节点去执行,从节点先把自己的数据清空,然后执行主节点发送过来的rdb文件,这样就保持了一致
当然,如果在rdb生成执行期间,依然有请求到了主节点,而主节点会以命令的方式记录到缓冲区,缓冲区是一个日志文件,最后把这个日志文件发送给从节点,这样就能保证主节点与从节点完全一致了,后期再同步数据的时候,都是依赖于这个日志文件,这个就是全量同步
增量同步指的是,当从节点服务重启之后,数据就不一致了,所以这个时候,从节点会请求主节点同步数据,主节点还是判断不是第一次请求,不是第一次就获取从节点的offset值,然后主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步