Java 热门面试题 200 道
Java 热门面试题 200 道(Markdown表格版)
Java 热门面试题 200 道(Markdown列表版)
文章摘要:
本文整理了Java热门面试题200道,涵盖Java基础、集合框架、并发、MySQL索引等核心知识点。以HashMap为例,解析了其哈希表实现原理(数组+链表/红黑树)、扩容机制(负载因子0.75)及JDK1.8的优化(尾插法、红黑树)。对比了ConcurrentHashMap在1.7(分段锁)与1.8(CAS+synchronized)的差异,并阐述最左前缀原则在MySQL联合索引中的应用(必须连续匹配左列)。内容聚焦高频考点,适合面试速查与技术巩固。
关键词: Java面试、HashMap、ConcurrentHashMap、红黑树、MySQL索引
文章目录
Java 热门面试题 200 道
题目列表
1. 说说 Java 中 HashMap 的原理?
2. Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别?
3. 为什么 JDK 1.8 对 HashMap 进行了红黑树的改动?
4. JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动?
5. Java 中有哪些集合类?请简单介绍
6. MySQL 索引的最左前缀匹配原则是什么?
7. 数据库的脏读、不可重复读和幻读分别是什么?
8. MySQL 的存储引擎有哪些?它们之间有什么区别?
9. MySQL 的覆盖索引是什么?
10. MySQL 的索引类型有哪些?
11. MySQL 的索引下推是什么?
12. MySQL InnoDB 引擎中的聚簇索引和非聚簇索引有什么区别?
13. MySQL 中的回表是什么?
14. MySQL 中使用索引一定有效吗?如何排查索引效果?
15. RabbitMQ 怎么实现延迟队列?
16. MySQL 中的索引数量是否越多越好?为什么?
17. 为什么 RocketMQ 不使用 Zookeeper 而选择自己实现 NameServer?
18. 请详细描述 MySQL 的 B+ 树中查询数据的全过程
19. RabbitMQ 中消息什么时候会进入死信交换机?
20. 为什么 MySQL 选择使用 B+ 树作为索引结构?
21. RabbitMQ 中无法路由的消息会去到哪里?
22. MySQL 三层 B+ 树能存多少数据?
23. Kafka为什么要抛弃 Zookeeper?
24. 详细描述一条 SQL 语句在 MySQL 中的执行过程。
25. Kafka 中 Zookeeper 的作用?
26. MySQL 是如何实现事务的?
27. 为什么 Java 8 移除了永久代(PermGen)并引入了元空间(Metaspace)?
28. 说一下 Kafka 中关于事务消息的实现?
29. MySQL 事务的二阶段提交是什么?
30. 说一下 RocketMQ 中关于事务消息的实现?
31. MySQL 中长事务可能会导致哪些问题?
32. RocketMQ 的事务消息有什么缺点?你还了解过别的事务消息实现吗?
33. MySQL 中的 MVCC 是什么?
34. 为什么需要消息队列?
35. MySQL 中的事务隔离级别有哪些?
36. 说一下消息队列的模型有哪些?
37. MySQL 默认的事务隔离级别是什么?为什么选择这个级别?
38. 谈谈你了解的最常见的几种设计模式,说说他们的应用场景
39. MySQL 中有哪些锁类型?
40. 什么是策略模式?一般用在什么场景?
41. MySQL 的乐观锁和悲观锁是什么?
42. 什么是责任链模式?一般用在什么场景?
43. MySQL 中如果发生死锁应该如何解决?
44. 什么是模板方法模式?一般用在什么场景?
45. MySQL 中 count(*)、count(1) 和 count(字段名) 有什么区别?
46. 什么是观察者模式?一般用在什么场景?
47. MySQL 中如何进行 SQL 调优?
48. 什么是代理模式?一般用在什么场景?
49. 说说 Spring 启动过程?
50. Redis 集群的实现原理是什么?
51. 如何使用 MySQL 的 EXPLAIN 语句进行查询分析?
52. 你了解的 Spring 都用到哪些设计模式?
53. Redis 集群会出现脑裂问题吗?
54. 请描述简单工厂模式的工作原理。
55. Spring 有哪几种事务传播行为?
56. Redis 中如何实现分布式锁?
57. MySQL 中如何解决深度分页的问题?
58. 说说 Springboot 的启动流程?
59. Redis 实现分布式锁时可能遇到的问题有哪些?
60. 工厂模式和抽象工厂模式有什么区别?
61. SpringBoot 是如何实现自动配置的?
62. 什么是 MySQL 的主从同步机制?它是如何实现的?
63. 说说 Redisson 分布式锁的原理?
64. 如何理解 Spring Boot 中的 starter?
65. 如何使用 Redis 快速实现排行榜?
66. Redis 中如何保证缓存与数据库的数据一致性?
67. 说说 TCP 的三次握手?
68. 简单说说 Netty 的零拷贝机制?
69. 什么是配置中心?有哪些常见的配置中心?
70. 说说 TCP 的四次挥手?
71. Netty 是如何解决粘包和拆包问题的?
72. Spring Boot 是如何通过 main 方法启动 web 项目的?
73. 什么情况下需要使用分布式事务,有哪些方案?
74. Redis 为什么这么快?
75. 如何使用 Redis 快速实现布隆过滤器?
76. 为什么 Java 中 HashMap 的默认负载因子是 0.75?
77. 如何处理 MySQL 的主从同步延迟?
78. Netty 如何解决 JDK NIO 中的空轮询 Bug?
79. Java 中 HashMap 的扩容机制是怎样的?
80. 为什么 Redis 设计为单线程?6.0 版本为何引入多线程?
81. 为什么 TCP 挥手需要有 TIME_WAIT 状态?
82. Spring Boot 的核心特性有哪些?
83. 为什么 HashMap 在 Java 中扩容时采用 2 的 n 次方倍?
84. 你在项目中使用的 Redis 客户端是什么?
85. TCP 超时重传机制是为了解决什么问题?
86. 简述 MyBatis 的插件运行原理,以及如何编写一个插件?
87. 数组和链表在 Java 中的区别是什么?
88. Redis 中常见的数据类型有哪些?
89. TCP 滑动窗口的作用是什么?
90. 介绍一下 Reactor 线程模型?
91. Java 线程池核心线程数在运行过程中能修改吗?如何修改?
92. TCP/IP 四层模型是什么?
93. 说说 MyBatis 的缓存机制?
94. 什么是 Spring Boot?
95. Java 中如何创建多线程?
96. Redis 中跳表的实现原理是什么?
97. Redis 性能瓶颈时如何处理?
98. OSI 七层模型是什么?
99. 说说 AQS 吧?
100. Cookie、Session、Token 之间有什么区别?
101. 什么是分库分表?分库分表有哪些类型(或策略)?
102. MyBatis 中 #{} 和 ${} 的区别是什么?
103. Java 中的 final 关键字是否能保证变量的可见性?
104. Redis 的 hash 是什么?
105. 从网络角度来看,用户从输入网址到网页显示,期间发生了什么?
106. Dubbo 和 Spring Cloud Gateway 有什么区别?
107. 什么是 Java 中的原子性、可见性和有序性?
108. 线程和进程有什么区别?
109. 说说你知道的几种 I/O 模型
110. MyBatis 与 Hibernate 有哪些不同?
111. 什么是 Java 内存模型(JMM)?
112. Redis 和 Memcached 有哪些区别?
113. 什么是物理地址,什么是逻辑地址?
114. 说说什么是 API 网关?它有什么作用?
115. 什么是 Java 的 CAS(Compare-And-Swap)操作?
116. Select、Poll、Epoll 之间有什么区别?
117. “什么是 MyBatis-Plus?它有什么作用?它和 MyBatis 有哪些区别?”
118. 为什么 Java 中的 ThreadLocal 对 key 的引用为弱引用?
119. 编译执行与解释执行的区别是什么?JVM 使用哪种方式?
120. Redis 支持事务吗?如何实现?
121. 到底什么是 TCP 连接?
122. 如何处理重复消息?
123. Java 中什么情况会导致死锁?如何避免?
124. 如何保证消息的有序性?
125. 说一下 Netty 的应用场景?
126. 什么是 Spring IOC?
127. 你了解 Java 线程池的原理吗?
128. Redis 数据过期后的删除策略是什么?
129. 如何处理消息堆积?
130. 什么是服务熔断?
131. Java 线程池有哪些拒绝策略?
132. HTTP 1.0 和 2.0 有什么区别?
133. 如何保证消息不丢失?
134. Spring AOP默认用的是什么动态代理,两者的区别?
135. 如何合理地设置 Java 线程池的线程数?
136. Redis 中有哪些内存淘汰策略?
137. MySQL 中 如果我 select * from 一个有 1000 万行的表,内存会飙升么?
138. 消息队列设计成推消息还是拉消息?推拉模式的优缺点?
139. 你使用过哪些 Java 并发工具类?
140. 什么是设计模式?请简述其作用。
141. 什么是 AOP?
142. 什么是服务降级?
143. Synchronized 和 ReentrantLock 有什么区别?
144. Redis 的 Lua 脚本功能是什么?如何使用?
145. HTTP 2.0 和 3.0 有什么区别?
146. 单例模式有哪几种实现?如何保证线程安全?
147. Java 的 synchronized 是怎么实现的?
148. 如何设计一个秒杀功能?
149. 为什么不选择使用原生的 NIO 而选择使用 Netty 呢?
150. 看过源码吗?说下 Spring 由哪些重要的模块组成?
151. 如何优化 Java 中的锁的使用?
152. Redis 的 Pipeline 功能是什么?
153. 让你设计一个分布式 ID 发号器,怎么设计?
154. 什么是服务雪崩?
155. JVM 由哪些部分组成?
156. Redis 通常应用于哪些场景?
157. 让你设计一个短链系统,怎么设计?
158. 什么是循环依赖(常问)?
159. JVM 垃圾回收调优的主要目标是什么?
160. Redis 中的 Big Key 问题是什么?如何解决?
161. HTTP 和 HTTPS 有什么区别?
162. 分布式锁一般都怎样实现?
163. 如何对 Java 的垃圾回收进行调优?
164. 如何设计一个点赞系统?
165. Spring 如何解决循环依赖?
166. HTTP 与 RPC 之间的区别?
167. 常用的 JVM 配置参数有哪些?
168. 如何解决 Redis 中的热点 key 问题?
169. TCP 是用来解决什么问题?
170. 让你设计一个 RPC 框架,怎么设计?
171. Java 中常见的垃圾收集器有哪些?
172. 什么是限流?限流算法有哪些?怎么实现的?
173. Netty 性能为什么这么高?
174. 为什么 Spring 循环依赖需要三级缓存,二级不够吗?
175. JVM 的内存区域是如何划分的?
176. Redis 的持久化机制有哪些?
177. 如果发现 Redis 内存溢出了?你会怎么做?请给出排查思路和解决方案
178. 负载均衡算法有哪些?
179. Java 中有哪些垃圾回收算法?
180. Redis 中的缓存击穿、缓存穿透和缓存雪崩是什么?
181. 线上发现 Redis 机器爆了,如何优化?
182. 说下 Spring Bean 的生命周期?
183. JVM 有那几种情况会产生 OOM(内存溢出)?
184. Redis 在生成 RDB 文件时如何处理请求?
185. TCP 和 UDP 有什么区别?
186. 线上 CPU 飙高如何排查?
187. Java 中 volatile 关键字的作用是什么?
188. 怎么分析 JVM 当前的内存占用情况?OOM 后怎么分析?
189. Spring MVC 具体的工作原理?
190. 分布式和微服务有什么区别?
191. 什么是 Java 中的 ABA 问题?
192. Redis 的哨兵机制是什么?
193. TCP 的粘包和拆包能说说吗?
194. 线程的生命周期在 Java 中是如何定义的?
195. 在 MySQL 中建索引时需要注意哪些事项?
196. Netty 采用了哪些设计模式?
197. Spring 中的 DI 是什么?
198. Redis 主从复制的实现原理是什么?
199. 在什么情况下,不推荐为数据库建立索引?
200. 什么是 Seata?
Java 热门面试题 200 道
描述: 2024 最新 Java 热门面试题 200 道,涵盖 Java、 MySQL、Redis、Spring、SpringBoot、SpringCloud、计算机网络、操作系统、消息队列、Netty、后端场景题、线上问题排查、后端系统设计题等
标签: 热门,后端
题目列表
1. 说说 Java 中 HashMap 的原理?
默认解法:
HashMap 基于哈希表实现,使用数组+链表/红黑树存储数据。通过 key 的 hashCode 计算桶位置(数组索引),发生哈希冲突时采用链地址法解决(JDK1.8 后链表长度≥8时转为红黑树)。允许 null 键/值,非线程安全。扩容时容量翻倍并重新哈希。
其他解法一:
数据结构:基于数组+链表/红黑树(JDK1.8+)。
哈希计算:通过key的hashCode()计算桶位置。
冲突解决:链表法(O(1)+O(n)),链表过长时转红黑树(O(log n))。
扩容机制:默认负载因子0.75,扩容时容量翻倍并rehash。
其他解法二:
数据结构:JDK 1.8 后采用数组 + 链表 + 红黑树结构。数组是桶(Bucket),每个桶可存储链表或红黑树。
哈希计算:通过 key.hashCode() 的高 16 位异或低 16 位计算哈希值,再通过 (n-1) & hash 确定桶下标,减少哈希冲突。
插入逻辑:若桶为空则直接插入;若为链表,遍历至尾节点插入(尾插法),若链表长度 ≥8 且数组长度 ≥64,链表转红黑树。
扩容机制:默认负载因子 0.75,当元素数量超过容量 × 负载因子时,扩容为原 2 倍。扩容后重新计算索引,元素要么在原位置,要么在 原位置 + 原容量。
线程安全问题:多线程下可能因并发扩容导致死循环(JDK 1.7)或数据丢失(1.7/1.8)。
标签: Java,Java 基础
2. Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别?
默认解法:
1.7 使用分段锁(Segment),每个段独立加锁;1.8 改为 CAS + synchronized 锁单个桶(Node)。1.7 只有链表,1.8 引入红黑树优化长链表查询。1.8 的 size() 计算改用 baseCount + CounterCell 避免全局锁。
其他解法一:
数据结构:1.7用Segment分段锁;1.8改用数组+链表/红黑树+CAS+synchronized。
并发度:1.7锁粒度是Segment;1.8锁粒度是单个桶。
哈希冲突:1.8引入红黑树优化长链表查询。
API扩展:1.8新增forEach/reduce等并行方法。
其他解法二:
数据结构:
JDK 1.7:分段锁(Segment 数组 + HashEntry 链表),锁粒度较粗。
JDK 1.8:数组 + 链表/红黑树 + CAS + synchronized(锁单个桶),锁粒度更细。
并发控制:
1.7 使用 ReentrantLock 锁 Segment。
1.8 对桶头节点用 synchronized 加锁,结合 CAS 实现无锁化插入。
扩容机制:
1.7 分段扩容,各 Segment 独立扩容。
1.8 支持多线程协同扩容(通过 ForwardingNode 标记迁移状态)。
查询性能:1.8 的 volatile + Unsafe 提供无锁读,效率更高。
红黑树支持:1.8 在链表长度 >8 时转红黑树,避免查询退化至 O(n)。
标签: Java 集合,Java
3. 为什么 JDK 1.8 对 HashMap 进行了红黑树的改动?
默认解法:
解决哈希冲突严重时链表过长导致的查询效率退化(O(n) 降至 O(logn))。当链表长度 ≥8 且数组容量 ≥64 时转为红黑树;节点数 ≤6 时退化为链表。平衡空间与时间开销。
其他解法一:
解决极端情况:当哈希冲突严重时,链表过长导致查询效率退化至O(n)。
性能优化:红黑树将查询时间复杂度优化至O(log n)。
阈值控制:链表长度>8且数组长度≥64时转红黑树;树节点<6时退化为链表。
其他解法二:
解决哈希冲突退化问题:极端情况下(如大量哈希碰撞),链表长度过长会导致查询效率从 O(1) 退化为 O(n)。
优化查询性能:红黑树(自平衡二叉查找树)保证最坏情况查询复杂度为 O(log n),显著提升哈希冲突时的性能。
平衡空间与时间:红黑树占用空间比链表大,但权衡后对性能提升更关键。转换阈值(链表 ≥8 转树,树 ≤6 退链表)避免频繁转换开销。
标签: Java 集合,Java
4. JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动?
默认解法:
头插法改为尾插法(避免多线程扩容死循环)
扩容时旧链表拆分为高低位链表(优化重新哈希计算)
计算哈希值:高位参与运算 (h = key.hashCode()) ^ (h >>> 16)
初始化延迟到首次 put 时。
其他解法一:
扩容优化:旧链表拆分为高位链和低位链(无需rehash计算)。
插入逻辑:头插法改为尾插法(避免并发扩容死循环)。
哈希算法:简化hash计算(高位参与运算减少冲突)。
新增方法:compute(), merge()等函数式API。
其他解法二:
尾插法替代头插法:解决并发扩容时链表死循环问题(1.7 头插法导致环状链表)。
扩容索引计算优化:元素迁移后位置 = 原位置 或 原位置 + 旧容量,利用高位掩码判断,避免重新计算哈希。
哈希计算优化:key.hashCode() 高 16 位异或低 16 位((h = key.hashCode()) ^ (h >>> 16)),使低位更分散,减少冲突。
链表拆分逻辑:扩容时链表按高位 0/1 拆分为两条链,分别存入新数组的 i 和 i+oldCap 位置。
标签: Java 集合,Java
5. Java 中有哪些集合类?请简单介绍
默认解法:
List:ArrayList(数组)、LinkedList(双向链表)、Vector(线程安全数组)
Set:HashSet(基于HashMap)、TreeSet(红黑树)、LinkedHashSet(链表保序)
Map:HashMap、TreeMap(键有序)、LinkedHashMap(插入序)、ConcurrentHashMap(线程安全)
Queue:ArrayDeque、PriorityQueue(堆)
其他解法一:
List:有序可重复 – ArrayList(数组)、LinkedList(链表)、Vector(线程安全数组)。
Set:无序唯一 – HashSet(哈希表)、TreeSet(红黑树)、LinkedHashSet(链表+哈希表)。
Map:键值对 – HashMap、TreeMap(红黑树)、ConcurrentHashMap(线程安全)。
Queue:队列 – LinkedList、PriorityQueue(堆)、ArrayDeque(双端队列)。
其他解法二:
List 有序集合:
ArrayList:基于动态数组,随机访问快(O(1)),插入删除慢(O(n))。
LinkedList:基于双向链表,插入删除快(O(1)),随机访问慢(O(n))。
Vector:线程安全版 ArrayList(方法 synchronized),性能差。
Set 无序唯一集合:
HashSet:基于 HashMap 实现,元素需实现 hashCode() 和 equals()。
LinkedHashSet:维护插入顺序的双向链表。
TreeSet:基于红黑树,元素可排序(实现 Comparable 或传入 Comparator)。
Map 键值对集合:
HashMap:数组 + 链表/红黑树,非线程安全。
LinkedHashMap:记录插入顺序或访问顺序(LRU 实现基础)。
TreeMap:基于红黑树,按键排序。
ConcurrentHashMap:线程安全的 HashMap。
Hashtable:线程安全但全表锁,已淘汰。
Queue 队列:
LinkedList:可作双向队列。
PriorityQueue:基于堆的优先级队列。
ArrayDeque:数组实现的双端队列。
标签: Java 集合,Java
6. MySQL 索引的最左前缀匹配原则是什么?
默认解法:
联合索引 (a,b,c) 生效条件:查询必须包含最左列 a。能匹配 a、a,b、a,b,c 的组合,但无法跳过 a 直接查 b,c。原理:索引按列顺序存储,缺失左列时无法利用有序性。
其他解法一:
联合索引规则:从最左列开始连续匹配,遇到范围查询(>、<、between)后停止。
示例:索引(A,B,C)可生效查询:WHERE A=1;WHERE A=1 AND B=2;WHERE A=1 AND B=2 AND C=3;但WHERE B=2无效。
排序优化:索引列顺序影响ORDER BY性能。
其他解法二:
定义:联合索引中,查询条件必须从索引最左列开始连续匹配,不能跳过中间列。
匹配规则:
精确匹配最左列后,后续列可范围查询或精确匹配。
跳过最左列或中间列,则后续列无法使用索引(如索引 (a,b,c),查询 where b=1 and c=2 失效)。
例子:索引 (a,b,c) 生效场景:
where a=1 and b=2 and c=3(全匹配)
where a=1 and b>2(a 精确,b 范围)
where a=1(仅最左列)
失效场景:
查询条件未包含最左列(如 b=2)。
最左列使用范围查询后,后续列无法索引(如 a>1 and b=2,b 无法索引)。
标签: 后端,MySQL,数据库
7. 数据库的脏读、不可重复读和幻读分别是什么?
默认解法:
脏读:读到未提交的数据(事务A读事务B未提交修改)
不可重复读:同事务内两次读同一数据结果不同(因其他事务修改)
幻读:同事务内两次查询结果集数量不同(因其他事务增删数据)。
其他解法一:
脏读:读取到其他事务未提交的数据(违反原子性)。
不可重复读:同一事务内多次读取同一数据,结果不同(由UPDATE引起)。
幻读:同一事务内多次查询,返回不同行数(由INSERT/DELETE引起)。
隔离级别:脏读(READ UNCOMMITTED)、不可重复读(READ COMMITTED)、幻读(REPEATABLE READ)。
其他解法二:
脏读(Dirty Read):事务 A 读取了事务 B 未提交的修改数据,若 B 回滚,A 读到的数据无效。
例:A 读到 B 未提交的余额更新,后 B 回滚,A 基于错误数据操作。
不可重复读(Non-Repeatable Read):事务 A 多次读取同一数据,期间事务 B 修改并提交了该数据,导致 A 两次读取结果不一致。
例:A 第一次读余额 100,B 更新为 50 并提交,A 再读余额变为 50。
幻读(Phantom Read):事务 A 多次查询同一范围数据,期间事务 B 插入或删除了符合该范围的记录并提交,导致 A 两次查询结果集不一致。
例:A 查询年龄 <30 的用户得 10 条,B 插入一条年龄 25 的记录并提交,A 再查得 11 条。
隔离级别解决:
读未提交(Read Uncommitted):可能发生所有问题。
读已提交(Read Committed):避免脏读。
可重复读(Repeatable Read):避免脏读、不可重复读(MySQL InnoDB 通过 MVCC 避免幻读)。
串行化(Serializable):避免所有问题。
标签: 后端,MySQL,数据库
8. MySQL 的存储引擎有哪些?它们之间有什么区别?
默认解法:
InnoDB:支持事务、行锁、外键、聚簇索引,适用OLTP
MyISAM:表锁、全文索引、高读性能,不支持事务,适用OLAP
Memory:内存存储,数据易丢失
区别核心:事务支持(InnoDB支持)、锁粒度(InnoDB行锁 vs MyISAM表锁)、崩溃恢复能力。
其他解法一:
InnoDB:支持事务、行锁、外键;聚簇索引;适用写密集型场景。
MyISAM:不支持事务/行锁;表锁;独立索引文件(.MYI);适用读密集型。
Memory:数据存内存;哈希索引;重启数据丢失。
核心差异:事务支持、锁粒度、崩溃恢复能力、索引结构。
其他解法二:
InnoDB:
特性:支持事务、行级锁、外键、MVCC(多版本并发控制),提供 ACID 兼容。
适用场景:高并发 OLTP(如订单、支付系统)。
MyISAM:
特性:不支持事务和行锁(仅表锁),支持全文索引,读取性能高。
适用场景:读多写少(如日志分析),已逐渐被淘汰。
Memory:
特性:数据存内存,读写快,重启后数据丢失。
适用场景:临时表或缓存。
Archive:
特性:高压缩比存储,只支持插入和查询。
适用场景:归档历史数据(如日志)。
核心区别:
| 特性 | InnoDB | MyISAM |
|---|---|---|
| 事务 | 支持 | 不支持 |
| 锁粒度 | 行级锁 | 表锁 |
| 外键 | 支持 | 不支持 |
| 崩溃恢复 | 支持(Redo Log) | 不支持 |
| 索引类型 | 聚簇索引 | 非聚簇索引 |
标签: 后端,MySQL,数据库
9. MySQL 的覆盖索引是什么?
默认解法:
索引包含查询所需所有字段(如 SELECT a,b FROM t WHERE c=1,索引 (c,a,b))。避免回表查询(直接从索引取数据),显著提升性能。EXPLAIN 显示 “Using index” 即使用了覆盖索引。
其他解法一:
定义:查询所需列均包含在索引中,无需回表查数据文件。
优势:减少I/O(仅扫描索引),提升查询速度。
示例:索引(A,B),查询SELECT A,B FROM table WHERE A=1。
限制:SELECT *无法覆盖索引(除非索引包含所有列)。
其他解法二:
定义:索引包含查询所需的所有字段,无需回表查询数据行。
优势:
减少 I/O:仅读取索引树,不访问数据文件。
提升性能:索引数据量通常远小于行数据。
实现条件:
SELECT 的列必须全部包含在索引中。
WHERE 条件使用索引列。
示例:
表 user(id PK, name, age),索引 idx_name_age(name, age)。
查询 SELECT name, age FROM user WHERE name = 'Alice' 可覆盖索引(索引含 name,age)。
若查询 SELECT * 则需回表查数据行。
标签: 后端,MySQL,数据库
10. MySQL 的索引类型有哪些?
默认解法:
主键索引(唯一 + 非空)
唯一索引(列值唯一)
普通索引(加速查询)
全文索引(FULLTEXT,文本搜索)
组合索引(多列联合)
空间索引(GIS 数据)。
其他解法一:
数据结构:B+Tree(默认)、Hash(Memory引擎)、Full-Text(全文索引)。
物理存储:聚簇索引(InnoDB主键索引,叶节点存数据)、非聚簇索引(叶节点存主键值)。
逻辑分类:主键索引、唯一索引、普通索引、联合索引、前缀索引。
其他解法二:
按数据结构:
B+Tree 索引:默认类型,支持范围查询和排序,适用于等值、范围查询。
Hash 索引:仅 Memory 引擎支持,等值查询 O(1),不支持范围查询。
全文索引:MyISAM/InnoDB 支持,用于文本关键词搜索(如 MATCH AGAINST)。
R-Tree 索引:空间索引,支持地理数据(如 GIS 系统)。
按逻辑功能:
主键索引(PRIMARY):唯一且非空,聚簇索引(InnoDB)。
唯一索引(UNIQUE):确保列值唯一,可空。
普通索引(INDEX):加速查询,无唯一约束。
联合索引:多列组合索引,遵循最左前缀匹配。
按存储方式:
聚簇索引:数据行与索引存储在一起(如 InnoDB 主键索引)。
非聚簇索引:索引与数据分离(如 MyISAM 索引,指向数据文件位置)。
标签: 后端,MySQL,数据库
11. MySQL 的索引下推是什么?
默认解法:
ICP(Index Condition Pushdown):在存储引擎层提前过滤索引条件(即使非最左前缀)。例如索引 (a,b),查询 WHERE a=1 AND b>2,引擎层直接过滤 b>2,减少回表次数。需满足条件:range/ref/eq_ref 查询,且存储引擎支持(默认开启)。
其他解法一:
优化场景:针对联合索引的模糊查询(如WHERE A LIKE ‘a%’ AND B=1)。
原理:在存储引擎层过滤B列(无需回表后再由Server层过滤)。
优势:减少回表次数,提升查询效率。
支持版本:MySQL 5.6+默认开启。
其他解法二:
定义:Index Condition Pushdown (ICP),将 WHERE 条件中索引列的过滤操作下推到存储引擎层执行,减少回表次数。
作用:
减少回表:引擎层提前过滤不符合条件的索引项,避免回表后再过滤。
提升性能:尤其对联合索引中非最左列的条件过滤有效。
工作流程(以索引 (a,b) 查询 WHERE a>10 AND b=20 为例):
无 ICP:存储引擎按 a>10 检索索引,回表取完整数据行,再由 Server 层过滤 b=20。
有 ICP:存储引擎按 a>10 检索索引后,直接在引擎层过滤 b=20,仅对符合条件的记录回表。
启用条件:
查询类型为 range/ref/eq_ref。
需回表查询(覆盖索引无需 ICP)。
需 MySQL 5.6+ 且默认开启(optimizer_switch=index_condition_pushdown=on)。
标签: 后端,MySQL,数据库
12. MySQL InnoDB 引擎中的聚簇索引和非聚簇索引有什么区别?
默认解法:
聚簇索引:叶子节点存数据行(主键索引即聚簇索引)
非聚簇索引(二级索引):叶子节点存主键值,需回表查询数据
区别:1. 数据存储位置(聚簇索引与数据共存) 2. 查询效率(聚簇索引无需回表) 3. 数量限制(每表仅一个聚簇索引)。
其他解法一:
聚簇索引:叶节点存储行数据(1个表仅1个),主键自动生成。
非聚簇索引:叶节点存储主键值(需二次查找数据)。
性能:聚簇索引范围查询更快(数据物理连续);非聚簇索引可能需回表。
存储:聚簇索引即数据文件;非聚簇索引是独立B+树。
其他解法二:
聚簇索引(Clustered Index):
数据存储:索引的叶子节点直接存储完整数据行。
数量限制:每表仅一个聚簇索引(通常为主键)。
性能优势:范围查询和排序高效(数据物理有序)。
缺点:插入速度依赖插入顺序(乱序插入可能导致页分裂)。
非聚簇索引(Secondary Index):
数据存储:叶子节点存储主键值(非数据行物理地址)。
数量限制:可存在多个。
查询流程:需二次查询(回表)——先查主键值,再用主键查聚簇索引获取数据行。
覆盖索引优化:若索引包含查询所需字段,可避免回表。
关键区别:
| 特性 | 聚簇索引 | 非聚簇索引 |
|---|---|---|
| 存储内容 | 数据行 | 主键值 |
| 索引数量 | 1 个 | 多个 |
| 查询速度 | 直接访问数据 | 需回表 |
| 页分裂影响 | 影响大 | 影响小 |
标签: 后端,MySQL,数据库
13. MySQL 中的回表是什么?
默认解法:
通过二级索引查询时,需先查索引找到主键值,再根据主键到聚簇索引中取完整数据行。额外 IO 操作导致性能下降。避免方式:使用覆盖索引或优化索引设计。
其他解法一:
现象:通过非聚簇索引查询时,需根据主键值回聚簇索引查找完整数据。
代价:额外I/O操作(随机读),降低查询效率。
优化方案:使用覆盖索引(避免回表);减少SELECT *。
其他解法二:
定义:当非聚簇索引无法覆盖查询所需字段时,需根据索引查得的主键值,回到聚簇索引中检索完整数据行。
触发条件:
查询字段未全包含在非聚簇索引中(非覆盖索引)。
查询需访问索引外的列。
性能影响:
额外 I/O:每行数据需两次索引查找(先查二级索引,再查聚簇索引)。
随机读:主键值无序时,回表可能导致磁盘随机 I/O。
优化方法:
覆盖索引:索引包含所有查询字段。
索引下推(ICP):减少回表前的无效记录。
聚簇索引设计:避免过长主键(二级索引需存储主键值)。
标签: 后端,MySQL,数据库
14. MySQL 中使用索引一定有效吗?如何排查索引效果?
默认解法:
不一定有效。失效场景:函数操作索引列、类型隐式转换、OR 条件未全索引、LIKE 以通配符开头、不符合最左前缀。排查:1. EXPLAIN 看 type/possible_keys/key 2. 观察 rows 字段预估扫描行数 3. 开启慢查询日志分析。
其他解法一:
无效场景:索引列计算/函数、隐式类型转换、OR条件未全覆盖、LIKE以通配符开头。
排查工具:EXPLAIN分析(查看key/type/Extra)。
优化手段:force index强制索引;analyze table更新统计信息;调整索引顺序。
其他解法二:
索引不一定有效的情况:
索引失效场景:
未遵循最左前缀原则(联合索引跳过最左列)。
对索引列使用函数或表达式(如 WHERE YEAR(create_time)=2023)。
隐式类型转换(如字符串列用数字查询)。
OR 连接非索引列条件(如 WHERE a=1 OR b=2,若 b 无索引则全表扫描)。
数据量小时(优化器认为全表扫描更快)。
排查方法:
EXPLAIN 分析:
type 列:index 或 range 表示索引生效,ALL 为全表扫描。
key 列:实际使用的索引。
Extra 列:Using index 表示覆盖索引。
慢查询日志:记录执行时间长的 SQL,分析是否索引缺失。
优化器追踪:SET optimizer_trace="enabled=on"; 查看索引选择过程。
优化建议:
避免索引列参与计算。
使用覆盖索引减少回表。
定期 ANALYZE TABLE 更新索引统计信息。
标签: 后端,MySQL,数据库,场景题
15. RabbitMQ 怎么实现延迟队列?
默认解法:
两种方式:
死信队列(DLX):消息设置 TTL 过期后转投 DLX
插件(rabbitmq_delayed_message_exchange):使用 x-delayed-type 交换机,消息头设置 x-delay 参数(毫秒)。推荐插件方式避免 TTL 队列阻塞问题。
其他解法一:
死信队列:消息设置TTL过期后进入死信交换机(需配合x-dead-letter-exchange)。
插件方案:rabbitmq-delayed-message-exchange插件(支持毫秒级延迟)。
实现步骤:生产者发延迟消息 → 交换机路由到临时队列 → 到期后转发到业务队列。
其他解法二:
死信队列(DLX):
设置消息 TTL(过期时间),过期后成为死信,路由到死信队列。
步骤:
创建业务队列 A,绑定死信交换机(DLX)和死信路由键。
消息发送到 A 并设置 TTL。
消息过期后转发至死信队列 B,由消费者处理。
插件 rabbitmq-delayed-message-exchange:
安装官方延迟插件,创建 x-delayed-message 类型交换机。
发送消息时设置 headers.put("x-delay", 5000) 指定延迟 5 秒。
消息在交换机延迟后路由到队列。
对比:
死信队列:无需插件,但 TTL 在队列固定,灵活性差。
插件方案:支持消息级延迟,灵活但需安装插件。
标签: 后端,消息队列,RabbitMQ
16. MySQL 中的索引数量是否越多越好?为什么?
默认解法:
不是。原因:1. 写性能下降(增删改需维护所有索引) 2. 空间占用增加 3. 优化器选择困难可能选错索引。建议:根据查询需求创建必要索引,优先组合索引覆盖多查询,定期分析索引使用率(SHOW INDEX 观察 Cardinality)。
其他解法一:
不是。原因:
写性能下降:INSERT/UPDATE需维护所有索引
空间占用:索引文件增大磁盘消耗
优化器负担:索引过多可能选错执行计划
优化建议:优先复用联合索引;删除未使用索引(performance_schema监控)。
其他解法二:
不是越多越好,原因:
写性能下降:
INSERT/UPDATE/DELETE 需更新所有索引,索引越多写操作越慢。
更新可能引起页分裂和索引树调整。
空间占用:
每个索引占用独立存储空间(尤其 B+Tree 非叶子节点)。
大表索引过多显著增加磁盘和内存压力。
优化器负担:
索引过多时,优化器选择执行计划时间增加,可能选错索引。
建议:
优先为高频查询和排序字段建索引。
使用联合索引替代多个单列索引。
定期清理无用索引(通过慢查询日志分析索引使用率)。
标签: 后端,MySQL,数据库
17. 为什么 RocketMQ 不使用 Zookeeper 而选择自己实现 NameServer?
默认解法:
轻量化:NameServer 无选举逻辑(AP 系统),ZK 是 CP 系统较重 2. 性能:NameServer 纯内存操作,吞吐更高 3. 去中心化:各 NameServer 节点独立无状态,避免 ZK 集群瓶颈 4. RocketMQ 只需简单服务发现,无需 ZK 强一致性。
其他解法一:
轻量化:NameServer无状态(ZK需选举/持久化),部署简单。
性能:ZK写性能瓶颈(RocketMQ元数据更新频繁)。
解耦:减少外部依赖,避免ZK集群故障影响MQ可用性。
功能定制:NameServer仅需服务发现(路由管理),无需ZK强一致性。
其他解法二:
设计目标不同:
ZooKeeper:强一致性(CP),适合分布式协调(如选主、配置同步),但写入性能较低。
NameServer:最终一致性(AP),轻量级元数据管理(Topic 路由信息),追求高可用和低延迟。
简化依赖与部署:
NameServer 无状态且节点独立,部署简单(RocketMQ 仅需 2-3 台)。
ZooKeeper 需奇数节点集群部署,运维复杂。
性能优化:
NameServer 基于内存存储路由信息,响应快(微秒级)。
ZooKeeper 写操作需集群广播,延迟较高。
降低复杂度:
RocketMQ 无需 ZooKeeper 的强一致性,路由信息短暂不一致可接受(客户端有容错机制)。
标签: RocketMQ,消息队列,后端
18. 请详细描述 MySQL 的 B+ 树中查询数据的全过程
默认解法:
从根节点开始二分查找
沿非叶子节点(仅存索引键 + 指针)逐层向下定位
到达叶子节点(存数据行或主键)
在叶子节点链表二分查找目标键
若聚簇索引直接返回数据;若二级索引则取主键回表查询。
其他解法一:
磁盘读取:从根节点(常驻内存)开始,加载对应页到内存。
节点遍历:比较查询值,定位下一层子节点指针(非叶节点存键值和指针)。
叶层搜索:到达叶节点后二分查找目标键值(叶节点双向链表串联)。
结果获取:若聚簇索引直接取数据;若二级索引则回表查询。
其他解法二:
从根节点开始:
加载根节点页(常驻内存)。
逐层查找:
比较查询键与节点中的键值(有序),找到键值所在区间的子节点指针。
递归向下查找,直至叶子节点。
叶子节点定位:
在叶子节点中二分查找目标键:
等值查询:找到精确匹配的键。
范围查询:找到起始键后顺序遍历后续键。
数据获取:
聚簇索引:叶子节点直接存储数据行,返回行数据。
非聚簇索引:叶子节点存储主键值,需回表查询聚簇索引获取完整数据。
示例:查询 id=25(假设 B+ 树高 3):
根节点:键 [10, 20, 30] → 25 在 20 和 30 间,进入中间节点 P2。
中间节点 P2:键 [20, 25, 28] → 25 匹配,进入叶子节点 L3。
叶子节点 L3:找到 id=25 的数据行(或主键值)。
标签: 后端,MySQL,数据库
19. RabbitMQ 中消息什么时候会进入死信交换机?
默认解法:
触发条件:
消息被消费端拒绝(basic.reject/nack)且 requeue=false
消息 TTL 过期
队列达到最大长度(溢出丢弃)。需配置队列的 x-dead-letter-exchange 参数绑定死信交换机。
其他解法一:
消息被拒绝(basic.reject/nack)且requeue=false。
消息TTL过期。
队列达到最大长度(x-max-length)。
配置:需队列声明时指定x-dead-letter-exchange参数。
其他解法二:
消息在以下情况成为死信(Dead Letter)并进入死信交换机(DLX):
消费者拒绝且不重新入队:basic.reject 或 basic.nack 设置 requeue=false。
消息过期:消息设置 TTL 且未被消费。
队列满:队列达到最大长度限制(x-max-length)。
配置方法:
声明队列时设置参数:
x-dead-letter-exchange:指定死信交换机。
x-dead-letter-routing-key:指定路由键(可选)。
标签: RabbitMQ,消息队列,后端
20. 为什么 MySQL 选择使用 B+ 树作为索引结构?
默认解法:
对比 B 树:
更矮胖:非叶子节点不存数据,单节点存更多键,减少 IO 次数
范围查询高效:叶子节点双向链表串联
查询稳定:所有查询均需到叶子节点,时间复杂度稳定 O(log n)
更适合磁盘存储:节点大小匹配磁盘页。
其他解法一:
磁盘友好:矮胖树结构减少I/O次数(节点大小=磁盘页)。
范围查询:叶节点双向链表支持高效范围扫描。
查询稳定:所有查询均需到叶层(时间复杂度O(log n))。
对比优势:比B树更适合磁盘存储(非叶节点无数据,单页存更多指针)。
其他解法二:
相比 B 树、哈希、红黑树等,B+ 树优势:
磁盘 I/O 友好:
树矮胖(3-4 层存百万级数据),减少磁盘访问次数。
节点大小固定(如 16KB),匹配磁盘页大小。
范围查询高效:
叶子节点形成有序双向链表,范围查询直接遍历链表。
查询更稳定:
所有查询均需到叶子节点,路径长度相同(O(log n))。
存储效率高:
非叶子节点仅存键和指针(不存数据),单节点可存更多键,降低树高。
数据全存叶子节点,避免非叶子节点数据冗余。
标签: 后端,MySQL,数据库
21. RabbitMQ 中无法路由的消息会去到哪里?
默认解法:
取决于 Mandatory 参数:
Mandatory=true:通过回调 ReturnListener 返回生产者
Mandatory=false(默认):直接丢弃。建议:设置备份交换机(Alternate Exchange)接收无法路由的消息。
其他解法一:
前提:生产者发送消息时设置mandatory=true。
处理机制:触发ReturnListener回调返回消息。
备选方案:通过alternate-exchange指定备用交换机(未配置则丢弃)。
其他解法二:
默认丢弃:未配置备用策略时,无法路由的消息直接被丢弃。
备用交换机(Alternate Exchange):
声明交换机时设置 alternate-exchange 参数指向备用交换机。
无法路由的消息转发到备用交换机,再由其路由到特定队列处理(如记录日志或告警)。
示例配置:
创建备用交换机 ae 和队列 unrouted_queue 并绑定。
声明主交换机:arguments.put("alternate-exchange", "ae")。
标签: RabbitMQ,消息队列,后端
22. MySQL 三层 B+ 树能存多少数据?
默认解法:
假设:
页大小 16KB
主键 BIGINT(8B),指针 6B
非叶节点每页存约 16KB/(8B+6B)≈1170 键
叶节点存数据行(假设1KB/行),每页约16行
计算:根节点有 1170 指针 → 二层 1170 页 → 三层 1170×1170≈137 万页 → 总行数 ≈ 137万 × 16 = 2190 万行。
其他解法一:
假设:页大小16KB,主键BIGINT(8B),指针6B。
非叶节点:单页存约 16KB/(8B+6B)≈1170个键值+指针。
叶节点:假设行数据1KB,单页存约16行。
估算:三层B+树总行数 = 1170 * 1170 * 16 ≈ 2190万行。
其他解法二:
计算依据:
假设条件:
页大小 16KB,主键 BIGINT(8B),指针 6B。
非叶子节点:存主键 + 指针 → 单条记录 14B。
叶子节点:存数据行(假设单行 1KB)。
计算过程:
根节点:可存记录数 = 16KB / 14B ≈ 1170 条。
第二层:1170 个节点,每节点存 1170 条 → 总记录数 = 1170 × 1170 ≈ 137 万。
第三层(叶子):137 万叶子节点,每节点存行数 = 16KB / 1KB = 16 行 → 总行数 = 137 万 × 16 ≈ 2190 万。
实际场景:
行大小、主键类型、填充因子影响实际容量。
通常 3 层 B+ 树可支持千万级数据存储。
标签: 后端,MySQL,数据库
23. Kafka为什么要抛弃 Zookeeper?
默认解法:
简化架构:减少外部依赖,运维更简单
提升扩展性:元数据管理内置(KIP-500),突破 ZK 集群写瓶颈
增强稳定性:避免 ZK 故障导致 Kafka 不可用
改进控制器选举:用 Raft 协议替代 ZK 监听。从 Kafka 2.8 起支持 KRaft 模式。
其他解法一:
简化架构:KIP-500用自管理元数据(KRaft)替代ZK。
提升性能:减少网络开销(ZK成为吞吐瓶颈)。
运维减负:避免维护两套分布式系统(ZK配置/扩容复杂)。
更强一致性:Raft协议保证元数据安全。
其他解法二:
简化架构:
ZooKeeper 是独立集群,运维复杂(需维护两套系统)。
Kafka 2.8+ 引入自管理元数据(KRaft 模式),移除 ZooKeeper 依赖。
提升性能:
ZooKeeper 写操作需集群广播,成为性能瓶颈(尤其分区数多时)。
KRaft 模式通过 Raft 协议直接管理元数据,降低延迟。
增强扩展性:
ZooKeeper 集群规模受限(通常 ≤7 节点),影响 Kafka 集群扩展。
KRaft 模式可线性扩展 Controller 节点。
降低故障风险:
ZooKeeper 故障导致 Kafka 不可用。
KRaft 模式减少外部依赖,提高系统自治性。
标签: Kafka,Zookeeper,消息队列
24. 详细描述一条 SQL 语句在 MySQL 中的执行过程。
默认解法:
连接器:建立连接,权限验证
查询缓存:若开启缓存且命中则直接返回(8.0 后移除)
解析器:词法/语法分析,生成语法树
优化器:选择索引,生成执行计划
执行器:调用存储引擎接口
存储引擎(InnoDB):读写数据(缓冲池、磁盘交互)
返回结果。
其他解法一:
连接器:管理连接,权限验证。
分析器:语法解析(AST),语义检查。
优化器:生成执行计划,选择索引(基于成本模型)。
执行器:调用存储引擎接口读写数据。
存储引擎(InnoDB):执行索引扫描/回表等操作。
其他解法二:
连接器:
管理客户端连接,验证权限。
建立连接后维持会话(若为长连接)。
查询缓存(8.0 已移除):
历史版本中缓存 SELECT 结果,因失效频繁被废弃。
分析器:
词法分析:拆分 SQL 为关键词(如 SELECT)、表达式等。
语法分析:检查语法正确性,生成抽象语法树(AST)。
优化器:
基于统计信息选择执行计划(如索引选择、JOIN 顺序)。
生成执行计划树。
执行器:
调用存储引擎接口执行计划。
操作流程:
开启事务(若需)。
根据索引定位数据。
调用存储引擎读写接口。
返回结果集。
存储引擎(如 InnoDB):
执行数据读写(访问内存缓冲池或磁盘)。
通过 undo log 实现回滚,redo log 保证持久性。
返回结果:将结果集返回客户端。
标签: 后端,MySQL,数据库
25. Kafka 中 Zookeeper 的作用?
默认解法:
在旧版本(KRaft 前)中负责:
Broker 注册:维护节点列表与状态
Topic 配置:存储分区、副本分配信息
控制器选举:选主 Broker 管理分区leader
消费者组:保存 offset 和组成员(新版 offset 存内部 Topic)。
其他解法一:
元数据存储:Topic分区、Broker列表、ISR集合。
控制器选举:选举Controller Broker(管理分区leader)。
集群协调:Broker注册/下线监听。
注意:Kafka 3.0+逐步弃用ZK(迁移至KRaft)。
其他解法二:
元数据管理:存储集群元数据(Broker 列表、Topic 配置、分区分配信息)。
控制器选举:选举集群控制器(Controller),负责分区 Leader 选举和副本管理。
Broker 注册与发现:Broker 启动时向 ZooKeeper 注册,客户端通过 ZooKeeper 发现可用 Broker。
分区状态跟踪:监控分区 Leader 状态变化(如故障转移)。
ACL 控制:存储访问控制列表(Kafka 0.9+ 已迁移至 Broker)。
注:Kafka 2.8+ 逐步移除 ZooKeeper 依赖(KRaft 模式)。
标签: Kafka,消息队列,后端
26. MySQL 是如何实现事务的?
默认解法:
通过 InnoDB 引擎实现:
原子性(A):Undo Log(回滚日志)记录修改前数据
一致性(C):由 A+I+D 共同保证
隔离性(I):锁 + MVCC(多版本并发控制)
持久性(D):Redo Log(重做日志)保证崩溃恢复。
其他解法一:
日志机制:redo log(崩溃恢复,保证持久性),undo log(事务回滚/MVCC)。
锁机制:行锁/间隙锁(保证隔离性)。
MVCC:多版本并发控制(ReadView+undo log链),实现非锁定读。
两阶段提交:binlog与redo log的协调(保证主从一致)。
其他解法二:
日志机制:
Redo Log:物理日志,保证持久性(事务提交前先写 Redo)。
Undo Log:逻辑日志,保证原子性(回滚时逆向操作)。
锁机制:
行锁(Record Lock)、间隙锁(Gap Lock)、Next-Key Lock 实现隔离性。
MVCC(多版本并发控制):
通过 ReadView 和版本链实现非锁定读(避免读写冲突)。
事务提交:
二阶段提交(2PC)保证 binlog 与存储引擎日志一致性。
标签: 后端,MySQL,数据库
27. 为什么 Java 8 移除了永久代(PermGen)并引入了元空间(Metaspace)?
默认解法:
永久代大小难调优:易触发 OOM
类元数据生命周期与类加载器绑定,回收复杂
元空间使用本地内存(非 JVM 堆),默认无上限(受物理内存限制)
简化 HotSpot 代码,合并 JRockit 特性。元空间 GC 由类加载器触发。
其他解法一:
内存管理:PermGen大小难调优(易OOM);元空间使用本地内存(自动扩展)。
垃圾回收:PermGen由Full GC回收效率低;元空间由MetaspaceSize控制。
兼容性:为HotSpot与JRockit合并做准备。
存储内容:存类元信息(永久代存部分移至堆,如字符串常量池)。
其他解法二:
永久代问题:
固定大小易导致 java.lang.OutOfMemoryError: PermGen space。
FGC 无法回收类信息(回收效率低)。
元空间优势:
内存管理:使用本地内存(非 JVM 堆),默认无上限(受物理内存限制)。
自动扩容:按需申请内存,避免 OOM。
垃圾回收:元数据由 GC 自动回收(类加载器死亡时)。
性能提升:
减少字符串常量池迁移开销(字符串池移至堆内)。
标签: JVM,Java
28. 说一下 Kafka 中关于事务消息的实现?
默认解法:
保证跨分区原子写入:
事务协调器:每个 Producer 对应一个,管理事务状态
事务ID:标识跨会话事务
两阶段提交:
发送消息到事务 Topic(未提交)
Commit:写事务结束标记到 __transaction_state
消费者设置 isolation.level=read_committed 过滤未提交消息。
其他解法一:
事务协调器:每个Producer对应一个TransactionCoordinator。
两阶段协议:
开启事务:向Coordinator注册
发送消息:消息标记PID(Producer ID)
提交事务:写事务标记到__transaction_state主题
消费端:需设置isolation.level=read_committed。
其他解法二:
事务协调器(Transaction Coordinator):
每个 Producer 绑定唯一协调器,管理事务状态。
事务日志(__transaction_state):
存储事务状态(Begin/Commit/Abort)。
两阶段提交(2PC)流程:
Begin:Producer 向协调器注册事务。
Add Messages:发送消息(标记为未提交)。
Commit:
协调器写 Prepare Commit 到事务日志。
向所有涉及 Broker 写事务标记。
写 Commit 到日志后消息可见。
隔离性:
读隔离:消费者设置 isolation.level=read_committed 过滤未提交消息。
标签: Kafka,消息队列,后端
29. MySQL 事务的二阶段提交是什么?
默认解法:
用于保证 binlog 和 redo log 一致性:
Prepare 阶段:InnoDB 写 redo log(prepare 状态)
Commit 阶段:
a. 写 binlog
b. InnoDB 写 redo log(commit 状态)
崩溃恢复时:若 binlog 完整则提交事务,否则回滚。
其他解法一:
目的:保证redo log与binlog的一致性(跨存储引擎)。
阶段:
Prepare:InnoDB写redo log(prepare状态)
Commit:Server层写binlog后通知InnoDB提交(redo log写commit)
崩溃恢复:根据binlog状态决定回滚(无binlog)或提交(有binlog)。
其他解法二:
目的:保证 binlog 与存储引擎(如 InnoDB)事务日志的一致性。
阶段流程:
Prepare 阶段:
InnoDB 写 redo log 并置为 prepare 状态。
Server 层写 binlog 到内存(未刷盘)。
Commit 阶段:
Server 层写 binlog 到磁盘。
InnoDB 提交事务(redo log 置为 commit)。
崩溃恢复:
若 binlog 完整:提交事务(有 redo log)。
若 binlog 不完整:回滚事务(无对应 binlog)。
标签: 后端,MySQL,数据库
30. 说一下 RocketMQ 中关于事务消息的实现?
默认解法:
半消息机制:
生产者发送半消息(对消费者不可见)
Broker 持久化半消息,返回 ACK
生产者执行本地事务
根据本地事务结果提交/回滚:
提交:消息可见,投递给消费者
回滚:删除消息
Broker 定时回查:若生产者未响应,回查事务状态。
其他解法一:
半消息:生产者发送事务消息(对消费者不可见)。
本地事务:生产者执行本地业务逻辑。
事务状态:生产者提交/回滚通知Broker。
消息提交:Broker将半消息转为正式消息(或丢弃)。
补偿机制:Broker回查未确认事务状态(checkLocalTransaction)。
其他解法二:
半消息(Half Message):
发送暂不可消费的消息(存储于 RMQ_SYS_TRANS_HALF_TOPIC)。
事务状态回查:
Broker 回调 Producer 检查本地事务状态(若半消息未确认)。
提交/回滚流程:
Commit:半消息转存真实 Topic,消费者可见。
Rollback:丢弃半消息。
补偿机制:
Producer 实现 TransactionListener 处理本地事务执行和回查。
标签: RocketMQ,后端,消息队列
31. MySQL 中长事务可能会导致哪些问题?
默认解法:
锁竞争:持有锁时间长,阻塞其他事务
回滚段膨胀:Undo Log 无法及时清理
数据一致性风险:未提交修改对其他事务可见(取决于隔离级别)
主从延迟:Binlog 需等待事务提交
内存消耗:事务相关缓冲区无法释放。
其他解法一:
锁阻塞:长期持有锁导致其他事务等待(甚至死锁)。
回滚段膨胀:undo log无法及时清理占用空间。
数据一致性:旧版本数据无法被purge(MVCC依赖)。
主从延迟:Binlog同步积压。
监控:information_schema.innodb_trx查询长事务。
其他解法二:
锁资源占用:
长事务持有锁不释放,阻塞其他查询(甚至死锁)。
回滚段膨胀:
Undo log 无法及时清理,占用大量存储空间。
MVCC 版本链过长:
老版本数据无法 purge,影响查询性能。
主从延迟:
Binlog 在事务提交后才写入,从库同步延迟。
解决方案:
监控 information_schema.innodb_trx。
设置事务超时时间(innodb_rollback_on_timeout)。
标签: 后端,MySQL,数据库
32. RocketMQ 的事务消息有什么缺点?你还了解过别的事务消息实现吗?
默认解法:
缺点:
消息可见延迟(需等待本地事务结果)
回查机制可能重复执行本地事务
不保证全局事务一致性(需结合业务补偿)
其他实现:
Kafka 事务:基于生产者幂等和事务协调器
最大努力通知:通过异步重试保证最终一致。
其他解法一:
缺点:
最终一致性(非强一致)
事务状态回查可能失败
网络波动导致消息丢失风险
其他方案:
Kafka事务消息(Exactly-Once语义)
本地事务表+消息表(事务中写DB与消息)
其他解法二:
RocketMQ 缺点:
消息可见延迟:需等待二次确认(半消息→提交)。
事务状态回查不可靠:网络故障可能导致消息状态不一致。
不保证原子性:本地事务与消息发送可能一个成功一个失败。
其他实现:
Kafka 事务:
优点:精确一次语义(EOS)。
缺点:性能开销大。
本地消息表:
业务库建消息表,与本地事务同库同事务提交。
异步任务补偿发送(最终一致性)。
标签: 消息队列,后端,RocketMQ
33. MySQL 中的 MVCC 是什么?
默认解法:
多版本并发控制:通过数据快照实现非锁定读。InnoDB 实现方式:
每行数据隐含 DB_TRX_ID(事务ID)和 DB_ROLL_PTR(回滚指针)
ReadView 结构:记录活跃事务ID列表,用于判断数据版本可见性
快照读(如 SELECT)基于 ReadView 访问 Undo Log 中的历史版本。
其他解法一:
多版本并发控制:通过数据快照实现非锁定读。
核心组件:
隐藏字段:DB_TRX_ID(事务ID)、DB_ROLL_PTR(回滚指针)
undo log:构建版本链
ReadView:可见性判断(活跃事务列表)
隔离级别:REPEATABLE READ(首次读建立ReadView)和READ COMMITTED(每次读重建ReadView)。
其他解法二:
定义:多版本并发控制(Multi-Version Concurrency Control),非锁定读机制。
核心组件:
Undo Log:存储数据历史版本链。
ReadView:事务快照,包含活跃事务 ID 列表。
可见性规则:
数据行隐藏字段:DB_TRX_ID(创建事务ID)、DB_ROLL_PTR(回滚指针)。
数据版本对当前事务可见需满足:
DB_TRX_ID < 当前 ReadView 最小活跃 ID(已提交)。
DB_TRX_ID 不在 ReadView 活跃事务列表中。
隔离级别支持:
RC:每次读生成新 ReadView(可能不可重复读)。
RR:首次读生成 ReadView,后续复用(避免不可重复读)。
标签: 后端,MySQL,数据库
34. 为什么需要消息队列?
默认解法:
核心价值:
解耦:生产者和消费者独立演进
异步:非必要操作异步执行,提升响应速度
削峰:缓冲突发流量,保护下游系统
可靠:消息持久化,确保不丢失
扩展:通过分区水平扩展消费者。
其他解法一:
解耦:生产/消费逻辑分离。
异步:非阻塞调用(提升响应速度)。
削峰:缓冲突发流量(避免系统过载)。
顺序保证:分区有序性。
最终一致性:分布式事务场景。
其他解法二:
解耦:
生产者和消费者异步通信,系统间不直接依赖。
削峰填谷:
突发流量写入队列,消费者按能力处理(避免系统崩溃)。
异步提速:
主流程快速响应(如订单创建),耗时操作异步执行(如发短信)。
顺序保证:
分区队列保证消息顺序性(如 Kafka)。
最终一致性:
分布式事务场景(如库存扣减与订单创建)。
标签: 消息队列,后端
35. MySQL 中的事务隔离级别有哪些?
默认解法:
读未提交(Read Uncommitted):可能脏读
读已提交(Read Committed):避免脏读,可能不可重复读
可重复读(Repeatable Read):避免脏读、不可重复读,可能幻读(InnoDB 通过 MVCC 部分避免)
串行化(Serializable):强制事务串行执行。
其他解法一:
READ UNCOMMITTED:读未提交(可能脏读)。
READ COMMITTED:读已提交(避免脏读,可能不可重复读)。
REPEATABLE READ:可重复读(InnoDB默认,避免脏读/不可重复读,可能幻读)。
SERIALIZABLE:串行化(最高隔离,锁表实现)。
其他解法二:
读未提交(Read Uncommitted):
可能读到未提交数据(脏读)。
读已提交(Read Committed):
只读已提交数据(避免脏读),但可能不可重复读。
可重复读(Repeatable Read):
同一事务多次读取结果一致(避免脏读、不可重复读),InnoDB 通过 MVCC 避免幻读。
串行化(Serializable):
事务串行执行(避免所有并发问题),性能最低。
标签: 后端,MySQL,数据库
36. 说一下消息队列的模型有哪些?
默认解法:
点对点(Queue):消息被一个消费者消费
发布/订阅(Topic):消息广播给所有订阅者
增强模型:
Kafka 分区模型:同一分区内顺序消费
RabbitMQ Exchange:Direct/Fanout/Topic/Headers 路由规则。
其他解法一:
点对点(Queue):消息被单个消费者消费(RabbitMQ)。
发布订阅(Topic):消息广播给所有订阅者(Kafka/RocketMQ)。
混合模型:
Kafka:Consumer Group内点对点,Group间发布订阅
RabbitMQ:Exchange路由策略(direct/fanout/topic)
其他解法二:
点对点(P2P):
消息仅被一个消费者消费(如 RabbitMQ Queue)。
发布/订阅(Pub/Sub):
消息广播到所有订阅者(如 Kafka Topic)。
主题模型(Topic):
消费者按 Topic 订阅(如 RocketMQ Tag 过滤)。
请求/响应(Request/Reply):
生产者等待消费者响应(如 RabbitMQ RPC)。
标签: 消息队列,后端
37. MySQL 默认的事务隔离级别是什么?为什么选择这个级别?
默认解法:
默认级别:可重复读(Repeatable Read)。原因:
平衡性能与一致性:避免脏读和不可重复读
InnoDB 通过 MVCC 和 Next-Key Lock 减少幻读
适合多数业务场景(如账户余额查询需结果稳定)。
其他解法一:
默认级别:REPEATABLE READ(可重复读)。
原因:
平衡性能与一致性:避免不可重复读(比READ COMMITTED严格)
实际防幻读:InnoDB通过间隙锁(Gap Lock)解决幻读
历史兼容性:早期版本遗留设定
其他解法二:
默认级别:可重复读(Repeatable Read)。
选择原因:
平衡性能与一致性:避免脏读和不可重复读,且通过 MVCC 和 Next-Key Lock 解决幻读。
历史兼容性:早期版本默认 RR,升级保持兼容。
与标准 SQL 差异:
SQL 标准默认 RC,但 InnoDB 在 RR 下通过 MVCC 提供高性能并发。
标签: 后端,MySQL,数据库
38. 谈谈你了解的最常见的几种设计模式,说说他们的应用场景
默认解法:
单例模式(全局唯一实例):配置管理、线程池
工厂模式(解耦创建逻辑):Spring BeanFactory
代理模式(控制访问):AOP 切面、RPC 动态代理
观察者模式(事件通知):GUI 事件、消息队列
策略模式(算法切换):支付方式选择、排序算法切换。
其他解法一:
单例模式:全局唯一实例(如Spring Bean)。
工厂模式:解耦对象创建(如JDBC连接池)。
观察者模式:事件通知(如GUI事件监听)。
代理模式:增强对象功能(如AOP动态代理)。
策略模式:算法切换(如支付方式选择)。
其他解法二:
单例模式:
场景:全局配置对象、线程池、数据库连接池(如 Spring Bean 默认单例)。
工厂模式:
场景:JDBC 的 DriverManager.getConnection()、Spring BeanFactory。
代理模式:
场景:Spring AOP、RPC 远程调用(动态代理)。
观察者模式:
场景:事件监听(如 GUI 按钮点击)、消息队列发布订阅。
策略模式:
场景:支付方式选择(微信/支付宝)、排序算法切换(Comparator)。
标签: 设计模式
39. MySQL 中有哪些锁类型?
默认解法:
按粒度:
表锁:MyISAM 默认,开销小但并发低
行锁:InnoDB 默认,分记录锁(锁定单行)、间隙锁(锁定范围)、临键锁(记录+间隙)
按行为:共享锁(S 锁,读锁)、排他锁(X 锁,写锁)、意向锁(IS/IX)。
其他解法一:
按粒度:
表锁(MyISAM)
行锁(InnoDB)
按功能:
共享锁(S锁):读锁
排他锁(X锁):写锁
特殊锁:
意向锁(IS/IX):快速判断表级冲突
间隙锁(Gap Lock):防止幻读
临键锁(Next-Key Lock):行锁+间隙锁
其他解法二:
按粒度:
表锁:MyISAM 默认,开销小但并发低。
行锁:InnoDB 支持,并发高但开销大。
按功能:
共享锁(S Lock):读锁,允许多事务并发读。
排他锁(X Lock):写锁,独占数据。
InnoDB 行锁变种:
记录锁(Record Lock):锁索引记录。
间隙锁(Gap Lock):锁索引区间(防幻读)。
临键锁(Next-Key Lock):记录锁+间隙锁(RR 隔离默认)。
意向锁:
IS/IX 锁:表级锁,快速判断表内是否有行锁。
标签: 后端,MySQL,数据库
40. 什么是策略模式?一般用在什么场景?
默认解法:
定义:封装可互换的算法族,使它们独立于客户端变化。场景:
支付方式选择(支付宝/微信/银行卡)
排序算法切换(快速/归并/堆排序)
折扣策略(满减/折扣率/立减)。
其他解法一:
定义:定义算法族并封装,使它们可互相替换。
核心:Context持有Strategy接口,运行时切换具体实现。
场景:
支付方式选择(微信/支付宝/银行卡)
排序算法切换(快速/归并/堆排序)
折扣策略(满减/打折/积分抵扣)
其他解法二:
定义:定义算法族并封装,使它们可互换,让算法独立于使用它的客户。
核心组件:
Context:持有具体策略的引用。
Strategy:策略接口(定义算法)。
ConcreteStrategy:具体策略实现。
应用场景:
支付方式选择(微信/支付宝/银行卡)。
排序算法切换(冒泡/快排/归并)。
折扣策略(满减/折扣率/固定优惠)。
优点:避免多重条件判断,便于扩展新策略。
标签: 设计模式
41. MySQL 的乐观锁和悲观锁是什么?
默认解法:
悲观锁:假定冲突高,先加锁再操作(SELECT … FOR UPDATE)
乐观锁:假定冲突低,通过版本号/时间戳校验(UPDATE … SET version=new_version WHERE version=old_version),失败重试。
其他解法一:
悲观锁:
思想:先加锁再操作
实现:SELECT … FOR UPDATE(行锁)
场景:写密集操作
乐观锁:
思想:无锁提交,冲突检测
实现:版本号/时间戳(UPDATE … SET version=new_version WHERE version=old_version)
场景:读多写少
其他解法二:
悲观锁:
思想:假设会冲突,操作前先加锁(如 SELECT … FOR UPDATE)。
场景:写多读少,强一致性要求高。
乐观锁:
思想:假设无冲突,提交时检查版本号(如 CAS 或版本字段)。
实现:
版本号:UPDATE … SET version=version+1 WHERE id=1 AND version=old_version。
时间戳:类似版本号。
场景:读多写少,冲突概率低。
标签: 后端,MySQL,数据库
42. 什么是责任链模式?一般用在什么场景?
默认解法:
定义:多个处理器依次处理请求,每个处理器决定是否传递给下一个。场景:
过滤器链(Servlet Filter)
审批流程(经理→总监→CEO)
异常处理(逐级捕获)。
其他解法一:
定义:多个处理器依次处理请求(形成链)。
核心:Handler持有next对象,处理完传递请求。
场景:
过滤器链(如Servlet Filter)
审批流程(如报销多级审批)
异常处理(逐级上报)
其他解法二:
定义:多个处理器依次处理请求,形成链条,请求沿链传递直到被处理。
核心组件:
Handler:处理接口(定义处理方法和后继设置)。
ConcreteHandler:具体处理器(可决定处理或传递)。
应用场景:
过滤器链(如 Servlet Filter、Spring Security)。
审批流程(如报销多级审批)。
异常处理(多层 catch)。
优点:解耦请求发送者和接收者,动态增减处理器。
标签: 设计模式
43. MySQL 中如果发生死锁应该如何解决?
默认解法:
设置 innodb_deadlock_detect=on(自动检测,默认开启)
InnoDB 自动回滚代价较小的事务
人工处理:分析 SHOW ENGINE INNODB STATUS 的死锁日志
预防:按固定顺序访问资源、减小事务粒度、使用索引减少锁范围。
其他解法一:
自动处理:InnoDB自动检测并回滚代价小的事务。
手动排查:
SHOW ENGINE INNODB STATUS查看死锁日志
监控information_schema.innodb_lock_waits
预防措施:
事务保持短小
按固定顺序访问资源
合理设计索引(减少锁范围)
其他解法二:
死锁检测:
InnoDB 自动检测(innodb_deadlock_detect=on),回滚代价小的事务。
手动处理:
查询死锁日志:SHOW ENGINE INNODB STATUS。
终止会话:KILL [session_id]。
预防措施:
按固定顺序访问多张表(如先 A 后 B)。
小事务及时提交(减少锁持有时间)。
为高频冲突场景加表锁(慎用)。
重试机制:代码捕获死锁异常(MySQL error 1213)后重试。
标签: 后端,MySQL,数据库
44. 什么是模板方法模式?一般用在什么场景?
默认解法:
定义:父类定义算法骨架,子类重写特定步骤。场景:
JdbcTemplate 执行流程(获取连接→执行SQL→处理结果→释放连接)
工作流审批(提交→各级审批→归档)
框架生命周期初始化(init()→destroy())。
其他解法一:
定义:父类定义算法骨架,子类实现具体步骤。
核心:抽象类声明final模板方法(调用抽象钩子方法)。
场景:
框架流程固定(如Spring JdbcTemplate)
业务流程标准化(如订单处理:校验→计算→支付→通知)
其他解法二:
定义:定义算法骨架(模板方法),子类重写特定步骤而不改变结构。
核心组件:
AbstractClass:模板方法(final)+ 抽象步骤(钩子方法)。
ConcreteClass:实现具体步骤。
应用场景:
框架流程(如 Spring JdbcTemplate 的 execute())。
业务审批流程(固定步骤,不同实现)。
算法骨架(如排序的 compareTo() 由子类实现)。
优点:复用代码,扩展性强(好莱坞原则:不要调用我们,我们会调用你)。
标签: 设计模式
45. MySQL 中 count(*)、count(1) 和 count(字段名) 有什么区别?
默认解法:
count():统计所有行数(含 NULL)
count(1):统计行数(等效 count())
count(字段):统计该字段非 NULL 的行数。性能:InnoDB 中 count(*)≈count(1)>count(主键)>count(非索引字段),因需扫描索引或表。
其他解法一:
count(*):统计所有行(含NULL),优化后不取值。
count(1):统计所有行(含NULL),等效count(*)。
count(字段名):统计该字段非NULL的行数。
性能:无WHERE时count(*)≈count(1)>count(字段名)(二级索引优先)
其他解法二:
count(*):
统计所有行数(含 NULL),优化器自动选择最优索引。
count(1):
统计所有行数(含 NULL),性能 ≈ count(*)(InnoDB 遍历最小索引)。
count(字段名):
统计非 NULL 行数,若字段无索引则全表扫描。
性能对比:
count(*) = count(1) > count(主键) > count(非索引字段)
建议:用 count(*) 替代 count(1),语义更清晰。
标签: 后端,MySQL,数据库
46. 什么是观察者模式?一般用在什么场景?
默认解法:
定义:主题(Subject)状态变化时通知所有观察者(Observer)。场景:
事件监听(按钮点击通知)
消息发布订阅(MQ 消费者订阅 Topic)
数据绑定(Vue.js 响应式更新)。
其他解法一:
定义:对象(Subject)状态变化时通知所有依赖对象(Observer)。
核心:Subject维护Observer列表,提供注册/通知方法。
场景:
事件驱动(如GUI按钮点击事件)
发布订阅(如消息队列消费者)
模型-视图更新(如MVC架构)
其他解法二:
定义:对象(Subject)维护观察者列表,状态变化时自动通知所有观察者。
核心组件:
Subject:被观察对象(注册/移除观察者,通知方法)。
Observer:观察者接口(定义更新方法)。
ConcreteObserver:具体观察者。
应用场景:
事件驱动(如 GUI 按钮点击监听)。
发布订阅(如消息队列消费者)。
数据监控(如股票价格变动通知)。
优点:解耦观察者和被观察者,支持广播通信。
标签: 设计模式
47. MySQL 中如何进行 SQL 调优?
默认解法:
步骤:
定位慢 SQL:慢查询日志、EXPLAIN
分析执行计划:已关注 type(ALL→ref/range)、key、rows、Extra(Using filesort/temporary)
优化手段:
加索引(覆盖索引、最左前缀)
避免 SELECT *
分页优化(避免 OFFSET 过大)
重构查询(拆复杂 SQL)
参数调优:缓冲池大小、日志配置。
其他解法一:
分析工具:EXPLAIN查看执行计划;慢查询日志。
索引优化:避免索引失效;覆盖索引;联合索引最左前缀。
SQL改写:减少子查询;分页优化(避免OFFSET大偏移)。
结构优化:大表拆分;字段类型精简。
参数调整:调整buffer_pool_size等。
其他解法二:
分析工具:
EXPLAIN:查看执行计划(已关注 type、key、rows、Extra)。
慢查询日志:定位耗时 SQL。
索引优化:
避免索引失效(如函数转换、隐式类型转换)。
使用覆盖索引减少回表。
联合索引最左前缀匹配。
SQL 改写:
分页优化(避免 OFFSET 过大,用 ID 分页)。
避免 SELECT *,只取所需字段。
分解复杂 JOIN(小表驱动大表)。
参数调优:
调整 innodb_buffer_pool_size(通常为物理内存 70%)。
优化排序区(sort_buffer_size)。
标签: 后端,MySQL,数据库,场景题
48. 什么是代理模式?一般用在什么场景?
默认解法:
定义:为对象提供代理以控制访问。场景:
远程代理(RPC 调用隐藏网络细节)
虚拟代理(延迟加载大对象)
保护代理(权限校验)
动态代理(AOP 切面增强)。
其他解法一:
定义:为对象提供代理以控制访问。
类型:
静态代理:手动实现代理类
动态代理:运行时生成(如JDK Proxy/CGLib)
场景:
AOP切面(日志/事务)
远程调用(RPC stub)
权限控制(拦截非法请求)
其他解法二:
定义:为对象提供代理,控制对原对象的访问(增强功能)。
类型:
静态代理:手动编写代理类(硬编码)。
动态代理:运行时生成代理类(如 JDK Proxy、CGLIB)。
应用场景:
AOP 编程(如 Spring 事务管理)。
远程代理(如 RPC 调用)。
保护代理(权限控制)。
虚拟代理(延迟加载大对象)。
优点:职责清晰(原对象专注核心功能),扩展性强。
标签: 设计模式
49. 说说 Spring 启动过程?
默认解法:
核心流程:
加载配置:读取 XML/注解配置
创建 BeanFactory:DefaultListableBeanFactory
解析并注册 BeanDefinition
执行 BeanFactoryPostProcessor(如 PropertySourcesPlaceholderConfigurer)
实例化单例 Bean(调用构造函数、依赖注入)
初始化 Bean(执行 Aware 接口、BeanPostProcessor、init-method)
发布 ContextRefreshedEvent 事件。
其他解法一:
加载配置:读取XML/注解配置生成BeanDefinition。
实例化:调用BeanFactoryPostProcessor(修改Bean定义)。
依赖注入:填充属性(@Autowired)。
初始化:调用InitializingBean.afterPropertiesSet()和init-method。
AOP代理:BeanPostProcessor生成代理对象。
完成:容器就绪(ContextRefreshedEvent事件)。
其他解法二:
加载配置:
读取 XML/注解配置,创建 BeanDefinition。
实例化 BeanFactory:
DefaultListableBeanFactory 存储 Bean 定义。
执行 BeanFactoryPostProcessor:
修改 BeanDefinition(如 PropertySourcesPlaceholderConfigurer 解析占位符)。
实例化 Bean:
调用构造函数创建 Bean 实例。
依赖注入:
通过 setter 或字段注入属性。
执行 BeanPostProcessor:
前置处理(如 @PostConstruct)和后置处理(如 AOP 代理)。
初始化:
调用 InitializingBean.afterPropertiesSet() 或 init-method。
完成启动:
发布 ContextRefreshedEvent 事件。
标签: 后端,Spring
50. Redis 集群的实现原理是什么?
默认解法:
Redis Cluster 采用去中心化架构:
分片:16384 个槽(Slot)分配给多个节点
请求路由:客户端根据 key 的 CRC16 值计算 Slot,直连对应节点(或重定向)
高可用:主从复制 + 故障转移(Gossip 协议通信,半数以上主节点同意则故障切换)
数据迁移:支持槽在线迁移。
其他解法一:
数据分片:16384个slot(槽)分到多个节点(CRC16(key) mod 16384)。
节点通信:Gossip协议交换状态信息。
故障转移:主节点宕机时,从节点通过Raft选举升主。
客户端路由:MOVED/ASK重定向(或代理模式)。
其他解法二:
数据分片:
16384 个 Slot 分配到多个节点(CRC16(key) mod 16384)。
节点通信:
Gossip 协议交换节点状态信息(PING/PONG)。
请求路由:
客户端直连节点:若 key 不在当前节点,返回 MOVED 重定向。
代理模式(如 Redis Proxy)。
高可用:
主从复制:每个主节点有 N 个从节点。
故障转移:从节点选举新主(类似 Raft 算法)。
扩容/缩容:
重分片(Resharding):迁移 Slot 数据到新节点。
标签: 后端,Redis
51. 如何使用 MySQL 的 EXPLAIN 语句进行查询分析?
默认解法:
执行 EXPLAIN SELECT … 查看:
type:访问类型(const > ref > range > index > ALL)
key:实际使用索引
rows:预估扫描行数
Extra:额外信息(Using index 覆盖索引;Using temporary 临时表;Using filesort 文件排序)
优化目标:减少 rows 值,避免出现 ALL 和文件排序。
其他解法一:
执行:EXPLAIN SELECT …
关键字段:
type:访问类型(const > ref > range > index > ALL)
key:实际使用索引
rows:预估扫描行数
Extra:额外信息(Using index/Using temporary/Using filesort)
优化目标:避免ALL(全表扫描);减少rows;优化Extra异常项。
其他解法二:
执行命令:EXPLAIN SELECT ... 或 EXPLAIN FORMAT=JSON SELECT ...。
关键字段解读:
type:访问类型(性能:const > ref > range > index > ALL)。
key:实际使用的索引。
rows:预估扫描行数。
Extra:额外信息(如 Using index、Using temporary、Using filesort)。
优化建议:
若 type=ALL:检查是否缺索引。
若 Extra=Using filesort:优化排序字段索引。
若 rows 远大于实际:更新统计信息(ANALYZE TABLE)。
标签: 后端,MySQL,数据库
52. 你了解的 Spring 都用到哪些设计模式?
默认解法:
工厂模式:BeanFactory
单例模式:默认 Bean 作用域
代理模式:AOP 动态代理
模板方法:JdbcTemplate、RestTemplate
观察者:ApplicationEvent 事件机制
适配器:HandlerAdapter 处理多种 Controller
策略:Resource 接口(ClassPathResource/UrlResource)。
其他解法一:
工厂模式:BeanFactory
单例模式:Bean默认单例
代理模式:AOP(JDK/CGLib)
模板方法:JdbcTemplate/RestTemplate
观察者模式:ApplicationEvent事件机制
适配器模式:HandlerAdapter(MVC)
其他解法二:
工厂模式:BeanFactory 创建 Bean 对象。
单例模式:Bean 默认作用域为单例。
代理模式:AOP 动态代理(JDK Proxy/CGLIB)。
模板方法:JdbcTemplate、RestTemplate 封装通用流程。
观察者模式:ApplicationEvent 事件发布/监听。
适配器模式:HandlerAdapter 适配不同 Controller。
策略模式:ResourceLoader 根据前缀选择资源加载策略。
标签: 后端,Spring
53. Redis 集群会出现脑裂问题吗?
默认解法:
可能发生。原因:网络分区导致主节点被孤立,原从节点选举新主,出现双主。解决方案:
配置 min-replicas-to-write:主节点需至少 N 个从节点才可写
设置 cluster-node-timeout:合理超时触发故障转移。
其他解法一:
可能发生:网络分区导致多个主节点同时写入。
预防机制:
min-slaves-to-write:主节点需至少N个从节点连接
min-slaves-max-lag:从节点最大延迟时间
结果:当主节点无法满足条件时拒绝写入(避免数据不一致)。
其他解法二:
脑裂定义:集群分裂为多个独立子集群,各自选举主节点导致数据不一致。
Redis 集群防脑裂机制:
节点投票:主节点需获得多数节点(N/2+1)认可才能当选。
最小主节点数:配置 min-replicas-to-write(写操作需至少 N 个从节点确认)。
风险场景:
网络分区时,少数派主节点拒绝写请求(需人工介入)。
解决方案:
合理设置超时时间(cluster-node-timeout)。
部署奇数节点保证投票多数决。
标签: 后端,Redis
54. 请描述简单工厂模式的工作原理。
默认解法:
定义一个工厂类,根据传入参数动态创建不同产品实例。例如:
class ProductFactory {
public Product createProduct(String type) {
if (“A”.equals(type)) return new ProductA();
if (“B”.equals(type)) return new ProductB();
throw new IllegalArgumentException();
}
}
其他解法一:
定义:通过工厂类创建对象(隐藏实例化逻辑)。
核心:Factory类根据输入参数返回具体产品实例。
示例:
class CarFactory {
public static Car createCar(String type) {
if(“SUV”.equals(type)) return new Suv();
if(“Sedan”.equals(type)) return new Sedan();
}
}
其他解法二:
定义:通过工厂类静态方法创建对象,隐藏实例化细节。
核心组件:
Product:产品接口(定义通用方法)。
ConcreteProduct:具体产品类(实现接口)。
Factory:工厂类(根据参数创建对应产品)。
工作流程:
客户端调用 Factory.createProduct(type)。
工厂方法根据 type 返回 ConcreteProduct 实例。
示例:
数据库连接工厂(根据参数返回 MySQL/Oracle 连接)。
缺点:新增产品需修改工厂类(违反开闭原则)。
标签: 设计模式
55. Spring 有哪几种事务传播行为?
默认解法:
REQUIRED(默认):存在事务则加入,否则新建
SUPPORTS:存在则加入,否则非事务运行
MANDATORY:必须存在事务,否则抛异常
REQUIRES_NEW:新建事务,挂起当前事务
NOT_SUPPORTED:非事务运行,挂起当前事务
NEVER:必须非事务运行,否则抛异常
NESTED:嵌套事务(依赖 Savepoint)。
其他解法一:
REQUIRED(默认):当前有事务则加入,无则新建。
REQUIRES_NEW:新建事务(挂起当前事务)。
SUPPORTS:有事务则加入,无则非事务运行。
NOT_SUPPORTED:非事务运行(挂起当前事务)。
MANDATORY:必须存在事务(否则抛异常)。
NEVER:必须无事务(否则抛异常)。
NESTED:嵌套事务(Savepoint机制)。
其他解法二:
REQUIRED(默认):
当前有事务则加入,无则新建。
REQUIRES_NEW:
新建独立事务,挂起当前事务(完全独立)。
SUPPORTS:
有事务则加入,无则以非事务执行。
NOT_SUPPORTED:
非事务执行,挂起当前事务。
MANDATORY:
必须有事务,否则抛异常。
NEVER:
必须无事务,否则抛异常。
NESTED:
嵌套事务(依赖 Savepoint),外层回滚内层必回滚,内层回滚不影响外层。
标签: 后端,Spring
56. Redis 中如何实现分布式锁?
默认解法:
使用 SET key value NX PX timeout:
NX:仅当 key 不存在时设置
PX:设置过期时间(防死锁)
value 为唯一标识(如 UUID),释放锁时先 GET 再 DEL(Lua 脚本保证原子性)
问题:需解决锁续期(看门狗)、主从切换安全问题。
其他解法一:
核心命令:SET lock_key unique_value NX PX 30000(原子操作)。
释放锁:Lua脚本验证值再删除(if redis.call(“get”,KEYS[1])==ARGV[1] then return redis.call(“del”,KEYS[1]))。
问题:锁续期(Redisson看门狗机制);集群脑裂(RedLock算法)。
其他解法二:
SETNX 命令:
SET lock_key unique_value NX PX 30000(原子操作)。
释放锁:
Lua 脚本保证原子性:if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end
Redisson 实现:
加锁:RLock lock = redisson.getLock("lock"); lock.lock();
看门狗:后台线程续期锁超时时间。
注意事项:
设置唯一值(防误删)。
锁超时时间需大于业务执行时间。
集群模式用 RedLock 算法(争议较大)。
标签: 后端,Redis
57. MySQL 中如何解决深度分页的问题?
默认解法:
问题:OFFSET 过大导致扫描大量无效行。解决方案:
子查询优化:SELECT * FROM t WHERE id > (SELECT id FROM t ORDER BY id LIMIT 1000000, 1) LIMIT 20
记录上一页最大ID:WHERE id > last_max_id ORDER BY id LIMIT 20
覆盖索引 + 延迟关联:先查主键,再回表。
其他解法一:
问题:LIMIT 1000000,10 需扫描前1000010行。
优化方案:
子查询:SELECT * FROM table WHERE id > (SELECT id FROM table LIMIT 1000000,1) LIMIT 10
游标分页:WHERE id > last_id ORDER BY id LIMIT 10
覆盖索引:仅查索引列(无需回表)
其他解法二:
问题本质:LIMIT 1000000, 10 需先扫描前 1000000 行(性能差)。
优化方案:
子查询优化:
SELECT * FROM table WHERE id > (SELECT id FROM table LIMIT 1000000, 1) LIMIT 10
游标分页(连续翻页):
SELECT * FROM table WHERE id > last_id ORDER BY id LIMIT 10
延迟关联:
SELECT * FROM table INNER JOIN (SELECT id FROM table LIMIT 1000000, 10) AS t USING(id)
业务妥协:
禁止跳页(仅允许上一页/下一页)。
滚动分页(如搜索引擎)。
标签: 后端,MySQL,数据库,场景题
58. 说说 Springboot 的启动流程?
默认解法:
创建 SpringApplication 对象:初始化监听器(ApplicationListeners)
执行 run():
a. 加载 SpringApplicationRunListeners
b. 准备环境(Environment)
c. 创建 ApplicationContext(AnnotationConfigServletWebServerApplicationContext)
d. 刷新上下文(调用 AbstractApplicationContext.refresh(),同 Spring)
e. 执行 Runner 接口(ApplicationRunner/CommandLineRunner)。
其他解法一:
初始化:SpringApplication.run()加载配置。
监听器:发布ApplicationStartingEvent事件。
环境准备:加载配置文件(application.yml)。
上下文创建:创建AnnotationConfigApplicationContext。
Bean加载:执行refreshContext()(同Spring流程)。
执行Runner:调用CommandLineRunner/ApplicationRunner。
其他解法二:
创建 SpringApplication:
初始化 ApplicationContext(根据 classpath 推断 Web 类型)。
运行 run():
加载 SpringApplicationRunListener(发布启动事件)。
准备环境:
读取配置(application.properties、环境变量等)。
创建 ApplicationContext:
根据类型创建(如 AnnotationConfigServletWebServerApplicationContext)。
刷新上下文:
执行 AbstractApplicationContext.refresh()(同 Spring 流程)。
执行 Runner:
调用 ApplicationRunner/CommandLineRunner。
启动内嵌容器:
如 Tomcat、Netty(通过 ServletWebServerFactory)。
标签: Java,Spring Boot,后端
59. Redis 实现分布式锁时可能遇到的问题有哪些?
默认解法:
锁过期但业务未完成:需续期机制(Redisson 看门狗)
主从切换导致锁失效:RedLock 算法(多实例投票)
锁误删:value 存唯一标识,释放时校验
锁不可重入:使用 ThreadLocal 记录重入次数
锁竞争:自旋等待或阻塞队列。
其他解法一:
死锁:客户端崩溃未释放锁(设置过期时间解决)。
误删锁:A超时释放后B加锁,A又删除B的锁(value存唯一标识)。
锁续期:业务超时(Redisson看门狗自动续期)。
集群脑裂:主从切换导致多客户端持锁(RedLock算法)。
其他解法二:
死锁:
原因:客户端崩溃未释放锁。
解决:设置锁超时时间(EX 参数)。
锁误删:
原因:A 超时释放锁,B 获取锁后 A 误删 B 的锁。
解决:锁值设唯一标识(UUID),删除时校验。
锁续期问题:
原因:业务执行超时,锁提前释放。
解决:Redisson 看门狗机制自动续期。
集群脑裂:
原因:主从切换导致多客户端持锁。
解决:RedLock 算法(需多数节点加锁成功)。
标签: 后端,Redis
60. 工厂模式和抽象工厂模式有什么区别?
默认解法:
工厂模式:
一个工厂类创建一种产品
通过参数选择具体产品
抽象工厂模式:
一个工厂接口创建一族相关产品
每个具体工厂实现该族所有产品的创建
更强调产品间的约束关系(如 GUI 工厂生产按钮+文本框)。
其他解法一:
工厂模式:
创建单一产品
核心:一个工厂类
示例:CarFactory.createCar()
抽象工厂模式:
创建产品族(多个相关产品)
核心:抽象工厂接口+多个工厂实现
示例:VehicleFactory.createCar() + createTruck()
其他解法二:
工厂模式(简单工厂/工厂方法):
生产单一类型产品(如数据库连接)。
工厂方法:每个产品对应一个工厂类(开闭原则)。
抽象工厂模式:
生产产品族(多个相关产品),如 GUI 库(按钮+文本框)。
抽象工厂定义接口(createButton() + createTextBox())。
具体工厂实现整套产品(如 MacFactory/WinFactory)。
核心区别:
工厂模式已关注产品实例化。
抽象工厂已关注产品族创建。
标签: 设计模式
61. SpringBoot 是如何实现自动配置的?
默认解法:
基于条件注解和 SPI 机制:
@EnableAutoConfiguration 导入 AutoConfigurationImportSelector
扫描 META-INF/spring.factories 中的自动配置类
配置类使用 @ConditionalOnClass/@ConditionalOnMissingBean 等条件注解动态装配 Bean
通过 @ConfigurationProperties 绑定外部配置(application.properties)。
其他解法一:
条件注解:@ConditionalOnClass等(按需加载)。
配置定位:META-INF/spring.factories定义AutoConfiguration类。
流程:
@EnableAutoConfiguration导入AutoConfigurationImportSelector
扫描所有spring.factories中的配置类
根据条件注解筛选生效的配置
定制:application.properties覆盖默认配置。
其他解法二:
@SpringBootApplication:
组合注解:@EnableAutoConfiguration + @ComponentScan。
@EnableAutoConfiguration:
加载 META-INF/spring.factories 中的 AutoConfiguration 类。
条件注解:
@ConditionalOnClass:类路径存在才生效。
@ConditionalOnMissingBean:容器无该 Bean 才生效。
配置绑定:
@ConfigurationProperties 将配置文件映射到 Bean 属性。
示例:
当引入 spring-boot-starter-data-redis 时,自动配置 RedisTemplate。
标签: Java,Spring Boot,后端
62. 什么是 MySQL 的主从同步机制?它是如何实现的?
默认解法:
主从复制流程:
主库写 Binlog
从库 I/O 线程拉取 Binlog 到 Relay Log
从库 SQL 线程重放 Relay Log 中的事件
复制模式:
异步复制(默认):主库不等待从库ACK
半同步复制:至少一个从库ACK才返回
GTID 复制:全局事务ID避免重复执行。
其他解法一:
主库写Binlog:事务提交前写二进制日志。
从库IO线程:拉取Binlog到本地(relay log)。
从库SQL线程:重放relay log中的事件。
同步模式:
异步(默认):主库不等待从库ACK
半同步:至少一个从库ACK
全同步:所有从库ACK
其他解法二:
主从架构:
主库(Master)处理写,从库(Slave)复制数据处理读。
同步流程:
主库写 binlog:事务提交前写二进制日志。
从库 I/O 线程:拉取主库 binlog 到本地 relay log。
从库 SQL 线程:重放 relay log 中的 SQL 事件。
复制模式:
异步复制(默认):主库不等待从库 ACK。
半同步复制:至少一个从库确认才提交(rpl_semi_sync_master_wait_for_slave_count)。
全同步复制:所有从库确认(MySQL 不支持,需集群方案)。
数据一致性:
主从不一致常见原因:网络延迟、从库重放慢。
标签: 后端,MySQL,数据库
63. 说说 Redisson 分布式锁的原理?
默认解法:
核心机制:
加锁:Lua 脚本设置 key(hash 结构,field=客户端ID,value=重入次数),设置过期时间
看门狗:后台线程定时续期(默认 30 秒过期,每 10 秒续期)
释放锁:Lua 脚本校验客户端ID,减少重入计数,计数为0则删除key
可重入:通过 hash 结构记录同一线程多次加锁。
其他解法一:
加锁:Lua脚本尝试设置hash结构(key:锁名, field:客户端ID, value:重入计数)。
看门狗:后台线程定时续期(默认30秒续到30秒)。
解锁:Lua脚本减少计数,计数=0则删除key。
可重入:同一线程多次加锁计数+1。
红锁(RedLock):多主节点多数加锁成功。
其他解法二:
加锁机制:
执行 Lua 脚本原子性操作:if redis.call('exists', KEYS[1]) == 0 then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return 1; end;
通过 Hash 结构存储锁(Key:锁名,Field:客户端ID,Value:重入次数)。
看门狗续期:
后台线程每隔 10 秒检查锁是否存在,若存在则重置过期时间(默认 30 秒),防止业务未完成锁过期。
解锁逻辑:
Lua 脚本验证客户端ID匹配后减少重入次数,若重入次数为0则删除Key。
锁竞争处理:
获取锁失败时订阅锁释放事件,通过信号量唤醒等待线程尝试重新加锁。
标签: 后端,Redis,Redisson
64. 如何理解 Spring Boot 中的 starter?
默认解法:
Starter 是预置依赖 + 自动配置的模块化组件。作用:
简化依赖管理:引入一个 starter 即整合相关库(如 spring-boot-starter-web 包含 Tomcat+SpringMVC)
自动配置:根据类路径条件装配 Bean
自定义 starter:创建 Maven 项目,添加 spring.factories 定义配置类。
其他解法一:
定义:预置依赖+自动配置的JAR包(约定优于配置)。
作用:简化依赖管理(如spring-boot-starter-web包含Tomcat/SpringMVC)。
原理:
META-INF/spring.factories声明配置类
@Configuration + @Conditional按需装配Bean
自定义:实现自己的XXX-spring-boot-starter。
其他解法二:
核心作用:
简化依赖配置:通过一个依赖引入某功能所需的所有库(如 spring-boot-starter-web 包含 Tomcat、Spring MVC)。
自动配置机制:
spring.factories 文件声明配置类(如 EnableAutoConfiguration 下的 WebMvcAutoConfiguration)。
条件注解(如 @ConditionalOnClass)控制配置生效条件。
依赖管理:
父项目 spring-boot-dependencies 统一管理版本号。
自定义 Starter:
创建 xxx-spring-boot-starter 模块,包含自动配置类及依赖。
标签: Spring Boot,后端
65. 如何使用 Redis 快速实现排行榜?
默认解法:
使用 ZSET(有序集合):
ZADD key score member:添加成员(如用户ID)和分数(如积分)
ZREVRANGE key 0 9 WITHSCORES:取 Top10
ZRANK key member:查排名
ZINCRBY key increment member:增加分数。时间复杂度 O(log N)。
其他解法一:
数据结构:有序集合(ZSET)。
核心命令:
ZADD key score member(添加成员)
ZREVRANGE key 0 9 WITHSCORES(取TOP10)
ZRANK key member(查排名)
优势:O(log N)复杂度;天然有序。
其他解法二:
数据结构:使用 ZSET(有序集合),成员为玩家ID,分数为排序依据(如积分)。
核心命令:
ZADD rank 1000 user1:添加/更新分数。
ZREVRANGE rank 0 9 WITHSCORES:获取前十名。
ZRANK rank user1:查询排名(升序)。
扩展功能:
分段查询:ZRANGEBYSCORE 按分数范围筛选。
实时刷新:结合 INCRBY 原子更新分数。
性能优势:
操作时间复杂度 O(log N),百万数据毫秒级响应。
标签: 后端,Redis
66. Redis 中如何保证缓存与数据库的数据一致性?
默认解法:
常用策略:
旁路缓存(Cache Aside):
读:先查缓存,未命中则读DB并回填
写:先更新DB,再删除缓存(非更新)
设置缓存过期时间:兜底方案
延迟双删:写DB后删缓存,延迟再删一次
消息队列:异步重试删除。注意无法强一致,需业务容忍短暂不一致。
其他解法一:
更新策略:
旁路缓存(Cache Aside):先更DB再删缓存(延迟双删防脏读)
写穿透(Write Through):Cache代理写(需Cache支持)
异步补偿:
订阅Binlog同步(如Canal)
消息队列重试
最终一致:接受短时不一致(设置缓存过期时间)。
其他解法二:
缓存更新策略:
Cache Aside:读时先读缓存,未命中读DB并回填;写时更新DB后删除缓存。
Write Through:写请求同时更新缓存和DB(需事务保证)。
删除而非更新:避免并发写导致缓存脏数据。
延迟双删:写DB后删除缓存 → 延迟毫秒 → 再次删除(防旧数据回填)。
订阅Binlog:通过 Canal 监听数据库变更,异步更新/删除缓存。
设置缓存过期:最终一致性兜底(如设置 TTL 30 分钟)。
标签: 后端,Redis
67. 说说 TCP 的三次握手?
默认解法:
目的:建立可靠连接。过程:
客户端 → 服务端:SYN=1, seq=x
服务端 → 客户端:SYN=1, ACK=1, seq=y, ack=x+1
客户端 → 服务端:ACK=1, seq=x+1, ack=y+1
握手后双方分配资源(缓冲区、连接变量)。SYN 洪泛攻击:伪造 IP 发送大量 SYN 不回复 ACK。
其他解法一:
过程:
客户端→服务端:SYN=1, seq=x
服务端→客户端:SYN=1, ACK=1, seq=y, ack=x+1
客户端→服务端:ACK=1, seq=x+1, ack=y+1
目的:双方确认收发能力正常;协商初始序列号(防历史连接)。
其他解法二:
流程:
SYN:客户端发送 SYN=1 和初始序列号 seq=x。
SYN+ACK:服务端回复 SYN=1、ACK=1,ack=x+1,并发送自身序列号 seq=y。
ACK:客户端发送 ACK=1,ack=y+1,seq=x+1。
目的:
确认双方收发能力正常。
协商初始序列号(防历史连接混淆)。
未握手风险:
二次握手时服务端直接建立连接,可能因客户端超时重发导致资源浪费。
标签: 网络
68. 简单说说 Netty 的零拷贝机制?
默认解法:
减少数据复制,提升性能:
文件传输:FileRegion 调用 transferTo() 利用操作系统 sendfile 机制
复合缓冲区:CompositeByteBuf 逻辑组合多个 Buffer,避免合并复制
堆外内存:DirectByteBuffer 直接操作内核空间
内存池:重用 ByteBuf 减少分配开销。
其他解法一:
目的:减少数据拷贝次数(提升I/O效率)。
实现方式:
文件传输:FileRegion调用transferTo(利用DMA)
缓冲区:CompositeByteBuf合并多个Buffer(逻辑组合)
内存池:重用DirectByteBuffer(避免堆外内存分配开销)
其他解法二:
堆外内存 DirectBuffer:
避免 JVM 堆与 Native 堆间数据拷贝(通过 ByteBuf.allocDirect() 创建)。
CompositeByteBuf:
合并多个 Buffer 逻辑视图,避免物理合并拷贝(如协议拆包)。
文件传输优化:
FileRegion 调用 sendfile 系统调用,文件数据直接由内核缓冲区到 Socket 缓冲区。
内存池复用:
重用已分配的 ByteBuf 对象,减少内存分配开销。
标签: Netty,后端
69. 什么是配置中心?有哪些常见的配置中心?
默认解法:
集中管理应用配置的服务。功能:
动态更新配置(无需重启)
环境隔离(dev/test/prod)
版本管理、权限控制
常见实现:
Spring Cloud Config:配合 Git/SVN
Nacos:配置管理 + 服务发现
Apollo:携程开源的配置中心
Consul:服务网格的配置方案。
其他解法一:
定义:集中管理应用配置的系统(支持动态更新)。
核心功能:
配置存储/版本管理
动态推送(长轮询/WebSocket)
权限控制
常见实现:
Spring Cloud Config(Git存储)
Nacos(配置+服务发现)
Apollo(携程开源)
Consul
其他解法二:
定义:统一管理应用配置的服务,支持动态更新、版本控制、环境隔离。
核心功能:
配置集中存储与分发。
变更实时推送(如长轮询、WebSocket)。
权限审计与历史版本回滚。
常见系统:
Spring Cloud Config:Git 存储,配合 Bus 实现批量刷新。
Nacos:支持配置与服务发现,AP/CP 模式切换。
Apollo(携程):灰度发布、权限管控完善。
Consul:基于 Raft 协议,强一致性保证。
标签: 后端,Spring Cloud
70. 说说 TCP 的四次挥手?
默认解法:
目的:安全关闭连接。过程:
主动方 → 被动方:FIN=1, seq=u
被动方 → 主动方:ACK=1, seq=v, ack=u+1(被动方进入 CLOSE_WAIT)
被动方 → 主动方:FIN=1, ACK=1, seq=w, ack=u+1(被动方发完数据后)
主动方 → 被动方:ACK=1, seq=u+1, ack=w+1(主动方进入 TIME_WAIT 等待 2MSL)。
其他解法一:
过程:
主动方→被动方:FIN=1, seq=u
被动方→主动方:ACK=1, seq=v, ack=u+1
被动方→主动方:FIN=1, ACK=1, seq=w, ack=u+1
主动方→被动方:ACK=1, seq=u+1, ack=w+1
目的:双方确认关闭连接;处理残留数据。
TIME_WAIT作用:确保最后一个ACK送达(防旧连接数据干扰)。
其他解法二:
流程:
FIN:主动方发送 FIN=1 和序列号 seq=u。
ACK:被动方回复 ACK=1,ack=u+1,seq=v(此时进入 CLOSE_WAIT)。
FIN:被动方处理完数据后发送 FIN=1,ACK=1,seq=w,ack=u+1。
ACK:主动方回复 ACK=1,ack=w+1,seq=u+1(进入 TIME_WAIT)。
挥手次数原因:
TCP 全双工特性需双方独立关闭通道。
TIME_WAIT 作用:
确保被动方收到最后一个 ACK(防重发 FIN 导致新连接混乱)。
默认等待 2MSL(报文最大生存时间)。
标签: 网络
71. Netty 是如何解决粘包和拆包问题的?
默认解法:
通过解码器(Decoder)处理:
固定长度:FixedLengthFrameDecoder
分隔符:DelimiterBasedFrameDecoder
长度字段:LengthFieldBasedFrameDecoder(最常用)
自定义协议:实现 ByteToMessageDecoder 解析消息头(含长度)。
其他解法一:
问题:TCP是字节流协议,消息边界不固定。
解决策略:
固定长度:FixedLengthFrameDecoder
分隔符:DelimiterBasedFrameDecoder
消息头定长:LengthFieldBasedFrameDecoder(头含长度字段)
自定义协议:设计Header(length字段)
其他解法二:
粘包/拆包原因:
TCP 是字节流协议,消息边界不固定。
解决方案:
固定长度解码器:FixedLengthFrameDecoder(如每条消息 100 字节)。
分隔符解码器:DelimiterBasedFrameDecoder(如 分割)。
长度字段解码器:LengthFieldBasedFrameDecoder(消息头含长度字段)。
自定义协议:定义 Header(长度+类型)+ Body 结构。
编码配合:
发送方按相同规则封包(如先写 4 字节长度再写内容)。
标签: Netty,后端
72. Spring Boot 是如何通过 main 方法启动 web 项目的?
默认解法:
执行 SpringApplication.run()
创建 AnnotationConfigServletWebServerApplicationContext
在 onRefresh() 阶段调用 createWebServer()
自动装配 ServletWebServerFactory(如 Tomcat)
工厂创建 WebServer(内嵌 Tomcat)并启动
注册 DispatcherServlet。
其他解法一:
入口:SpringApplication.run(Application.class, args)
内嵌容器:根据依赖自动选择(Tomcat/Jetty/Undertow)。
启动流程:
创建Spring容器(AnnotationConfigApplicationContext)
刷新容器(加载Bean)
调用ServletWebServerFactory创建WebServer
启动容器监听端口
其他解法二:
入口方法:SpringApplication.run(Application.class, args)。
流程:
创建 SpringApplication 实例,加载 META-INF/spring.factories 中的 ApplicationContextInitializer 和 ApplicationListener。
执行 run():初始化环境 → 打印 Banner → 创建应用上下文(如 AnnotationConfigServletWebServerApplicationContext)。
刷新上下文:AbstractApplicationContext.refresh()(加载 Bean 定义、初始化单例)。
内嵌 Web 服务器启动:通过 ServletWebServerFactory 创建 Tomcat/Jetty 实例并启动。
自动装配:@EnableAutoConfiguration 触发 Web 相关配置(如 DispatcherServlet 注册)。
标签: Spring Boot,后端
73. 什么情况下需要使用分布式事务,有哪些方案?
默认解法:
场景:跨数据库、跨服务的原子操作(如订单扣库存)。方案:
2PC(两阶段提交):协调者主导,性能低
TCC(Try-Confirm-Cancel):业务补偿,开发复杂
本地消息表:最终一致,配合消息队列
Saga:长事务拆分,每个步骤有补偿
Seata:AT 模式(自动生成回滚 SQL)。
其他解法一:
场景:跨服务/数据库的数据一致性(如:订单+库存服务)。
方案:
2PC(两阶段提交):强一致,性能低(如Seata AT模式)
TCC(Try-Confirm-Cancel):补偿型,需业务改造
消息事务:最终一致(如RocketMQ事务消息)
Saga:长事务拆分+逆向补偿
其他解法二:
使用场景:
跨服务调用(如订单创建 + 库存扣减)。
跨数据库更新(分库分表环境)。
解决方案:
2PC(两阶段提交):协调者管理参与者提交/回滚,强一致但阻塞(如 XA 协议)。
TCC(Try-Confirm-Cancel):业务层实现补偿(Try 预留资源 → Confirm/Cancel 确认或回滚)。
本地消息表:事务提交时插入消息表,异步任务推送消息(最终一致)。
Seata:AT 模式自动回滚(通过代理 SQL 生成 undo log)。
选型:
强一致选 2PC,高可用选 TCC/消息队列。
标签: Spring Cloud,微服务,分布式事务,后端
74. Redis 为什么这么快?
默认解法:
内存操作:数据存内存,读写无磁盘IO
单线程:避免上下文切换和锁竞争(6.0 后多线程处理网络IO)
高效数据结构:SDS、跳表、哈希表
I/O 多路复用:epoll/kqueue 处理高并发连接
优化编码:不同长度数据使用不同存储方式。
其他解法一:
内存存储:数据操作在内存完成。
单线程:避免上下文切换/锁竞争(6.0后多线程仅限网络I/O)。
I/O多路复用:epoll/kqueue高效处理连接。
高效数据结构:跳表/哈希表/压缩列表。
优化编码:不同场景用不同存储结构(如ziplist节省内存)。
其他解法二:
内存存储:数据操作在内存完成(纳秒级响应)。
单线程模型:
避免多线程竞争和上下文切换(6.0 后多线程仅用于网络 I/O)。
原子操作无锁冲突。
高效数据结构:
动态字符串(SDS)、跳表(ZSET)、压缩列表(Hash/List)。
I/O 多路复用:
epoll/kqueue 非阻塞处理高并发连接。
优化编码:
小数据时使用 ziplist 减少内存占用。
标签: 后端,Redis
75. 如何使用 Redis 快速实现布隆过滤器?
默认解法:
方案:
原生支持(4.0+):BF.ADD key item, BF.EXISTS key item
自实现:
使用位图(BITFIELD)和多个哈希函数
添加:计算多个位偏移并置为1
查询:检查所有位是否为1(可能误判)
场景:防止缓存穿透(判断 key 是否存在)。
其他解法一:
原理:位数组+多个哈希函数(判断元素可能存在或一定不存在)。
命令:
BF.RESERVE:创建过滤器
BF.ADD:添加元素
BF.EXISTS:检查存在性
注意:需Redis 4.0+并加载RedisBloom模块。
其他解法二:
原理:
多个哈希函数映射元素到位数组,检查所有位为1则可能存在(有误判率)。
Redis 实现:
使用 BITMAP 存储位数组,通过 SETBIT 和 GETBIT 操作。
执行步骤:
初始化:SETBIT bloom 1000000 0(设置100万位)。
添加元素:对元素做 k 次哈希,置位对应下标(SETBIT bloom [hash] 1)。
检查存在:若所有 GETBIT bloom [hash] 返回1则可能存在。
Redisson 封装:
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("bloom");
bloomFilter.tryInit(1000000, 0.01); // 100万元素,1%误判率
标签: 后端,Redis
76. 为什么 Java 中 HashMap 的默认负载因子是 0.75?
默认解法:
权衡时间与空间:
负载因子高:空间利用率高,但哈希冲突增加(查询变慢)
负载因子低:冲突减少,但空间浪费
0.75 是数学推导(泊松分布)和实验的平衡点,此时平均链表长度约 0.5,扩容阈值合理。
其他解法一:
空间与时间权衡:
负载因子高:空间利用率高,但冲突概率增加(查询慢)
负载因子低:冲突少,但空间浪费
泊松分布:当负载因子=0.75时,链表长度≥8的概率极低(约0.000006%)。
工程经验:0.75是空间成本与时间成本的折衷值。
其他解法二:
空间与时间权衡:
负载因子 = 元素数 / 容量,决定扩容阈值。
数学依据:
Poisson 分布计算:当负载因子 0.75 时,链表长度 ≥8 的概率极小(约 0.00000006):cite[4]。
实际效果:
小于 0.5:扩容频繁,空间浪费。
大于 0.75:哈希冲突增加,链表/红黑树退化风险升高。
工程折中:
0.75 时平均查找复杂度接近 O(1),空间利用率较合理。
标签: Java 集合,Java
77. 如何处理 MySQL 的主从同步延迟?
默认解法:
方案:
优化主库写速度:批量插入、减少锁范围
优化从库:硬件升级、关闭 binlog(若仅备份)、并行复制(5.6+)
业务层:
读操作分流到主库(牺牲读写分离)
关键业务强制读主
延迟敏感操作走主库
监控:SHOW SLAVE STATUS 查看 Seconds_Behind_Master。
其他解法一:
架构优化:
分库分表(减少单库压力)
读从写主(业务容忍延迟)
参数调整:
增大从库innodb_buffer_pool_size
开启并行复制(slave_parallel_workers)
强制读主:对实时性要求高的查询走主库。
其他解法二:
定位延迟:
SHOW SLAVE STATUS 查看 Seconds_Behind_Master。
优化方案:
写后读主库:关键业务读主库(如支付结果)。
半同步复制:至少一个从库确认才返回成功(牺牲部分写入性能)。
并行复制:启用 slave_parallel_workers 多线程应用 binlog。
缓存抵消:写操作后读数据走缓存(Redis)。
硬件/配置优化:
从库 SSD 磁盘提升 I/O。
增大 innodb_buffer_pool_size 减少磁盘访问。
标签: 后端,MySQL,数据库
78. Netty 如何解决 JDK NIO 中的空轮询 Bug?
默认解法:
Bug:epoll 空轮询导致 CPU 100%。Netty 方案:
记录 select 空转次数
超过阈值(默认 512)时重建 Selector
将原 Channel 重新注册到新 Selector
源码见 NioEventLoop 的 rebuildSelector()。
其他解法一:
问题:Selector.select()在无就绪事件时立即返回(导致CPU 100%)。
Netty方案:
记录空轮询次数
超过阈值(默认512)重建Selector
将原Channel重新注册到新Selector
其他解法二:
Bug 现象:
JDK NIO 的 Selector.select() 在无事件时立即返回,导致 CPU 100%。
Netty 解决方案:
计数检测:记录空轮询次数,超过阈值(默认 512)时重建 Selector。
替换流程:
创建新 Selector。
将旧 Channel 注册到新 Selector。
关闭旧 Selector。
源码关键:
io.netty.channel.nio.NioEventLoop 中的 select() 方法实现空轮询保护逻辑。
标签: Netty,后端
79. Java 中 HashMap 的扩容机制是怎样的?
默认解法:
触发条件:元素数 > 容量 × 负载因子(默认0.75)
过程:
创建新数组(原容量×2)
遍历旧数组每个桶:
链表节点:拆分为低位链表(原位置)和高位链表(原位置+旧容量)
红黑树节点:split() 拆树(若长度≤6 则退化为链表)
迁移后旧数组丢弃。
其他解法一:
触发条件:元素数量 > 容量 × 负载因子(默认0.75)。
扩容过程:
创建新数组(2倍原大小)
遍历旧数组,重新计算桶位置(高位运算优化)
链表拆分为低位链(原索引)和高位链(原索引+旧容量)
并发问题:多线程扩容可能导致链表成环(头插法问题,1.8尾插法解决)。
其他解法二:
触发条件:
元素数量 > 容量 × 负载因子(默认 0.75)。
扩容流程:
新容量 = 旧容量 × 2(保持 2 的 n 次方)。
创建新数组,重新计算索引:(e.hash & oldCap) == 0 则位置不变,否则新位置 = 原位置 + 旧容量。
元素迁移:
JDK 1.8 优化:链表拆分为高低位两组,避免重新计算哈希。
线程安全问题:
多线程扩容可能导致循环链表(JDK 1.7)或数据丢失。
性能影响:
扩容时读写阻塞,初始化时预分配足够容量避免频繁扩容。
标签: Java 集合,Java
80. 为什么 Redis 设计为单线程?6.0 版本为何引入多线程?
默认解法:
单线程原因:
避免锁开销和上下文切换
内存操作本身快速
I/O 多路复用处理并发连接
6.0 引入多线程原因:
网络 I/O 成为瓶颈(特别是大键操作)
后台线程处理过期键删除、AOF 刷盘等阻塞任务。注意:命令执行仍是单线程。
其他解法一:
单线程原因:
避免锁竞争/上下文切换
内存操作本身快速
I/O多路复用处理并发连接
6.0引入多线程:
仅限网络I/O解析(read/write)
核心命令执行仍单线程(保持原子性)
提升高并发场景吞吐量
其他解法二:
单线程优势:
避免锁竞争和上下文切换,内存操作原子性天然保障。
足够应对10万级QPS(内存操作+epoll)。
6.0 多线程原因:
网络 I/O 成为瓶颈:单线程处理大流量时网络包解析延迟升高。
实现方式:
I/O 线程池(默认不启用)处理读写事件(io-threads 4)。
命令执行仍由主线程串行处理(保证原子性)。
适用场景:
网络延迟高或大包场景(如批量操作)性能提升显著。
标签: 后端,Redis
81. 为什么 TCP 挥手需要有 TIME_WAIT 状态?
默认解法:
作用:
确保被动方收到最后一个 ACK(若丢失可重传)
让旧连接的重复报文在网络中消失(等待 2MSL,MSL 通常 30s/1min)
问题:高并发短连接时端口耗尽。解决方案:SO_REUSEADDR 选项复用 TIME_WAIT 端口。
其他解法一:
确保最后一个ACK送达:若ACK丢失,被动方能重发FIN。
防止旧连接数据混淆:等待2MSL(报文最大生存时间)使本连接数据包失效。
时间:通常2分钟(Linux可调tcp_fin_timeout)。
其他解法二:
确保被动关闭方收到 ACK:
若最后一次 ACK 丢失,被动方重发 FIN,TIME_WAIT 可重传 ACK。
防止旧连接数据混淆:
等待 2MSL(报文最大生存时间 × 2)使本次连接所有报文在网络中消失。
默认时长:
Linux 默认 60 秒(可通过 net.ipv4.tcp_fin_timeout 调整)。
副作用:
高并发短连接时端口耗尽(可通过 SO_REUSEADDR 复用端口)。
标签: 网络
82. Spring Boot 的核心特性有哪些?
默认解法:
自动配置:基于条件装配 Bean
起步依赖:预置库版本管理
Actuator:应用监控端点
嵌入式容器:内嵌 Tomcat/Jetty
外部化配置:支持 properties/yaml/环境变量
生产就绪:健康检查、指标收集
无代码生成:纯注解配置。
其他解法一:
自动配置:基于条件注解智能装配Bean。
起步依赖:预置依赖组合(如spring-boot-starter-web)。
内嵌容器:无需部署WAR(Tomcat/Jetty)。
Actuator:生产级监控端点(健康/指标)。
外部化配置:支持多环境配置(application-{profile}.yml)。
其他解法二:
自动配置(Auto-configuration):根据项目依赖自动配置Spring应用,减少手动配置。
起步依赖(Starter Dependencies):提供预定义的依赖组合,简化Maven/Gradle配置。
嵌入式服务器(Embedded Servers):内置Tomcat、Jetty等服务器,无需单独部署WAR文件。
Actuator:提供生产级监控端点(如健康检查、指标收集)。
外部化配置:支持通过属性文件、环境变量等灵活管理配置。
无代码生成与XML配置:基于约定优于配置原则,减少样板代码。
标签: Spring Boot,后端
83. 为什么 HashMap 在 Java 中扩容时采用 2 的 n 次方倍?
默认解法:
优化哈希计算与索引定位:
计算桶下标:index = (n – 1) & hash,n 为 2^k 时等价于 hash % n,且位运算更快
扩容迁移时:节点新位置 = 原位置 或 原位置 + 旧容量(利用高位 bit 判断,无需重新哈希)。
其他解法一:
哈希优化:计算桶下标时用 (n-1) & hash 代替取模(位运算高效)。
均匀分布:当n为2^k时,(n-1)的二进制全1,减少哈希冲突。
扩容迁移:旧桶数据仅可能分到新表两个位置(高位链/低位链),迁移效率高。
其他解法二:
索引计算优化:
index = (n - 1) & hash 等价于取模运算(hash % n),位运算效率更高。
哈希分布均匀:
当 n 为 2^k 时,(n-1) 的二进制全为 1(如 15=0b1111),减少哈希冲突。
扩容迁移优化:
JDK 1.8 中通过 e.hash & oldCap 判断高位,只需移动一半元素(0 或 oldCap 位置)。
非 2^k 的缺点:
取模需复杂运算(除法),性能下降。
标签: Java 集合,Java
84. 你在项目中使用的 Redis 客户端是什么?
默认解法:
常见客户端:
Jedis:同步阻塞,线程不安全需连接池
Lettuce:基于 Netty 异步非阻塞,支持响应式
Redisson:分布式功能丰富(锁、队列等)
根据场景选择:高并发选 Lettuce,需分布式对象选 Redisson。
其他解法一:
常见客户端:
Jedis:同步阻塞(连接池管理)
Lettuce:基于Netty异步(支持响应式)
Redisson:分布式对象/锁高级功能
Spring整合:Spring Data Redis封装Jedis/Lettuce。
其他解法二:
在Java项目中,常用的Redis客户端包括:
Jedis:轻量级、同步阻塞式客户端,适用于简单场景。
Lettuce:基于Netty的异步非阻塞客户端,支持响应式编程,性能更高。
Redisson:提供分布式对象和服务(如锁、队列),适合复杂分布式系统。
实际项目中,根据需求选择——高并发场景优先Lettuce,需分布式功能则用Redisson。
标签: 后端,Redis
85. TCP 超时重传机制是为了解决什么问题?
默认解法:
解决数据包丢失问题。原理:
发送数据后启动定时器
超时未收到 ACK 则重传
动态计算 RTO(重传超时时间):基于 RTT(往返时间)自适应
快速重传:收到 3 个重复 ACK 立即重传(无需等待超时)。
其他解法一:
问题:网络丢包导致数据未送达。
机制:发送数据后启动定时器,超时未收到ACK则重发。
动态计算:RTO(重传超时时间)基于RTT(往返时间)动态调整。
快速重传:收到3个重复ACK立即重传(无需等超时)。
其他解法二:
TCP超时重传机制主要解决网络传输中的丢包问题:
数据包丢失:当发送方未收到接收方的ACK确认时,重传数据包以确保可靠性。
延迟或乱序:通过动态计算RTT(往返时间)设置重传超时阈值,避免因网络延迟误判丢包。
核心目标是实现可靠传输,确保数据完整到达。
标签: 网络
86. 简述 MyBatis 的插件运行原理,以及如何编写一个插件?
默认解法:
原理:基于责任链模式拦截四大对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)。步骤:
实现 Interceptor 接口,重写 intercept()
用 @Intercepts 注解指定拦截方法
配置 注册插件
通过 Plugin.wrap() 生成代理对象。
其他解法一:
原理:基于责任链模式(Interceptor Chain)拦截四大组件:
Executor(执行器)
StatementHandler(SQL处理)
ParameterHandler(参数处理)
ResultSetHandler(结果集处理)
编写步骤:
实现Interceptor接口,注解指定拦截目标
重写intercept方法(调用Invocation.proceed()继续链)
在SqlSessionFactory中注册插件
其他解法二:
运行原理:
基于拦截器(Interceptor):通过动态代理拦截MyBatis的核心方法(如Executor的query/update)。
责任链模式:多个插件按配置顺序形成拦截链,每个插件可修改或增强原逻辑。
编写步骤:
实现Interceptor接口:重写intercept()方法定义拦截逻辑,plugin()方法生成代理对象。
使用@Intercepts注解:指定拦截的目标方法(如@Signature(type=Executor.class, method=“update”, args={MappedStatement.class, Object.class}))。
注册插件:在MyBatis配置文件中通过标签添加。
标签: 后端,MyBatis
87. 数组和链表在 Java 中的区别是什么?
默认解法:
数组:
连续内存,支持随机访问(O(1))
大小固定,插入删除需移动元素
内存紧凑(无额外指针)
链表:
非连续内存,通过指针连接
插入删除高效(O(1))
访问需遍历(O(n))
内存开销大(存储指针)。
其他解法一:
内存结构:
数组:连续内存,预分配大小
链表:节点分散(指针链接),动态扩展
访问效率:
数组:O(1)随机访问
链表:O(n)顺序访问
插入删除:
数组:O(n)(需移动元素)
链表:O(1)(修改指针)
线程安全:Vector(数组)线程安全;LinkedList非线程安全。
其他解法二:
数据结构:数组是连续内存空间,链表(如LinkedList)通过节点指针非连续存储。
访问效率:数组支持O(1)随机访问;链表需遍历,O(n)访问。
插入删除:链表在头尾插入/删除为O(1),数组需移动元素,平均O(n)。
内存占用:数组预分配固定大小,链表每个元素额外存储指针(空间开销更大)。
扩容:数组需手动复制扩容,链表动态增删节点更灵活。
标签: Java 集合,Java
88. Redis 中常见的数据类型有哪些?
默认解法:
String:字符串、整数
List:双向链表(支持阻塞操作)
Hash:键值对集合
Set:无序唯一集合
ZSet:有序集合(score 排序)
Bitmap:位操作
HyperLogLog:基数统计
Stream:消息流(5.0+)。
其他解法一:
String:字符串/整数/浮点数(最大512MB)。
Hash:键值对集合(适合存储对象)。
List:双向链表(支持阻塞操作)。
Set:无序唯一集合(交并差运算)。
Sorted Set:有序集合(分值排序)。
Bitmap:位图操作。
HyperLogLog:基数统计。
Stream:消息流(5.0+)。
其他解法二:
String:最基本类型,存储文本或二进制数据(如缓存键值)。
List:有序字符串列表,支持双向操作(如消息队列)。
Set:无序唯一集合,适用于去重(如标签系统)。
Hash:键值对集合,适合存储对象(如用户信息)。
ZSet(Sorted Set):带分数的有序集合,用于排行榜。
其他:Bitmaps(位图)、HyperLogLog(基数统计)、Streams(流处理)。
标签: 后端,Redis
89. TCP 滑动窗口的作用是什么?
默认解法:
流量控制:接收方通过窗口大小(rwnd)限制发送速率,防止淹没缓冲区
提高吞吐:允许发送方连续发送多个报文(无需逐个确认),利用带宽
拥塞控制:结合拥塞窗口(cwnd)动态调整发送量。
其他解法一:
流量控制:接收方通过窗口大小(win)限制发送速率(防淹没)。
可靠传输:窗口内数据可批量发送/确认(减少等待)。
拥塞控制:动态调整窗口大小(慢启动/拥塞避免)。
其他解法二:
滑动窗口主要解决两个核心问题:
流量控制:接收方通过窗口大小(Window Size)通知发送方可发送的数据量,防止接收方缓冲区溢出。
提高传输效率:发送方无需每包等待ACK,可连续发送窗口内数据,减少网络空闲时间(如窗口大小动态调整以适应网络状况)。
标签: 网络
90. 介绍一下 Reactor 线程模型?
默认解法:
处理高并发的 I/O 事件驱动模型:
单 Reactor 单线程:所有操作(accept/read/send)在同一线程
单 Reactor 多线程:I/O 操作在主线程,业务处理在线程池
主从 Reactor:主 Reactor 处理连接,子 Reactor 处理 I/O(Netty 默认)。
其他解法一:
核心:事件驱动+多路复用(I/O事件分发)。
单Reactor单线程:所有操作(accept/read/send)在同一线程。
单Reactor多线程:I/O操作在主线程,业务处理用线程池。
主从Reactor:主线程处理accept,子线程处理I/O(如Netty/Nginx)。
其他解法二:
Reactor模型是一种事件驱动的IO处理模式,核心组件:
Reactor:监听事件(如连接请求),分发给对应Handler。
Handlers:处理具体IO事件(如读写操作)。
常见变体:
单Reactor单线程:所有操作在单线程完成,简单但性能低。
单Reactor多线程:Reactor线程处理IO事件,耗时操作交给线程池。
主从Reactor多线程:主Receptor处理连接,子Reactor处理读写,高效(如Netty实现)。
标签: Netty,后端
91. Java 线程池核心线程数在运行过程中能修改吗?如何修改?
默认解法:
可以。通过 ThreadPoolExecutor 的 setCorePoolSize() 方法:
若新值 > 原值:立即创建新线程
若新值 < 原值:空闲线程在下次保活时间超时后被回收
注意:需根据业务负载动态调整(如配置中心监听变化)。
其他解法一:
可以修改:通过ThreadPoolExecutor.setCorePoolSize()。
内部逻辑:
若新值 > 旧值:立即创建新线程
若新值 < 旧值:空闲线程超时会被回收(需设置allowCoreThreadTimeOut=true)
注意:修改后需调用prestartCoreThread()预热核心线程。
其他解法二:
能修改。方法如下:
ThreadPoolExecutor提供setCorePoolSize(int)方法动态调整核心线程数。
原理:新值大于旧值时,立即创建新线程;小于旧值时,空闲线程将在下次空闲时终止。
注意:需结合业务场景谨慎调整,避免频繁变更引发性能波动。
标签: 后端,Java,Java并发
92. TCP/IP 四层模型是什么?
默认解法:
应用层:HTTP、FTP、DNS(用户交互)
传输层:TCP、UDP(端到端通信)
网络层:IP、ICMP(路由寻址)
网络接口层:以太网、WiFi(物理传输)。对比 OSI:合并上三层为应用层,合并下两层为网络接口层。
其他解法一:
应用层:HTTP/FTP/DNS(用户数据)。
传输层:TCP/UDP(端到端连接)。
网络层:IP/ICMP(路由寻址)。
网络接口层:Ethernet/ARP(物理传输)。
其他解法二:
应用层:提供应用服务(如HTTP、FTP)。
传输层:端到端数据传输(TCP/UDP)。
网络层:路由和寻址(IP协议)。
网络接口层:物理网络访问(如以太网帧封装)。
标签: 网络
93. 说说 MyBatis 的缓存机制?
默认解法:
MyBatis 采用两级缓存:
一级缓存(本地缓存):SqlSession 级别,默认开启。同一会话中相同查询直接从缓存返回结果,执行更新操作(增删改)时自动清空
二级缓存(全局缓存):Mapper 级别,需手动配置()。跨 SqlSession 共享,通过序列化机制存储,更新操作清空整个 Mapper 缓存
注意:分布式环境需集成 Redis 等中央缓存,避免脏读
其他解法一:
一级缓存:
作用域:SqlSession级别(默认开启)
失效:执行UPDATE/DELETE/INSERT或clearCache()
二级缓存:
作用域:Mapper级别(需手动开启)
存储:序列化到磁盘/其他缓存服务(如Redis)
注意:事务提交后才缓存
其他解法二:
MyBatis提供两级缓存:
一级缓存:默认开启,作用域为SqlSession级别(同一会话内相同查询直接返回缓存)。
二级缓存:需手动配置,作用域为Mapper级别(跨SqlSession共享,通过序列化存储)。
注意:缓存可能导致脏读,可通过flushCache属性或@CacheNamespaceRef注解管理。
标签: 后端,MyBatis
94. 什么是 Spring Boot?
默认解法:
Spring Boot 是 Spring 的脚手架框架,简化配置和部署。核心价值:
自动配置:减少 XML 配置
内嵌容器:独立运行 Jar 包
生产监控:Actuator 提供健康检查
起步依赖:管理依赖版本
约定优于配置:提供默认设置。
其他解法一:
定义:简化Spring应用初始搭建和开发的框架。
核心思想:约定优于配置(如默认依赖/目录结构)。
核心功能:
自动配置(Auto-Configuration)
起步依赖(Starter Dependencies)
内嵌容器(Embedded Container)
Actuator监控
优势:快速构建独立、生产级应用。
其他解法二:
Spring Boot是基于Spring框架的快速开发工具:
目标:简化Spring应用初始搭建和配置,提供开箱即用功能。
核心能力:自动配置、起步依赖、嵌入式服务器、生产监控(Actuator)。
优点:减少XML配置,提升开发效率,适合微服务架构。
标签: Spring Boot,后端
95. Java 中如何创建多线程?
默认解法:
继承 Thread 类,重写 run()
实现 Runnable 接口,传入 Thread 构造器
实现 Callable 接口 + FutureTask(可获取返回值)
线程池:ExecutorService.submit()/execute()
推荐方式:线程池管理资源,避免频繁创建销毁线程。
其他解法一:
继承Thread类:重写run()方法,调用start()启动。
实现Runnable接口:实现run(),传入Thread构造器。
实现Callable接口:结合FutureTask获取返回值。
线程池:ExecutorService提交任务(推荐)。
注意:直接调用run()是同步执行,非多线程。
其他解法二:
继承Thread类:重写run()方法,调用start()启动线程。
实现Runnable接口:实现run()方法,将实例传入Thread对象。
实现Callable接口:结合FutureTask获取异步结果(支持返回值)。
线程池:通过ExecutorService提交任务(如Executors.newFixedThreadPool())。
推荐使用Runnable或Callable,避免单继承限制。
标签: 后端,Java,Java并发
96. Redis 中跳表的实现原理是什么?
默认解法:
ZSet 的底层结构之一(元素多时使用)。原理:
多层有序链表:底层全量数据,上层索引(概率性提升)
查询:从顶层开始向右向下搜索(O(log n))
插入:随机生成层高(幂次定律),更新前后指针
对比平衡树:实现简单,范围查询高效,内存稍多。
其他解法一:
结构:多层有序链表(底层全量数据,上层索引稀疏)。
节点:
分值(score)排序
后退指针(向后遍历)
层数组(每层前进指针和跨度)
查询:从最高层向下查找(类似二分)。
插入:随机生成层高(概率1/4),更新前后指针。
其他解法二:
跳表(SkipList)是ZSet的底层实现之一:
结构:多层有序链表,上层是下层的索引(加速查找)。
查询:从顶层开始,向右或向下遍历(平均O(log n)复杂度)。
插入:随机生成层数,更新前后节点指针(类似平衡树但更简单)。
优势:比平衡树实现简单,支持范围查询。
标签: 后端,Redis
97. Redis 性能瓶颈时如何处理?
默认解法:
内存:
增加内存或集群分片
优化数据结构(如 hash 使用 ziplist)
CPU:
慢查询优化(KEYS → SCAN)
大 Key 拆分
网络:
管道/Pipeline 批量操作
客户端连接池
持久化:AOF 重写调为后台执行。
其他解法一:
定位瓶颈:
慢查询(slowlog get)
监控命令耗时(redis-cli –latency)
资源监控(CPU/内存/网络)
优化手段:
大Key拆分(hash分field)
热Key分散(添加随机后缀)
Pipeline批量操作
集群分片(扩容)
其他解法二:
内存优化:使用压缩数据结构(如Hash的ziplist),设置过期时间。
分片:通过集群或客户端分片分散数据(如Redis Cluster)。
持久化调整:根据场景选择RDB(快照)或AOF(日志),避免频繁写盘。
连接池:复用连接减少开销。
监控:用Redis INFO命令分析慢查询、内存占用。
标签: 后端,Redis
98. OSI 七层模型是什么?
默认解法:
物理层:比特流传输(网线、光纤)
数据链路层:帧传输(MAC 地址、交换机)
网络层:路由寻址(IP 协议、路由器)
传输层:端到端连接(TCP/UDP)
会话层:建立/管理会话
表示层:数据格式转换(加密/压缩)
应用层:用户接口(HTTP/FTP)。
其他解法一:
物理层:比特流传输(网线/光纤)。
数据链路层:帧传输(MAC地址,交换机)。
网络层:IP寻址(路由器)。
传输层:端到端连接(TCP/UDP)。
会话层:建立/管理会话。
表示层:数据格式转换(加密/压缩)。
应用层:用户接口(HTTP/SMTP)。
其他解法二:
物理层:传输原始比特流(如电缆、光纤)。
数据链路层:帧传输(MAC地址、交换机)。
网络层:路由和IP寻址。
传输层:端到端连接(TCP/UDP)。
会话层:建立/维护会话。
表示层:数据格式转换(加密/压缩)。
应用层:用户接口(如HTTP协议)。
标签: 网络
99. 说说 AQS 吧?
默认解法:
AbstractQueuedSynchronizer:并发包锁的基石。核心:
状态变量 state(volatile int)
CLH 队列(双向链表存储等待线程)
模板方法:tryAcquire/tryRelease(需子类实现)
获取锁:CAS 修改 state,失败则入队阻塞
释放锁:修改 state 并唤醒后继节点
应用:ReentrantLock、CountDownLatch、Semaphore。
其他解法一:
定义:AbstractQueuedSynchronizer(抽象队列同步器)。
核心:
state:同步状态(如ReentrantLock中0未锁,>0重入次数)
CLH队列:双向链表存储等待线程
模板方法:
tryAcquire():尝试获取锁
tryRelease():尝试释放锁
应用:ReentrantLock/CountDownLatch/Semaphore的底层实现。
其他解法二:
AQS(AbstractQueuedSynchronizer)是Java并发包的基础框架:
作用:通过CLH队列实现阻塞锁(如ReentrantLock)和同步器(如CountDownLatch)。
核心:一个volatile int state表示状态,线程竞争失败时入队阻塞。
模式:独占模式(单线程访问)和共享模式(多线程共享)。
扩展:子类重写tryAcquire()/tryRelease()定义获取/释放逻辑。
标签: Java并发,Java
100. Cookie、Session、Token 之间有什么区别?
默认解法:
Cookie:
客户端存储(键值对)
自动携带在请求头
Session:
服务端存储会话数据
依赖 Cookie 传递 SessionID
Token(如 JWT):
客户端存储(无状态)
自包含用户信息(签名防篡改)
适合分布式系统。
其他解法一:
Cookie:客户端存储(键值对),自动携带(同域请求)。
Session:服务端存储会话状态(依赖Cookie传SessionID)。
Token:无状态凭证(如JWT),自包含用户信息(签名防篡改)。
对比:
扩展性:Token易跨域/分布式
安全性:Token无CSRF风险
存储压力:Session在服务端,Token在客户端
其他解法二:
Cookie:客户端存储的小型数据(由服务端Set-Cookie设置),每次请求自动携带。
Session:服务端存储的用户状态,Session ID通过Cookie传递,依赖服务器内存。
Token(如JWT):自包含令牌(存储用户信息),客户端存储(如localStorage),无状态化。
核心区别:存储位置(客户端/服务端)与状态管理(有状态/无状态)。
标签: 网络
101. 什么是分库分表?分库分表有哪些类型(或策略)?
默认解法:
目的:解决单库性能瓶颈。类型:
垂直分库:按业务拆分(如订单库、用户库)
垂直分表:大表拆小表(如商品表拆基本信息+详情)
水平分库:同一表数据分到不同库(按用户ID哈希)
水平分表:同一库内分多表(如 order_001, order_002)
策略:哈希取模、范围分片、一致性哈希。
其他解法一:
定义:水平拆分数据(缓解单库性能瓶颈)。
分库:按业务模块拆分(如订单库/用户库)。
分表:单表数据拆分到多表(如按用户ID取模)。
策略:
哈希取模:均匀分布
范围分片:按时间/ID区间
一致性哈希:减少扩容迁移量
工具:ShardingSphere/MyCat。
其他解法二:
分库分表是数据库水平拆分策略:
目的:解决单库性能瓶颈(高并发、大数据量)。
类型:
垂直拆分:按业务模块分库(如订单库、用户库)。
水平拆分:按规则分表(如订单表按ID哈希分到多个库)。
策略:
哈希分片:均匀分布数据。
范围分片:按ID区间划分(如1-1000万表1)。
地理位置分片:就近存储。
标签: 后端,MySQL,数据库
102. MyBatis 中 #{} 和 ${} 的区别是什么?
默认解法:
#{}:
预编译处理(占位符 ?)
防 SQL 注入
自动类型转换(如字符串加引号)
${}:
字符串替换(直接拼 SQL)
有注入风险
需手动处理类型
使用场景:动态表名/列名用 ${}(需过滤),参数值用 #{}。
其他解法一:
#{}:预编译处理(生成?),防SQL注入(自动加单引号)。
${}:字符串替换(原样拼入SQL),有注入风险。
使用场景:
#{}:参数值(如WHERE user_id = #{id})
${}:动态表名/列名(如ORDER BY ${columnName})
其他解法二:
#{}:预编译处理(参数占位符?),防止SQL注入(如where name = #{value})。
${}:字符串替换(直接拼接到SQL),有注入风险(如动态表名order by KaTeX parse error: Expected 'EOF', got '#' at position 16: {column})。 优先使用#̲{},{}仅用于动态SQL片段(如非用户输入字段)。
标签: 后端,MyBatis
103. Java 中的 final 关键字是否能保证变量的可见性?
默认解法:
不能。final 仅保证:
引用不可变(但对象内部状态可变)
构造函数初始化安全(其他线程可见 final 字段初始值)
可见性需通过 volatile 或 synchronized 保证。
其他解法一:
能:final修饰的字段在构造完成后对其他线程可见(禁止重排序)。
原理:JMM(Java内存模型)保证final字段初始化前不会引用对象。
注意:仅限构造方法内赋值(方法外赋值不保证)。
其他解法二:
是的,但需满足条件:
可见性保障:final字段在对象构造函数中初始化完成后,对其他线程立即可见(JVM插入内存屏障)。
前提:对象必须正确发布(如避免this引用逸出),否则可能看到未初始化状态。
限制:仅适用于对象构造后的初始值,不保证引用对象内部状态的可见性(如final数组的元素可变)。
标签: Java并发,Java
104. Redis 的 hash 是什么?
默认解法:
Redis Hash 是 field-value 映射表(类似 Java HashMap)。常用命令:
HSET key field value
HGET key field
HGETALL key
适用场景:存储对象(如用户信息),对比 String 存储可部分更新。底层:ziplist(小哈希)或 hashtable。
其他解法一:
数据结构:键值对集合(field-value),类似Java的HashMap。
底层:ziplist(元素少时)或hashtable。
命令:
HSET key field value
HGET key field
HGETALL key
适用场景:存储对象(如用户信息)。
其他解法二:
数据结构:键值对集合(类似 Java HashMap),适合存储对象。
底层实现:
小数据时:ziplist(连续内存,节省空间)。
大数据时:hashtable(哈希表,O(1) 复杂度)。
核心命令:
HSET user:1 name "Alice":设置字段值。
HGET user:1 name:获取字段值。
HGETALL user:1:获取所有字段和值。
应用场景:
存储用户信息(避免序列化整个对象)。
聚合统计(如 HINCRBY 累加商品点击量)。
标签: 后端,Redis
105. 从网络角度来看,用户从输入网址到网页显示,期间发生了什么?
默认解法:
DNS 解析:域名 → IP
TCP 连接:与服务器三次握手
HTTPS:TLS 握手(如需)
HTTP 请求:发送请求报文
服务器处理:后端服务 + 数据库
HTTP 响应:返回 HTML/CSS/JS
浏览器渲染:解析资源并显示
TCP 断开:四次挥手。
其他解法一:
DNS解析:域名→IP地址。
TCP连接:与服务器三次握手。
HTTP请求:发送请求报文(GET/POST)。
服务器处理:返回响应(HTML/CSS/JS)。
浏览器渲染:解析HTML构建DOM树,加载资源,渲染页面。
TCP断开:四次挥手。
其他解法二:
DNS 解析:浏览器查询域名对应 IP(递归查询 → 根域名 → 顶级域名 → 权威 DNS)。
TCP 连接:与目标 IP 的 80/443 端口三次握手。
TLS 握手(HTTPS):协商加密算法,交换密钥(非对称加密)。
HTTP 请求:发送 GET/POST 请求(含 Cookie、User-Agent)。
服务器处理:
Web 服务器(Nginx)转发请求到应用服务器(Tomcat)。
应用服务器执行业务逻辑(查询数据库)。
HTTP 响应:返回状态码 + HTML/CSS/JS 资源。
浏览器渲染:
解析 HTML 构建 DOM 树,CSS 构建 CSSOM 树。
合并为渲染树 → 布局 → 绘制。
连接关闭:TCP 四次挥手。
标签: 网络
106. Dubbo 和 Spring Cloud Gateway 有什么区别?
默认解法:
Dubbo:
RPC 框架(服务间调用)
基于 TCP 传输,性能高
需配合 ZooKeeper/Nacos 注册中心
Spring Cloud Gateway:
API 网关(边缘服务)
HTTP 请求路由、过滤、限流
整合 Spring Cloud 生态(如熔断、认证)
定位不同:Dubbo 专注微服务调用,Gateway 专注入口流量管理。
其他解法一:
定位:
Dubbo:RPC框架(服务间调用)
Spring Cloud Gateway:API网关(请求入口路由/过滤)
功能:
Dubbo:服务注册发现/负载均衡/容错
Gateway:路由转发/限流/熔断/鉴权
协议:Dubbo支持Dubbo/HTTP;Gateway基于WebFlux(HTTP)。
其他解法二:
定位不同:
Dubbo:RPC 框架(服务间调用)。
Spring Cloud Gateway:API 网关(统一入口、路由、过滤)。
功能对比:
| 特性 | Dubbo | Spring Cloud Gateway |
|---|---|---|
| 核心功能 | 服务调用、负载均衡 | 路由、限流、鉴权 |
| 协议 | Dubbo 协议/TCP | HTTP/WebSocket |
| 生态 | 微服务治理(注册中心) | Spring Cloud 全家桶 |
使用场景:
Dubbo 用于微服务内部通信。
Gateway 作为外部请求入口(如聚合多个微服务 API)。
标签: 后端,分布式,Spring Cloud,Dubbo
107. 什么是 Java 中的原子性、可见性和有序性?
默认解法:
原子性:操作不可分割(如 synchronized)
可见性:一个线程修改共享变量,其他线程立即可见(volatile 强制主内存刷新)
有序性:程序执行顺序按代码顺序(volatile 禁止指令重排,happens-before 原则)。
其他解法一:
原子性:操作不可中断(如synchronized)。
可见性:一个线程修改后,其他线程立即可见(如volatile)。
有序性:程序执行顺序按代码顺序(volatile/synchronized禁止指令重排序)。
JMM保证:happens-before原则(如锁规则/volatile规则)。
其他解法二:
原子性:
操作不可分割(如 i++ 非原子,AtomicInteger 的 incrementAndGet() 是原子)。
可见性:
线程修改共享变量后,其他线程立即可见(通过 volatile 或 synchronized 保证)。
有序性:
程序执行顺序按代码顺序(禁止指令重排序,volatile 和 final 可保证)。
JMM 保证:
synchronized 同时满足三者。
volatile 保证可见性和有序性(不保证原子性)。
标签: Java并发,Java
108. 线程和进程有什么区别?
默认解法:
进程:
操作系统资源分配单位
独立内存空间(进程间通信需 IPC)
切换开销大
线程:
CPU 调度单位
共享进程内存
切换开销小
关系:一个进程包含多个线程。
其他解法一:
资源:进程是资源分配单位(独立内存);线程是CPU调度单位(共享进程内存)。
开销:进程创建/切换开销大;线程轻量(共享资源)。
通信:进程间通信需IPC(管道/消息队列);线程可直接读写共享变量。
健壮性:进程崩溃不影响其他进程;线程崩溃可能导致整个进程退出。
其他解法二:
资源占用:
进程:独立内存空间(堆、栈、数据段),切换开销大。
线程:共享进程内存,私有栈空间,切换开销小。
通信方式:
进程:管道、消息队列、共享内存、Socket。
线程:直接读写共享变量(需同步)。
健壮性:
进程崩溃不影响其他进程。
线程崩溃可能导致整个进程退出。
并发性:
多线程更易实现高并发(如 Web 服务器处理请求)。
标签: 操作系统,计算机基础
109. 说说你知道的几种 I/O 模型
默认解法:
阻塞 I/O:调用后线程阻塞等待数据
非阻塞 I/O:立即返回,轮询检查就绪
I/O 多路复用:select/poll/epoll 监听多个 fd,就绪时通知
信号驱动 I/O:内核数据就绪时发送 SIGIO 信号
异步 I/O:内核完成所有操作后回调(如 AIO)。
其他解法一:
阻塞I/O:调用recv()时线程阻塞(直到数据就绪)。
非阻塞I/O:轮询检查数据就绪(CPU空转)。
I/O多路复用:select/poll/epoll单线程监听多个fd(事件驱动)。
信号驱动I/O:内核数据就绪时发SIGIO信号(少用)。
异步I/O:aio_read()发起后立即返回,内核完成所有操作后通知。
其他解法二:
阻塞 I/O:
线程等待数据就绪(如 read() 阻塞)。
非阻塞 I/O:
轮询检查数据就绪(浪费 CPU)。
I/O 多路复用:
select/poll/epoll 监控多个 fd,就绪时通知(高并发基石)。
信号驱动 I/O:
内核数据就绪时发送 SIGIO 信号(少用)。
异步 I/O:
系统调用后立即返回,数据就绪后回调通知(如 AIO)。
对比:
阻塞/非阻塞:调用者是否等待。
同步/异步:数据就绪由谁处理(同步需调用者读写,异步由内核完成)。
标签: Netty,后端
110. MyBatis 与 Hibernate 有哪些不同?
默认解法:
MyBatis:
SQL 映射(半自动 ORM)
需手写 SQL,灵活优化
学习曲线低
Hibernate:
全自动 ORM(对象-关系映射)
HQL 操作,少写 SQL
缓存、延迟加载等高级特性
适用:复杂 SQL/性能优化选 MyBatis;快速开发选 Hibernate。
其他解法一:
SQL控制:MyBatis需手动写SQL(灵活优化);Hibernate自动生成(HQL)。
对象映射:MyBatis是半ORM(ResultMap映射);Hibernate是全ORM(对象-表直接映射)。
性能:MyBatis更易优化SQL;Hibernate缓存机制强大。
学习曲线:MyBatis简单;Hibernate复杂(Session管理/缓存策略)。
其他解法二:
SQL 控制:
MyBatis:手动编写 SQL(灵活优化)。
Hibernate:HQL 自动生成 SQL(开发快)。
对象关系映射:
Hibernate 全自动 ORM(对象与表严格映射)。
MyBatis 半自动(SQL 与结果集手动映射)。
性能:
MyBatis 更易优化复杂 SQL(避免 N+1 问题)。
学习曲线:
MyBatis 简单易上手。
Hibernate 需掌握 Session、缓存等概念。
适用场景:
MyBatis:需精细控制 SQL 的互联网应用。
Hibernate:快速开发的企业应用(CRM、ERP)。
标签: 后端,MyBatis
111. 什么是 Java 内存模型(JMM)?
默认解法:
JMM 定义线程与主内存的交互规范:
所有变量存主内存
线程私有工作内存(缓存)
线程操作变量需从主内存拷贝到工作内存
volatile 保证可见性(写立即刷新,读重新加载)
happens-before 原则解决指令重排问题。
其他解法一:
定义:规范多线程下共享变量的访问规则(保证跨平台一致性)。
核心:
主内存:存储共享变量
工作内存:线程私有(存储共享变量副本)
规则:
线程操作变量需从主内存读取/写入
volatile保证可见性/禁止重排序
synchronized保证原子性/可见性
其他解法二:
定义:规范线程如何通过主内存交互共享变量(定义工作内存与主内存交互规则)。
核心规则:
可见性:volatile 写先于读(写操作刷新主内存,读操作清空工作内存)。
有序性:happens-before 原则(如锁解锁先于加锁,volatile 写先于读)。
内存屏障:
volatile 和 synchronized 插入屏障禁止指令重排序。
目标:解决多线程可见性、有序性问题(不解决原子性)。
标签: Java并发,Java
112. Redis 和 Memcached 有哪些区别?
默认解法:
Redis:
支持多种数据结构
持久化(RDB/AOF)
主从复制、集群
单线程模型(6.0 前)
Memcached:
仅 Key-Value
纯内存,无持久化
多线程模型
内存管理更高效(Slab 分配)
适用:需丰富功能选 Redis;纯缓存场景 Memcached 性能更高。
其他解法一:
数据类型:Redis支持String/Hash/List等;Memcached仅String。
持久化:Redis支持RDB/AOF;Memcached纯内存。
线程模型:Redis单线程;Memcached多线程。
集群:Redis原生集群;Memcached需客户端分片。
适用场景:Redis功能更丰富;Memcached更简单(纯缓存)。
其他解法二:
数据类型:
Redis:String/Hash/List/Set/ZSet 等。
Memcached:仅 String。
持久化:
Redis:支持 RDB 快照和 AOF 日志。
Memcached:纯内存,重启数据丢失。
线程模型:
Redis:单线程(6.0 后多线程 I/O)。
Memcached:多线程(利用多核)。
功能扩展:
Redis:支持 Lua 脚本、事务、发布订阅。
适用场景:
Redis:复杂数据结构、持久化需求(缓存、队列)。
Memcached:简单键值缓存(高并发读)。
标签: 后端,Redis
113. 什么是物理地址,什么是逻辑地址?
默认解法:
逻辑地址:程序使用的地址(如段地址:偏移地址)
物理地址:内存单元的实际地址
转换:CPU 通过分段单元(逻辑→线性地址)和分页单元(线性→物理地址)转换。
其他解法一:
逻辑地址:程序使用的地址(如汇编指令中的地址)。
物理地址:内存单元的实际地址。
转换:MMU(内存管理单元)通过页表将逻辑地址→物理地址。
目的:进程地址空间隔离;支持虚拟内存(换页机制)。
其他解法二:
逻辑地址:
程序使用的地址(如汇编中的 mov eax, [0x8048000])。
分段机制:段选择符 + 偏移量。
物理地址:
内存芯片上的实际地址(通过总线访问)。
转换过程:
CPU 通过分段单元(段基址 + 偏移)→ 线性地址 → 分页单元(页表)→ 物理地址。
虚拟内存作用:
每个进程有独立逻辑地址空间(隔离性)。
分页机制实现物理内存扩展(Swap 空间)。
标签: 操作系统
114. 说说什么是 API 网关?它有什么作用?
默认解法:
作用:
统一入口:路由请求到后端服务
认证鉴权:校验身份与权限
限流熔断:保护后端服务
日志监控:收集请求指标
请求转换:协议/格式转换
实现:Spring Cloud Gateway、Kong、Nginx。
其他解法一:
定义:所有客户端请求的统一入口(反向代理)。
作用:
路由转发:根据URL分发到后端服务
认证鉴权:统一身份验证
限流熔断:保护后端服务
日志监控:集中收集请求日志
负载均衡:分发流量到多个实例
其他解法二:
定义:系统统一入口,处理所有客户端请求(类似门面模式)。
核心功能:
路由转发:将请求分发到对应微服务。
认证鉴权:JWT 验证、OAuth2.0。
限流熔断:限制 QPS、防止雪崩。
日志监控:记录请求指标。
负载均衡:轮询、权重分发。
常见实现:
Spring Cloud Gateway、Kong、Nginx。
优势:
解耦客户端与微服务。
统一安全管控。
标签: 后端,分布式,Spring Cloud
115. 什么是 Java 的 CAS(Compare-And-Swap)操作?
默认解法:
原子操作:比较并交换。过程:
读取内存值 V
比较 V 与预期值 A
相等则更新为 B,否则重试
底层:Unsafe 类调用 CPU 指令(如 x86 的 LOCK CMPXCHG)
问题:ABA 问题(通过版本号 AtomicStampedReference 解决)。
其他解法一:
定义:无锁原子操作(硬件CPU指令支持)。
参数:内存位置V、预期值A、新值B。
操作:当V的值等于A时,将V更新为B;否则放弃。
应用:AtomicInteger/并发容器实现。
问题:ABA问题(版本号解决);自旋开销。
其他解法二:
定义:无锁并发原语,基于硬件指令(如 x86 的 CMPXCHG)保证原子性。
操作语义:
CAS(V, E, N):若内存值 V 等于期望值 E,则更新为 N,否则放弃。
Java 实现:
Unsafe.compareAndSwapInt(),AtomicInteger 基于 CAS 实现自增。
优点:
避免锁开销(非阻塞算法)。
缺点:
ABA 问题(通过 AtomicStampedReference 加版本号解决)。
自旋消耗 CPU(竞争激烈时)。
标签: Java并发,Java
116. Select、Poll、Epoll 之间有什么区别?
默认解法:
Select:
线性扫描 fd_set(O(n))
最大 1024 fd
Poll:
链表存储 fd,无数量限制
仍需遍历所有 fd
Epoll:
事件驱动:epoll_wait 返回就绪 fd(O(1))
支持水平触发(LT)和边缘触发(ET)
无 fd 数量限制。
其他解法一:
Select:
轮询所有fd(O(n))
fd数量限制(通常1024)
Poll:
无fd数量限制
仍O(n)轮询
Epoll:
事件驱动(O(1)就绪fd检查)
无fd限制
支持ET/LT模式
其他解法二:
Select:
线性扫描 fd 集合(O(n)),fd 数量受限(1024)。
Poll:
链表存储 fd,无数量限制(仍 O(n) 扫描)。
Epoll:
事件驱动:注册 fd 回调(无需遍历)。
高效:O(1) 获取就绪 fd。
支持模式:LT(水平触发)和 ET(边缘触发)。
对比:
| 特性 | Select | Poll | Epoll |
|---|---|---|---|
| 效率 | O(n) | O(n) | O(1) |
| fd上限 | 1024 | 无 | 10万+ |
| 内存开销 | 固定大小 | 动态 | 高效 |
标签: 操作系统
117. “什么是 MyBatis-Plus?它有什么作用?它和 MyBatis 有哪些区别?”
默认解法:
MyBatis-Plus(MP)是 MyBatis 的增强工具包:
作用:
提供通用 CRUD 接口(BaseMapper),减少 80% 样板代码
强大的条件构造器(QueryWrapper/LambdaQueryWrapper)
分页插件自动分页
代码生成器一键生成实体/Controller/Service
区别:
MyBatis 需手写 SQL 和基础 CRUD
MP 封装常用操作,开发效率更高但灵活性略低
MP 在 MyBatis 基础上扩展,非替代关系
其他解法一:
定义:MyBatis增强工具(简化CRUD)。
作用:
内置通用Mapper/Service(减少SQL编写)
分页插件
代码生成器
乐观锁支持
区别:
MyBatis需手动写SQL
MyBatis-Plus提供开箱即用CRUD接口
其他解法二:
MyBatis-Plus(MP)是MyBatis的增强工具:
作用:简化CRUD操作(内置通用Mapper、Service),提供分页插件、代码生成器等。
区别:
MyBatis:需手动编写SQL和基础CRUD。
MP:自动生成SQL(如save()/updateById()),减少70%代码量。
MP兼容MyBatis,扩展其功能但不改变核心。
标签: 后端,Mybatis-plus,编程导航
118. 为什么 Java 中的 ThreadLocal 对 key 的引用为弱引用?
默认解法:
防止内存泄漏:
若 key 强引用:Thread 存活时即使外部无引用,Entry 也不会回收
弱引用 key:当外部无强引用时,GC 可回收 key,下次访问 ThreadLocalMap 时清除无效 Entry
注意:需及时调用 remove() 彻底避免泄漏。
其他解法一:
防内存泄漏:当ThreadLocal对象无强引用时,Entry的key可被GC回收。
残留问题:Entry的value仍强引用(需调用remove()手动清除)。
最佳实践:线程池中必须remove()(否则复用线程导致旧value残留)。
其他解法二:
内存泄漏风险:
若 key 强引用,ThreadLocal 对象即使不再使用,Entry 仍被 Thread 的 ThreadLocalMap 强引用,导致无法回收。
弱引用作用:
当 ThreadLocal 对象无强引用时(如置为 null),GC 可回收 key,下次调用 set()/get() 时清除无效 Entry。
残留问题:
Value 仍被 Entry 强引用(需调用 remove() 显式清除)。
最佳实践:
使用后务必 threadLocal.remove()(尤其线程池场景)。
标签: Java并发,Java
119. 编译执行与解释执行的区别是什么?JVM 使用哪种方式?
默认解法:
编译执行:源代码整体编译为机器码执行(如 C++)
解释执行:逐行解释源代码执行(如早期 Python)
JVM:混合模式(-Xmixed,默认)
热点代码编译执行(JIT)
非热点代码解释执行
AOT 编译(GraalVM):预先编译。
其他解法一:
编译执行:源码→机器码(执行快,启动慢)。
解释执行:源码→逐行解释执行(启动快,执行慢)。
JVM:混合模式(-Xmixed):
热点代码编译执行(JIT)
非热点代码解释执行
其他解法二:
编译执行:
源码 → 编译器 → 机器码 → 直接执行(如 C++)。
优点:运行快。
缺点:跨平台差。
解释执行:
源码 → 解释器逐行翻译执行(如 Python)。
优点:跨平台好。
缺点:运行慢。
JVM 混合模式:
解释器:快速启动执行字节码。
JIT 编译器:热点代码编译为机器码(如 C1/C2 编译器)。
AOT:GraalVM 支持提前编译(减少运行时开销)。
标签: JVM,Java
120. Redis 支持事务吗?如何实现?
默认解法:
支持,通过 MULTI/EXEC 命令:
MULTI:开启事务
命令入队(不立即执行)
EXEC:执行所有命令(原子性)
DISCARD:取消事务
注意:
无回滚(命令错误时继续执行)
非隔离性(其他客户端命令可能插队)。
其他解法一:
支持:但不保证原子性(单条命令原子,事务整体非原子)。
命令:
MULTI:开启事务
EXEC:执行事务
DISCARD:取消事务
WATCH:监控键(CAS乐观锁)
注意:事务中命令错误(如语法错误)会导致EXEC失败;运行时错误(如类型错误)仅当前命令失败。
其他解法二:
支持事务:MULTI + EXEC 命令组合。
执行流程:
MULTI:开启事务。
入队命令:SET/GET 等(不立即执行)。
EXEC:原子执行所有命令(按顺序)。
错误处理:
语法错误:EXEC 时拒绝执行(如 SET key 缺参数)。
运行时错误:继续执行后续命令(如 INCR 对字符串操作)。
无回滚:Redis 事务不支持回滚(设计哲学:错误由编程导致,应在开发阶段解决)。
标签: 后端,Redis
121. 到底什么是 TCP 连接?
默认解法:
本质:通信双方维护的一组状态数据,包括:
源/目的 IP 和端口
序列号(seq)和确认号(ack)
窗口大小(rwnd)
连接状态(ESTABLISHED 等)
通过三次握手建立状态,四次挥手释放状态。
其他解法一:
逻辑概念:通信双方维护的状态信息(非物理链路)。
状态信息:
源/目的IP+端口
序列号/确认号
窗口大小
连接状态(ESTABLISHED等)
建立过程:三次握手初始化状态。
其他解法二:
本质:通信双发的状态记录(非物理链路)。
状态信息:
四元组:源 IP、源端口、目标 IP、目标端口。
序列号与窗口:发送方序列号(seq)、接收方窗口大小(win)。
连接状态:ESTABLISHED(已连接)、TIME_WAIT(等待关闭)。
建立过程:三次握手初始化序列号(ISN)和窗口参数。
资源占用:内核维护连接状态表(如 Linux 的 /proc/net/tcp)。
标签: 网络
122. 如何处理重复消息?
默认解法:
方案:
幂等设计:业务逻辑天然支持重复执行(如 SET key value)
唯一标识:消息带唯一 ID,服务端记录处理状态(Redis 或 DB)
版本号:更新数据时校验版本(CAS)
数据库约束:唯一索引防重。
其他解法一:
幂等设计:业务逻辑天然幂等(如UPDATE SET a=1)。
唯一标识:消息带唯一ID,消费端去重(Redis Set/数据库唯一索引)。
事务表:消费前插入业务流水表(主键/唯一索引防重)。
状态机:业务状态流转(如订单状态只能从已支付→已完成)。
其他解法二:
幂等设计:
业务逻辑支持多次执行结果不变(如 UPDATE table SET status=2 WHERE id=1 AND status=1)。
唯一标识:
消息携带唯一 ID(如 UUID),消费前查 Redis 是否已处理。
数据库去重:
建消息表,主键为消息 ID(重复插入报错)。
消息队列特性:
RocketMQ/Kafka:支持 Exactly-Once 语义(事务消息 + 幂等)。
人工干预:
记录日志,异常时人工补偿。
标签: 消息队列,后端
123. Java 中什么情况会导致死锁?如何避免?
默认解法:
条件(全部满足):
互斥
占有且等待
不可抢占
循环等待
避免:
顺序获取锁(破坏循环等待)
超时释放(Lock.tryLock())
避免嵌套锁
银行家算法。
其他解法一:
死锁条件:
互斥
持有并等待
不可剥夺
循环等待
避免方法:
顺序加锁(固定锁获取顺序)
超时释放(tryLock(timeout))
死锁检测(JStack/ThreadMXBean)
其他解法二:
死锁条件:
互斥、请求与保持、不可剥夺、循环等待。
示例:
// 线程A: lock1→lock2
// 线程B: lock2→lock1
避免方法:
顺序加锁:所有线程按固定顺序获取锁(如先 lock1 后 lock2)。
超时释放:tryLock(timeout) 获取失败后退回重试。
死锁检测:JConsole 或 jstack 分析线程栈。
预防:
减少锁粒度(如 ConcurrentHashMap 分段锁)。
标签: Java并发,Java
124. 如何保证消息的有序性?
默认解法:
全局有序:单分区(Topic 仅一个 Partition/Queue),性能低
局部有序:同业务 ID 的消息发到同一分区(如订单ID哈希到固定分区)
消费者单线程处理同分区消息
注意:Kafka 分区内有序,RabbitMQ 需单队列单消费者。
其他解法一:
全局有序:单分区(Topic+单队列,性能低)。
局部有序:
同一业务ID哈希到同一分区(如订单ID)
生产者同步发送(等待上一条ACK)
消费者单线程处理(或线程内按业务ID分组处理)
其他解法二:
全局有序:
单分区队列(如 Kafka 单 Partition),性能受限。
局部有序:
相同 Key 的消息发到同一分区(如 Kafka 指定 Key)。
消费者处理:
单线程消费(性能差)。
内存队列按 Key 分组处理(如线程池按 Key 哈希分配线程)。
消息队列支持:
RocketMQ:顺序消息(同一 Sharding Key 发到同一队列)。
Kafka:同一 Partition 内有序。
标签: 消息队列,后端
125. 说一下 Netty 的应用场景?
默认解法:
高性能 RPC 框架(Dubbo、gRPC)
即时通讯(IM 系统)
游戏服务器
物联网设备接入
HTTP 服务器(如 Spring WebFlux)
优势:异步非阻塞、零拷贝、高并发连接处理。
其他解法一:
高性能RPC框架:Dubbo/gRPC底层通信。
即时通讯:WebSocket服务器。
游戏服务器:长连接/高并发。
物联网:MQTT协议设备接入。
HTTP服务器:如Spring WebFlux。
其他解法二:
高性能 RPC 框架:
Dubbo、gRPC 底层网络通信。
实时通信系统:
即时聊天(WebSocket 支持)。
游戏服务器(长连接、低延迟)。
物联网网关:
海量设备接入(MQTT 协议支持)。
API 网关:
Spring Cloud Gateway 基于 Netty 实现异步转发。
大数据传输:
文件服务器(零拷贝优化)。
标签: Netty,后端
126. 什么是 Spring IOC?
默认解法:
控制反转:将对象创建和依赖注入交给容器管理。实现方式:
容器管理 Bean 的生命周期
依赖注入(DI):通过构造器/setter/字段注入依赖对象
配置方式:XML、注解(@Autowired)、Java Config
目的:解耦组件,提高可测试性和可维护性。
其他解法一:
定义:控制反转(Inversion of Control)。
核心:将对象创建/依赖注入交给容器管理。
实现:
容器:BeanFactory/ApplicationContext
配置:XML/注解/JavaConfig
优势:解耦(对象间依赖由容器注入)。
其他解法二:
定义:控制反转(Inversion of Control),将对象创建和依赖注入交给容器管理。
核心思想:
容器托管 Bean:ApplicationContext 管理对象生命周期。
依赖注入(DI):通过构造函数、Setter 或注解自动装配依赖(如 @Autowired)。
优势:
解耦组件(面向接口编程)。
便于单元测试(Mock 依赖)。
实现原理:
反射创建对象,BeanFactory 存储 Bean 定义(BeanDefinition)。
标签: 后端,Spring
127. 你了解 Java 线程池的原理吗?
默认解法:
核心组件:
工作队列(BlockingQueue):存储待执行任务
线程集合(HashSet):执行任务
拒绝策略(RejectedExecutionHandler):队列满时处理新任务
工作流程:
提交任务 → 核心线程未满则创建线程执行
核心线程满 → 任务入队
队列满 → 创建非核心线程(未达最大线程数)
线程数达最大值 → 触发拒绝策略。
其他解法一:
核心参数:
corePoolSize:核心线程数
workQueue:任务队列
maxPoolSize:最大线程数
RejectedExecutionHandler:拒绝策略
工作流程:
任务提交→核心线程未满则创建线程
核心线程满则入队
队列满则创建非核心线程
线程数达max则执行拒绝策略
线程复用:线程循环从队列取任务执行。
其他解法二:
核心参数:
corePoolSize:核心线程数(常驻)。
maximumPoolSize:最大线程数(临时线程)。
workQueue:任务队列(如 ArrayBlockingQueue)。
RejectedExecutionHandler:拒绝策略(AbortPolicy/CallerRunsPolicy)。
工作流程:
提交任务 → 核心线程未满?创建线程执行:入队。
队列满?创建临时线程执行(≤ maxPoolSize)。
线程数达 max 且队列满?触发拒绝策略。
线程复用:
线程通过 Worker 循环从队列取任务执行。
资源回收:
临时线程空闲超时(keepAliveTime)后销毁。
标签: Java并发,Java
128. Redis 数据过期后的删除策略是什么?
默认解法:
惰性删除:访问 key 时检查过期则删除
定期删除:随机抽取部分 key 检查过期并删除(hz 参数控制频率)
内存淘汰:当内存不足时触发主动删除(LRU/LFU/TTL 等策略)。
其他解法一:
惰性删除:访问key时检查过期则删除(节省CPU,内存释放不及时)。
定期删除:随机抽取部分key检查并删除(平衡CPU与内存)。
内存淘汰:当内存不足时触发(如LRU/LFU/随机淘汰)。
其他解法二:
惰性删除:
访问 key 时检查是否过期,过期则删除(节省 CPU,可能内存泄漏)。
定期删除:
随机抽取 key 检查过期(每秒 10 次,每次 20 个 key)。
内存淘汰策略(当内存不足时):
volatile-lru:从已设置过期时间的 key 中淘汰最近最少使用。
allkeys-lru:所有 key 中淘汰 LRU。
volatile-ttl:淘汰剩余生存时间最短的 key。
noeviction:不淘汰,写操作返回错误(默认)。
标签: 后端,Redis
129. 如何处理消息堆积?
默认解法:
增加消费者:水平扩展消费者数量
提升消费能力:批量处理、异步消费、优化业务逻辑
扩大分区数(Kafka):需提前规划
降级:跳过非关键消息
监控:设置堆积阈值告警
预防:合理评估生产消费速率,设置消息 TTL。
其他解法一:
紧急扩容:增加消费者实例(需提前规划分区数)。
批量消费:消费者批量拉取消息处理。
降级处理:跳过非核心消息。
定位原因:
生产者流量激增(限流)
消费者处理慢(优化业务/线程池)
其他解法二:
定位原因:
消费者宕机或处理慢(监控消费延迟)。
紧急扩容:
增加消费者实例(分区数需 ≥ 消费者数)。
优化消费逻辑:
批量处理(如 Kafka 的 max.poll.records)。
异步处理(非核心操作异步化)。
降级处理:
跳过非关键消息(如日志)。
写入死信队列人工处理。
预防措施:
设置消费超时时间(避免单条卡死)。
监控堆积告警。
标签: 消息队列,后端
130. 什么是服务熔断?
默认解法:
微服务容错机制:当依赖服务故障达到阈值(如错误率>50%),熔断器打开,后续请求直接拒绝(快速失败),避免级联故障。熔断后:
经过休眠时间进入半开状态
试探请求成功则关闭熔断,否则重新打开。实现:Hystrix、Sentinel。
其他解法一:
定义:当下游服务故障时,上游主动中断调用(防止级联故障)。
状态机:
关闭:正常调用
打开:错误率超阈值,拒绝所有请求
半开:尝试放行部分请求探测恢复状态
实现:Hystrix/Sentinel。
其他解法二:
定义:当下游服务故障时,上游服务中断调用(快速失败),防止雪崩。
状态机:
Closed:正常调用。
Open:熔断开启(直接返回错误)。
Half-Open:试探性放行部分请求。
熔断器实现:
Hystrix:基于滑动窗口统计错误率(如 50% 错误开启熔断)。
Sentinel:基于 QPS/响应时间熔断。
恢复机制:
熔断后等待时间窗(如 5 秒)→ Half-Open → 成功则关闭熔断。
标签: Spring Cloud,服务熔断,后端,微服务
131. Java 线程池有哪些拒绝策略?
默认解法:
AbortPolicy(默认):抛 RejectedExecutionException
CallerRunsPolicy:由提交任务的线程直接执行
DiscardPolicy:静默丢弃任务
DiscardOldestPolicy:丢弃队列中最旧任务,重试提交
自定义策略:实现 RejectedExecutionHandler 接口。
其他解法一:
AbortPolicy:默认,抛RejectedExecutionException。
CallerRunsPolicy:由提交任务的线程直接执行。
DiscardPolicy:静默丢弃新任务。
DiscardOldestPolicy:丢弃队列中最旧任务,重试提交。
其他解法二:
AbortPolicy(默认):
抛 RejectedExecutionException。
CallerRunsPolicy:
由提交任务的线程直接执行任务(同步阻塞)。
DiscardPolicy:
静默丢弃任务(不通知)。
DiscardOldestPolicy:
丢弃队列中最老的任务,重试提交。
自定义策略:
实现 RejectedExecutionHandler,如记录日志或持久化存储。
标签: Java并发,Java
132. HTTP 1.0 和 2.0 有什么区别?
默认解法:
HTTP/1.0:
短连接(每请求新建 TCP 连接)
无状态
HTTP/2.0:
多路复用:一个连接并行处理多个请求
二进制分帧(提升解析效率)
头部压缩(HPACK)
服务端推送(主动推送资源)。
其他解法一:
多路复用:2.0一个连接并行处理多个请求(解决1.x队头阻塞)。
二进制分帧:2.0数据以二进制帧传输(1.x是文本)。
头部压缩:2.0用HPACK压缩header(减少重复)。
服务器推送:2.0主动推送资源(如CSS/JS)。
其他解法二:
多路复用:
1.0:每个请求独立 TCP 连接(队头阻塞)。
2.0:一个 TCP 连接并行处理多个请求(二进制分帧)。
头部压缩:
2.0 使用 HPACK 压缩头部(减少冗余)。
服务器推送:
2.0 支持主动推送资源(如 CSS/JS)。
优先级:
2.0 可设置请求优先级(优先加载关键资源)。
协议格式:
1.0 文本协议(可读性好)。
2.0 二进制协议(解析高效)。
标签: 网络
133. 如何保证消息不丢失?
默认解法:
生产者:
事务消息(如 Kafka)
确认机制(ACK,如 RabbitMQ 的 publisher confirm)
Broker:
持久化(磁盘写入)
副本机制(主从同步)
消费者:
手动提交 offset(处理完业务再 commit)
幂等处理。
其他解法一:
生产者:
同步发送+重试
事务消息(如RocketMQ)
Broker:
同步刷盘(非异步)
主从同步(多副本)
消费者:
手动提交offset(业务处理成功后ACK)
其他解法二:
生产者确认:
RabbitMQ:事务或 Confirm 模式(等待 Broker ACK)。
Kafka:acks=all(所有副本确认)。
Broker 持久化:
Kafka:消息刷盘(flush.interval.ms)。
RabbitMQ:队列声明为持久化(durable=true)。
消费者手动提交:
Kafka:关闭 auto.commit,业务成功后 commitSync()。
RabbitMQ:手动 Ack(basic.ack)。
备份与高可用:
Kafka 多副本(ISR 机制)。
RabbitMQ 镜像队列。
标签: 消息队列,后端
134. Spring AOP默认用的是什么动态代理,两者的区别?
默认解法:
默认策略:
目标类实现接口 → JDK 动态代理(基于接口)
未实现接口 → CGLIB 代理(基于子类)
区别:
JDK 代理:InvocationHandler 接口,依赖目标接口
CGLIB:MethodInterceptor 接口,可代理普通类,通过字节码生成子类。
其他解法一:
默认策略:
实现接口:JDK动态代理(基于接口)
无接口:CGLIB代理(基于子类)
区别:
JDK代理:InvocationHandler接口,反射调用方法
CGLIB:MethodInterceptor接口,字节码生成子类
强制CGLIB:@EnableAspectJAutoProxy(proxyTargetClass=true)
其他解法二:
默认策略:
有接口:JDK 动态代理(基于 InvocationHandler)。
无接口:CGLIB 字节码增强(生成子类)。
区别:
| 特性 | JDK 动态代理 | CGLIB |
|---|---|---|
| 代理对象 | 实现接口的代理类 | 目标类的子类 |
| 性能 | 创建快,调用慢 | 创建慢(字节码生成),调用快 |
| 限制 | 只能代理接口 | 无法代理 final 方法/类 |
强制使用 CGLIB:@EnableAspectJAutoProxy(proxyTargetClass=true)
标签: 后端,Spring
135. 如何合理地设置 Java 线程池的线程数?
默认解法:
依据任务类型:
CPU 密集型:N_cpu + 1(避免过多线程上下文切换)
I/O 密集型:N_cpu * (1 + 平均等待时间/平均计算时间)
通用公式:线程数 = N_cpu * U_cpu * (1 + W/C)
其中 U_cpu 为 CPU 利用率(0.5~1),W/C 为等待时间与计算时间比。
其他解法一:
CPU密集型:N_cpu + 1(避免上下文切换)。
I/O密集型:N_cpu * (1 + 等待时间/计算时间)。
经验值:
普通业务:核心线程数=2N_cpu
高并发:压测调整
监控:根据ThreadPoolExecutor的活跃线程数动态调整。
其他解法二:
CPU 密集型:
线程数 = CPU 核数 + 1(避免上下文切换)。
I/O 密集型:
线程数 = CPU 核数 × (1 + 平均等待时间/平均计算时间)。
经验值:2 × CPU 核数 ~ 5 × CPU 核数。
混合型:
拆分为 CPU 密集和 I/O 密集任务,分别设置线程池。
动态调整:
监控线程池指标(队列堆积、活跃线程数),实时调整。
公式参考:
N_threads = N_cpu * U_cpu * (1 + W/C)
N_cpu:CPU 核数
U_cpu:目标 CPU 利用率(0.8)
W/C:等待时间与计算时间比
标签: Java并发,Java
136. Redis 中有哪些内存淘汰策略?
默认解法:
配置 maxmemory-policy:
noeviction:不淘汰,写操作报错
allkeys-lru:全体 key 中淘汰最近最少使用
allkeys-lfu:全体 key 中淘汰最不经常使用
volatile-lru:仅过期 key 中淘汰 LRU
volatile-lfu:仅过期 key 中淘汰 LFU
random:随机淘汰(allkeys 或 volatile)。
其他解法一:
noeviction:默认,不淘汰(写操作返回错误)。
allkeys-lru:全体key中淘汰最近最少使用。
allkeys-random:全体key中随机淘汰。
volatile-lru:设过期时间的key中淘汰LRU。
volatile-ttl:设过期时间的key中淘汰剩余TTL小的。
volatile-random:设过期时间的key中随机淘汰。
其他解法二:
过期键淘汰:
volatile-lru:从已设置过期时间的 key 中淘汰 LRU。
volatile-ttl:淘汰剩余生存时间最短的 key。
volatile-random:随机淘汰有过期时间的 key。
全局淘汰:
allkeys-lru:所有 key 中淘汰 LRU(推荐)。
allkeys-random:随机淘汰任意 key。
不淘汰:
noeviction:写操作返回错误(默认)。
LFU 策略(Redis 4.0+):
volatile-lfu/allkeys-lfu:淘汰访问频率最低的 key。
标签: 后端,Redis
137. MySQL 中 如果我 select * from 一个有 1000 万行的表,内存会飙升么?
默认解法:
不会。InnoDB 通过缓冲池(Buffer Pool)管理数据:
查询按需加载数据页(Page),非全表加载
结果集分批返回(游标机制)
内存不足时淘汰旧页
但若客户端处理慢(如 ORM 全量映射对象),可能导致客户端 OOM。
其他解法一:
可能不会:MySQL采用流式传输(边读边发,不缓存全结果)。
风险点:
客户端缓存(如JDBC默认全加载到内存)
大字段(BLOB/TEXT)占用内存
优化:
分页查询
避免SELECT *
使用游标(StreamingResultSet)
其他解法二:
取决于查询方式和客户端:
服务端内存:MySQL逐行返回结果,但需维护临时结果集(可能占用大量内存)。
客户端内存:若一次性获取全部数据(如JDBC的fetchSize过大),JVM堆可能OOM。
优化:
分页查询(LIMIT)。
流式读取(JDBC设置ResultSet.TYPE_FORWARD_ONLY)。
避免SELECT *,指定必要列。
标签: 后端,场景题
138. 消息队列设计成推消息还是拉消息?推拉模式的优缺点?
默认解法:
推模式(如 RabbitMQ):
优点:实时性高
缺点:可能压垮消费者
拉模式(如 Kafka):
优点:消费者控制速率
缺点:增加延迟
折中:长轮询(如 RocketMQ PULL 模式)。
其他解法一:
推模式(如Kafka Consumer Pull):
优点:低延迟(Broker主动推)
缺点:消费者可能过载(需背压机制)
拉模式(如RocketMQ):
优点:消费者控制速率(避免堆积)
缺点:轮询开销(空轮询)
混合:长轮询(如Kafka Consumer阻塞拉取)。
其他解法二:
推模式(Push):
Broker 主动推送消息到 Consumer(如 RabbitMQ)。
优点:实时性好,延迟低。
缺点:Consumer 可能过载(需背压机制)。
拉模式(Pull):
Consumer 主动从 Broker 拉取消息(如 Kafka)。
优点:Consumer 按能力消费(可控)。
缺点:实时性差(需轮询)。
混合模式:
长轮询(Long Polling):Consumer 拉取,Broker 无消息时等待(如 RocketMQ)。
标签: 消息队列,后端,场景题
139. 你使用过哪些 Java 并发工具类?
默认解法:
CountDownLatch:等待多线程完成
CyclicBarrier:线程到达屏障点等待
Semaphore:控制并发线程数
Exchanger:线程间交换数据
Phaser:分阶段协同
CompletableFuture:异步编程
ForkJoinPool:并行计算。
其他解法一:
锁:ReentrantLock/StampedLock
同步器:CountDownLatch/CyclicBarrier/Semaphore
集合:ConcurrentHashMap/CopyOnWriteArrayList
线程池:ThreadPoolExecutor/ScheduledThreadPool
原子类:AtomicInteger/LongAdder
Future:CompletableFuture
其他解法二:
同步工具:
CountDownLatch:等待多个任务完成(如启动初始化)。
CyclicBarrier:多线程相互等待(如分布式计算)。
Semaphore:控制并发资源数(如数据库连接池)。
线程安全容器:
ConcurrentHashMap:分段锁实现的并发 Map。
CopyOnWriteArrayList:写时复制 List(读多写少)。
原子类:
AtomicInteger:CAS 实现的原子操作。
LongAdder:高并发下性能优于 AtomicLong。
执行框架:
ThreadPoolExecutor:自定义线程池。
CompletableFuture:异步编程(链式调用)。
标签: Java并发,Java
140. 什么是设计模式?请简述其作用。
默认解法:
设计模式是解决软件设计中常见问题的可复用方案。作用:
代码复用:避免重复设计
提高可维护性:规范代码结构
团队协作:提供通用术语
解耦:分离变化与不变部分
分类:创建型(如工厂)、结构型(如代理)、行为型(如观察者)。
其他解法一:
定义:解决软件设计中常见问题的可复用方案。
作用:
代码复用(避免重复造轮子)
提升可维护性(标准结构易理解)
保证代码可靠性(经过验证的方案)
分类:创建型(对象创建)、结构型(对象组合)、行为型(对象交互)。
其他解法二:
定义:
针对软件设计中反复出现问题的通用可复用解决方案模板。
核心作用:
代码复用:避免重复造轮子(如单例模式)。
解耦:降低模块依赖(如观察者模式)。
可维护性:统一设计规范(如 MVC 模式)。
可扩展性:方便功能扩展(如策略模式)。
分类:
创建型(5种):对象创建(工厂、单例)。
结构型(7种):类/对象组合(代理、适配器)。
行为型(11种):对象交互(观察者、责任链)。
标签: 设计模式
141. 什么是 AOP?
默认解法:
面向切面编程:将横切已关注点(如日志、事务)从业务逻辑中分离。核心概念:
切面(Aspect):模块化横切逻辑
连接点(Joinpoint):方法执行/异常抛出点
切点(Pointcut):匹配连接点的表达式
通知(Advice):切面在连接点的动作(前置/后置/环绕)
织入(Weaving):将切面应用到目标对象的过程。
其他解法一:
定义:面向切面编程(Aspect-Oriented Programming)。
核心:将横切已关注点(日志/事务/安全)与业务逻辑分离。
关键概念:
Aspect:切面(模块化横切逻辑)
JoinPoint:连接点(方法执行/异常处理)
Advice:增强(切面在连接点的动作)
Pointcut:切入点(匹配连接点的表达式)
其他解法二:
定义:面向切面编程(Aspect-Oriented Programming),将横切已关注点(如日志、事务)与业务逻辑分离。
核心概念:
切面(Aspect):模块化横切已关注点(如 @Aspect 类)。
连接点(Join Point):程序执行点(如方法调用)。
通知(Advice):切面在连接点的动作(@Before/@After)。
切入点(Pointcut):匹配连接点的表达式(如 @Pointcut("execution(* com.service.*.*(..))"))。
实现原理:
动态代理(JDK/CGLIB)在运行时增强目标方法。
应用场景:日志记录、事务管理、权限校验。
标签: 后端,Spring
142. 什么是服务降级?
默认解法:
在系统高负载时,暂时关闭非核心服务,保障核心功能可用。方式:
熔断降级:依赖服务不可用时返回兜底数据
限流降级:拒绝部分请求
功能降级:关闭次要功能(如推荐模块)
实现:Hystrix fallback 方法、Sentinel 降级规则。
其他解法一:
定义:系统过载时,暂时关闭非核心服务(保障核心功能可用)。
场景:
熔断后返回降级结果(如默认值)
限流触发时拒绝部分请求
实现:
Hystrix:@HystrixCommand(fallbackMethod)
Sentinel:配置降级规则
其他解法二:
定义:系统过载时,暂时关闭非核心服务,保障核心功能可用。
触发条件:
高并发流量(如秒杀场景)。
依赖服务故障(如第三方接口超时)。
降级策略:
返回兜底数据:如缓存默认值(“服务繁忙”)。
功能屏蔽:关闭次要功能(如商品详情页隐藏评论)。
熔断降级联动:Hystrix 熔断后自动降级。
实现方式:
配置中心动态开关(如 Apollo)。
注解 @HystrixCommand(fallbackMethod = "fallback")。
标签: Spring Cloud,微服务,后端,服务降级
143. Synchronized 和 ReentrantLock 有什么区别?
默认解法:
语法:synchronized 是关键字;ReentrantLock 是类
公平锁:synchronized 非公平;ReentrantLock 可选公平/非公平
条件变量:ReentrantLock 支持多个 Condition
超时:ReentrantLock 支持 tryLock(timeout)
中断:ReentrantLock 支持 lockInterruptibly()
性能:JDK6 后 synchronized 优化后接近。
其他解法一:
本质:Synchronized是JVM关键字;ReentrantLock是API(java.util.concurrent)。
功能:
ReentrantLock支持公平锁
可中断锁(lockInterruptibly)
条件变量(newCondition)
限时等待(tryLock)
性能:JDK1.6后Synchronized优化(偏向锁/轻量级锁)后接近。
其他解法二:
底层实现:
synchronized:JVM 层面实现(monitorenter/monitorexit)。
ReentrantLock:JDK 代码实现(基于 AQS)。
功能对比:
| 特性 | Synchronized | ReentrantLock |
|---|---|---|
| 公平锁 | 非公平 | 可选公平/非公平 |
| 条件变量 | 无 | 支持多个 Condition |
| 可中断等待 | 不支持 | lockInterruptibly() 支持 |
| 尝试获取锁 | 不支持 | tryLock() 支持 |
使用建议:
简单场景用 synchronized(自动释放锁)。
高级功能(如超时)用 ReentrantLock。
标签: Java并发,Java
144. Redis 的 Lua 脚本功能是什么?如何使用?
默认解法:
Lua 脚本功能:
原子性执行多个 Redis 命令(脚本整体执行,不会被其他命令打断)
减少网络开销(多个命令合并为一个脚本传输)
使用方式:
EVAL “脚本内容” key数量 key1 key2… arg1 arg2…
EVALSHA(执行缓存脚本)
示例(原子计数器):
EVAL “return redis.call(‘INCRBY’, KEYS[1], ARGV[1])” 1 counter 5
注意:脚本不宜过长(阻塞 Redis),且需处理脚本哈希缓存
其他解法一:
作用:原子执行多个Redis命令。
命令:EVAL script numkeys key [key …] arg [arg …]
示例:
EVAL “return redis.call(‘set’, KEYS[1], ARGV[1])” 1 name Tom
优势:
减少网络开销(批量操作)
原子性(单线程执行)
其他解法二:
作用:
原子执行多个命令(避免事务缺陷)。
减少网络开销(合并操作)。
使用方式:
执行脚本:
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 key1 value1
预加载脚本:
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
EVALSHA <sha1> 1 key1
优势:
原子性:脚本执行期间不会被其他命令打断。
高性能:脚本在服务端解析执行。
注意:
避免长时间阻塞(Lua 脚本单线程执行)。
标签: 后端,Redis
145. HTTP 2.0 和 3.0 有什么区别?
默认解法:
核心区别:
HTTP/2.0:
二进制分帧(提升解析效率)
多路复用(一个连接并行处理请求)
头部压缩(HPACK)
服务端推送
HTTP/3.0:
传输层改用 QUIC 协议(基于 UDP)
解决队头阻塞(丢包只影响单个流)
0-RTT 快速建连(减少握手延迟)
连接迁移(IP 变化无感切换)
性能:HTTP/3 在高丢包网络下性能提升 50%+
其他解法一:
HTTP/2:
二进制分帧
多路复用
头部压缩(HPACK)
HTTP/3:
传输层改用QUIC(基于UDP)
解决队头阻塞(独立流控制)
0-RTT建连(加速首次访问)
连接迁移(IP切换无感)
其他解法二:
HTTP/2:
二进制分帧(Binary Framing)。
多路复用(Multiplexing)。
头部压缩(HPACK)。
HTTP/3:
传输层协议:QUIC(基于 UDP)替代 TCP。
0-RTT 连接:首次访问即可加密传输(降低延迟)。
改进拥塞控制:内置 BBR 算法。
无队头阻塞:数据包独立传输(解决 TCP 重传阻塞)。
性能对比:
HTTP/3 在高丢包网络下延迟降低 30%~50%。
标签: 网络
146. 单例模式有哪几种实现?如何保证线程安全?
默认解法:
实现方式:
饿汉式:类加载时创建(线程安全但可能浪费资源)
懒汉式:双重检查锁(DCL)+ volatile(需防止指令重排)
静态内部类:利用类加载机制保证懒加载和线程安全
枚举:天然线程安全,防反射攻击
线程安全保证:
关键操作加锁(synchronized)
使用 volatile 禁止重排序
避免构造函数非原子操作
其他解法一:
饿汉式:类加载时初始化(线程安全)。
懒汉式:
同步方法(性能差)
DCL双重检查锁(volatile+同步块)
静态内部类:Holder类延迟加载(推荐)。
枚举:天然线程安全(防反射破坏)。
其他解法二:
饿汉式:
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance; }
}
线程安全(类加载时初始化)。
懒汉式(DCL):
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) instance = new Singleton();
}
}
return instance;
}
}
volatile 防止指令重排序。
静态内部类:
public class Singleton {
private static class Holder {
static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return Holder.instance; }
}
懒加载 + 线程安全(JVM 类加载机制)。
枚举:
public enum Singleton {
INSTANCE; }
绝对防止反射破坏(《Effective Java》推荐)。
标签: 设计模式
147. Java 的 synchronized 是怎么实现的?
默认解法:
实现原理:
修饰代码块:通过 monitorenter/monitorexit 字节码指令,基于对象监视器(Monitor)实现
修饰方法:ACC_SYNCHRONIZED 标志位
底层机制:
锁升级过程:无锁 → 偏向锁 → 轻量级锁(CAS)→ 重量级锁(OS 互斥量)
依赖对象头 Mark Word 存储锁状态
重量级锁使用操作系统互斥量(Mutex),涉及用户态/内核态切换
其他解法一:
字节码:monitorenter/monitorexit指令。
锁升级:
无锁 → 偏向锁(单线程访问)
偏向锁 → 轻量级锁(多线程竞争)
轻量级锁 → 重量级锁(自旋失败)
重量级锁:依赖操作系统mutex(用户态到内核态切换开销大)。
其他解法二:
字节码层面:
同步代码块:monitorenter 和 monitorexit 指令。
同步方法:方法标志位 ACC_SYNCHRONIZED。
JVM 层面:
每个对象关联一个 Monitor(管程)。
获取锁:
无竞争:通过 CAS 将对象头 Mark Word 指向当前线程。
有竞争:线程进入 EntryList 阻塞(内核态切换)。
锁升级过程:
无锁 → 偏向锁(单线程重入)→ 轻量级锁(CAS 自旋)→ 重量级锁(阻塞)。
优化机制:
自适应自旋(JDK 6+)。
锁消除(逃逸分析)。
锁粗化(合并连续锁操作)。
标签: Java并发,Java
148. 如何设计一个秒杀功能?
默认解法:
关键点:
流量削峰:
答题验证
消息队列缓冲请求
读优化:
缓存商品库存(Redis)
写优化:
Redis 预减库存(原子操作)
异步下单(消息队列)
防作弊:
限流(IP/用户)
隐藏秒杀接口
降级:服务不可用时返回兜底页。
其他解法一:
架构分层:
前端:静态资源CDN;按钮防重复点击
网关:限流(令牌桶)
服务:库存缓存(Redis扣减);MQ削峰
数据库:最终库存持久化
关键点:
预扣库存(防超卖)
异步下单(MQ解耦)
热点数据隔离(如Redis分片)
其他解法二:
架构分层:
前端:静态资源 CDN 加速,按钮防重复点击。
网关:限流(令牌桶)、黑名单过滤。
流量削峰:
消息队列缓冲请求(如 RocketMQ 顺序消息)。
答题验证码(分散峰值)。
库存管理:
Redis 预减库存(DECR 原子操作)。
数据库最终扣减(异步同步)。
防作弊:
用户限购(Redis 记录购买状态)。
风控系统(设备指纹、IP 限制)。
降级预案:
超时未支付库存回滚。
故障时降级到排队页面。
标签: 后端,系统设计
149. 为什么不选择使用原生的 NIO 而选择使用 Netty 呢?
默认解法:
原生 NIO 缺点:
API 复杂(需处理 Selector、Buffer 等)
需处理断连重连、网络闪断等问题
需解决 NIO 空轮询 Bug
需自行实现协议编解码
Netty 优势:
封装底层细节,提供易用 API
高性能(零拷贝、对象池)
社区活跃,文档丰富。
其他解法一:
API复杂性:Netty封装NIO细节(简化开发)。
健壮性:Netty处理了NIO BUG(如空轮询)。
性能优化:
内存池(减少GC)
零拷贝
高效线程模型(Reactor)
生态:丰富的编解码器(HTTP/WebSocket等)。
其他解法二:
API 复杂性:
NIO 需手动处理 Selector、Buffer 状态(易出错如空轮询 Bug)。
Netty 封装 ChannelHandler 简化开发。
内存管理:
Netty 的 ByteBuf 支持池化(减少 GC)和零拷贝。
协议支持:
Netty 内置 HTTP/WebSocket/Protobuf 等编解码器。
健壮性:
心跳检测、断线重连等完善机制。
性能优化:
Reactor 线程模型优化(主从多线程)。
FastThreadLocal 提升并发性能。
社区生态:
广泛验证(如 Dubbo、RocketMQ 使用)。
标签: Netty,后端
150. 看过源码吗?说下 Spring 由哪些重要的模块组成?
默认解法:
核心模块:
spring-core:IoC 容器基础(BeanFactory)
spring-beans:Bean 定义与装配
spring-context:应用上下文(ApplicationContext)
spring-aop:切面编程
spring-jdbc:数据库访问
spring-web:Web 支持
spring-test:测试支持。
其他解法一:
Core Container:BeanFactory/IoC容器。
AOP:面向切面编程支持。
Data Access:JDBC/ORM/事务管理。
Web:Spring MVC/WebFlux。
Test:单元测试/集成测试支持。
Context:ApplicationContext(事件传播/国际化)。
其他解法二:
核心容器:
spring-beans:Bean 定义与装配(BeanFactory)。
spring-core:IoC 基础(Resource 抽象)。
AOP 模块:
spring-aop:动态代理实现切面。
spring-aspects:集成 AspectJ。
数据访问:
spring-jdbc:JDBC 模板。
spring-tx:事务管理(PlatformTransactionManager)。
Web 模块:
spring-web:基础 Web 功能(Servlet 监听器)。
spring-webmvc:DispatcherServlet 实现 MVC。
测试模块:
spring-test:JUnit 集成(MockMvc)。
上下文:
spring-context:ApplicationContext 扩展(事件、国际化)。
标签: 后端,Spring
151. 如何优化 Java 中的锁的使用?
默认解法:
减小锁粒度:如 ConcurrentHashMap 分段锁
缩短持有时间:锁内代码尽量少
读写分离:ReentrantReadWriteLock
无锁化:CAS 操作(Atomic 类)
锁粗化:连续操作合并加锁
使用线程本地变量:ThreadLocal
避免嵌套锁。
其他解法一:
减少锁范围:同步块而非同步方法。
减小锁粒度:拆分锁(如ConcurrentHashMap分段锁)。
读写分离:ReadWriteLock(读多写少)。
无锁化:CAS(Atomic类)/ThreadLocal。
锁升级:synchronized偏向锁/轻量级锁优化。
其他解法二:
减小锁粒度:
分段锁(如 ConcurrentHashMap 分 16 段)。
锁拆分(读写锁 ReentrantReadWriteLock)。
降低锁竞争:
无锁数据结构(AtomicLong)。
ThreadLocal 避免共享。
缩短锁持有时间:
同步块内只包含必要代码。
用局部变量替代同步块内耗时操作。
锁升级策略:
优先偏向锁/轻量级锁(减少内核切换)。
避免死锁:
按固定顺序获取锁。
tryLock 超时释放。
标签: Java并发,Java
152. Redis 的 Pipeline 功能是什么?
默认解法:
批量发送命令:
客户端将多个命令打包发送
服务端顺序执行后批量返回结果
减少 RTT(往返时间)和系统调用次数
注意:
非原子性(命令间可能插入其他请求)
需合理控制批量大小(避免客户端缓冲区溢出)。
其他解法一:
作用:批量发送命令(减少RTT时间)。
原理:客户端缓存多个命令→一次性发送→一次性接收所有响应。
注意:
非原子性(命令间可能插入其他客户端命令)
管道内命令有序执行
性能提升:高延迟网络下效果显著。
其他解法二:
作用:批量发送多个命令,减少网络往返时间(RTT)。
使用方式:
try (Pipeline pipeline = jedis.pipelined()) {
pipeline.set("key1", "v1");
pipeline.get("key2");
List<Object> results = pipeline.syncAndReturnAll();
}
注意事项:
Pipeline 非原子操作(只是批量发送)。
命令数不宜过多(避免客户端内存溢出)。
性能对比:
10 条命令:1 次 Pipeline ≈ 1 次普通请求耗时。
标签: 后端,Redis
153. 让你设计一个分布式 ID 发号器,怎么设计?
默认解法:
要求:全局唯一、趋势递增、高可用。方案:
UUID:简单但无序,存储大
数据库自增 ID:分库分表需设置步长
Redis INCR:集群需分片
Snowflake:64 位 = 时间戳(41位)+ 机器ID(10位)+ 序列号(12位)
美团 Leaf:基于 Snowflake 或数据库号段(双 Buffer 预取)。
其他解法一:
要求:全局唯一、趋势递增、高性能。
方案:
雪花算法:时间戳+机器ID+序列号(64位)
Redis INCR:原子递增(需持久化)
数据库分段:号段缓存(如Leaf-segment)
UUID:无序(索引效率低)
容灾:多机房机器ID分配;时钟回拨处理(雪花算法)。
其他解法二:
需求分析:
全局唯一、趋势递增、高可用、低延迟。
方案对比:
UUID:无序,索引性能差(不推荐)。
数据库自增:性能瓶颈,扩展难。
Redis INCR:集群下需分片(如 INCR user_id_shard1)。
雪花算法(Snowflake):
64 位 ID = 符号位(0) + 时间戳(41ms) + 机器ID(10) + 序列号(12)。
单机每秒 409.6 万个 ID。
优化点:
机器 ID 分配:ZooKeeper 或配置中心管理。
时钟回拨:缓存历史时间戳或等待。
开源方案:
百度 UidGenerator、美团 Leaf。
标签: 后端,系统设计
154. 什么是服务雪崩?
默认解法:
微服务中,一个服务故障引发依赖服务连锁故障,最终导致整个系统瘫痪。原因:
服务不可用(如宕机)
重试风暴(超时重试放大流量)
资源耗尽(线程池满)
预防:熔断降级、限流、异步调用、服务隔离。
其他解法一:
定义:服务A故障→服务B调用A超时→B资源耗尽→B故障→连锁故障(如多米诺骨牌)。
诱因:
高并发
重试风暴
同步阻塞调用
防御:
熔断降级
限流
异步调用
其他解法二:
定义:
单个服务故障引发级联失败,导致整个系统崩溃。
形成原因:
服务依赖:A → B → C,C 故障导致 B 阻塞 → A 阻塞。
线程池耗尽:下游延迟升高,上游调用线程全部阻塞。
解决方案:
熔断降级:Hystrix 快速失败。
限流控制:Sentinel 限制 QPS。
超时设置:避免无限等待。
异步调用:线程池隔离(Bulkhead 模式)。
标签: 服务容灾,Spring Cloud,微服务,后端
155. JVM 由哪些部分组成?
默认解法:
类加载器(ClassLoader):加载字节码
运行时数据区:
方法区(元数据)
堆(对象实例)
栈(线程私有,存栈帧)
程序计数器
本地方法栈
执行引擎:解释器、JIT 编译器、GC
本地库接口(JNI)。
其他解法一:
类加载器:加载.class文件。
运行时数据区:
方法区(类信息/常量)
堆(对象实例)
栈(局部变量/操作数栈)
程序计数器(当前指令地址)
本地方法栈(Native方法)
执行引擎:解释器/JIT编译器。
本地库接口(JNI)。
其他解法二:
类加载子系统:
加载 .class → 验证 → 准备 → 解析 → 初始化。
运行时数据区:
线程私有:程序计数器、虚拟机栈、本地方法栈。
线程共享:堆、方法区(元空间)。
执行引擎:
解释器:逐行执行字节码。
JIT 编译器:热点代码编译为机器码。
GC:垃圾回收器(如 CMS、G1)。
本地库接口(JNI):
调用 Native 方法(如 System.currentTimeMillis())。
标签: 后端,Java,JVM
156. Redis 通常应用于哪些场景?
默认解法:
缓存:加速读请求
会话存储:分布式 Session
排行榜:ZSET
计数器:INCR(阅读量)
消息队列:List/Stream
分布式锁:SETNX
社交关系:Set(共同已关注)
布隆过滤器:防缓存穿透。
其他解法一:
缓存:加速数据访问(减轻DB压力)。
会话存储:分布式Session。
排行榜:有序集合(ZSET)。
消息队列:List/Stream。
分布式锁:SETNX命令。
计数器:INCR(阅读量)。
其他解法二:
缓存:
数据库查询缓存(减轻 MySQL 压力)。
会话存储:
分布式 Session(如 Spring Session)。
排行榜:
ZSET 实现实时排名。
计数器:
INCR 实现阅读量/点赞数。
消息队列:
List 实现简单队列(LPUSH/BRPOP)。
分布式锁:
SETNX 实现跨进程互斥。
地理位置:
GEO 存储附近的人(GEORADIUS)。
标签: 后端,Redis
157. 让你设计一个短链系统,怎么设计?
默认解法:
核心:
发号器:生成唯一 ID(Snowflake 或 数据库自增)
进制转换:10 进制 ID → 62 进制短码(a-zA-Z0-9)
存储映射:短码 → 原 URL(Redis + MySQL)
跳转:301/302 重定向
过期清理:设置 TTL
防攻击:限流、黑名单。
其他解法一:
短链生成:
哈希算法(MurmurHash)→ 62进制转短码
发号器(自增ID转62进制)
存储:
KV数据库(如Redis):短码→原URL
持久化(MySQL):防丢失
跳转:
301永久重定向(SEO友好)
302临时重定向(统计访问量)
优化:
缓存热点短链
防攻击(短码随机性)
其他解法二:
短链生成:
哈希算法:MD5(长链) 取前 6 位(冲突时加盐重试)。
自增 ID 转 62 进制(0-9a-zA-Z)。
存储设计:
映射关系:短链码 → 原始 URL(Redis 缓存 + MySQL 持久化)。
跳转流程:
用户访问 t.cn/abc123 → 服务端 302 重定向到原地址。
优化点:
缓存预热:高频短链预加载 Redis。
过期清理:设置 TTL 自动清理。
防攻击:限制同一 IP 生成频率。
扩展功能:
访问统计(Redis HyperLogLog 统计 UV)。
标签: 后端,系统设计
158. 什么是循环依赖(常问)?
默认解法:
Bean A 依赖 B,同时 B 依赖 A。Spring 通过三级缓存解决:
一级缓存:存放完整 Bean(singletonObjects)
二级缓存:存放半成品 Bean(earlySingletonObjects)
三级缓存:存放 Bean 工厂(singletonFactories)
过程:A 创建 → 暴露工厂到三级缓存 → 填充属性 B → B 创建 → 从三级缓存获取 A 的早期引用 → 完成 B → 完成 A。
其他解法一:
定义:Bean A依赖B,同时B依赖A(形成闭环)。
Spring解决:
三级缓存:
singletonFactories(早期暴露工厂)
流程:A创建→注入B→B创建→注入A(从三级缓存获取A的半成品)
限制:仅支持单例作用域的Setter/Field注入(构造器注入无法解决)。
其他解法二:
定义:
Bean A 依赖 B,同时 Bean B 依赖 A(形成闭环)。
Spring 解决流程(以构造器注入为例):
一级缓存(单例池)→ 二级缓存(早期曝光对象)→ 三级缓存(ObjectFactory)。
流程:
创建 A → 放入三级缓存。
A 发现依赖 B → 创建 B。
B 发现依赖 A → 从三级缓存获取 A 的 ObjectFactory(得到 A 的代理)。
B 完成创建 → 放入一级缓存。
A 注入 B 完成创建 → 移入一级缓存。
限制:
只支持单例作用域的循环依赖。
构造器注入无法解决(需改用 Setter 注入)。
标签: 后端,Spring
159. JVM 垃圾回收调优的主要目标是什么?
默认解法:
低延迟:减少 STW(Stop-The-World)时间(CMS/G1/ZGC)
高吞吐:单位时间处理更多请求(Parallel Scavenge/Old)
内存占用:合理分配堆大小,避免 OOM
平衡点:根据业务选择(如 Web 服务已关注延迟,计算服务已关注吞吐)。
其他解法一:
低延迟:减少STW停顿时间(CMS/G1/ZGC)。
高吞吐:单位时间处理更多请求(ParallelGC)。
内存占用:合理分配堆大小(避免频繁GC)。
平衡策略:根据业务选择(如Web服务侧重低延迟,计算服务侧重高吞吐)。
其他解法二:
降低停顿时间:
减少 STW(Stop-The-World)时间(如 G1 的 MaxGCPauseMillis)。
提高吞吐量:
单位时间内业务运行时间占比(如 Parallel GC 调大堆)。
减少内存占用:
避免堆过大导致 FGC 时间过长。
避免 OOM:
合理设置堆大小(-Xmx)。
排查内存泄漏(如 MAT 分析堆转储)。
调优手段:
选择合适 GC 器(低延迟选 ZGC/Shenandoah)。
调整分代比例(-XX:NewRatio)。
标签: JVM,Java
160. Redis 中的 Big Key 问题是什么?如何解决?
默认解法:
Big Key 问题:
内存不均(单个 Key 过大)
操作阻塞(删除/序列化耗时)
网络拥堵(传输延迟)
集群迁移失败
解决方案:
拆分:Hash 拆分为多个 Key(如 user:1000 → user:1000:base, user:1000:contact)
压缩:使用 Gzip 或 MessagePack
过期设置:自动清理
分片存储:使用 Redis Cluster
渐进式删除:UNLINK 替代 DEL,或 SCAN 分批删除
其他解法一:
定义:
String类型 > 10KB
集合元素 > 5000个
危害:
阻塞操作(如del大key)
网络流量激增
内存不均(集群)
解决:
拆分:hash分field;set分片
压缩:序列化优化
异步删除:unlink代替del
其他解法二:
定义:
String 类型 > 10KB,集合类型元素 > 5000 个或总大小 > 10MB。
危害:
操作阻塞(删除 1MB Key 耗时约 1ms)。
网络拥塞(传输大 Key 占带宽)。
内存不均(集群数据倾斜)。
解决方案:
拆分:大 Hash 拆分为多个小 Key(如 user:1:info → user:1:base + user:1:contact)。
压缩:序列化压缩(如 Protobuf)。
异步删除:UNLINK 替代 DEL。
监控:redis-cli --bigkeys 扫描大 Key。
标签: 后端,Redis
161. HTTP 和 HTTPS 有什么区别?
默认解法:
安全性:HTTP 明文传输;HTTPS = HTTP + SSL/TLS 加密
端口:HTTP 80;HTTPS 443
证书:HTTPS 需 CA 证书验证身份
性能:HTTPS 有加密开销(可通过 TLS 1.3 优化)
SEO:搜索引擎优先索引 HTTPS 站点。
其他解法一:
安全性:
HTTP明文传输
HTTPS = HTTP + SSL/TLS(加密)
端口:HTTP(80);HTTPS(443)。
证书:HTTPS需CA证书验证身份。
性能:HTTPS握手开销大(可通过会话复用优化)。
其他解法二:
安全性:
HTTP:明文传输(易被窃听)。
HTTPS:SSL/TLS 加密(防窃听、篡改、冒充)。
端口:
HTTP:80。
HTTPS:443。
工作流程:
HTTPS 需 CA 证书验证身份 → 非对称加密交换密钥 → 对称加密传输数据。
性能:
HTTPS 增加 1~2 个 RTT(握手延迟)。
硬件加速减少性能损耗(如 TLS 硬件加速卡)。
SEO 影响:
谷歌优先索引 HTTPS 网站。
标签: 网络
162. 分布式锁一般都怎样实现?
默认解法:
数据库:唯一索引 + 乐观锁
Redis:SET key value NX PX timeout(Redlock 算法)
ZooKeeper:创建临时有序节点,最小节点获锁
Etcd:租约(Lease)+ 事务(TXN)
选型:Redis 性能高;ZK 可靠性强。
其他解法一:
数据库:唯一索引(insert锁记录)。
Redis:SETNX + Lua原子操作(Redisson看门狗续期)。
Zookeeper:临时有序节点(最小节点获锁)。
对比:
性能:Redis > ZK > DB
可靠性:ZK(CP) > Redis(AP)
其他解法二:
基于数据库:
唯一索引实现(INSERT INTO lock_table (lock_name) VALUES ('order_lock'))。
缺点:性能差,无自动释放。
基于 Redis:
SET lock_name unique_value NX PX 30000(推荐 Redisson 看门狗续期)。
基于 ZooKeeper:
创建临时有序节点(最小节点获锁),监听前序节点释放。
基于 Etcd:
租约(Lease)机制 + Revision 版本号。
对比:
| 方案 | 性能 | 可靠性 | 实现复杂度 |
|---|---|---|---|
| Redis | 高 | AP | 低 |
| ZK | 中 | CP | 高 |
标签: 后端,系统设计
163. 如何对 Java 的垃圾回收进行调优?
默认解法:
步骤:
监控:GC 日志(-Xloggc)、JVisualVM、Prometheus
分析:停顿时间、吞吐量、内存占用
参数调整:
堆大小:-Xms/-Xmx
新生代比例:-XX:NewRatio
收集器:-XX:+UseG1GC
停顿目标:-XX:MaxGCPauseMillis
避免内存泄漏。
其他解法一:
参数调整:
堆大小:-Xms/-Xmx
新生代比例:-XX:NewRatio
收集器:-XX:+UseG1GC
监控工具:
jstat查看GC统计
GC日志分析(-Xloggc)
策略:
减少Full GC(增加老年代空间)
减少STW(G1替代CMS)
其他解法二:
参数调优:
堆大小:-Xms4g -Xmx4g(避免动态扩展)。
新生代比例:-XX:NewRatio=2(老年代:新生代=2:1)。
晋升阈值:-XX:MaxTenuringThreshold=15。
GC 器选择:
高吞吐:Parallel GC(-XX:+UseParallelGC)。
低延迟:G1(-XX:+UseG1GC)或 ZGC(-XX:+UseZGC)。
监控工具:
jstat -gcutil 实时 GC 统计。
GC 日志分析(-Xloggc:gc.log -XX:+PrintGCDetails)。
优化策略:
减少对象分配速率(对象池复用)。
避免大对象直接进入老年代(-XX:PretenureSizeThreshold)。
标签: JVM,Java
164. 如何设计一个点赞系统?
默认解法:
需求:防刷、计数、按时间排序。方案:
存储:
点赞关系:Redis Set(文章ID+用户ID)
点赞数:Redis Hash(文章ID → 计数)
防刷:
用户频率限制(Redis INCR)
IP 限制
持久化:异步写入 DB
排行榜:ZSET 按时间排序。
其他解法一:
存储:
Redis:Hash存储用户点赞状态(key:内容ID, field:用户ID)
MySQL:持久化点赞总数(异步更新)
性能:
批量写入(合并DB操作)
缓存总数(Redis原子INCR)
防刷:用户ID/IP限流。
其他解法二:
存储设计:
Redis 计数:INCR 统计点赞数(Hash 存储用户-对象关系)。
数据库持久化:异步同步到 MySQL(用户ID+内容ID+时间)。
去重与限额:
用户维度:SADD user:1:likes post:100 防重复点赞。
内容维度:每日/总点赞数限制(Redis Lua 脚本原子检查)。
数据统计:
热帖排行:ZSET 按点赞数排序。
最近点赞:List 存储最新 N 个点赞用户。
性能优化:
批量写入:定时合并 Redis 数据到 DB。
缓存穿透:布隆过滤器过滤无效内容ID。
标签: 系统设计,场景题
165. Spring 如何解决循环依赖?
默认解法:
三级缓存:
singletonFactories(三级):存 ObjectFactory(可返回早期代理对象)
earlySingletonObjects(二级):存半成品 Bean
singletonObjects(一级):存完整 Bean
流程(以 A→B→A 为例):
A 实例化 → 加入三级缓存
A 填充属性 B → 触发 B 创建
B 填充属性 A → 从三级缓存获取 A 的早期引用 → B 完成 → A 完成。
其他解法一:
三级缓存:
singletonObjects:完整Bean
earlySingletonObjects:早期Bean(未注入属性)
singletonFactories:Bean工厂(可生成早期Bean)
流程:
A创建→放入三级缓存→注入B→B创建→注入A(从三级缓存获取A的工厂→生成早期A)→B完成→A完成
限制:仅支持单例作用域的Setter/Field注入。
其他解法二:
三级缓存机制:
singletonObjects(一级):完整 Bean。
earlySingletonObjects(二级):提前曝光的半成品 Bean(已构造未注入属性)。
singletonFactories(三级):Bean 工厂(可生成代理对象)。
解决流程(以 Setter 注入为例):
创建 A → 放入三级缓存(ObjectFactory)。
A 发现依赖 B → 创建 B。
B 发现依赖 A → 从三级缓存获取 A 的代理对象(提前曝光)→ B 创建完成放入一级缓存。
A 注入 B → 完成初始化 → 移入一级缓存。
限制:
仅支持单例作用域。
构造器注入无法解决(需改用 Setter 注入)。
标签: 后端,Spring
166. HTTP 与 RPC 之间的区别?
默认解法:
HTTP:
应用层协议,基于文本(可读性好)
通用性强(跨语言)
性能较低(头较大)
RPC:
远程过程调用协议(可基于 TCP/HTTP)
高性能(二进制序列化)
服务治理(熔断、负载均衡)
定位:HTTP 适合开放接口;RPC 适合内部微服务。
其他解法一:
协议:HTTP是应用层协议;RPC是框架(可基于TCP/HTTP)。
性能:RPC通常定制协议(二进制序列化)比HTTP+JSON高效。
服务治理:RPC框架内置负载均衡/熔断(HTTP需网关)。
跨语言:HTTP更通用(RPC需跨语言支持如gRPC)。
其他解法二:
协议层:
HTTP:应用层协议(文本头 + 可读性强)。
RPC:可基于 TCP/HTTP 的自定义协议(如 Dubbo 二进制协议)。
性能:
HTTP/1.1 有队头阻塞,RPC 协议通常更高效(如 gRPC/Protobuf)。
服务治理:
RPC 框架内置负载均衡、熔断、链路追踪(如 Dubbo + Sentinel)。
HTTP 需配合网关(Spring Cloud Gateway)+ 服务网格(Istio)。
适用场景:
HTTP:跨平台、浏览器兼容(如 RESTful API)。
RPC:微服务内部高性能调用。
标签: 微服务,Spring Cloud,远程调用,后端
167. 常用的 JVM 配置参数有哪些?
默认解法:
堆大小:-Xms(初始堆)、-Xmx(最大堆)
新生代:-Xmn(大小)、-XX:SurvivorRatio(Eden/Survivor 比例)
元空间:-XX:MetaspaceSize、-XX:MaxMetaspaceSize
GC 日志:-Xloggc:file -XX:+PrintGCDetails
收集器:-XX:+UseG1GC
OOM 时 dump:-XX:+HeapDumpOnOutOfMemoryError。
其他解法一:
堆内存:-Xms(初始堆)、-Xmx(最大堆)。
新生代:-Xmn(大小)、-XX:SurvivorRatio(Eden/Survivor)。
方法区:-XX:MetaspaceSize(元空间初始)、-XX:MaxMetaspaceSize(最大)。
GC日志:-Xloggc:gc.log、-XX:+PrintGCDetails。
收集器:-XX:+UseG1GC(启用G1)。
其他解法二:
堆内存:
-Xms4g:初始堆大小。
-Xmx4g:最大堆大小。
元空间:
-XX:MetaspaceSize=256m:初始大小。
-XX:MaxMetaspaceSize=512m:上限。
垃圾回收:
-XX:+UseG1GC:启用 G1 回收器。
-XX:MaxGCPauseMillis=200:目标停顿时间。
OOM 处理:
-XX:+HeapDumpOnOutOfMemoryError:自动 Dump 堆。
-XX:HeapDumpPath=/logs/dump.hprof:Dump 路径。
监控:
-XX:+PrintGCDetails:打印 GC 日志。
-Xloggc:/logs/gc.log:GC 日志路径。
标签: JVM,Java
168. 如何解决 Redis 中的热点 key 问题?
默认解法:
热点 key:高频访问的 key。方案:
本地缓存:客户端缓存热点 key(需解决一致性)
分散热点:
拆 key:将 key 拆为 key1、key2…
哈希分片:同一业务不同 key(如 suffix 取模)
备份 key:多副本 key_{1…N},随机访问。
其他解法一:
本地缓存:客户端缓存热key(如Caffeine)。
分片:为热key添加随机后缀(分散到多个节点)。
备份:在多个节点存副本(读写分流)。
限流:针对热key接口限流。
其他解法二:
识别热点:
redis-cli --hotkeys 或监控客户端调用统计。
解决方案:
本地缓存:客户端缓存热点 Key(如 Caffeine)。
分片打散:为 Key 添加随机后缀(如 key_{1..10})。
读写分离:从节点处理读请求。
限流降级:熔断保护(如 Sentinel)。
预防措施:
避免大 Value(压缩或拆分)。
设置合理过期时间。
标签: 后端,Redis
169. TCP 是用来解决什么问题?
默认解法:
解决 IP 协议不可靠传输问题:
数据包丢失:通过超时重传和快速重传机制
数据包乱序:通过序列号和重组机制
流量控制:滑动窗口协议防止接收方被淹没
拥塞控制:慢启动、拥塞避免、快恢复算法
连接管理:三次握手建立连接,四次挥手释放连接
本质:在不可靠的 IP 层上实现可靠的数据流传输
其他解法一:
可靠传输:解决IP层不可靠(丢包/乱序)。
流量控制:接收方控制发送速率(滑动窗口)。
拥塞控制:避免网络过载(慢启动/拥塞避免)。
端到端连接:抽象应用层通信(端口号标识应用)。
其他解法二:
核心目标:在不可靠的 IP 网络上提供可靠的数据传输。
解决的关键问题:
数据丢失:超时重传机制。
乱序到达:序列号重组数据包。
流量控制:滑动窗口匹配接收方处理能力。
拥塞控制:慢启动、拥塞避免、快重传算法。
适用场景:
需要可靠传输的应用(HTTP、FTP、数据库连接)。
代价:
连接建立开销(三次握手)。
协议头额外开销(至少 20 字节)。
标签: 网络
170. 让你设计一个 RPC 框架,怎么设计?
默认解法:
核心模块设计:
传输层:Netty 实现 TCP 通信
协议层:自定义二进制协议(魔数+长度+序列化方式+数据)
序列化:Protobuf/Kryo/Hessian
服务注册发现:ZooKeeper/Nacos
动态代理:JDK/CGLib 生成客户端代理
负载均衡:随机/轮询/一致性哈希
容错机制:超时重试、熔断降级
监控:调用链路跟踪(TraceID)
其他解法一:
通信层:Netty实现TCP传输(编解码)。
序列化:Protobuf/JSON(高效跨语言)。
服务治理:
注册中心(Zookeeper/Nacos)
负载均衡(随机/轮询)
容错(重试/熔断)
动态代理:客户端生成服务接口代理(透明调用)。
异步:支持Future/回调。
其他解法二:
通信层:
基于 Netty 实现 NIO 网络传输。
自定义二进制协议(消息头 + 体)。
序列化:
支持 JSON/Protobuf/Hessian(可配置)。
服务治理:
注册中心(ZooKeeper/Nacos)管理服务提供者。
负载均衡(随机/轮询/一致性哈希)。
代理层:
动态代理(JDK/CGLIB)透明化远程调用。
容错机制:
超时重试、熔断降级(集成 Sentinel)。
扩展点:
SPI 机制支持插件化(如 Filter 链)。
标签: 后端,系统设计
171. Java 中常见的垃圾收集器有哪些?
默认解法:
新生代收集器:
Serial:单线程,复制算法
ParNew:多线程版 Serial
Parallel Scavenge:吞吐量优先
老年代收集器:
Serial Old:标记-整理
Parallel Old:多线程版 Serial Old
CMS:标记-清除,低停顿(四阶段)
整堆收集器:
G1:分区域收集,可预测停顿
ZGC:<10ms 停顿,染色指针
Shenandoah:低停顿,并发整理
其他解法一:
新生代:
Serial:单线程STW
ParNew:多线程版Serial
Parallel Scavenge:吞吐优先
老年代:
Serial Old
Parallel Old
CMS:并发标记清除(低停顿)
整堆:
G1:分区收集(JDK9默认)
ZGC:TB级堆,<10ms停顿
Shenandoah:低停顿
其他解法二:
新生代收集器:
Serial:单线程 STW(Client 模式默认)。
ParNew:Serial 的多线程版(配合 CMS)。
Parallel Scavenge:吞吐量优先(Server 模式默认)。
老年代收集器:
Serial Old:Serial 的老年代版。
Parallel Old:Parallel Scavenge 的老年代搭档。
CMS:低延迟(标记-清除算法,已废弃)。
全堆收集器:
G1:分 Region 回收(JDK 9+ 默认)。
ZGC:亚毫秒延迟(TB 级堆)。
Shenandoah:低延迟(Red Hat 贡献)。
标签: JVM,Java
172. 什么是限流?限流算法有哪些?怎么实现的?
默认解法:
限流:控制系统单位时间内的请求量,防止服务过载
算法:
计数器:固定窗口(实现简单,但临界突变)
滑动窗口:细分时间片(更平滑)
漏桶:恒定速率流出(队列缓冲)
令牌桶:定时添加令牌,突发流量适应(Guava RateLimiter)
分布式限流:Redis + Lua(原子操作)
实现:
网关层:Nginx limit_req
应用层:Spring Cloud Gateway/Sentinel
其他解法一:
定义:控制单位时间请求量(防系统过载)。
算法:
计数器:固定窗口(简单,临界问题)
滑动窗口:多个小窗口统计(解决临界)
漏桶:恒定速率流出(平滑流量)
令牌桶:按速率放令牌(允许突发)
实现:Guava RateLimiter(令牌桶)/Sentinel(滑动窗口)。
其他解法二:
定义:控制单位时间内的请求量(防系统过载)。
常见算法:
固定窗口:
实现:计数器统计每秒请求数(AtomicInteger)。
缺点:窗口切换时突发流量超限。
滑动窗口:
实现:将秒拆分为 N 个子窗口(如 10 个 100ms),统计最近 1 秒请求。
漏桶算法:
实现:队列缓冲请求,恒定速率处理(BlockingQueue)。
令牌桶:
实现:定时添加令牌(ScheduledThreadPool),请求获取令牌执行(Guava RateLimiter)。
工具:
网关层:Nginx limit_req。
应用层:Sentinel、Resilience4j。
标签: 后端,系统设计
173. Netty 性能为什么这么高?
默认解法:
高性能原因:
异步非阻塞:基于 NIO 事件驱动模型
零拷贝:
FileRegion 实现 sendfile
CompositeByteBuf 合并缓冲区
内存池:重用 ByteBuf 减少 GC
高效线程模型:主从 Reactor 多线程
无锁化设计:串行化处理 Channel 事件
优化协议:支持 Protobuf 等高效序列化
其他解法一:
线程模型:主从Reactor(bossGroup处理连接,workerGroup处理I/O)。
内存管理:池化DirectByteBuffer(减少GC)。
零拷贝:FileRegion减少内核态拷贝。
无锁化:串行化处理Channel事件(避免锁竞争)。
高效序列化:Protobuf等二进制协议。
其他解法二:
Reactor 线程模型:
主从多线程:Boss 组处理连接,Worker 组处理 I/O。
零拷贝优化:
FileRegion 支持 sendfile 系统调用。
CompositeByteBuf 减少内存拷贝。
内存管理:
池化 ByteBuf(Recycler 对象池)。
堆外内存避免 GC 压力。
高效并发:
FastThreadLocal 优于 JDK ThreadLocal。
无锁化设计(如 MPSC 队列)。
协议优化:
预置编解码器(如 ProtobufVarint32FrameDecoder)。
标签: Netty,后端
174. 为什么 Spring 循环依赖需要三级缓存,二级不够吗?
默认解法:
三级缓存必要性:
一级缓存(singletonObjects):存放完整 Bean
二级缓存(earlySingletonObjects):存放半成品 Bean(已实例化未初始化)
三级缓存(singletonFactories):存放 Bean 工厂(可生成代理对象)
关键点:当存在 AOP 代理时,二级缓存无法处理代理对象创建。三级缓存通过 ObjectFactory 延迟生成代理对象,保证代理的唯一性
其他解法一:
二级缓存(earlySingletonObjects)问题:无法处理AOP代理对象。
三级缓存优势:singletonFactories存储ObjectFactory,可返回代理对象。
流程:
普通Bean:工厂直接返回原始对象
AOP代理:工厂返回代理对象(确保依赖注入的是代理)
其他解法二:
二级缓存问题:
若只有二级缓存(earlySingletonObjects),无法处理 AOP 代理对象循环依赖。
三级缓存必要性:
三级缓存存储 ObjectFactory(() -> getEarlyBeanReference(beanName, mbd, bean))。
在需要提前曝光时,通过 ObjectFactory 生成代理对象(解决 AOP 代理问题)。
示例流程:
若 A 需生成代理:
A 工厂放入三级缓存。
B 依赖 A 时,通过三级缓存工厂创建代理对象 → 放入二级缓存。
A 初始化完成后替换为完整代理对象。
结论:三级缓存确保代理对象被正确创建和复用。
标签: 后端,Spring
175. JVM 的内存区域是如何划分的?
默认解法:
线程私有:
程序计数器:当前指令地址
虚拟机栈:栈帧(局部变量表/操作数栈等)
本地方法栈:Native 方法
线程共享:
堆:对象实例(新生代 Eden/S0/S1 + 老年代)
方法区(元空间):类信息/常量/静态变量
直接内存:NIO 使用的堆外内存
注意:JDK8 移除永久代,方法区由本地内存实现的元空间替代
其他解法一:
线程私有:
程序计数器:当前指令地址
虚拟机栈:Java方法栈帧(局部变量表/操作数栈)
本地方法栈:Native方法
线程共享:
堆:对象实例
方法区:类信息/常量/静态变量(JDK8后为元空间)
其他解法二:
线程私有:
程序计数器:当前指令地址(唯一无 OOM 区域)。
虚拟机栈:方法调用的栈帧(局部变量表、操作数栈),可能 StackOverflowError。
本地方法栈:Native 方法调用。
线程共享:
堆:对象实例存储区(GC 主战场),可 OOM。
方法区:类信息、常量、静态变量(JDK 8 后为元空间,使用本地内存)。
直接内存:
NIO 的 DirectBuffer 分配的内存(不归 JVM 管,但可能 OOM)。
标签: JVM,Java
176. Redis 的持久化机制有哪些?
默认解法:
RDB(快照):
定时生成数据快照(二进制)
恢复快
可能丢失最后一次快照后的数据
AOF(日志):
记录写命令(文本)
支持每秒同步/每命令同步
恢复慢但数据更完整
混合持久化(4.0+):AOF 包含 RDB 头 + 增量 AOF 命令。
其他解法一:
RDB:
定时生成数据快照(二进制文件)
命令:SAVE(阻塞)/BGSAVE(后台)
优点:恢复快
缺点:可能丢数据
AOF:
记录写命令(追加日志)
刷盘策略:always/everysec/no
优点:数据安全
缺点:文件大,恢复慢
混合:AOF重写时用RDB格式(Redis 4.0+)。
其他解法二:
RDB(快照):
定时生成数据集的二进制快照(SAVE 阻塞 / BGSAVE 后台)。
优点:恢复快(适合备份)。
缺点:可能丢失最后一次快照后的数据。
AOF(追加日志):
记录每个写命令(appendonly yes)。
刷盘策略:
always:每条命令刷盘(强一致,性能差)。
everysec:每秒刷盘(推荐)。
no:由系统决定。
优点:数据完整性高。
缺点:文件大(需重写优化)。
混合持久化(Redis 4.0+):
AOF 包含 RDB 头 + 增量 AOF 命令(结合两者优势)。
标签: 后端,Redis
177. 如果发现 Redis 内存溢出了?你会怎么做?请给出排查思路和解决方案
默认解法:
排查:
info memory:分析 used_memory
扫描大 Key:redis-cli –bigkeys
查看 Key 数量:dbsize
检查过期策略:ttl key
解决:
清理大 Key:拆分或删除
设置过期时间
增加内存或集群分片
优化数据结构(如 Hash 用 ziplist)
升级 Redis 版本(6.0 多线程优化)。
其他解法一:
排查:
info memory查看内存分布
redis-cli –bigkeys找大Key
检查过期策略(ttl key)
解决:
删除大Key(unlink异步)
增加内存/集群分片
调整maxmemory-policy(如LRU)
优化数据结构(如hash用ziplist)
其他解法二:
定位原因:
info memory 查看内存分布(used_memory_human)。
redis-cli --bigkeys 扫描大 Key。
slowlog get 分析慢查询。
解决方案:
清理大 Key:分拆或删除(UNLINK key)。
调整淘汰策略:config set maxmemory-policy allkeys-lru。
增加内存:集群分片或升级机器。
优化数据结构:
小集合用 ziplist(hash-max-ziplist-entries 512)。
HyperLogLog 替代 Set 统计 UV。
预防措施:
监控内存使用率(超过 80% 告警)。
设置 maxmemory 限制内存。
标签: 后端,场景题
178. 负载均衡算法有哪些?
默认解法:
轮询(Round Robin):依次分配
加权轮询:按权重比例分配
随机(Random)
加权随机
最少连接(Least Connections):选连接数最少的
IP 哈希(IP Hash):同一 IP 固定到同一服务
一致性哈希:分布式场景减少 rehash 影响。
其他解法一:
轮询(Round Robin):依次分配。
加权轮询:按权重分配。
随机:随机选择。
最少连接:选当前连接数最少的。
IP哈希:按客户端IP哈希固定分配。
一致性哈希:扩缩容影响小。
其他解法二:
静态算法:
轮询(Round Robin):按顺序分配请求。
加权轮询:根据服务器权重分配(性能高的多分)。
源地址哈希(IP Hash):相同 IP 固定访问某服务器(保持会话)。
动态算法:
最少连接(Least Connections):优先选当前连接数少的服务器。
加权最少连接:结合权重和连接数。
响应时间加权:优先选响应快的(需监控探测)。
特殊算法:
一致性哈希:节点增减时影响最小(分布式缓存)。
实现工具:
Nginx、Spring Cloud Ribbon、LVS。
标签: 负载均衡,Spring Cloud,微服务,后端
179. Java 中有哪些垃圾回收算法?
默认解法:
标记-清除:碎片多
复制算法:分两块,存活对象复制到另一块(新生代)
标记-整理:移动存活对象(老年代)
分代收集:新生代(复制)、老年代(标记-清除/整理)
G1:分区收集,可预测停顿
ZGC:着色指针、读屏障,低延迟。
其他解法一:
标记-清除:标记可达对象→清除未标记(内存碎片)。
复制:内存分两块,存活对象复制到另一块(无碎片,空间浪费)。
标记-整理:标记→存活对象向一端移动→清理边界外(无碎片)。
分代收集:新生代(复制算法),老年代(标记-清除/整理)。
其他解法二:
标记-清除:
标记可达对象 → 清除未标记对象。
缺点:内存碎片。
复制算法:
内存分两块,存活对象复制到另一块(新生代 Eden/S0/S1)。
优点:无碎片。
缺点:空间浪费。
标记-整理:
标记后存活对象向一端移动(老年代常用)。
分代收集:
新生代:复制算法(Minor GC)。
老年代:标记-清除/整理(Full GC)。
分区算法(G1/ZGC):
堆划分为多个 Region,优先回收价值高的 Region。
标签: JVM,Java
180. Redis 中的缓存击穿、缓存穿透和缓存雪崩是什么?
默认解法:
缓存击穿:热点 key 过期,大量请求直击 DB
缓存穿透:查询不存在的数据(如恶意请求),绕过缓存
缓存雪崩:大量 key 同时过期,DB 压力骤增
解决:
击穿:热点数据永不过期 + 互斥锁
穿透:布隆过滤器 + 空值缓存
雪崩:随机过期时间 + 集群高可用。
其他解法一:
缓存击穿:热key过期瞬间大量请求直达DB(解决:永不过期/互斥锁重建)。
缓存穿透:查询不存在的数据(解决:布隆过滤器/空值缓存)。
缓存雪崩:大量key同时过期(解决:随机过期时间/集群部署)。
其他解法二:
缓存穿透:
问题:查询不存在的数据(绕过缓存击穿数据库)。
解决:布隆过滤器拦截无效请求;缓存空值(SET null 5s)。
缓存击穿:
问题:热点 Key 失效瞬间大量请求压垮数据库。
解决:永不过期(逻辑过期时间);互斥锁重建缓存(Redis SETNX)。
缓存雪崩:
问题:大量 Key 同时失效或 Redis 宕机。
解决:
过期时间加随机值(避免同时失效)。
集群高可用(哨兵/Cluster)。
多级缓存(本地缓存 + Redis)。
标签: 后端,Redis
181. 线上发现 Redis 机器爆了,如何优化?
默认解法:
紧急扩容:增加内存或节点
分析内存:redis-cli –bigkeys 找大 Key
清理数据:
删除非关键数据
设置过期时间
优化数据结构:
压缩存储(hash 用 ziplist)
拆分大 Key
升级集群:分片存储。
其他解法一:
紧急:
扩容(增加实例/集群分片)
删除非关键数据
优化:
大Key拆分
热Key分散(添加随机后缀)
内存淘汰策略调整
数据压缩(如snappy)
监控:设置内存阈值告警。
其他解法二:
紧急处理:
清理大 Key(UNLINK)。
调整淘汰策略(CONFIG SET maxmemory-policy allkeys-lru)。
重启释放碎片(MEMORY PURGE)。
扩容:
垂直扩容:升级更大内存机器。
水平扩容:集群分片(如 Codis/Redis Cluster)。
内存优化:
小数据用 ziplist(调整 hash-max-ziplist-entries)。
数据压缩(如 Snappy)。
预防措施:
监控内存告警(超过 80% 预警)。
定期扫描大 Key(redis-cli --bigkeys)。
设置 maxmemory 限制。
标签: 后端,场景题
182. 说下 Spring Bean 的生命周期?
默认解法:
实例化:调用构造函数
属性赋值:注入依赖(@Autowired)
BeanPostProcessor.postProcessBeforeInitialization
初始化:
实现 InitializingBean.afterPropertiesSet()
自定义 init-method
BeanPostProcessor.postProcessAfterInitialization
使用中
销毁:
实现 DisposableBean.destroy()
自定义 destroy-method。
其他解法一:
实例化:new对象。
属性赋值:populateBean()注入依赖。
初始化:
Aware接口(BeanNameAware)
BeanPostProcessor前置处理
InitializingBean.afterPropertiesSet()
自定义init-method
BeanPostProcessor后置处理(AOP代理在此生成)
销毁:DisposableBean.destroy() + 自定义destroy-method。
其他解法二:
实例化:
通过构造函数或工厂方法创建 Bean 实例。
属性赋值:
注入依赖(@Autowired 或 XML 配置)。
初始化:
调用 BeanPostProcessor.postProcessBeforeInitialization()。
执行 InitializingBean.afterPropertiesSet() 或 init-method。
调用 BeanPostProcessor.postProcessAfterInitialization()。
使用中:
Bean 可被应用使用。
销毁:
执行 DisposableBean.destroy() 或 destroy-method。
调用 DestructionAwareBeanPostProcessor.postProcessBeforeDestruction()。
标签: 后端,Spring
183. JVM 有那几种情况会产生 OOM(内存溢出)?
默认解法:
Java 堆溢出:对象过多(-Xmx 不足)
元空间溢出:加载类过多(-XX:MaxMetaspaceSize 不足)
栈溢出:递归过深(-Xss 不足)
直接内存溢出:NIO 操作(-XX:MaxDirectMemorySize 不足)
GC 开销超限:垃圾回收效率过低。
其他解法一:
堆溢出:对象过多(java.lang.OutOfMemoryError: Java heap space)。
栈溢出:递归过深(StackOverflowError)。
方法区溢出:类过多(OOM: Metaspace)。
直接内存溢出:NIO使用不当(OOM: Direct buffer memory)。
GC效率低下:频繁Full GC但回收少(OOM: GC Overhead limit exceeded)。
其他解法二:
堆内存溢出:
原因:对象过多或大对象(java.lang.OutOfMemoryError: Java heap space)。
元空间溢出:
原因:加载类过多(java.lang.OutOfMemoryError: Metaspace)。
栈溢出:
原因:无限递归(java.lang.StackOverflowError)。
直接内存溢出:
原因:NIO DirectBuffer 分配过多(java.lang.OutOfMemoryError: Direct buffer memory)。
线程溢出:
原因:创建线程数超过限制(java.lang.OutOfMemoryError: unable to create new native thread)。
标签: 后端,Java,JVM
184. Redis 在生成 RDB 文件时如何处理请求?
默认解法:
fork 子进程:父进程继续处理请求(写时复制)
子进程遍历内存生成 RDB 快照
父进程的写操作会复制内存页(修改数据时)
RDB 生成后替换旧文件
注意:
fork 可能阻塞主线程(内存大时)
写操作多时内存占用翻倍。
其他解法一:
写时复制(Copy-On-Write):
主进程fork子进程(共享内存)
子进程遍历内存生成RDB
写请求处理:
父进程修改数据时复制内存页(保证子进程数据一致性)
新数据写入新内存页
结果:RDB生成期间可继续处理请求(但写操作触发内存复制影响性能)。
其他解法二:
写时复制(Copy-On-Write):
父进程(主线程)继续处理请求。
子进程遍历内存数据写入 RDB 文件(不阻塞主线程)。
数据一致性保证:
子进程基于 fork 瞬间的内存快照生成 RDB。
父进程修改的数据在内存中复制副本(不影响子进程)。
性能影响:
写操作增加(需复制修改的页)。
内存占用翻倍(极端情况下)。
配置建议:
低峰期执行 BGSAVE(避免频繁 fork)。
标签: 后端,Redis
185. TCP 和 UDP 有什么区别?
默认解法:
TCP:
面向连接(三次握手)
可靠传输(确认重传)
有序交付
流量控制
头部大(20 字节)
UDP:
无连接
不可靠(可能丢包)
无序
头部小(8 字节)
适用:TCP 文件传输;UDP 音视频、DNS。
其他解法一:
连接:TCP面向连接(三次握手);UDP无连接。
可靠性:TCP可靠(确认重传/有序);UDP可能丢包乱序。
效率:TCP头部大(20字节);UDP头部小(8字节)。
场景:TCP(文件传输/网页);UDP(视频流/DNS)。
其他解法二:
可靠性:
TCP:有连接、可靠交付(确认重传)。
UDP:无连接、尽最大努力交付(可能丢包)。
传输效率:
TCP:效率低(头部至少 20 字节,建立连接慢)。
UDP:效率高(头部 8 字节,无连接开销)。
数据边界:
TCP:字节流(无消息边界,需应用层处理粘包)。
UDP:数据报(有明确边界)。
应用场景:
TCP:文件传输、Web 浏览(HTTP)。
UDP:视频流、DNS、实时游戏。
标签: 网络
186. 线上 CPU 飙高如何排查?
默认解法:
步骤:
top 定位高 CPU 进程
top -Hp pid 定位高 CPU 线程
printf “%x” tid 转线程 ID 为十六进制
jstack pid | grep nid -A 30 查看线程栈
分析代码:
死循环?
频繁 GC?
锁竞争?
工具:Arthas(thread -n 3)。
其他解法一:
定位线程:top -Hp pid(找高CPU线程)。
线程转储:jstack pid > thread.log(查线程堆栈)。
分析:
将线程ID(十进制)转16进制
在thread.log中匹配nid
原因:
死循环
频繁GC
锁竞争
其他解法二:
定位高 CPU 进程:
top -c 查看进程列表(按 P 排序)。
定位高 CPU 线程:
top -Hp <PID> → 记录线程 ID(转为十六进制)。
分析线程栈:
jstack <PID> > stack.log。
查找对应线程栈(nid=0x<hex>)。
常见原因:
死循环(如 while(true))。
频繁 GC(jstat -gcutil 查看 GC 情况)。
锁竞争(BLOCKED 状态线程)。
工具辅助:
Arthas:thread -n 3 查看最忙线程。
标签: 后端,线上问题排查
187. Java 中 volatile 关键字的作用是什么?
默认解法:
可见性:写操作立即刷新到主内存,读操作重新从主内存加载
禁止指令重排:内存屏障(LoadLoad/StoreStore)
注意:不保证原子性(如 i++ 需配合 synchronized 或 Atomic)。
其他解法一:
可见性:写操作立即同步到主存;读操作从主存读取。
有序性:禁止指令重排序(内存屏障)。
注意:不保证原子性(如i++需配合synchronized/CAS)。
其他解法二:
可见性:
写操作刷新到主内存,读操作从主内存读取(禁用工作内存缓存)。
有序性:
禁止指令重排序(内存屏障)。
非原子性:
不保证复合操作原子性(如 i++ 需配合 synchronized 或 AtomicInteger)。
实现原理:
写操作后加 StoreLoad 屏障(强制刷内存)。
应用场景:
状态标志(volatile boolean running)。
双重检查锁单例(DCL 需 volatile 防重排序)。
标签: Java并发,Java
188. 怎么分析 JVM 当前的内存占用情况?OOM 后怎么分析?
默认解法:
分析工具:
jmap -heap pid:堆概要
jmap -histo pid:对象直方图
jstat -gcutil pid:GC 统计
VisualVM:图形化分析
OOM 后:
配置 -XX:+HeapDumpOnOutOfMemoryError 自动生成 dump 文件
用 MAT(Memory Analyzer Tool)或 VisualVM 分析 dump 文件定位泄漏对象。
其他解法一:
分析工具:
jmap -heap pid(堆概要)
jmap -histo pid(对象统计)
jstat -gcutil(GC统计)
OOM分析:
-XX:+HeapDumpOnOutOfMemoryError(自动生成dump)
MAT/JVisualVM分析dump文件(找内存泄漏对象)
其他解法二:
实时监控:
jmap -heap <PID>:堆概要。
jstat -gc <PID> 1000:每秒 GC 统计。
OOM 分析:
自动 Dump:启动参数加 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./。
手动 Dump:jmap -dump:format=b,file=heap.hprof <PID>。
工具分析:
MAT:解析堆转储,查找 Retained Heap 最大的对象。
VisualVM:实时监控内存泄漏(对象创建跟踪)。
常见原因:
内存泄漏(如静态集合持有对象)。
大对象分配(如一次性加载大文件)。
标签: 后端,场景题,JVM
189. Spring MVC 具体的工作原理?
默认解法:
流程:
用户请求 → DispatcherServlet
DispatcherServlet 调用 HandlerMapping 获取 Handler(Controller 方法)
通过 HandlerAdapter 执行 Handler
Handler 返回 ModelAndView
ViewResolver 解析视图
渲染视图(填充 Model)→ 返回响应。
其他解法一:
请求流程:
DispatcherServlet接收请求
HandlerMapping找Controller
HandlerAdapter调用Controller
Controller返回ModelAndView
ViewResolver解析视图
View渲染响应
关键组件:
拦截器(Interceptor):预处理/后处理
参数解析器(HandlerMethodArgumentResolver)
返回值处理器(HandlerMethodReturnValueHandler)
其他解法二:
请求流程:
用户请求 → DispatcherServlet(前端控制器)。
处理器映射:
HandlerMapping 根据 URL 找到对应 Controller。
处理器适配:
HandlerAdapter 执行 Controller 方法(反射调用)。
模型处理:
Controller 返回 ModelAndView(包含数据和视图名)。
视图解析:
ViewResolver 将视图名解析为 View 对象(如 JSP)。
渲染响应:
View 渲染模型数据 → 返回客户端。
关键组件:
拦截器(Interceptor):AOP 式预处理请求。
参数解析器(HandlerMethodArgumentResolver):处理 @RequestParam 等。
标签: 后端,Spring
190. 分布式和微服务有什么区别?
默认解法:
分布式:
系统拆分部署在不同节点
解决单机性能瓶颈
技术异构性(不同服务不同语言)
微服务:
分布式的一种实现
按业务拆分独立服务
强调服务自治(独立开发、部署、扩展)
依赖轻量通信(HTTP/RPC)。
其他解法一:
分布式:系统拆分部署在不同节点(解决单机瓶颈)。
微服务:
更细粒度拆分(按业务模块)
独立开发/部署/扩展
强调去中心化治理(如独立数据库)
关系:微服务是分布式的一种实现方式。
其他解法二:
分布式:
系统拆分到不同机器(解决单机性能瓶颈)。
可能包含多个单体服务(如订单、库存服务独立部署)。
微服务:
服务按业务边界细粒度拆分(单一职责)。
强调独立开发、部署、扩展(如用户服务、支付服务)。
核心区别:
| 维度 | 分布式 | 微服务 |
|---|---|---|
| 拆分粒度 | 较粗(模块级) | 极细(功能单元) |
| 技术栈 | 可统一(如 Java) | 异构(不同服务不同语言) |
| 治理能力 | 弱(依赖基础设置) | 强(服务发现、熔断等) |
联系:
微服务是分布式架构的一种最佳实践。
标签: Spring Cloud,微服务,后端,分布式
191. 什么是 Java 中的 ABA 问题?
默认解法:
CAS 操作时,值从 A→B→A,检查仍为 A 但已变化。解决方案:
版本号:AtomicStampedReference(携带 stamp)
时间戳:记录修改时间
布尔标记:AtomicMarkableReference。
其他解法一:
场景:CAS操作时,值从A→B→A(版本未变但实际被修改过)。
危害:可能导致数据不一致。
解决:
版本号:AtomicStampedReference(带戳引用)
时间戳:记录修改时间
其他解法二:
问题描述:
线程 1 读取值 A → 线程 2 改 A 为 B 再改回 A → 线程 1 CAS 成功(但值已被修改过)。
风险:
链表操作中可能导致数据丢失(如原头节点被复用)。
解决方案:
版本号:AtomicStampedReference 维护(值,版本号)。
时间戳:类似版本号机制。
示例:
AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
ref.compareAndSet("A", "B", 0, 1); // 需版本号匹配
标签: Java并发,Java
192. Redis 的哨兵机制是什么?
默认解法:
哨兵(Sentinel):Redis 高可用解决方案
功能:
监控:定期检测主从节点状态
自动故障转移:主节点宕机时选举新主
通知:向客户端推送主节点变更
工作流程:
主观下线(SDOWN):单个 Sentinel 判定节点不可用
客观下线(ODOWN):多个 Sentinel 确认节点失效
选举 Leader Sentinel
Leader 执行故障转移(选最优从节点升级为主)
其他解法一:
作用:监控主从节点,自动故障转移。
功能:
监控:定期检测节点健康
通知:向管理员发送告警
自动故障转移:主节点宕机时选新主
配置中心:提供新主地址
部署:至少3个哨兵(避免误判)。
其他解法二:
作用:实现 Redis 主从架构的高可用(自动故障转移)。
核心功能:
监控:Sentinel 节点定期检查主/从状态。
通知:服务状态变化时发送告警。
自动故障转移:
主节点失效时,选举新主(从节点中选)。
更新客户端配置(通过 Pub/Sub 通知)。
部署要求:
至少 3 个 Sentinel 节点(避免脑裂)。
客户端集成:
连接 Sentinel 获取当前主节点地址(SENTINEL get-master-addr-by-name)。
标签: 后端,Redis
193. TCP 的粘包和拆包能说说吗?
默认解法:
粘包:多个数据包被合并成一个 TCP 报文发送
拆包:一个数据包被拆分成多个 TCP 报文
原因:
应用层报文大于 TCP 发送缓冲区大小
MSS(最大报文段)限制
滑动窗口机制导致合并发送
解决方案:
固定长度:浪费空间
分隔符:如
(需转义)
长度字段:最常用(如协议头中定义 length 字段)
其他解法一:
粘包:多个数据包被合并成一个TCP包发送。
拆包:一个数据包被拆成多个TCP包发送。
原因:TCP是字节流协议(无消息边界)。
解决:
固定长度
分隔符
消息头包含长度字段(如LengthFieldBasedFrameDecoder)
其他解法二:
粘包:
发送方多次发送的小数据包被合并为一个包接收(如 send("AB") + send("CD") → 收到 "ABCD")。
拆包:
大数据包被拆分为多个包接收(如 send("12345") → 收到 "12" + "345")。
原因:
TCP 是字节流协议,无消息边界。
网络 MTU 限制(1500 字节)。
解决方案:
固定长度:FixedLengthFrameDecoder。
分隔符:DelimiterBasedFrameDecoder(如 )。
长度字段:LengthFieldBasedFrameDecoder(头 4 字节表长度)。
标签: 网络
194. 线程的生命周期在 Java 中是如何定义的?
默认解法:
通过 Thread.State 枚举定义:
NEW:新建未启动
RUNNABLE:可运行(包括就绪和运行中)
BLOCKED:等待监视器锁(synchronized)
WAITING:无限期等待(Object.wait()/LockSupport.park())
TIMED_WAITING:限期等待(Thread.sleep()/Object.wait(timeout))
TERMINATED:线程终止
注意:RUNNABLE 包含操作系统层面的就绪(Ready)和运行中(Running)
其他解法一:
NEW:新建未启动。
RUNNABLE:可运行(就绪/运行中)。
BLOCKED:等待监视器锁(synchronized)。
WAITING:无限期等待(Object.wait())。
TIMED_WAITING:限期等待(Thread.sleep())。
TERMINATED:终止。
其他解法二:
NEW:
创建未启动(new Thread())。
RUNNABLE:
可运行状态(含就绪和运行中)。
BLOCKED:
等待监视器锁(如 synchronized 竞争失败)。
WAITING:
无限期等待(Object.wait() / LockSupport.park())。
TIMED_WAITING:
限期等待(Thread.sleep(1000) / Object.wait(1000))。
TERMINATED:
线程执行完毕。
状态转换:
start():NEW → RUNNABLE。
synchronized 竞争失败:RUNNABLE → BLOCKED。
wait():RUNNABLE → WAITING。
notify():WAITING → BLOCKED(需重新竞争锁)。
标签: Java并发,Java
195. 在 MySQL 中建索引时需要注意哪些事项?
默认解法:
选择区分度高的列(Cardinality 大)
避免过长字段(前缀索引)
联合索引最左前缀原则
排序/分组字段加索引
避免过多索引(影响写性能)
使用覆盖索引减少回表
NULL 值处理(IS NULL 可能不走索引)。
其他解法一:
选择列:高区分度列(如ID);常查询/排序的列。
避免冗余:联合索引覆盖多个查询。
前缀索引:对长字符串建索引(节省空间)。
控制数量:索引过多影响写性能。
最左前缀:合理设计联合索引顺序。
其他解法二:
选择合适列:
WHERE/JOIN/ORDER BY/GROUP BY 的列。
避免冗余索引:
联合索引 (a,b) 可替代单列索引 (a)。
控制索引数量:
过多索引降低写性能(每次写更新索引)。
前缀索引:
长字符串用前缀(INDEX(email(10)))。
区分度原则:
选择区分度高列(如 ID > 状态标志)。
避免索引失效:
函数转换(WHERE YEAR(create_time)=2023 → 失效)。
隐式类型转换(WHERE name=123 → 失效)。
标签: 后端,MySQL,数据库
196. Netty 采用了哪些设计模式?
默认解法:
责任链:ChannelPipeline(Inbound/Outbound Handler)
观察者:Future-Listener(异步回调)
工厂:ChannelFactory
单例:EventLoopGroup 共享资源
适配器:ChannelHandlerAdapter(空实现)
策略:EventExecutorChooser(选择 EventExecutor)。
其他解法一:
责任链:ChannelPipeline(事件处理器链)。
观察者:Future.addListener()(异步回调)。
工厂:ChannelFactory创建Channel。
单例:全局共享的EventLoopGroup。
适配器:ChannelInboundHandlerAdapter(空实现接口)。
其他解法二:
Reactor 模式:
主从多线程处理 I/O 事件(事件驱动)。
责任链模式:
ChannelPipeline 由多个 ChannelHandler 组成(如编解码、业务处理)。
观察者模式:
Future 监听操作结果(ChannelFuture.addListener())。
单例模式:
共享的 ChannelHandler(如 SharedEventExecutorGroup)。
建造者模式:
ServerBootstrap.group().channel().childHandler() 链式构建。
工厂模式:
EventLoopGroup 创建 EventLoop。
标签: Netty,后端
197. Spring 中的 DI 是什么?
默认解法:
依赖注入(Dependency Injection):容器动态注入对象所需依赖。实现方式:
构造器注入:@Autowired 在构造器上
Setter 注入:@Autowired 在 setter 方法
字段注入:@Autowired 在字段(不推荐)
目的:解耦组件,方便测试(Mock 依赖)。
其他解法一:
定义:依赖注入(Dependency Injection)。
核心:容器将依赖对象自动注入目标对象(无需new)。
方式:
构造器注入
Setter注入
字段注入(@Autowired)
目的:解耦(对象只已关注接口,不已关注具体实现)。
其他解法二:
定义:依赖注入(Dependency Injection),对象通过外部传入依赖而非自己创建。
实现方式:
构造器注入:
@Autowired
public UserService(UserRepository repo) {
this.repo = repo; }
Setter 注入:
@Autowired
public void setRepo(UserRepository repo) {
this.repo = repo; }
字段注入(不推荐):
@Autowired
private UserRepository repo;
优点:
解耦组件(依赖接口而非实现)。
方便单元测试(Mock 依赖)。
标签: 后端,Spring
198. Redis 主从复制的实现原理是什么?
默认解法:
从节点执行 SLAVEOF 命令
主节点 fork 生成 RDB 文件发送给从节点
从节点加载 RDB
主节点将写命令缓存并发送给从节点(复制缓冲区)
从节点执行命令保持同步
增量同步:通过 offset 断点续传。
其他解法一:
全量同步:
从节点发送psync ? -1
主节点执行BGSAVE生成RDB→发送给从
从加载RDB
增量同步:
主维护复制积压缓冲区(repl_backlog)
从断线重连后发送psync [offset]
主发送offset后的命令
命令传播:主持续发送写命令给从。
其他解法二:
建立连接:
从节点执行 REPLICAOF <master_ip> <port> 向主节点发起同步请求。
全量同步:
主节点执行 BGSAVE 生成 RDB → 发送给从节点。
从节点清空旧数据 → 加载 RDB。
增量同步:
主节点写命令存入复制缓冲区(Repl Backlog Buffer)。
从节点从缓冲区获取增量命令执行。
持续同步:
主节点每执行写命令,异步发送给从节点。
断线重连:
从节点用 PSYNC <runid> <offset> 请求增量同步(若 offset 在缓冲区则增量,否则全量)。
标签: 后端,Redis
199. 在什么情况下,不推荐为数据库建立索引?
默认解法:
数据量小(全表扫描更快)
写多读少(索引维护代价高)
列区分度低(如性别,索引效果差)
频繁更新的字段
查询中很少涉及的列
长文本字段(可考虑前缀索引)。
其他解法一:
数据量小:全表扫描更快。
写多读少:索引维护开销大。
区分度低:如性别(索引效率低)。
频繁更新的列:更新需维护索引。
长文本:全文索引替代普通索引。
其他解法二:
数据量极小:
全表扫描更快(如配置表仅 100 行)。
写多读少:
索引降低写性能(每次 INSERT/UPDATE 维护索引)。
区分度极低:
如性别列(值只有男/女),索引效果差。
频繁更新的列:
维护索引代价过高(如状态标志位)。
长文本列:
全文索引替代普通索引(如 FULLTEXT 索引)。
不参与查询的列:
避免无效索引占用空间。
标签: MySQL,后端,数据库
200. 什么是 Seata?
默认解法:
阿里开源的分布式事务解决方案。提供:
AT 模式(默认):基于 SQL 解析自动生成回滚日志(undo_log)
TCC 模式:需实现 Try/Confirm/Cancel 接口
Saga 模式:长事务补偿
XA 模式:两阶段提交
核心组件:TC(事务协调者)、TM(事务管理器)、RM(资源管理器)。
其他解法一:
定义:分布式事务解决方案(Simple Extensible Autonomous Transaction Architecture)。
模式:
AT:自动补偿型事务(基于SQL解析)
TCC:手动补偿事务(Try-Confirm-Cancel)
Saga:长事务(状态机+补偿)
核心组件:TC(事务协调器)、TM(事务管理器)、RM(资源管理器)。
其他解法二:
定义:开源的分布式事务解决方案(Simple Extensible Autonomous Transaction Architecture)。
核心模式:
AT 模式(默认):
一阶段:解析 SQL 生成快照(undo_log)。
二阶段:提交时异步删除 undo_log,回滚时用 undo_log 补偿。
TCC 模式:
Try:预留资源(如冻结库存)。
Confirm:确认操作(扣减库存)。
Cancel:取消操作(解冻库存)。
Saga 模式:长事务补偿(逆向服务调用)。
架构组件:
TC(Transaction Coordinator):事务协调器(独立部署)。
TM(Transaction Manager):事务管理器(发起全局事务)。
RM(Resource Manager):资源管理器(管理分支事务)。
标签: Seata,微服务,Spring Cloud,后端















暂无评论内容