JAVA技术路线
2026-03-31 23:55:54 0 举报AI智能生成
java面试资料
入职离职
模版推荐
作者其他创作
大纲/内容
1、JAVA基础
Java基础知识
1、数据类型
1、基本数据类型
byte(1)
short(2)
int(4)
long(8)
float(4)
double(8)
char(2)
boolean(1)
2、包装类
Integer, Double…
自动装箱拆箱
包装类缓存(IntegerCache 的 -128~127 范围缓存)
3、泛型
Java 泛型允许在编译时对类、接口、方法的类型进行参数化,提高代码复用性和类型安全性
2、关键字
1、final
修饰变量(常量)、方法(不可重写)、类(不可继承)
2、static
类变量、静态方法、静态代码块、内部类
3、volatile
可见性 & 禁止指令重排
4、synchronized
对象锁、类锁、可重入
5、transient
序列化时忽略字段
3、面向对象语言的特征
1、封装
隐藏实现细节,提供公共访问方式
2、继承
子类继承父类属性和方法
3、多态
1、编译时多态
方法重载
2、运行时多态
方法重写 + 向上转型
4、抽象类
5、接口
6、内部类
成员内部类、静态内部类、局部内部类、匿名内部类
13、面试题补充
1、final finally finalize的区别
final 是关键字,用来修饰类、方法和变量,起到“不可变/不可重写”的作用。<br>finally 是异常处理结构的一部分,保证资源释放。<br>finalize 是 Object 类的一个方法,垃圾回收前调用,但不推荐使用
2、utf-8编码中的中文占几个字节;int占几个字节
在 UTF-8 编码下,英文字符是 1 个字节,常用中文字符是 3 个字节,个别生僻字可能是 4 个字节。<br>而在 Java 中,int 类型是固定的 4 个字节(32 位),范围是 -2³¹ 到 2³¹-1
3、静态代理和动态代理的区别,什么场景使用
静态代理在编译时就生成代理类,每个目标类都要写一个代理类,优点是结构清晰,但扩展性差;动态代理在运行时生成代理类,可以灵活代理多个对象,常用的有 JDK 动态代理和 CGLIB。<br>一般项目中如果只是简单的代理,可以用静态代理;而在框架或需要通用代理逻辑的场景,比如 Spring AOP、MyBatis Mapper,就会使用动态代理
4、数组在内存中如何分配
基本类型数组的元素按类型大小连续存储;引用类型数组的元素是引用,指向堆上真实对象;多维数组本质是“数组的数组”,内存可以不连续。
5、说说对Java注解的理解
Java注解是一种为代码提供元数据的机制,它本身不影响代码逻辑,但可以通过注解处理器在编译时或运行时改变程序行为。主要分为源码注解、编译时注解和运行时注解,通过@Target指定使用目标,@Retention控制生命周期。注解让代码更加简洁,支持声明式编程。
6、说说对Java反射的理解
Java 反射机制是在程序运行时动态获取类的完整信息(包括字段、方法、构造器等)并能够动态操作对象的能力,是 Spring、MyBatis 等主流框架的底层基石,为依赖注入、对象映射、动态代理等核心功能提供了技术支撑,虽然反射会带来一定的性能开销和安全隐患,但它极大增强了 Java 的灵活性和扩展性。
7、说一下泛型原理,并举例说明
Java 泛型的本质是参数化类型,它通过类型擦除在编译期实现类型安全检查,在运行时擦除类型信息。比如 List<String> 在编译时确保只能添加 String 类型,编译后却变成原始类型 List,插入必要的类型转换。这种设计既提供了类型安全,又保持了与老版本 Java 的兼容性。
8、String StringBuffer StringBuild的区别
String、StringBuffer 和 StringBuilder 的主要区别体现在可变性、线程安全和性能三个方面。<br>String 是不可变类,任何修改都会创建新对象,线程安全但性能较差,适合字符串常量场景。<br>StringBuffer 是可变类,内部使用 synchronized 保证线程安全,性能比 String 好但比 StringBuilder 差,适合多线程环境下的字符串操作。<br>StringBuilder 也是可变类,但非线程安全,性能在三者中最高,适合单线程环境下的频繁字符串操作。
9、String.intern()方法
String.intern() 方法用于将字符串对象加入字符串常量池,如果常量池中已有相同内容的字符串,则返回已有引用,否则将当前字符串加入池中。<br>它可以保证相同内容的字符串只在常量池中存在一份,从而节省内存,也可以用来通过 == 判断字符串内容是否相等。
10、String为什么要设计成不可变的
安全性:防止关键数据被篡改。<br>线程安全:可以在多线程中共享,无需同步。<br>哈希缓存:hashCode 可缓存,提高 HashMap/HashSet 性能。<br>常量池优化:可复用字符串对象,节省内存,保证池中对象不被修改。
11、String在堆栈中的存储
String 变量本身的引用存放在栈中,字符串对象存放在堆上,字面量存放在字符串常量池中以实现复用
12、深拷贝和浅拷贝的区别
浅拷贝只复制对象本身,引用类型字段仍共享原对象;<br>深拷贝会复制对象及其引用的所有对象,保证新对象与原对象互不影响。<br>浅拷贝可以通过 Object.clone() 实现,深拷贝可以手动复制或者通过序列化实现
计算机网络基础知识
1、OSI七层模型与TCP/IP四层模型
1、OSI七层模型
物理层
数据链路层
网络层
传输层
会话层
表示层
应用层
2、TCP/IP四层模型
网络接口层
网际层
传输层
应用层
2、传输层核心协议
1、TCP和UDP
1、TCP
面向连接
可靠传输
速度慢
头部大小大小20-60字节
主要应用文件传输、web浏览
2、UDP
无连接
不可靠传输
速度快
头部大小8字节
主要应用视频流、DNS查询
2、三次握手
1、客户端发送SYN(请求建立连接)
2、服务端返回SYN+ACK(确认并同意)
3、客户端发送ACK(确认)-> 连接建立
作用:确认双方收发能力正常、初始化序列号、防止旧连接混淆
3、四次挥手
1、客户端发送FIN(请求断开)
2、服务端ACK(确认)
3、服务端FIN(同意断开)
4、客户端ACK -> 完成关闭
作用:确保双方都能安全关闭,不丢数据
4、TCP可靠传输机制
1、序号确认与应答
1、每个字节都有一个序号
2、接收方收到数据后会返回一个确认应答号ACK=下一个期望接收的字节序号
3、发送方根据ACK来判断哪些数据已经成功到达
作用:保证数据不丢失、按序到达
2、超时重传
1、发送方在发送数据后启动一个计时器
2、如果在规定时间内没有收到ACK,认为数据丢失,重新发送该数据包
3、超时时间RTO是动态计算的:RTO≈RTT(往返时延)的加权平均+安全余量
作用:解决数据包丢失问题
3、快速重传
1、当接收方收到乱序数据,会重复发送同一个ACK
2、若当发送方连续收到三个相同ACK,立即触发重传(不等超时)
作用:比超时重传更高效
4、滑动窗口
1、TCP使用滑动窗口进行流量控制
2、窗口大小表示可以连续发送、但未被确认的最大字节数
3、接收方会根据自身缓存动态调整窗口大小
作用:解决发送方太快、接收方太慢问题
5、拥塞控制
1、慢启动
一开始逐步增加发送速率
2、拥塞避免
检测到网络变慢时减速
3、快重传
连续3个重复ACK即重传
4、快恢复
丢包后减半串口而不是重置
作用:防止网络过载导致大面积丢包
5、TCP粘包/拆包问题
1、粘包
一次性读到了多个消息
原因
发送方发送速度太快,多个小包合并在一起发送;接收方一次性读出多个包
2、拆包
一次只读到了一个消息的一部分
原因
单个包太大,超过TCP缓冲区;或应用层一次读取不完整
3、解决
1、固定长度协议
每个消息固定字节长度
适用场景
定长数据,如心跳包
2、特殊分隔符
在包尾加分隔符,如\n、#END#
适用场景
文本通信协议
3、长度字段
在包头写入消息长度
适用场景
最常见,如Netty、HTTP、MQTT
4、应用层协议
由上层协议定义边界
适用场景
HTTP、WebSocket自带边界
3、HTTP协议与应用层
1、HTTP发展
HTTP/1.0
每次请求建立连接
HTTP/1.1
长连接、管道化请求
HTTP/2.0
二进制分帧、多路复用
HTTP/3.0
基于QUIC(UDP),更快
2、HTTP常见状态码
1XX
信息(继续)
2XX
成功(200 OK,204 No Content)
3XX
重定向(301,302)
4XX
客户端错误
400,401,403,404
5XX
服务器错误
500,502,503
3、GET与POST的区别
1、GET
参数位置在URL里
不安全
幂等
可缓存
2、POST
参数在请求体里
相对安全
非幂等
不缓存
4、HTTP与HTTPS的区别
HTTPS=HTTP+SSL/TLS
提供加密传输、身份认证、数据完整性
适用443端口(HTTP为80)
5、Cookie、Session、Token区别
都是为了解决HTTP无状态问题
cookie
保存在客户端浏览器中
有状态(服务器端需配合)
浏览器自动携带cookie
明文存储,易被篡改
不支持跨域
简单信息存储(如主题、语言)
session
保存在服务端(SessionID存到cookie中)
有状态(服务端保存会话数据)
cookie中的SessionID用于找回服务端数据
较安全(存服务端)
不支持跨域
传统网站登录(单体应用)
token
保存在客户端(一般是localStorage或Header)
无状态(服务端不存状态)
每次请求携带Token由服务器验证签名
较安全(签名验证防篡改)
支持跨域(适合前后端分离)
分布式/移动端/前后端分离登录
4、网络安全
1、常见网络攻击
1、XSS跨站脚本注入
原理
攻击者在网页中注入恶意 JavaScript 代码,用户浏览时执行,导致用户信息被窃取或页面被篡改
防御
1、对输入内容做 HTML 转义
2、使用框架自带的防 XSS 过滤(如 Spring Security 的 XSSFilter)
3、使用 CSP(Content Security Policy) 限制外部脚本执行
2、CSRF跨站请求伪造
原理
攻击者诱导用户点击恶意链接,借助浏览器自动携带的 Cookie,在用户不知情的情况下执行操作(如转账、改密码)
防御
1、CSRF Token 校验(最有效
2、Referer 校验(判断来源站点)
3、关键操作要求 二次确认或验证码
3、SQL注入
原理
攻击者在输入参数中注入恶意 SQL,导致数据库被篡改或数据泄露
防御
1、使用 预编译语句(PreparedStatement)
2、校验输入内容(禁止特殊字符)
3、最小权限原则(数据库用户最小化)
4、DDoS分布式拒绝服务攻击
原理
攻击者利用大量肉鸡机器向服务器发起并发请求,耗尽系统资源,使正常用户无法访问
常见
1、SYN Flood(TCP 半连接耗尽)
2、HTTP Flood(高频访问页面)
3、UDP Flood / ICMP Flood(带宽耗尽)
防御
1、使用 CDN + WAF(Web 应用防火墙)
2、限流(Rate Limiting)、黑名单机制
3、扩容 / 负载均衡(横向扩展抗压)
2、常见优化手段
1、CDN加速
原理
把静态资源(图片、JS、CSS 等)分布到全球多个节点,就近访问、减少延迟
效果
1、减轻源站压力
2、提升用户访问速度
2、Keep-Alive长连接
原理
复用 TCP 连接,在同一个连接上发送多个 HTTP 请求,减少握手开销
HTTP/1.1默认开启
HTTP/2、HTTP/3原生支持多路复用,功能更强
3、压缩传输(gzip)
原理
在服务端压缩响应体,浏览器自动解压缩,减少传输体积
效果
压缩率可达 60%~80%,显著减少带宽占用
4、缓存(强制缓存+协商缓存)
1、浏览器缓存
1、强制缓存
2、协商缓存
2、服务端缓存
1、Redis、Memcached
2、页面缓存
3、CDN缓存
5、负载均衡
原理
通过算法(轮询、最少连接、权重等)把请求分配到多个服务器上
实现方式
1、Nginx / LVS / HAProxy
2、云服务(如阿里 SLB、AWS ELB)
5、面试题补充
1、TCP和UDP的区别?
连接性:TCP面向连接(需三次握手),UDP无连接
可靠性:TCP可靠(确认、重传、排序),UDP不可靠(尽最大努力交付)
传输方式:TCP面向字节流,UDP面向报文
效率:TCP慢(拥有拥塞控制),UDP快(无拥塞控制)
应用场景:TCP用于HTTP、FTP等,UDP用于DNS、视频通话等
2、TCP三次握手和四次挥手的过程?
三次握手(建立连接,全双工)
第一次握手
客户端发送SYN报文(seq=x),进入SYN_SENT状态,请求建立连接
第二次握手
服务端受到后,回复SYN+ACK报文(ack=x+1,seq=y),进入SYN_RCVD状态,确认客户端请求并同步自身序列号
第三次握手
客户端收到后,回复ACK报文(ack=y+1,seq=x+1),双方进入ESTABLISHED状态,建立连接完成
四次挥手(释放连接,全双工通道分别关闭)
第一次挥手
双方主动关闭发送FIN报文(seq=m),进入FIN_WAIT_1状态,告知对方不再发送数据
第二次挥手
被动关闭方收到通知后,回复ACK报文(ack=m+1),进入CLOSE_WAIT状态,确认受到关闭请求,此时仍可向主动方发送剩余数据
第三次挥手
被动关闭方数据发送完毕,发送FIN报文(seq=n),进入LAST_ACK状态,告知主动方数据已发完,可以关闭连接
第四次挥手
主动方收到通知后,回复ACK报文(ack=n+1),进入TIME_WAIT状态(等待2MSL),被动方收到后直接关闭连接;主动方等待结束后关闭连接
3、HTTP和HTTPS的区别?
安全性:HTTP明文传输,HTTPS加密(SSL/TLS)
端口:HTTP 80,HTTPS 443
证书:HTTPS需要CA证书,验证服务器身份
性能:HTTPS因加密解密略慢
应用:HTTPS用于敏感信息(如登录、支付)
4、GET和POST的区别?
用途:GET获取数据,POST提交数据
参数位置:GET在URL中,POST在请求体中
安全性:GET参数暴露,POST相对安全
长度限制:GET受URL长度限制,POST无明确限制
幂等性:GET幂等,POST不幂等
缓存:GET可缓存,POST一般不缓存
5、Session和Cookie的区别?
存储位置:Cookie客户端浏览器存储;Session服务端存储
安全性:Cookie存在客户端,易被窃取、篡改;Session仅传递SessionID,核心数据在服务端,安全性高
存储限制:Cookie单域名下有大小(通常4kb)和数量限制;Session无大小限制,仅受服务器资源约束
有效期:Cookie可设置长国企时间,关闭浏览器仍可保留;Session默认关闭浏览器失效,也可设置过期时间
跨域与依赖:Cookie支持跨域属性(domain属性);Session依赖Cookie存储SessionID,禁用Cookie时可通过URL重写传递,默认不支持跨域
性能开销:Cookie每次请求都携带在HTTP头中,增加带宽开销;Session仅传递SessionID,核心数据在服务端,插叙会占用服务端资源
6、DNS解析过程?
浏览器缓存查询:先查浏览器本地DNS缓存,有记录返回,无记录下一步
系统缓存查询:查询操作系统本地存储(hosts文件、系统DNS缓存),有记录返回,无记录向本地DNS服务器发起递归查询
本地DNS服务查询:先查自身缓存,有记录返回,无记录发起迭代查询
根DNS服务器:本地DNS向根服务器发起请求,根服务器返回顶级域名(TLD,如.com)服务器地址
顶级域名服务器:本地DNS向TLD服务器发起请求,返回对应域名的权威DNS服务器地址
权威DNS服务器:本地DNS向权威服务器发起请求,获取域名对应IP地址,缓存后返回给客户端
客户端拿到IP后,缓存完成,完成解析
7、从输入URL到页面显示发生了什么?
DNS解析:域名->IP
建立TCP连接:三次握手
发送HTTP请求:GET/POST
服务器处理:后端处理,返回HTTP响应
浏览器解析渲染
解析HTML->DOM树
解析CSS->CSSOM树
执行JS->可能修改DOM/CSSOM
构建渲染树、布局、绘制等
断开连接:四次挥手(HTTP/1.1 keep-alive可复用)
8、HTTP状态码的含义?
1xx:信息性(如100 Continue)
2xx:成功(200 OK,201 Created)
3xx:重定向(301永久,302临时,304未修改)
4xx:客户端错误(400 Bad Request,401未授权,403禁止,404 Not Found)
5xx:服务端错误(500内部错误,502 Bad Gateway,503 服务不可用)
9、TCP如何保证可靠性?
校验和:检测数据是否损坏
序列号:确保数据有序,解决乱序
确认应答:ACK机制,发送发需受到确认
超时重传:未收到ACK则重传
流量控制:华东窗口,防止接收方过载
拥塞控制:避免网络拥堵(慢启动、拥塞避免等)
10、HTTPS的加密原理?
混合加密
非对称加密(RSA/ECDHE)
交换对称密钥,保证密钥安全
对称加密(AES)
加密实际传输数据,高效
数字证书
CA签发,验证服务器身份,防止中间人攻击
SSL/TLS握手
协商加密套件、交换密钥、验证证书
11、TCP拥塞控制算法?
慢启动:指数增长拥塞窗口(cwnd),直到阈值
拥塞避免:线性增长cwnd,防止过载
快重传:收到三个重复ACK,立即重传丢失段,不等超时
快恢复:快重传后,将阈值设为当前cwnd的一半,cwnd设为阈值,进入拥塞避免
12、WebSocket与HTTP的区别?
协议
WebSocket是独立协议(ws/wss),HTTP是HTTP(http/https)
连接方式
HTTP短连接/半双工;WebSocket长连接/全双工
通信方向
HTTP由客户端发起;WebSocket双方可主动推送
实时性
WebSocket适合实时应用(聊天、游戏),HTTP需轮询
头部开销
WebSocket头部小,适合频繁通信
13、CDN的工作原理?
用户请求:向域名发起请求
DNS解析:DNS返回CDN的智能DNS服务器地址
调度:智能DNS根据用户IP、节点负载等返回最优边缘节点IP
边缘节点:弱缓存命中直接返回内容,未命中回源(向源站请求)并缓存
返回响应:内容从边缘节点交付给用户,提高速度与稳定性
CDN内容分发网络核心是分布式边缘节点缓存+全局负载均衡,让用户就近访问资源,降低访问延迟,减轻源站压力
14、如何排查网络连接问题?
本地检查:ping目标IP(测连通性)、telnet/nc(测端口)
路由追踪:traceroute/tracert看路径
抓包分析:tcpdump/Wireshark查看TCP握手、丢包、重传
查看连接状态:netstat/ss看端口监听、连接队列
检查DNS:nsloopup/dig验证解析是否正确
防火墙/安全组:确认是否拦截
15、网站访问慢的可能原因?
客户端:网络慢、DNS解析慢
网络层面:带宽不足、丢包、跨运营商延迟高
服务器:CPU/内存/磁盘IO高、连接数满、慢SQL
应用代码:代码效率低、未使用缓存
外部依赖:第三方API慢、CND失效
数据库:索引失效、锁竞争、慢查询
16、如何防御DDoS攻击?
流量清洗:使用云防护(如Cloudflare、阿里云高防)分流恶意流量
限流/降级:Nginx/网关限流、限制单IP请求频率
CDN:隐藏资源站IP,分散攻击流量
SYN Cookie:防SYN Flood攻击
黑洞路由:极端情况下将受攻击IP指向空路由
扩容/弹性:水平扩展资源应对突发流量
17、HTTPS为什么安全?
加密传输:对称加密保证数据不被窃听
身份验证:数字证书验证服务器身份,防止中间人伪装
数据完整性:通过MAC或AEAD保证数据在传输中未被篡改
安全握手:TLS握手过程中使用非对称加密安全交换密钥,避免密钥泄露
2、JAVA集合
集合部分接口的继承关系图
Collection
List
ArrayList
动态数组,默认容量为10,扩容机制1.5倍,插入删除慢O(n),随机访问快O(1),线程不安全
Vector
线程安全,扩容机制2倍,已过时
LinkedList
双向链表,随机访问慢O(n),插入删除快O(1),线程不安全
Set
HashSet
基于HashMap实现,无序,唯一,通过hashCode()+equals()判断重复
TreeSet
底层红黑树,元素有序(自然排序或自定义比较器),查询、插入、删除O(logn)
LinkedList
底层HashMap+双向链表,插入有序
Queue
阻塞队列
ArrayBlockingQueue
有界数组,一把锁,适用固定大小队列
LinkedBlockingQueue
可选有界链表,两把锁,适用高吞吐场景
PriorityBlockingQueue
无界优先级队列,适用任务调度
SynchronousQueue
不存储元素,适用直接传递
非阻塞队列
ConcurrentLinkedQueue
CAS实现得无界线程安全队列
PriorityQueue
基于堆得优先级队列
Map
HashMap
JDK1.7(数组+链表)
JDK 1.8+:数组 + 链表/红黑树(链表长度 ≥ 8 且容量 ≥ 64 时树化)
初始容量16,负载因子0.75,扩容机制容量翻倍,元素重新计算哈希值
key/value允许为空
线程不安全,性能快
HashTable
key/value不允许为空,线程安全,性能慢,已淘汰
TreeMap
底层红黑树,按key有序,可自定义Comparator
面试题补充
1、Collection和Collections有什么区别
Collection 是接口,Collections 是工具类
2、HashMap的扩容操作是怎么实现的
1. 触发时机:当元素数量超过容量×加载因子时触发,比如默认容量16,加载因子0.75,元素超过12个就扩容<br>2. 扩容过程:创建2倍大小的新数组(16→32),遍历旧数组所有元素重新分布,对于链表和红黑树分别处理<br>3. 性能优化:JDK1.8通过(hash & oldCap)快速判断元素新位置:结果为0:保持原位置不变,结果为1:新位置=原位置+旧容量
3、HashMap是怎么解决哈希冲突的(链地址法 + 红黑树)
HashMap 使用链地址法解决哈希冲突,相同哈希值的元素组成链表。JDK 1.8 引入了红黑树优化,当链表长度超过8且数组容量≥64时,链表转为红黑树,将查询性能从O(n)提升到O(log n);当节点数少于6时,再转回链表
4、能否使用任何类作为Map的key
理论上任何类都可以作为 Map 的 key,但要求它正确重写了 hashCode() 和 equals() 方法。因为 HashMap 在定位元素时先根据 hashCode() 确定桶位置,再通过 equals() 判断键是否相等。若使用可变对象或未重写这两个方法,可能导致无法正确查找或重复存储相同逻辑键。实际开发中常用 String、Integer 这类不可变类作为 key
5、HashMap与HashTable的区别
1、HashMap 线程不安全,HashTable 方法带有 synchronized 是线程安全的,但性能差<br>2、HashMap 允许 null 键和值,而 HashTable 不允许<br>3、HashMap 在 JDK 8 之后引入了红黑树优化,查询效率更高<br>4、HashTable 是过时类,建议使用 ConcurrentHashMap 替代
6、如何决定使用HashMap还是TreeMap
HashMap 基于哈希表实现,插入、查询效率更高,平均是 O(1),但是无序;TreeMap 基于红黑树实现,键是有序的,支持范围查询,但性能略低,O(log n),如果我不需要顺序、只关注性能,会用 HashMap;如果需要按照 key 排序,或者做区间查询(比如获取所有 key 小于某值的元素),就会用 TreeMap
7、ConcurrentHashMap和HashTable的区别
Hashtable 是通过在方法上加 synchronized 实现线程安全的,锁的粒度很大,所以并发性能低。ConcurrentHashMap 是在 JDK7 中引入的,它使用分段锁机制(Segment)或在 JDK8 后使用 CAS + synchronized 局部加锁,实现了高效的并发访问
8、HashMap和ConcurrentHashMap的区别
HashMap 是非线程安全的,在多线程环境下可能会造成数据不一致甚至死循环。<br>ConcurrentHashMap 是线程安全的 Map 实现,JDK7 采用分段锁机制(Segment),JDK8 改进为 CAS + synchronized 的局部加锁,提升了并发性能。<br>两者的底层结构类似,都是数组 + 链表 + 红黑树,但 ConcurrentHashMap 不允许使用 null 键或值
9、LinkedHashMap的应用
LinkedHashMap 是 HashMap 的子类,内部通过一个双向链表维护元素的插入顺序或访问顺序,它常用于需要保持插入顺序遍历的场景,以及实现 LRU 缓存
10、HashMap 扩容为什么容易死循环
JDK 1.7 中,HashMap 扩容使用 链表头插法。多线程同时向同一个桶插入元素时,扩容过程中链表节点可能形成循环引用,导致 CPU 自旋死循环。<br>JDK 1.8 改为 尾插法 + 红黑树,扩容时保持链表顺序,避免了死循环问题,但多线程仍然可能造成数据不一致。
11、fail-fast 机制
遍历时修改集合会抛出 ConcurrentModificationException
12、HashMap的加载因子为什么是0.75?
空间与时间的平衡:<br>- 加载因子小:空间利用率低,哈希冲突少<br>- 加载因子大:空间利用率高,哈希冲突多<br>0.75是数学计算和实验得出的最优值
13、如何优化HashMap性能?
1. 设置合适的初始容量,避免频繁扩容<br>2. 使用String、Integer等不可变对象作为key<br>3. 重写hashCode()和equals()方法<br>4. 在已知元素数量时,提前指定容量
14、HashMap实现原理
HashMap 是基于数组 + 链表/红黑树实现的哈希表。<br>put 操作时,先通过 hash 计算桶下标,再插入链表或红黑树。<br>当链表长度超过阈值时,JDK8 会转红黑树优化查找效率。当元素数量超过负载因子阈值时,会进行扩容,容量翻倍。<br>get 操作通过 hash 定位桶,然后遍历链表或红黑树查找,平均时间复杂度 O(1),最坏 O(log n)。<br>JDK7 使用链表头插法,扩容多线程可能死循环;JDK8 改为尾插法 + 红黑树,避免死循环,同时提高性能。
15、ConcurrentHashMap实现原理
ConcurrentHashMap 是线程安全、高并发的哈希表。<br>JDK7 使用 Segment 分段锁:每个 Segment 是一个小 HashMap,读操作无锁,写操作锁住 Segment,提高并发性能。<br>JDK8 改为 数组 + 链表/红黑树 + CAS + synchronized 局部锁:<br>读操作无锁(volatile 保证可见性),写操作先 CAS,失败才同步锁住桶,链表长度 ≥ 8 且容量 ≥ 64 → 红黑树优化,扩容采用多线程协助,链表拆分到新桶。<br>相比 JDK7,JDK8 并发性能更高,线程安全机制更细粒度,避免了分段锁的限制
16、为什么 HashMap 的容量必须是 2 的幂?
HashMap 的容量必须是 2 的幂,是为了配合 (n-1) & hash 位运算快速定位桶下标。这样可以保证哈希值均匀分布,减少冲突,同时扩容时只需简单调整链表位置,提高性能。
17、TreeSet 如何保证顺序?
TreeSet 底层是 红黑树,它是一种自平衡的二叉搜索树。插入元素时,TreeSet 会根据 自然顺序(Comparable) 或 自定义比较器(Comparator) 确定节点位置,并通过红黑树旋转和变色保持平衡。因为红黑树的中序遍历是 有序的,所以 TreeSet 遍历时可以保证元素顺序。同时,红黑树保证查找、插入、删除操作的时间复杂度为 O(log n)。
3、多线程与高并发
1、线程
1、创建线程的方式
1、继承Thread类创建线程
简单,可直接重写 run()
不支持多继承
2、实现Runnable接口创建线程
可复用线程任务
适合资源共享
3、通过Callable和Future创建线程
可返回结果,可抛异常
Future.get() 阻塞获取结果
4、线程池
线程复用,控制线程数量
2、线程的生命周期
新建(new thread)
新建状态,线程对象已创建,但未调用 start()
Thread t = new Thread()
就绪(runnable)
可运行状态,线程已调用 start(),等待 JVM 调度执行
t.start()
运行(running)
正在运行状态,线程获得 CPU 执行权
JVM 线程调度器选择线程执行
阻塞(blocked)
阻塞状态,等待获取对象锁
进入 synchronized 同步块但锁被占用
等待(waiting)
等待状态,线程无限期等待另一个线程显式唤醒
Object.wait()、Thread.join()、LockSupport.park()
超时等待(timed_waiting)
超时等待状态,线程等待时间到后自动唤醒
Thread.sleep(ms)、wait(ms)、join(ms)、LockSupport.parkNanos()
终止(terminated)
终止状态,线程执行完毕或异常结束
run() 方法执行结束
2、线程池
1、创建线程池的几种方式
1、newFixedThreadPool(固定数目线程的线程池)
2、newCachedThreadPool(可缓存线程的线程池)
3、newSingleThreadExecutor(单线程的线程池)
4、newScheduledThreadPool(定时及周期执行的线程池)
2、线程池的7大参数
1、corePoolSize
核心线程数
2、maximumPoolSize
最大线程数
3、keepAliveTime
空闲线程存活时间
4、unit
时间单位
5、workQueue
工作队列
6、threadFactory
线程工厂
7、handler
拒绝策略
3、线程池执行过程
1、提交任务,先判断核心线程数是否已满,未满则创建核心线程直接执行;<br>2、核心线程满时,将任务放入阻塞队列等待;<br>3、阻塞队列满时,尝试创建非核心线程执行任务;<br>4、超过最大线程数时,执行拒绝策略;<br>5、线程执行完成后,会循环从队列获取任务执行,非核心线程空闲超过 keepAliveTime 会被回收。
4、线程池的工作队列类型
1、LinkedBlockingQueue
无界队列
任务量不可控<br>
2、ArrayBlockingQueue
有界队列
任务量可控<br>
3、SynchronousQueue
不存储元素
直接传递<br>
4、PriorityBlockingQueue
优先级队列
任务优先级调度<br>
5、线程池的拒绝策略
1、AbortPolicy
抛出RejectedExecutionException(默认)
2、CallerRunsPolicy
调用者线程执行
3、DiscardPolicy
静默丢弃
4、DiscardOldestPolicy
丢弃队列中最老任务
3、死锁
1、互斥条件:资源独占
2、请求与保持:持有资源并请求新资源
3、不剥夺条件:资源不能被强制剥夺
4、循环等待:形成等待环路
4、并发工具类
1、CountDownLatch(计数器)
倒计时锁存器,线程等待计数完成
等待 N 个线程完成任务
2、CyclicBarrier(同步屏障)
可循环的屏障,线程到达屏障后继续
N 个线程同步开始下一阶段任务
3、Semphore(信号量)
控制同时访问资源的线程数量
限流、资源池控制
5、ThreadLocal
每个线程都有一个 ThreadLocalMap,线程对象 Thread 内部维护一个 ThreadLocal.ThreadLocalMap,线程通过 ThreadLocal 的 key 访问自己线程的副本 value
不存在多线程共享,天然线程安全
内存泄漏问题
ThreadLocalMap 的 key 为 弱引用,value 为强引用
如果线程长时间不回收或忘记 remove(),可能导致内存泄漏
应用
数据库连接管理
用户会话管理
事务管理
避免加锁
6、volatile
volatile 是 Java 提供的轻量级同步机制,用于 保证线程间的可见性和禁止指令重排
1、保证可见性
当一个线程修改 volatile 变量后,其他线程立即可见<br>
底层实现
CPU 缓存:线程对普通变量的修改可能先写入 工作内存(线程私有缓存)<br>volatile 写操作 → 立即刷新到主内存<br>volatile 读操作 → 从主内存获取最新值
2、禁止指令重排
volatile 会在 JMM(Java Memory Model)中形成 happens-before 规则
底层实现
通过 内存屏障(Memory Barrier / Fence) 实现<br>写屏障(StoreStore + StoreLoad)<br>读屏障(LoadLoad + LoadStore)
防止编译器和 CPU 对指令进行重排序
3、不保证原子性
volatile 对单次读写是原子操作
对复合操作(i++、count += 1)不是原子操作
需要结合 锁(synchronized/Lock)或原子类(AtomicInteger) 保证
4、应用
1、状态标记
控制线程终止(stop、running 等标志)
2、轻量级同步
保证单次读写的可见性,不涉及复合操作
3、双重检查锁(DCL)
用于单例模式、安全初始化对象
4、计数器/标志位
多线程共享的简单标志变量(非累加操作)
7、锁
1、核心思想分类
1、乐观锁 vs 悲观锁
悲观锁
认为自己在操作数据时,一定会有其他线程来修改数据,因此在获取数据的时候会先加锁
实现
synchronized关键字、Lock接口的实现类如ReentrantLock
适用场景
写多的场景,能保证数据安全
乐观锁
认为自己在操作数据的时候,不会有其他线程来修改数据,所以不会加锁,只是在更新数据的时候,通过某种机制(通常是版本号或者CAS)去判断数据在此期间有没有被其他线程修改
实现
java.util.concurrent.atomic包下的原子类使用的是CAS操作
适用场景
读多写少,冲突少的场景
2、公平锁 vs 非公平锁
公平锁
多个线程按照申请锁的顺序来获取锁,线程直接进入一个队列中排队,队列中的第一个线程才能获得锁。
优点
等待锁的线程不会饿死
缺点
整体吞吐效率相对非公平锁较低,因为要维护一个队列,唤醒队列中的线程有开销
非公平锁
多个线程加锁时直接尝试获取锁,获取不到才会进入等待队列。如果能直接获取到,就不需要排队
优点
可以减少唤起线程的开销,整体的吞吐效率高
缺点
等待队列中的线程可能会饿死,或者等很久才获得锁
对比
ReentrantLock 默认是非公平锁,可以通过构造器设置为公平锁。synchronized 是非公平锁
3、可重入锁(递归锁)
同一个线程在外层方法获取锁之后,再进入该线程的内层方法会自动获取锁(前提是锁对象是同一个对象),不会因为之前已经获取过还没释放而阻塞
优点
避免死锁,简化并发编程
实现
ReentrantLock 和 synchronized 都是可重入锁
4、独占锁(排他锁) vs 共享锁
独占锁
一次只能被一个线程所持有。ReentrantLock 和 synchronized 都是独占锁
共享锁
锁可以被多个线程共同持有。最典型的应用就是 ReadWriteLock(读写锁),它的读锁是共享的,允许多个线程同时读;写锁是独占的
2、synchronized
1、使用方式
1、修饰实例方法:锁是当前实例对象 (this)。<br>
2、修饰静态方法:锁是当前类的 Class 对象。<br>
3、修饰代码块:可以指定锁对象,灵活性更高
2、底层原理
JVM 层面实现,通过 monitorenter 和 monitorexit 字节码指令来实现。锁信息存储在 Java 对象头中的 Mark Word 里
3、锁升级过程
为了在性能和开销之间取得平衡,synchronized 锁有四种状态,并且会从低到高进行升级,升级过程不可逆
1、无锁状态:一个新创建的对象
2、偏向锁:适用于只有一个线程访问同步块的场景。Mark Word 里会记录这个线程的 ID。以后该线程再来加锁时,无需任何 CAS 操作,直接进入,开销极小
3、轻量级锁:当有第二个线程尝试获取锁时,偏向锁就会升级为轻量级锁。线程会通过 CAS 操作 来竞争锁(自旋)。适用于线程交替执行同步块,追求响应时间的场景
4、重量级锁:当轻量级锁自旋超过一定次数(或一个线程在自旋,第三个线程又来竞争),锁就会升级为重量级锁。此时,未获取到锁的线程会被阻塞,进入等待队列,需要操作系统进行线程调度和上下文切换,开销最大
3、java.util.concurrent.locks.Lock 接口
这是一个显式锁,提供了比 synchronized 更丰富的功能
核心实现
ReentrantLock(可重入锁)
核心方法
lock(): 获取锁。<br>unlock(): 释放锁。<br>tryLock(): 尝试获取锁,立即返回成功或失败。<br>tryLock(long time, TimeUnit unit): 在超时时间内尝试获取锁。<br>lockInterruptibly(): 获取锁,但可以响应中断
4、读写锁(ReadWriteLock)
核心实现
ReentrantReadWriteLock
规则
读共享,写互斥,读写互斥
应用场景
非常适合 读多写少 的场景,能极大提升性能
8、AQS
1、AQS 是一个用于构建锁和同步器的框架。它使用了一个 FIFO 双向队列(CLH 锁的变体)来管理获取资源失败的线程,并提供了一个原子式的 int 类型状态量(state)来表示同步状态。
2、核心思想
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中
3、核心原理
1、状态(state)
这是一个使用 volatile 修饰的 int 变量,是 AQS 的灵魂
不同的锁通过不同的方式解释这个 state:<br>ReentrantLock: state 表示锁被重入的次数。state=0 表示锁空闲,state=1 表示被一个线程获取,state>1 表示被同一个线程重入了多次。<br>ReentrantReadWriteLock: state 的高 16 位表示读锁的共享计数,低 16 位表示写锁的重入计数。<br>Semaphore: state 表示当前可用的许可证数量。<br>CountDownLatch: state 表示计数器当前的值。
2、CLH同步队列
这是一个虚拟的双向 FIFO 队列。如果线程获取资源失败,AQS 会为当前线程和等待信息构建一个 Node 节点,并将其加入队列尾部
Node 节点 主要包含:<br>线程引用:等待锁的线程。<br>等待状态 (waitStatus):如 CANCELLED(取消)、SIGNAL(需要唤醒后继节点)、CONDITION(在条件队列中)等。<br>前驱 (prev) 和后继 (next) 指针:用于构建队列。
3、模板方法模式
AQS 是一个抽象类,它定义了获取和释放资源的核心流程(骨架),但将具体的资源获取和释放的逻辑以 protected 方法的形式留给子类去实现
子类需要重写的方法:<br>tryAcquire(int arg): 独占式获取同步状态。<br>tryRelease(int arg): 独占式释放同步状态。<br>tryAcquireShared(int arg): 共享式获取同步状态。<br>tryReleaseShared(int arg): 共享式释放同步状态。<br>isHeldExclusively(): 当前同步器是否被当前线程独占。
4、工作流程
1、获取锁(lock() -> acquire(int arg))
1、tryAcquire(arg)
1、子类(如 ReentrantLock 的 Sync)实现此方法,尝试通过 CAS 操作去修改 state。<br>2、如果成功(state 从 0 变为 1),则将当前线程设置为独占线程,并返回 true,流程结束。<br>3、如果失败(锁已被占用),进入步骤2。
2、addWaiter(Node.EXCLUSIVE)
将当前线程包装成一个 Node 节点(模式为 EXCLUSIVE 独占),并快速地用 CAS 操作将其添加到队列尾部。
3、acquireQueued(final Node node, int arg)
1、这是一个自旋过程。节点进入队列后,会不断观察自己的前驱节点。<br>2、如果前驱节点是头节点(head),说明自己是队列中的第一个等待线程,它会再次调用 tryAcquire(arg) 尝试获取锁(因为持有锁的线程可能刚好释放了)。<br>3、如果获取成功,则将当前节点设置为新的头节点,原头节点出队。<br>4、如果获取失败 或 前驱节点不是头节点,则会根据前驱节点的 waitStatus 来决定是否要阻塞(LockSupport.park())当前线程。线程被阻塞后,等待被唤醒。
2、释放锁(unlock() -> release(int arg))
1、tryRelease(arg)
1、子类实现此方法,通常是减少 state 的值。对于 ReentrantLock,如果 state 减到 0,则表示锁完全释放,将独占线程置为 null。<br>2、如果释放成功,进入步骤2
2、unparkSuccessor(Node node)
1、找到头节点(head)的后继节点(如果存在且状态正常),然后使用 LockSupport.unpark(thread) 唤醒该节点对应的线程。<br>2、被唤醒的线程会从之前在 acquireQueued 中的阻塞点 (park) 继续执行,然后再次尝试获取锁(tryAcquire)。
9、Java内存模型(JMM)
1、概念
JMM(Java Memory Model) 是 Java 虚拟机规范的一部分,定义了 Java 程序中线程如何访问共享内存,以及共享变量的可见性、原子性和有序性,是 解决多线程并发问题的理论基础
2、组成部分
1、主内存
Java 堆和方法区存储共享变量<br>所有线程共享
2、工作内存
每个线程私有的内存区域<br>线程操作共享变量,先从主内存复制到工作内存,修改后再刷新回主内存
3、线程间交互
通过 同步动作 保证可见性,如锁、volatile、final、线程启动和终止
3、三大特性
1、原子性
操作不可分割,要么成功要么失败
实现机制
单次读写操作原子,复合操作需锁或原子类
2、可见性
一个线程对共享变量修改对其他线程立即可见
实现机制
volatile、锁(synchronized/ReentrantLock)、线程启动/终止
3、有序性
程序执行顺序按照代码顺序
实现机制
编译器/CPU可能重排,volatile和锁可禁止重排
4、内存交互规则(Happens-Before原则)
Happens-Before:保证前一个操作的结果对后一个操作可见
常用规则:<br>1、程序顺序规则:同一个线程内,代码按顺序执行<br>2、监视器锁规则:解锁 Happens-Before 加锁<br>3、volatile 变量规则:写 Happens-Before 读<br>4、线程启动规则:Thread.start() Happens-Before 线程运行<br>5、线程终止规则:线程结束 Happens-Before 其他线程检测到结束<br>6、传递性:如果 A HB B 且 B HB C,则 A HB C
10、面试题补充
1、如何保证多线程环境下线程顺序执行
保证多线程顺序执行的核心思路是将并行转为串行或可控序列<br>1、Thread.join():简单但失去并行性<br>2、单线程线程池:生产环境推荐<br>3、CompletableFuture:函数式风格,适合异步任务链<br>4、CountDownLatch/CyclicBarrier:精确控制执行时机<br>5、阻塞队列:生产者消费者模式<br>6、Lock + Condition:最细粒度的控制
2、如何避免死锁
1、什么是死锁
死锁是指两个或多个线程在执行过程中,互相等待对方持有的资源而无法继续执行的状态
2、产生死锁的必要条件
1、互斥条件
每个资源只能被一个线程占用
2、请求与保持条件
线程已持有一个资源,又请求新的资源时不释放已占有资源
3、不可剥夺条件
资源不能被强制剥夺,只能由持有者主动释放
4、循环等待条件
存在一个线程等待环,例如 A 等 B,B 等 C,C 又等 A
3、如何避免
只要破坏其中任意一个条件,就可以避免死锁
保证加锁顺序一致
使用 tryLock 设置超时
减少嵌套锁
使用高级并发类(如 Concurrent 包)
通过工具如 jstack 定位排查
3、核心线程数会被回收吗?需要怎么设置
默认情况下,线程池的核心线程不会被回收,即使空闲也会一直存活。如果希望核心线程在空闲一定时间后也被销毁,可以调用allowCoreThreadTimeOut(true) 方法。这样核心线程在空闲超过 keepAliveTime 后也会被回收。一般在任务波动较大的场景下开启,以节省系统资源。
4、线程池被创建后里面有线程吗?没有的话如何预热?
线程池在创建后,默认是没有线程的,只有在第一次提交任务时才会创建线程。如果希望线程提前创建,可以使用 prestartCoreThread() 或 prestartAllCoreThreads() 方法来预热线程池。预热可以减少首次任务提交时的延迟,在高并发或低延迟系统中非常实用。
5、工作中单一的/固定的/可变的三种创建线程池的方法用哪个多?
一个都不用,只用自定义的
6、线程池提交execute和submit有什么区别
execute() 用于提交 Runnable 任务,没有返回值,也无法捕获异常;submit() 用于提交 Runnable 或 Callable 任务,会返回一个 Future,可通过它获取结果或异常;底层上,submit() 会把任务封装成 FutureTask,再调用 execute() 执行。一般需要获取任务执行结果或处理异常时使用 submit(),否则用 execute() 更轻量。
7、线程池怎么关闭
在线程池中,可以通过 shutdown() 或 shutdownNow() 来关闭线程池,shutdown() 是优雅关闭,不再接收新任务,但会执行完已提交的任务;shutdownNow() 是强制关闭,会中断正在执行的任务并清空队列。<br>实际开发中推荐先调用 shutdown(),再配合 awaitTermination() 等待线程池结束,超时后再调用 shutdownNow() 以确保资源释放。
8、sleep()、join()、yield()区别
sleep() 用于让当前线程暂停一段时间,不释放锁,可中断;<br>join() 用于让当前线程等待另一个线程执行完毕,可中断;<br>yield() 是一种让出 CPU 执行权的提示,不释放锁,不可中断,执行效果由调度器决定。
9、如何设置线程池的参数
1、核心线程数
CPU密集型任务:核心线程数 ≈ CPU 核心数
IO密集型任务:核心线程数 > CPU 核心数
2、最大线程数
根据任务特性和队列长度决定,队列满时才使用非核心线程
3、队列类型
无界队列(LinkedBlockingQueue):任务提交快 → 队列排队 → CPU 满负荷
有界队列(ArrayBlockingQueue):可防止任务无限增长
直接交付队列(SynchronousQueue):任务直接交给线程执行,无队列
4、keepAliveTime
控制非核心线程回收,可通过 allowCoreThreadTimeOut(true) 设置核心线程也可以回收
5、拒绝策略
推荐使用 CallerRunsPolicy 在高峰期降速,避免丢失任务,生产环境可自定义策略
10、synchronized和Lock的区别?
- 实现:JVM内置 vs JDK实现<br>- 使用:自动释放 vs 手动释放<br>- 功能:基础同步 vs 丰富功能(尝试锁、公平锁等)<br>- 性能:相当,不同场景各有优势
11、volatile的作用和原理?
作用:<br>- 保证可见性:修改立即对其他线程可见<br>- 禁止重排序:防止指令重排优化<br><br>原理:<br>- 内存屏障:写操作后插入StoreLoad屏障<br>- 缓存一致性:MESI协议保证缓存一致性
12、线程池的核心参数和工作原理?
核心参数:<br>- corePoolSize:核心线程数<br>- maximumPoolSize:最大线程数<br>- workQueue:工作队列<br>- handler:拒绝策略<br><br>工作原理:<br>1. 核心线程处理任务<br>2. 队列满时创建新线程(≤最大线程数)<br>3. 队列和线程都满时执行拒绝策略
13、ThreadLocal的原理和内存泄漏问题?
原理:<br>- 每个线程维护ThreadLocalMap<br>- key为ThreadLocal的弱引用<br>- value为强引用<br><br>内存泄漏:<br>- key被GC回收,value无法访问<br>- 解决方案:使用后调用remove()
14、AQS原理?
- state状态变量<br>- CLH队列管理等待线程<br>- 模板方法模式,子类实现tryAcquire/tryRelease<br>- ReentrantLock、Semaphore等基于AQS实现
4、JVM
1、JVM的类加载机制
1、类加载过程
1、加载
查找并加载类的二进制数据
2、验证
确保被加载的类的正确性(文件格式、元数据、字节码等)
3、准备
为类的静态变量分配内存,并将其初始化为默认值
4、解析
把类中的符号引用转换为直接引用
5、初始化
执行类构造器 <clinit>() 方法,为静态变量赋予真正的初始值
2、双亲委派模型
1、什么是双亲委派机制
当一个类加载器收到加载请求时,它首先不会自己去尝试加载,而是把这个请求委派给父类加载器去完成
2、为什么要用双亲委派机制
避免类的重复加载
保证Java核心API的安全性(防止核心类被篡改)
3、类加载器
BootstrapClassLoader(加载核心类库)
ExtensionClassLoader(加载扩展类库)
AppClassLoader(加载应用类)
自定义ClassLoader(自定义逻辑)
2、Java内存模型
1、线程私有
栈
存储栈帧,每个方法调用对应一个栈帧
栈帧包含:<br>局部变量表<br>操作数栈<br>动态链接<br>方法返回地址
本地方法栈
为Native方法服务
程序计数器
指向当前线程正在执行的字节码指令地址
2、线程共享
堆
存储内容:对象实例、数组
新生代
Eden区
两个Survivor区(S0, S1)
老年代
(JDK 8以后)永久代/元空间已移到堆外
方法区(元空间)
3、JVM调优
1、Java异常结构:Throwable
1、Error
1、虚拟机错误 VirtualMachineError
2、内存溢出 OutofMemoryError
1、堆溢出-java.lang.OutOfMemoryError: Java heap space
2、栈溢出-java.lang.OutOfMemoryError
3、栈溢出-java.lang.StackOverFlowError
4、元信息溢出-java.lang.OutOfMemeoryError: Metaspace
5、直接内存溢出-java.lang.OutOfMemoryError: Direct buffer memory
6、GC超限-java.lang.OutOfMemoryError: GC overload limit exceeded
3、线程死锁 ThreadDeath
2、Exception
1、运行时异常: RuntimeException
2、IO异常: IOException
3、SQL异常: SQLException
2、GC排查工具
1、jvisualvm
2、arthas
3、jconsole
4、MAT(Memory Analyzer)
3、jvm常见参数配置
1、堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和老年代的比值。如:为3,表示年轻代与老年代的比值为1:3.年轻代占整个年轻代和老年代的1/4
2、垃圾收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParallelOldGC:设置并行老年代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器<br><br>-XX:+UseParNewGC:年轻代使用ParNew
-XX:+UseG1GC:设置G1垃圾收集器
3、垃圾回收统计信息
-XX:+PrintGC:打印GC信息
-XX:+PrintGCDetail:打印GC详细信息
-XX:+PrintGCDateStamps:打印GC时间戳
-Xloggc:gc.log:GC日志输出文件
4、并行收集器设置
-XX:ParallelGCThreads=n:设置GC线程数
-XX:MaxGCPauseMillis=n:设置并行收集器最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比,公式为1/(1+n)
5、并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式,适用于单CPU情况
-XX:ParallelCMSThreads=n:设置CMS并发线程数
6、高级调优参数
-XX:+DisableExplicitGC:禁止System.gc()
-XX:+ExplicitGCInvokesConcurrent:System.gc()使用并发GC
-XX:softRefLRUPolicyMSPerMB=1000:软引用存活时间
-XX:MaxTenuringThreshold=15:对象晋升年龄阈值
4、垃圾回收
1、判断哪些对象已经死亡
1、引用计数法---无法解决循环引用问题
2、可达性分析算法
从GC Roots对象作为起点,向下搜索,走过的路径称为引用链
如果一个对象到GC Roots没有任何引用链,则判定为可回收
GC Roots包括
虚拟机栈中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI引用的对象
2、垃圾回收机制
1、标记清除
产生内存碎片
2、复制
新生代使用,将内存分为两块,每次使用一块
3、标记整理
老年代使用,标记后让所有存活对象向一端移动
4、分代收集
现代虚拟机采用,根据不同代的特点采用不同算法
3、Java中四种引用类型
1、强引用
2、软引用
内存不够时回收
3、弱引用
下次GC必回收
4、虚引用
用于跟踪对象被回收
4、垃圾回收器
1、新生代
Serial
复制算法,单线程,Stop The World,客户端模式
ParNew
复制算法,Serial的多线程版本,与CMS配合<br>
Parallel Scavenge
复制算法,吞吐量优先,后台运算<br>
2、老年代
Serial Old
标记-整理算法,Serial的老年代版本,客户端模式<br>
Paralled Old
标记-整理算法,Parallel Scavenge的老年代版本,吞吐量优先<br>
CMS(Concurrent Mark Sweep)
标记-清除算法,低延迟,并发收集,响应时间敏感<br>
3、整个堆
G1
分区收集,可预测的停顿时间,大内存,低延迟<br>
ZGC
着色指针,超低延迟(<10ms),超大堆内存<br>
5、面试题补充
1、哪些情况下对象会被垃圾回收机制处理掉
JVM 使用 可达性分析算法来判断对象是否可以被回收,从 GC Roots 出发进行搜索,如果一个对象到 GC Roots 之间没有任何引用链可达,则认为它是“垃圾对象”。常见的 GC Roots 包括:栈中引用的对象、静态变量、常量池引用、JNI 引用等。不可达对象会被标记并在两次标记后回收,如果对象在 finalize() 中重新被引用,可以“复活”一次,否则最终被 GC 清除。
2、哪些对象可以看作是GC Root
栈中引用的对象、静态变量、常量池引用、JNI 引用等
3、对象不可达,一定会被垃圾回收器回收吗
在 JVM 中,对象“不可达”意味着它不再被 GC Roots 引用,因此具备被 GC 回收的条件,但这并不代表一定会马上回收。<br>回收时机取决于:<br>GC 是否被触发(堆空间是否紧张);<br>对象是否在 finalize() 中复活;<br>当前使用的垃圾收集器策略。<br>所以,不可达对象只是“可回收”,不是“立即回收”。
4、描述从加载 class 到执行 main() 的全过程?
1、当我们执行 java MainClass 时,JVM 会首先通过类加载器读取字节码文件,并生成 Class 对象,把类信息加载到方法区;<br>2、接着进入链接阶段,包括验证字节码、准备静态变量、解析符号引用;<br>3、然后进行初始化阶段,执行父类 → 子类的静态变量赋值和静态代码块;<br>4、初始化完成后,JVM 在方法区找到 main 方法,为主线程创建栈帧,并通过执行引擎逐条执行字节码;<br>5、期间,堆、栈、PC 寄存器、本地方法栈和 GC 协同工作管理对象和线程;<br>6、main 方法执行完毕后,线程栈销毁,JVM 结束程序。
5、自定义类加载器的实现原理?
在 JVM 中,类的加载由类加载器完成,默认遵循双亲委派模型。如果我们需要按特定方式加载类,比如从网络、加密文件或自定义路径中加载,就可以通过继承 ClassLoader 并重写 findClass() 方法来实现自定义类加载器。实现时,findClass() 会先读取类的字节码,然后调用 defineClass() 把字节数组转换为 Class 对象。这种机制常用于热部署、插件化框架和版本隔离等场景。
6、解释下逃逸分析和栈上分配?
逃逸分析是 JVM JIT(即时编译器)在运行时对对象引用范围进行分析的技术,用于判断一个对象是否会被外部方法或线程访问。
如果逃逸分析发现某个对象不会被外部访问,那么 JVM 会不在堆中创建对象,而是直接在栈上分配内存。
7、G1 垃圾回收器的原理?
G1 垃圾回收器是一种面向服务端的低延迟、高吞吐量的 GC。它将整个堆划分为多个大小相等的 Region,不再严格区分年轻代和老年代。GC 会优先回收“垃圾最多”的 Region,这也是它名字的由来——Garbage First。
G1 的回收过程包括年轻代 GC、并发标记、混合回收和整理阶段。它基于标记-整理算法,避免了内存碎片,并且可以通过 MaxGCPauseMillis 参数控制停顿时间。相比 CMS,G1 支持并行、并发、可预测停顿和压缩整理,是 JDK9 以后的默认回收器。
8、JVM 调优参数有哪些?
常用 JVM 调优参数包括内存设置(-Xms/-Xmx)、GC 选择(-XX:+UseG1GC)、性能优化(-XX:+DoEscapeAnalysis)和诊断日志(-XX:+PrintGCDetails、-XX:+HeapDumpOnOutOfMemoryError)。<br>实际调优时重点是合理分配堆内存、控制 GC 停顿时间、开启必要日志和监控
9、线上 OOM 如何排查?
线上出现 OOM 时,首先查看日志确认是哪种类型的 OOM。然后通过 JVM 参数 -XX:+HeapDumpOnOutOfMemoryError 获取堆快照,使用 MAT 或 VisualVM 分析对象分布,定位大对象或泄漏链。同时结合 jstack 分析线程情况,排查死循环或资源未释放的问题。常见原因包括缓存过大、线程过多、类加载器泄漏或直接内存未释放。最后根据原因优化代码、限制缓存、释放资源或调整 JVM 内存参数。
10、JVM 内存泄漏常见原因有哪些?
Java 内存泄漏的本质是对象不再使用但仍被引用,导致 GC 无法回收。<br>常见原因包括:静态集合持有引用、ThreadLocal 未清理、缓存无限增长、监听器未注销、资源未关闭、类加载器或定时任务泄漏等。<br>排查时通常通过 -XX:+HeapDumpOnOutOfMemoryError 导出堆快照,用 MAT 或 VisualVM 分析引用链,定位无法回收的对象并优化代码逻辑。
11、Minor GC 与 Full GC 区别?
Minor GC 只回收新生代(Eden、Survivor),触发频繁、耗时短,采用复制算法;<br>Full GC 回收整个堆(新生代 + 老年代 + 元空间),耗时长,对系统性能影响大,通常在老年代空间不足或调用 System.gc() 时触发。<br>因此,我们在调优时通常重点是减少 Full GC 的发生频率,以保证系统吞吐量和响应时间。
12、对象在堆中的创建过程?
首先,JVM 会检查类是否已被加载(未加载则触发类加载);<br>然后在堆中为对象分配内存(采用指针碰撞或空闲列表);<br>接着对内存空间初始化为零值,保证成员变量有默认值;<br>随后设置对象头,包括哈希码、锁信息和类型指针;<br>最后调用构造方法 <init>() 进行初始化,返回对象引用。<br>通常对象在堆中创建,引用存储在栈中。
如果堆内存规整(无碎片),JVM 采用指针碰撞方式,只需移动指针即可完成内存分配,速度非常快,常用于复制算法或压缩后的堆,如 Serial、ParNew GC。<br>如果堆内存存在碎片(空间不连续),则采用空闲列表方式,通过维护空闲块链表寻找合适的空间,常用于 CMS 这样的标记-清除 GC。<br>前者效率高,后者更灵活,但开销稍大。
13、栈和堆的区别是什么?
栈是线程私有的,用于存储方法调用和局部变量,分配快,自动释放。堆是线程共享的,用于存储对象实例和数组,由 GC 管理,生命周期较长。栈中存的是对象引用,堆中存的是对象本身。
14、JVM 内存模型结构是怎样的?
方法区/Metaspace:存放类信息、常量、静态变量,线程共享。<br>堆:存放对象实例和数组,线程共享,由 GC 管理。<br>栈:线程私有,用于方法调用和局部变量,每个方法对应一个栈帧。<br>本地方法栈:线程私有,执行 native 方法。<br>程序计数器:线程私有,记录当前字节码执行位置。
15、CPU百分百如何排查
定位高CPU进程:top(Linux)或任务管理器,找到占用高的Java进程PID
查看线程CPU占用:top -Hp <pid>或ps -mp <pid> -o THREAD,tid,time,找到高CPU的线程ID(十进制)
转换线程ID为十六进制:printf "%x\n" <pid>
导出线程栈:jstack <pid> stack.log,或用kill -3 <pid>
查找对应线程栈:在栈日志中搜索十六进制线程ID,分析线程执行的方法
常见原因:死循环、频繁GC(GC线程高)、正则表达式回溯、序列化/反序列化热点、资源竞争导致自旋等
16、内存爆满如何排查
检查内存使用:free -m或top查看系统内存;jstack -gc <pid>查看JVM堆内存使用情况
分析GC情况:jstat -gcutil <pid> 1000观察老年代占用、Full GC次数以及耗时。频发Full GC且内存无法释放说明内存泄露或配置不足
生成堆Dump:jmap -dump:live,format=b,file=heap.hprof <pid>(注意可能触发GC)
分析Dump:使用MAT、Visual VM或JProfiler,定位大对象、泄露对象(如集合未释放、ThreadLocal未清理、缓存无边界等)
检查非堆内存:jstat -gc <pid>看Metaspace、CodeCache;jcmd <pid> VM.native_memory(需开启NMT)检查Native内存
常见原因:堆内存配置过小、内存泄漏、大对象频繁进入老年代、Metaspace泄露、直接内存(Direct Buffer)未释放
17、Java获取内存Dump的几种方式
jmap:jmap -dump:live,format=b,file=heap.hprof <pid>(live参数先触发Full GC,只转存活对象)
jcmd:jcmd <pid> GC.heap_dump /path/to/heap.hprof(推荐,执行时影响较小)
自动生成:JVM启动参数:-XX:+HeapDumpOnOutOfMemoryError - XX:HeapDumpPath=/Path,在OOM时自动生成
Visual VM/JConsole:通过图形界面连接,执行“堆 Dump”操作
arthas:heapdump /path/to/heap.hprof (Alibaba诊断工具)
JMX:通过MBean com.sun.management:type=HotSpotDiagnostic调用dumpHeap方法
18、能否对JVM进行调优,让其几乎不发生Full GC
理论上可以尽量降低Full GC频率,但完全消除通常很难。可以通过一下方式极大减少甚至避免Full GC
选择合适GC回收器:G1、ZGC、Shenandoah等可并发处理,避免Stop-The-World的Full GC(但极端情况仍可能发生)
合理设置堆大小:避免老年代过早填满。给老年代足够空间容纳长期存活对象
减少大对象分配:大对象直接进入老年代,若频繁分配会快速填满老年代。优化对象大小,使用对象池
调整晋升阈值:-XX:MaxTenuringThreshold让对象在Survivor区多停留几次,减少过早晋升
控制元空间大小:避免类加载器过多导致Metaspace Full GC
使用并发回收器:G1可配置-XX:MaxGCPauseMillis等,让GC尽量并行发生
注意:Full GC仍然可能因Concurrent Mode Failure、元空间扩容、System.gc()、大对象分配失败等原因触发,但通过精细调优可使其几乎不可发生
19、对于大内存的GC如何进行优化
选择合适的GC算法
堆>32G且对延迟敏感->ZGC(低延迟)、Shenandoah
堆8G-32G,延迟可接受毫秒级->G1(默认目标停顿200ms),可调
堆>64G,吞吐优先->Parallel GC(但会STW较久)
避免Full GC:通过ZGC/Shenandoah的并发处理,尽量减少或消除Full GC
优化分配对象
使用TLAB优化线程本地分配
减少大对象分配(大对象直接进入老年代)
使用对象池或复用对象,减少GC压力
调整GC参数
G1:-XX:MaxGCPauseMillis设置合理停顿时间;-XX:G1HeapRegionSize调整Region大小;-XX:InitiatingHeapOccupancyPercent控制并发标记启动阈值<br>
监控与日志
开启GC日志(-Xlog:gc*),分析GC事件,调整参数以平衡吞吐与延迟
硬件配合
大内存机器建议使用大内存页、高带宽内存,避免Swap
20、递归方法造成的栈内存的溢出
原因
递归调用深度过大,每次调用在栈帧分配局部变量,超出JVM栈容量(默认Linux 64位 1M)
表现:抛出StackOverflowError
排查方法
查看异常堆栈,定位递归方法
检查递归终止条件是否正确
分析递归深度,估算栈帧大小(局部变量、参数、返回地址)
解决方案
优化递归逻辑
改写为迭代(循环)算法,避免深度递归
尾递归优化
Java不原生支持尾递归消除,但可手动将尾递归改为循环
增加栈容量
-Xss参数调整堆栈大小(如-Xss2m),但治标不治本,且可能增加内存开销
使用堆模拟栈
将递归改为用显式栈(如Deque)在堆上模拟,避免栈溢出
注意
递归过深也可能是因为数据结构(如树、图)的遍历未正确处理,需检查算法设计
5、MySQL
1、索引
1、为什么要使用索引
提高查询效率
索引类似书的目录,可以快速定位数据位置,减少全表扫描
加快排序和分组
ORDER BY、GROUP BY可以利用索引加速
优化聚合查询
COUNT、MAX、MIN等可以适用索引直接获取
保证唯一性
UNIQUE索引约束数据唯一性
2、索引创建的原则
1、主键优先
主键列默认创建聚簇索引
2、高频查询字段
WHERE、JOIN、ORDERBY、GROUP BY中常用的字段
3、选择性高的字段
选择性=唯一值数量/总记录数,高选择性更适合做索引
4、避免过多索引
索引过多会降低写入性能
5、小字段优先
小字段索引占用空间少,查询效率高
3、索引的数据结构
1、B-tree
B 树是一种自平衡多路搜索树,叶子节点可以存储数据,支持范围查询
2、B+tree
B+ 树是 B 树的优化,叶子节点存数据,非叶子节点只存索引,便于范围扫描,是 InnoDB 默认索引结构
3、Hash
哈希索引通过哈希函数定位,查询精确值非常快,但不支持范围查询,常用于 MEMORY 引擎
4、bitmap(位图索引)
位图索引使用位向量记录列值出现情况,适合低基数列,位运算效率高,但写入成本高
4、密集索引(聚簇索引)与稀疏索引的区别
密集索引(聚簇索引)
数据按主键顺序存储,叶子节点就是数据
查询效率高
主键即聚簇索引
数据和索引同一颗 B+ 树
稀疏索引
索引叶子节点只存主键值,数据存放在其他地方
查询效率相对慢,需要二次定位
次级索引是稀疏索引
索引和数据分开,需要回表
5、联合索引与最左匹配原则
一个索引包含多个列,例如 (a, b, c)
查询条件必须从索引最左边的列开始连续使用,才能利用索引。
例:<br><br>索引 (a,b,c)<br><br>WHERE a → 利用<br><br>WHERE a AND b → 利用<br><br>WHERE b → 不利用<br><br>WHERE a AND c → 只利用 a
LIKE、OR、函数操作等可能导致索引失效
6、myisam与innodb
MyISAM 不支持事务和聚簇索引,表级锁,适合读多写少
InnoDB 支持事务、行级锁、主键聚簇索引,支持外键,适合高并发和写操作
2、数据库锁分类
按锁粒度划分
行锁
锁单条记录
InnoDB 默认行锁
并发高,开销大
页锁
锁一页数据(如 8KB)
一些 DBMS 支持
并发中等,冲突较多
表锁
锁整个表
MyISAM 默认锁、写入操作
开销小,但并发低
分区锁
锁表的一部分分区
适用分区表
并发较高
按锁级别划分
共享锁
多个事务可同时读,但不能写
用于 SELECT ... LOCK IN SHARE MODE
排他锁
事务独占资源,禁止读写
用于 UPDATE、DELETE、INSERT
按锁方式划分
悲观锁
默认加锁,假设并发冲突会发生
保证安全,但开销大
乐观锁
事务执行不加锁,通过版本号或时间戳检测冲突
并发高,适合读多写少场景
按使用方式划分
显示锁
开发者手动加锁
通过 SQL 指定,例如 SELECT ... FOR UPDATE
隐式锁
数据库自动加锁
DML 操作自动加锁,InnoDB 默认行锁
3、事务的四大特性
1、原子性Atomicity
事务是最小执行单位,要么全部执行成功,要么全部不执行
转账操作:从账户A扣款,到账户B,如果中间失败,全都回滚
2、一致性Consistency
事务执行前后,数据库必须保持一致状态
账户总金额不变,余额不能出现负值
3、隔离性Isolation
并发事务之间互不干扰
事务A未提交,事务B看不到A的修改
4、持久性Durability
事务提交后,数据永久保存在数据库中
系统崩溃后,已提交的转账记录依然存在
4、MVCC机制
MVCC(多版本并发控制)是一种通过为数据生成多个版本来实现并发访问的机制。在 InnoDB 中,每行数据有创建和删除版本号,事务通过版本号判断数据可见性。MVCC 支持快照读,即 SELECT 默认不加锁,读取历史版本数据,提高读操作并发性能,同时避免脏读和不可重复读;对于写操作(UPDATE/DELETE/SELECT ... FOR UPDATE)仍使用行级锁保证数据一致性。MVCC 结合隔离级别,可实现可重复读,并配合间隙锁避免幻读
5、当前读与快照读
当前读
读取最新数据,通常会加行级锁(X-lock),用于修改操作(UPDATE、DELETE)或 SELECT … FOR UPDATE,保证读取的数据最新并防止写冲突。
快照读
通过 MVCC 实现,读取事务开始时的数据快照,不加锁,避免脏读和不可重复读,高并发性能好,默认 SELECT 使用快照读。
6、事务隔离级别与各级别的并发访问问题
1、事务的隔离级别
Read uncommitted(读未提交)
一个事务可以读取另一个事务未提交的数据(可能脏读)
Read committed(读已提交)
只能读取已提交事务的数据,避免脏读,但可能不可重复读
Repeatable read(可重复读)
事务多次读取同一数据结果一致,避免脏读和不可重复读,但可能出现幻读
Serializable(串行化)
串行执行事务,避免所有并发问题,最安全但性能最差
2、各级别下的并发访问问题
1、脏读
事务 A 读取了事务 B 未提交的数据<br>如果 B 回滚,A 读取的数据就是错误的
2、不可重复读
事务 A 两次读取同一行数据,结果不同<br>由于事务 B 提交了更新
3、幻读
事务 A 两次查询,满足条件的记录数不同<br>由于事务 B 插入或删除了满足条件的行
3、Innodb可重复读隔离级别下如何避免幻读
MVCC:通过多版本快照解决脏读和不可重复读
间隙锁(Gap Lock):解决幻读
行级锁(Row Lock):用于当前读的写操作
4、RC、RR级别下Innodb的非阻塞读如何实现
redo日志
7、MySQL主从复制的原理
1、主从复制的流程
MySQL 主从复制通常基于 二进制日志(binlog) 实现
主库(Master)流程
1、事务提交
数据修改操作提交到主库
2、写入 binlog
事务写入二进制日志(顺序记录增删改操作)
3、更新 relay log 请求
Master 会将 binlog 提供给从库
从库(Slave)流程
1、IO 线程读取 binlog
从库通过 IO 线程读取主库 binlog<br>写入本地 relay log(中继日志)
2、SQL 线程执行 relay log
将 relay log 中的 SQL 操作应用到从库数据
3、数据同步完成
从库数据最终与主库保持一致(异步复制)
2、主从复制数据丢失问题
1、异步复制数据丢失
主库提交事务后宕机,从库未及时同步
使用半同步复制(Semi-Sync Replication)
半同步复制是 MySQL 的一种主从复制模式,介于异步复制和全同步复制之间。
主库提交事务后,不必等待所有从库确认,只需等待至少一个从库确认收到 binlog 就返回客户端<br><br>从库确认写入 relay log 后通知主库
减少异步复制可能造成的数据丢失,同时避免全同步复制的性能瓶颈。
2、从库执行失败
Relay log 执行报错
主从重新同步或跳过错误
3、网络延迟
主库 binlog 发送慢,从库落后
增加从库数量、优化网络、监控延迟
4、主库崩溃未持久化 binlog
未开启 sync_binlog
开启 sync_binlog=1 保证 binlog 持久化
3、主从复制的使用场景
1、读写分离
主库负责写操作(INSERT/UPDATE/DELETE)<br>从库负责读操作(SELECT),提升查询性能
2、备份与容灾
从库作为主库的备份,主库宕机时可提升为新主库
3、数据分析/报表
从库用于离线分析、报表统计,减少对主库的压力
4、扩展高可用集群
多从库集群实现负载均衡和高可用<br>配合 MHA、Orchestrator 可实现自动主从切换
8、分库分表
1、分库
将数据按规则拆分到 多个数据库实例。<br>每个库可以包含一个或多个表。
常见策略<br>
按用户 ID(user_id % N)<br>
按时间(按月份、年份拆分数据库)
优点
单库数据量减小,提高写入并发
缺点
跨库事务复杂,SQL 查询复杂
2、分表
将单表拆分成 多个物理表,放在同一个库或不同库中。
1、垂直拆分
按列拆分,将表拆成多个子表,减小单表宽度
2、水平拆分
按行拆分,如 user_0, user_1, …
优点
单表行数减少,查询更快
缺点
分表规则固定,可能导致查询需要聚合多个表
3、实现方式
应用层路由
应用代码根据规则(如 user_id % N)决定访问哪个库/表
中间件路由
使用中间件(如 ShardingSphere、MyCat)透明路由,应用无需修改 SQL
代理层
数据库代理(如 Atlas、Cobar)负责路由,应用无感知
3、工具
mycat
类型
数据库中间件 / 代理层
特点
透明代理 SQL,支持水平分库分表、跨库 JOIN、读写分离
适用
MySQL 集群,兼容现有应用
sharingjdbc
类型
开源分库分表中间件
特点
支持分库分表、分布式事务(XA、柔性事务)、读写分离、弹性扩容
适用
Java 应用,微服务场景
9、定位并优化慢sql
1、如何打开慢日志
1、动态开启(MySQL 5.7+)
-- 打开慢查询日志<br>SET GLOBAL slow_query_log = 'ON';<br>-- 设置慢查询阈值(秒)<br>SET GLOBAL long_query_time = 1;<br>-- 设置记录未使用索引的查询<br>SET GLOBAL log_queries_not_using_indexes = 'ON';<br>-- 查看慢查询日志文件路径<br>SHOW VARIABLES LIKE 'slow_query_log_file';
2、修改配置文件
[mysqld]<br>slow_query_log = 1<br>slow_query_log_file = /var/log/mysql/slow.log<br>long_query_time = 1<br>log_queries_not_using_indexes = 1
修改后重启mysql
2、步骤
1、收集慢SQL
从慢查询日志中收集 SQL<br>使用 mysqldumpslow 或 pt-query-digest 汇总分析
2、分析 SQL 执行计划
type(访问类型,越靠前越好:ALL < index < ref < eq_ref < const)<br>possible_keys / key(使用了哪个索引)<br>rows(扫描的行数)<br>Extra(如 Using filesort、Using temporary)
explain调优技巧
type = ALL(全表扫描)
添加合适索引,优化 WHERE 条件
Using temporary
尽量避免大范围排序,考虑覆盖索引或改写查询
Using filesort
建立排序索引,避免额外排序操作
扫描行数过多 (rows)
加索引、拆分查询、限制返回行数
3、确认索引使用情况
是否走了索引?<br><br>是否出现全表扫描?<br><br>索引失效原因
4、分析慢 SQL 的瓶颈
CPU 密集?IO 密集?锁等待?<br><br>使用 SHOW PROCESSLIST; 或 SHOW ENGINE INNODB STATUS; 查看锁等待
3、索引失效的原因
1、函数操作
SELECT * FROM t WHERE DATE(created_at) = '2025-11-03';
索引列使用函数,导致索引失效<br><br>优化:使用范围查询 WHERE created_at >= '2025-11-03' AND created_at < '2025-11-04'
2、隐式类型转换
SELECT * FROM t WHERE id = '123'; -- id 是 INT
字符串类型与整型比对,索引失效<br><br>优化:保持类型一致
3、前缀模糊匹配
SELECT * FROM t WHERE name LIKE '%abc';
前缀 % 会导致索引失效<br><br>优化:考虑倒序索引或全文索引
4、组合索引顺序不当
复合索引 (a,b),查询条件 WHERE b=? AND a=?,会失效<br><br>优化:遵循最左前缀原则
5、OR条件跨列
SELECT * FROM t WHERE a=1 OR b=2;
单列索引无法命中<br><br>优化:使用 UNION 分别查询
6、数据量太小或选择性太低
MySQL 优化器可能选择全表扫描而不是索引<br><br>优化:考虑覆盖索引或拆分表
4、常用策略
1、索引优化
建立合适的单列或复合索引<br><br>使用覆盖索引(SELECT 查询字段包含索引字段)
2、SQL重写
避免 SELECT *<br><br>避免子查询,尽量使用 JOIN<br><br>拆分大查询或分页查询
3、分库分表
数据量过大,单表查询慢,可水平拆分
4、缓存
热点数据使用 Redis / Memcached<br><br>减少数据库访问压力
5、表结构优化
合理分区<br><br>减少 TEXT/BLOB 类型大字段查询
4、做过哪些索引相关的优化
1、单列索引:对高频查询的 user_id、order_id 列添加索引,减少全表扫描
2、复合索引:针对 WHERE user_id=? AND status=? 的查询建立联合索引,遵循最左前缀原则
3、覆盖索引:在查询字段全部包含在索引中,避免回表,如 SELECT user_id, status
4、索引列优化:避免函数操作、类型转换,保证索引生效,例如使用范围查询替代 DATE(created_at)
5、索引整理:删除冗余或低选择性索引,减少写入开销
6、前缀索引:对大文本列如 username 使用前缀索引,加速模糊查询
10、sql执行过程
1. SQL 解析(Parsing)
语法检查:检查 SQL 语法是否合法<br>语义分析:检查表、列、权限是否存在<br>生成解析树(Parse Tree)
2. 优化(Optimization)
查询重写:如视图展开、子查询优化<br>选择执行计划:<br> 判断是否使用索引<br> 选择最优 join 顺序<br> 生成 执行计划(Execution Plan)
3. 执行(Execution)
存储引擎层执行:<br> InnoDB:通过 Buffer Pool 读取数据页<br> 检查索引:使用 B+ 树/哈希索引定位数据<br> 事务控制:读写操作记录 Redo/Undo Log<br>锁机制:加行锁/表锁,保证事务隔离
4. 返回结果(Result)
将查询结果返回给客户端<br>对 DML 操作,提交事务后写入 Redo Log 和 Binlog
11、Redo Log、Redo Log、Binlog
1、Redo Log
Redo Log 是 InnoDB 存储引擎的一种事务日志,用于保证 事务的持久性(Durability)。<br>属于 物理日志,记录数据页的修改操作(物理页级别)。
2、Undo Log
Undo Log 用于 事务回滚 和 MVCC 多版本并发控制。<br>记录事务修改前的数据状态。
3、Binlog
Binlog 是 MySQL 全局事务日志,用于 主从复制 和 数据恢复。<br>属于 逻辑日志,记录 SQL 语句或事务操作。
11、面试题补充
1、MySQL的回表
回表是指在 MySQL 使用索引查询数据时,如果索引中不包含查询需要的所有列,就需要通过索引记录找到主键,再去聚簇索引中读取整行数据的操作。<br>
InnoDB 聚簇索引的主键索引叶子节点存储整行数据,辅助索引叶子节点只存索引列和主键列,所以使用辅助索引查询非索引列时会回表。<br>
回表会增加 I/O,降低查询性能,优化方法包括:使用覆盖索引、复合索引设计、避免 SELECT * 查询以及优化表结构等。
3、MySQL查询缓存有什么弊端,什么情况下使用,8.0版本堆查询缓存有什么变更
MySQL 查询缓存是服务端内存缓存,用于缓存 SELECT 查询结果,提高重复查询性能。但它存在以下弊端:<br>1、写操作会导致缓存失效,写多读少场景收益有限;<br>2、内部使用全局锁,增加锁竞争,高并发下性能下降;<br>3、只对完全相同的 SQL 生效,参数化或别名不同可能不命中;<br>4、大表频繁更新导致缓存命中率低。<br>5、查询缓存适合读多写少、SQL 稳定、结果集不大的场景。<br>在 MySQL 8.0 中,查询缓存功能被移除,推荐使用 应用层缓存或中间件缓存 来替代。
4、MySQL怎么恢复半个月之前的数据
1、前提
必须有备份或日志文件
确保恢复环境
2、方法
方法一:基于全量备份 + Binlog 回滚
1、找到全量备份<br>比如 2025-10-15 的备份文件 full_backup.sql
2、恢复全量备份<br>mysql -u root -p < full_backup.sql
3、使用 Binlog 回滚到指定时间<br>找到 mysql-bin.0000x 对应日志<br>使用 mysqlbinlog 提取指定时间之前的事务:<br>mysqlbinlog --stop-datetime="2025-10-15 23:59:59" /var/lib/mysql/mysql-bin.0000* | mysql -u root -p<br>
方法二:基于物理备份恢复(XtraBackup / InnoDB)
1、使用 XtraBackup 做的物理备份(半个月前)
2、将备份文件恢复到独立数据库实例
3、如果有 Binlog,可以继续按时间回放
方法三:从应用层或其他数据源恢复
如果数据库没有完整备份:<br>从 数据快照、存档、日志表、应用日志 回滚数据
建议平时建立 全量备份 + 增量备份 + Binlog 保存 的策略,并定期演练恢复流程
5、订单数据表越来越大导致查询缓慢,如何处理
当订单表越来越大导致查询缓慢,可以从以下几个方面优化:<br>1、索引优化:建立合理单列索引、复合索引和覆盖索引,避免全表扫描和回表;<br>2、SQL 优化:避免 SELECT *,大表分页使用范围查询,批量操作分批处理;<br>3、分库分表:水平拆分(按用户或订单号)、垂直拆分(大字段单独拆表)、必要时分库;<br>4、缓存优化:应用层缓存热点数据,物化视图或汇总表减少实时计算;<br>5、分区表:按时间或范围分区,减少查询扫描数据量。
6、一千万条数据的单表,如何进行分页查询
对于千万级单表,传统 LIMIT offset, size 分页性能差,因为 MySQL 需要扫描前 offset 行。优化方法包括:<br>1、基于索引的范围分页:利用索引列范围查询代替 OFFSET,例如 WHERE id > last_id LIMIT size;<br>2、Keyset 分页:用上一页最后一条记录作为下一页起点;<br>3、物化分页表或临时表:提前计算主键,分页只扫描索引或临时表;<br>4、分区表分页:按时间或逻辑分区,减少扫描数据量;<br>5、业务层优化:限制最大页数,或采用“加载更多”方式。
7、唯一索引比普通索引快吗,为什么
唯一索引和普通索引的查询性能几乎相同,因为都基于 B+ 树结构,查询路径一致。唯一索引唯一的区别是写入时需要额外进行一次唯一性检查,所以写入性能会略低于普通索引。因此唯一索引并不比普通索引快,甚至写入场景下会更慢。
8、索引是建立的越多越好吗
索引不是越多越好。索引可以加快查询,但是会增加写入开销、占用更多存储空间,也会让优化器难以选择最优执行计划,甚至造成执行计划错误。因此,索引应根据查询场景按需设计,优先给高选择性的查询条件、排序字段、JOIN 字段建立索引,同时避免对低选择性、高频更新的列建立索引。
9、大表如何进行优化
大表优化一般从结构、索引、SQL、架构、缓存几个层面进行。首先对表结构进行规范化处理,通过字段精简、冷热分离、垂直拆分、水平分表降低单表大小;其次优化索引,避免低选择性索引和多余索引;再通过 SQL 优化(如覆盖索引、分页优化、JOIN 优化)降低扫描量;对高频查询数据使用 Redis 缓存,对历史数据进行归档;架构上使用读写分离、分库分表来进一步扩容;最后还可以优化 InnoDB 参数,比如增大 Buffer Pool、开启压缩、优化 redo/undo log。这样可以全面提升大表的性能。
6、Redis
1、redis单线程模型
Redis 的核心网络 I/O 和命令执行是基于 单线程事件循环(EPOLL) 完成的
优点
避免多线程上下文切换开销
避免锁竞争
内部操作大部分是内存级的,速度够快
redis单线程不是整体单线程
RDB/AOF 后台持久化是多进程/多线程
Redis 6.0 后网络 I/O 采用多线程处理
2、redis6.0的多线程模型
Redis 6.0 引入多线程主要是 网络 I/O 读写多线程化,命令执行仍然是单线程
流程
1、多线程读取 socket 数据<br>2、主线程执行命令<br>3、多线程回写结果到客户端
3、redis的几种数据结构
1、字符串 String
缓存、计数器
2、哈希 hash
对象存储、哈希表
3、列表 list
队列、消息流
4、集合 set
唯一集合、标签
5、有序集合 zset
排行榜、延迟队列
6、三种特殊类型
1、geospatial
2、hyperloglog
3、bitmap
统计、地理位置、消息队列
4、持久化方式
1、RDB(快照)
在指定时间间隔生成内存快照
文件紧凑、恢复快
但可能丢失最近一次快照后的数据
2、AOF(日志追加)
记录每条写命令
更安全,丢失数据少
文件大,恢复慢,但可以 rewrite 压缩
无硬盘复制是什么
无硬盘复制是 Redis 在主从同步时 不再把 RDB 写到磁盘,而是主节点 直接把 RDB 数据通过网络发送给从节点。<br>这样可以避免磁盘 IO 的瓶颈,提高复制速度。
5、缓存雪崩、缓存穿透、缓存击穿
缓存雪崩
大量缓存同一时刻过期 → 引起数据库压力暴增
解决
给缓存加随机过期时间
使用多级缓存(本地 + Redis)
开启 Redis Cluster 或分片
缓存穿透
访问的数据 DB 和缓存都不存在 → 导致每次都打 DB
解决
使用 布隆过滤器
缓存空对象(但要设置短 TTL)
缓存击穿
单个热点 key 过期,瞬间大量并发访问
解决
热点 key 永不过期
使用互斥锁/SETNX 实现 互斥加载(加锁更新缓存)
6、集群方案
1、哨兵模式
工作原理
主负责写,从负责读
哨兵组件的功能
哨兵监控主节点存活并自动故障转移
哨兵的选举过程
主观下线判断 → 多哨兵协商客观下线 → 使用基于 Raft 的多数派投票选出 Leader 哨兵 → Leader 执行故障转移
2、redis cluster
工作原理
分区(slot 0~16383)自动分片
多主多从
优点
支持自动 failover
客户端直连 cluster,无需代理
cluster故障恢复是怎么做的
节点先判断 PFAIL,再由多数主节点达成 FAIL 共识;故障主的从节点通过选举获得多数票后升级为新主节点,接管槽位并恢复服务
7、主从复制
1、核心原理
1、从节点发送 psync<br>2、主节点发送 RDB 快照<br>3、主节点实时复制写命令到从节点
2、全量复制
全量复制是把整个 RDB 发给从节点
全量成本高
3、增量复制
增量复制是根据复制偏移量用 backlog 补齐缺失命令
增量轻量快速
优先增量,增量失败才走全量。
8、基于redis实现分布式锁
1、分布式锁框架Redission
Redisson 提供分布式锁、可重入、watchdog 自动续期等,使用非常简单
Redisson 的默认策略是“watchdog”:如果你在不指定过期时间的情况下调用 lock.lock(),客户端会自动续期直到 JVM 断开或显式 unlock(),更安全
2、redlock红锁
1、在 5 个独立 Redis 实例上同时尝试加锁(每次请求用相同 key 和不同实例)<br>2、取得过半(≥3)节点的锁且总耗时小于 TTL,视为加锁成功<br>3、释放时在所有节点上删除自己的锁(同样需校验 value)
提升对单点故障和分区的容忍性
Redis 官方作者和社区对 RedLock 的严格正确性(在网络极端分区下)有争议。RedLock 复杂,部署成本高,且并非在所有场景都必要。生产常见选择是:单实例 + 正确 TTL + Lua 解锁 + Redisson 或者在强一致性需求下使用 Zookeeper / etcd
3、redis与zk在分布式架构上的异同
Redis 强调速度和可用性,Zookeeper 强调强一致和协调性
高性能缓存或轻量级分布式锁 → Redis<br><br>分布式协调、Leader 选举、强一致配置 → Zookeeper
9、redis过期键的删除策略
1、定期删除
每隔 N 毫秒随机抽取一部分 key,检查是否过期<br>效率高,但抽取策略复杂
2、惰性删除
访问 key 时才进行过期判断 → 补充删除<br>优点:省 CPU<br>缺点:会积累大量过期 key
10、内存淘汰策略
volatile-lru
只删除已设置过期时间的 key,按 LRU
volatile-ttl
删除 TTL 最小的
volatile-random
随机删部分 TTL key
allKeys-lru
删除所有 key 按 LRU(最常用)
allKeys-random
随机删 key
allKeys-lfu
按访问频率 LFU 删除(Redis4.0+)
noeviction
不删除数据,写入报错(默认)
11、内存淘汰算法
1、LRU算法
删除最近最少使用的 key<br>Redis 实现是“近似 LRU”,不是严格 LRU
2、LFU算法
删除访问频率最低的 key<br>适合热点数据明显的场景<br>Redis 维护一个访问计数(8bit counter,衰减)
12、面试题补充
1、redis查询慢如何定位及解决
大 key 问题:<br>查询的数据量过大(String、List、Hash、ZSet 等)<br>例如 LRANGE mylist 0 -1 直接返回上万条数据
拆分 key,避免单个 key 数据量过大<br>使用 分页 / 范围查询,避免一次性拉全部数据
慢命令(O(n) 操作):<br>List、Set、ZSet 遍历/排序<br>Hash 大对象频繁操作
替换 O(n) 命令:如 HGETALL → HMGET 指定字段<br>使用 pipeline / Lua 脚本 批量处理
过期/淘汰导致阻塞:<br>大量过期 key 被定期删除<br>内存淘汰策略触发(LRU/LFU)
缓存与内存优化:<br>合理 TTL,避免大量过期 key 集中删除<br>配置合适的 maxmemory-policy,避免频繁内存淘汰<br>开启 AOF 重写 / RDB 异步持久化,避免阻塞主线程
网络延迟:<br>大量数据返回时,占用带宽<br>客户端阻塞或 pipeline 不合理
网络优化:<br>压缩大数据,减少传输量<br>使用 pipeline 或 multi-get,减少 RTT
3、redis单线程快的原因
避免锁和上下文切换
单线程执行命令,无需加锁<br>避免多线程竞争共享数据<br>避免线程切换带来的 CPU 开销
内存操作快速
数据全部驻内存,访问延迟微秒级<br>内部数据结构(String/Hash/List/Set/ZSet)优化良好,操作复杂度低
事件驱动(IO 多路复用)
使用 epoll/kqueue/select 实现非阻塞网络 IO<br>单线程顺序处理请求和响应,减少并发协调开销
原子操作保证
单线程天然保证命令顺序和原子性<br>内置操作(如 INCR、HSET)无需额外锁
4、内存耗尽后redis会发生什么
当 Redis 内存达到 maxmemory 时,如果策略是 noeviction,会拒绝写入;如果配置了淘汰策略,会按 LRU、LFU、TTL 等策略淘汰 key;同时大 key 或批量删除可能导致延迟抖动。应对方法包括合理配置 maxmemory、选择合适淘汰策略、优化数据结构、分片扩容以及业务降级。
5、redis怎么实现延时任务
Redis 延时任务通常用 有序集合 ZSET 存储任务,score 为任务执行时间戳,value 为任务信息;通过 轮询或定时扫描 ZSET 查找到期任务执行,并用 ZREM 删除;在 分布式环境 下结合 分布式锁 避免重复消费,高并发时可通过 分片队列、分页拉取、或推送到工作队列(List/Stream) 优化性能,同时可使用 Redisson 的 RDelayedQueue 封装延时逻辑。
6、redis如何进行限流
固定窗口
在固定时间窗口内计数,超过阈值拒绝请求
简单、易实现
窗口边界会出现突发流量(突发“临界点”溢出)
滑动窗口
通过时间戳精确统计请求数,更平滑
1、使用 ZSET:ZADD key timestamp<br>2、删除过期时间戳:ZREMRANGEBYSCORE key 0 now-window<br>3、统计数量:ZCARD key<br>4、超过阈值则拒绝请求
平滑限流,不会出现固定窗口突发问题
ZSET 增删操作稍重,极高并发需优化(pipeline/Lua)
令牌桶 / 漏桶算法
控制请求速率,可平滑流量
使用 计数器 + TTL 或 Lua 脚本实现原子性更新<br>可以存储令牌数量,定期恢复令牌
7、如何解决redis的大key问题
1、什么是大key
存储过多数据在单个 key(如 List、Hash、ZSet 超大集合)<br>未合理拆分业务对象(例如单个 Hash 存储百万用户数据)<br>使用不当的数据结构(List 用作队列但太长)
2、大key的影响
命令执行时间长:LRANGE mylist 0 -1 遍历所有元素<br>阻塞主线程:Redis 单线程执行,CPU 被占用<br>复制压力大:主从同步全量复制时大 key 拷贝耗时<br>AOF/RDB 持久化压力:持久化大 key 时会阻塞
3、如何找到大key
MEMORY USAGE key:查看单个 key 占用内存<br>DEBUG OBJECT key:查看内部编码、访问频率<br>SCAN + TYPE + MEMORY USAGE 批量检查
4、大key的治理
1、拆分 key<br>将大对象拆成多个小 key,例如 user:1000:friends:1,2,3...<br>避免单个 key 过大<br><br>2、分页操作<br>对 List/Set/ZSet 使用 LRANGE、ZRANGE 分页获取数据<br><br>3、优化数据结构<br>使用 ziplist/intset 压缩存储<br>Hash 分片存储大对象<br><br>4、批量操作优化<br>使用 pipeline 或 Lua 脚本批量处理,减少阻塞<br><br>5、过期策略或淘汰策略<br>给大 key 设置 TTL<br>配合 LRU/LFU 策略释放内存
8、redis的缓存命中率大概多少
Redis 缓存命中率通常在 90%–99% 左右,但实际取决于访问模式、TTL 设置、缓存容量和淘汰策略,通过合理 TTL、热点缓存、预热、LRU/LFU 策略和监控可以提高命中率
9、如何解决redis缓存与数据库的双写不一致问题
原因
1、写 DB 成功,但缓存未更新<br>查询时仍返回旧数据
2、写缓存成功,但写 DB 失败<br>缓存存在脏数据
3、并发写操作<br>多线程/多服务同时更新,导致顺序错乱
解决方案
1、缓存失效策略
先写数据库,再删除或更新缓存
write DB<br>DEL cache_key
删除缓存可能有短暂旧数据被读到,需要加重试或延迟策略
2、双删策略
删除缓存 → 写 DB → 等待短时间 → 再删除缓存<br>避免写 DB 后旧缓存被读取
3、异步更新/消息队列
写 DB 后将更新消息放入 MQ,由消费者更新缓存<br>保证顺序写入,削峰填谷<br>常用于高并发场景
4、事务或lua脚本保证原子性
在 Redis 内部或 DB + Redis Lua 脚本原子操作<br>降低并发脏数据风险
5、读写分离/缓存预热
定期刷新缓存<br>先读缓存,缓存未命中再读 DB,并异步写回缓存
7、Netty 4.X
1、Netty是什么
Netty 是一个基于 NIO(非阻塞 IO) 的 高性能、异步事件驱动网络通信框架,用于开发 高并发、高性能的 TCP/UDP 服务,封装了 Java 原生 NIO 的复杂性,提供可靠的网络编程抽象
2、核心架构
Channel
表示一个网络连接(如 Socket)
EventLoop
处理 I/O 事件的循环线程,负责读写事件、任务执行
ChannelPipeline
管道,按顺序处理 I/O 数据,通过 Handler 链处理数据
ChannelHandler
数据处理逻辑(Inbound / Outbound)
ByteBuf
高性能缓冲区,替代 ByteBuffer,支持池化和零拷贝
3、Netty的线程模型
1、核心组件
EventLoop
一个线程 + 一个任务队列 + 多个 Channel<br>用于处理注册到该 EventLoop 的所有 Channel 的 I/O 事件
EventLoopGroup
EventLoop 的集合<br>提供线程池管理
BossGroup
接收客户端连接,注册 Channel 到 WorkerGroup
WorkerGroup
处理读写事件、执行业务 Handler 逻辑
2、工作流程
1、客户端连接到服务器 → BossGroup 的某个 EventLoop 接收连接<br>2、创建 Channel,并注册到 WorkerGroup 的 EventLoop<br>3、WorkerGroup 的 EventLoop 循环处理:<br>读事件:触发 Pipeline 中的 InboundHandler<br>写事件:触发 Pipeline 中的 OutboundHandler<br>任务队列:处理提交到 EventLoop 的异步任务
3、单线程顺序执行
每个 Channel 的 I/O 都绑定到单个 EventLoop
保证线程安全:<br>Channel 内部操作无需加锁<br>Handler 内部逻辑在同一线程顺序执行
4、线程复用与高并发
一个 EventLoop 可管理多个 Channel → 减少线程开销<br>
通过多 EventLoop 分布负载多个 Channel → 高并发<br>
WorkerGroup 的线程数量通常设置为 CPU 核心数 × 2
4、面试题补充
1、Netty是什么
Netty 是一个基于 NIO(非阻塞 IO) 的高性能、异步事件驱动网络通信框架,封装了 Java 原生 NIO 的复杂性,支持 TCP/UDP 通信,广泛用于 RPC、消息队列、高性能 HTTP/Socket 服务。
2、Netty 与 NIO 的区别
Netty 封装了 NIO Selector、Buffer 等底层 API,提供 事件驱动模型、ChannelPipeline、ByteBuf、Future/Promise 等高级抽象,简化了网络编程,同时优化了性能和扩展性。
3、Netty的核心组件有哪些
Channel:表示一个连接<br>EventLoop:负责 I/O 事件处理<br>EventLoopGroup:EventLoop 线程池<br>ChannelPipeline:事件处理管道<br>ChannelHandler:业务处理逻辑<br>ByteBuf:高性能缓冲区,支持零拷贝与池化
4、Netty 的线程模型
Netty 使用 BossGroup + WorkerGroup:<br>BossGroup:接收连接<br>WorkerGroup:处理读写事件<br>每个 Channel 绑定 单个 EventLoop,顺序执行 I/O,保证线程安全,一个 EventLoop 可管理多个 Channel,减少线程切换和锁竞争,长耗时任务需异步 offload 避免阻塞 I/O
5、Pipeline 与 Handler
Pipeline:数据流向的处理链<br>InboundHandler:处理入站数据(读取、解码、业务)<br>OutboundHandler:处理出站数据(编码、发送)
特点:<br>链式顺序处理<br>可插拔 Handler,提高扩展性<br>长耗时任务异步处理,避免阻塞 I/O
6、ByteBuf 特性
高性能缓冲区,替代 ByteBuffer<br>支持 读写索引分离<br>零拷贝:slice、duplicate、composite<br>池化内存:减少对象创建,降低 GC 压力<br>支持堆内存和直接内存
7、Netty的高性能设计
1、异步非阻塞 I/O
2、线程模型优化
3、Pipeline + Handler 链式处理
4、ByteBuf 池化与零拷贝
5、网络传输优化
6、高并发与扩展性
8、常见优化手段
使用 ByteBuf 池化与零拷贝<br>Pipeline 拆分编码/解码与业务逻辑<br>Boss/Worker 数量合理配置(CPU 核心 × 2)<br>异步 offload 耗时任务<br>批量写入 / 异步 flush 提高吞吐<br>调整 TCP 参数优化延迟和连接处理
8、Spring
1、IOC
将对象的创建、依赖注入的控制权从应用程序代码反转到容器中
1、Bean管理
1、生命周期
1. 实例化(Instantiation)→ <br>2. 属性填充(Population)→ <br>3. BeanNameAware →4. BeanFactoryAware →5. ApplicationContextAware → <br>6. BeanPostProcessor前置处理 → <br>7. InitializingBean → <br>8. 自定义init-method → <br>9. BeanPostProcessor后置处理 → <br>10. 使用中 → <br>11. DisposableBean → <br>12. 自定义destroy-method
2、作用域
1、singleton
单例,容器中唯一
需要保证线程安全
2、prototype
原型,每次获取新实例
3、request
每次HTTP请求新实例
4、session
每次会话新实例
5、application
ServletContext生命周期
需要保证线程安全<br>
3、依赖注入三种方式
1、构造器注入
2、Setter注入
3、字段注入
4、自动装配模式
1、no
默认,不自动装配
2、byName
按属性名匹配Bean名
3、byType
按属性类型匹配
4、constructor
构造器参数按类型匹配
2、源码分析
Bean创建流程关键方法
1、AbstractApplicationContext.refresh():容器刷新入口
2、AbstractApplicationContext.finishBeanFactoryInitialization():初始化单例Bean
3、DefaultListableBeanFactory.preInstantiateSingletons():预实例化单例
4、AbstractBeanFactory.getBean():获取Bean入口
5、AbstractAutowireCapableBeanFactory.createBean():创建Bean实例
3、Spring如何解决循环依赖
1、通过三级缓存机制解决Setter和字段注入的循环依赖
2、一级缓存存放完整Bean,二级缓存存放早期暴露Bean,三级缓存存放单例工厂
3、在Bean实例化后、属性填充前,将早期引用暴露到三级缓存
4、构造器循环依赖无法解决,因为实例化阶段就需要完整依赖
3、AOP
1、核心概念
1、切面(Aspect)
横切关注点的模块化(如日志、事务)
2、连接点(Joinpoint)
程序执行点(方法执行、异常处理)
3、通知(Advice)
切面在连接点执行的动作
4、切点(Pointcut)
匹配连接点的表达式
切点表达式语法
execution表达式
execution(修饰符? 返回类型 声明类型? 方法名(参数) 异常?)
execution(* com.example.service.*.*(..)) // service包下所有方法<br>execution(* com.example.service.UserService.*(..)) // UserService所有方法<br>execution(* save*(..)) // 所有save开头的方法
within表达式
within(com.example.service.*) // service包下所有类<br>within(com.example.service.UserService) // 指定类
5、引入(Introduction)
为类添加新接口
6、织入(Weaving)
将切面应用到目标对象的过程
2、五种通知类型
1、Before
方法执行前
参数校验、权限检查
2、AfterReturning
方法正常返回后
日志记录、结果处理
3、AfterThrowing
方法抛出异常后
异常处理、错误日志
4、After (Finally)
方法执行后(无论成败)
资源清理
5、Around
包围目标方法
性能监控、事务管理
3、实现原理
1、JDK动态代理
基于接口实现(反射 + Proxy.newProxyInstance())
生成接口的代理类
性能较好,但需要接口
2、CGLIB代理
基于类继承实现(继承 + ASM 字节码增强)
生成子类代理
不需要接口,但final类/方法无法代理
4、源码分析
代理创建流程
1、AbstractAutoProxyCreator.wrapIfNecessary():判断是否需要代理
2、AbstractAutoProxyCreator.createProxy():创建代理
3、DefaultAopProxyFactory.createAopProxy():选择JDK或CGLIB代理
4、JdkDynamicAopProxy/CglibAopProxy:具体代理实现
4、事务
1、事务传播行为
1、REQUIRED
支持当前事务,不存在则新建
2、REQUIRES_NEW
新建事务,挂起当前事务
3、NESTED
嵌套事务,支持部分回滚
4、SUPPORTS
支持当前事务,不存在则以非事务运行
5、NOT_SUPPORTED
以非事务运行,挂起当前事务
6、NEVER
以非事务运行,存在事务则异常
7、MANDATORY
必须存在事务,否则异常
2、源码分析
事务拦截器
1、TransactionInterceptor:事务方法拦截
2、TransactionAspectSupport.invokeWithinTransaction():事务处理核心
3、PlatformTransactionManager:事务管理器抽象
5、设计模式应用
1、工厂模式
BeanFactory、ApplicationContext
IOC容器本质
2、单例模式
Bean的singleton作用域
线程安全问题
3、代理模式
AOP动态代理
代理实现原理<br>
4、模板模式
JdbcTemplate、TransactionTemplate
代码复用设计<br>
5、观察者模式
事件驱动机制
解耦设计思想<br>
6、SpringMVC
执行过程
1、客户端发起请求
浏览器或客户端发送 HTTP 请求到应用服务器
请求 URL 由 DispatcherServlet 接收
2、DispatcherServlet(前端控制器)
接收请求。根据 URL 找到对应的 Handler(Controller 方法)
3、HandlerMapping(处理器映射器)
根据请求 URL 映射到对应 Controller
4、HandlerAdapter(处理器适配器)
调用 Handler(Controller)方法
5、Controller(控制器)
接收请求参数,调用业务逻辑,返回 ModelAndView
6、ViewResolver(视图解析器)
将 ModelAndView 中的逻辑视图名解析成实际视图(JSP、Thymeleaf、JSON 等),返回具体 View 对象
7、View 渲染
将模型数据(Model)渲染到视图(HTML、JSON 等)
8、响应返回客户端
DispatcherServlet 将渲染好的视图返回给客户端
7、SpringBoot
1、核心价值
1、简化配置
告别繁琐的XML配置,约定优于配置
2、快速启动
内嵌Web容器,一键启动Web应用
3、自动装配
根据依赖自动配置Spring组件
4、生产就绪
提供监控、健康检查等生产级特性
子主题
@Configuration
配置类
@EnableAutoConfiguration
启用自动配置
@ComponentScan
组件扫描
3、自动装配原理
条件化装配 + Starter依赖 + 配置属性
1、扫描所有jar包的META-INF/spring.factories文件,加载其中注册的自动配置类
2、对这些配置类进行条件判断,比如检查类路径是否存在特定类、容器中是否已有相关Bean等
3、只有满足所有条件的配置类才会生效,创建对应的Bean并加入Spring容器
举例
当我们引入spring-boot-starter-web依赖时,Spring Boot检测到Servlet相关类在类路径下,就会自动配置DispatcherServlet、视图解析器等Web组件,让我们直接拥有Web MVC能力
9、Mybatis
1、核心定位
1、简化jdbc操作
封装重复的JDBC代码
2、SQL与代码分离
SQL写在XML中,与Java代码解耦
3、结果集自动映射
自动将ResultSet映射为Java对象
4、灵活的参数传递
支持多种参数传递方式
2、核心机制
1、配置文件
mybatis-config.xml
环境配置(environments)
数据源(DataSource)
事务管理器(TransactionManager)
映射器(Mappers)
Mapper.xml
namespace
select/insert/update/delete
参数映射、结果映射
2、核心对象
SqlSessionFactoryBuilder
构造器工厂
SqlSessionFactory
会话工厂,生产 SqlSession
SqlSession
执行 SQL 的会话对象,类似 JDBC Connection
Executor
执行器,负责 SQL 执行
MappedStatement
封装 Mapper.xml 的 SQL 信息
Configuration
全局配置对象
3、动态SQL
<if>、<choose>、<when>、<otherwise>。<br><br><where>、<set>、<trim>。<br><br><foreach>
4、参数映射
#{}:预编译,占位符,防止 SQL 注入<br>
${}:直接拼接,可能有安全风险
5、结果映射
resultType:直接映射 Java 对象
resultMap:复杂字段映射(关联、嵌套)
一对一、一对多映射
3、进阶机制
1、缓存机制
一级缓存
SqlSession级别,默认开启
二级缓存
Mapper/namespace级别,需要手动开启,实体类需要实现Serializable
2、插件机制
MyBatis允许对四大对象进行拦截
Executor
StatementHandler
ParameterHandler
ResultSetHandler
应用
分页插件、SQL日志插件、审计插件
3、延迟加载
一对一/一对多关系映射时,可以设置懒加载
只有真正访问关联对象时才触发查询
4、事务管理
默认使用JDBC事务管理器
支持与Spring事务管理器整合
4、源码分析
1、SqlSession构建
Resources → XMLConfigBuilder → Configuration<br>→ SqlSessionFactoryBuilder → DefaultSqlSessionFactory → DefaultSqlSession
配置加载:解析mybatis-config.xml和所有Mapper.xml文件<br>配置对象构建:创建Configuration对象,包含所有配置信息<br>工厂创建:通过SqlSessionFactoryBuilder创建SqlSessionFactory
2、执行sql
<br>
sqlSession.getMapper() → MapperRegistry → MapperProxyFactory → MapperProxy <br>→ 调用方法 → MapperMethod → MappedStatement → <br>Executor(CachingExecutor → BaseExecutor → Simple/Reuse/BatchExecutor)→ StatementHandler → ParameterHandler → JDBC → ResultSetHandler → Java 对象返回<br>
会话创建:从工厂获取SqlSession(包含数据库连接和事务)<br>代理获取:通过动态代理获取Mapper接口实例<br>方法转发:代理对象将方法调用转为SqlSession的方法调用<br>执行器处理:Executor先检查两级缓存,未命中则查询数据库<br>语句执行:StatementHandler创建PreparedStatement,ParameterHandler设置参数<br>结果映射:ResultSetHandler将ResultSet转换为Java对象<br>资源清理:关闭SqlSession释放连接
10、MQ
1、为什么用消息队列
1、解耦
生产者和消费者不需要知道彼此的存在
2、异步
生产者发送消息后无需等待处理
3、削峰填谷
应对流量高峰,避免系统崩溃
2、消息队列的两种模式
1、点对点
消息被一个消费者消费后即被移除
2、发布-订阅
消息被多个消费者消费
3、常见消息队列对比
1、Kafka
高吞吐、分布式、持久化,适用于大数据领域
2、RabbitMQ
基于AMQP协议,消息可靠,支持多种消息模式
3、RocketMQ
阿里开源,分布式,高吞吐,适用于金融领域
2、Kafka
1、基本概念
1、topic
消息的主题,可以理解为消息的分类
2、partition
Topic的分区,每个分区是一个有序的日志序列
3、offset
消息在分区中的唯一标识,消费者通过Offset来追踪消费位置
4、broker
Kafka服务器实例,多个Broker组成集群
5、producer
向Topic发送消息的客户端
6、consumer
从Topic拉取消息的客户端
7、consumer group
多个Consumer组成一个组,共同消费一个Topic,每个分区只能被组内的一个消费者消费
2、架构
Producer -> Broker -> Consumer
一个典型的Kafka集群包含多个Broker,一个Topic有多个Partition,每个Partition可以有多个副本(Replica),分布在不同的Broker上
3、zookeeper作用
1、集群管理(Broker 的注册与存活检测)
2、Controller 选举
3、Topic、分区、副本等元数据管理
4、分区 Leader 选举
5、Consumer Group 的位移管理(早期版本< 0.9 版本)
4、生产者producer
1、作用
负责将消息发送到 Kafka 集群中的指定 Topic
2、核心功能
1、消息发送
Producer 将数据写入 Kafka 的 Topic,每个 Topic 又分为多个 Partition。发送时,Producer 会决定消息落在哪个 Partition(分区)
2、分区策略
1、轮询
平均分配到各分区,保证负载均衡
2、按key分区(hash)
根据消息的 key 计算 hash,保证相同 key 的消息落在同一个分区,维持有序性
3、自定义分区器
用户可以自己实现逻辑来选择分区
3、消息路由
生产者只需知道 Broker 集群的元数据,就能把消息路由到正确的分区 Leader
4、消息确认机制(acks参数)
acks=0:不等待 Broker 确认,最快,但可能丢消息
acks=1:等待 Leader 确认即可,性能和可靠性折中
acks=all/-1:Leader 和所有 ISR 副本确认才算成功,最安全但延迟较高
5、消息重试与幂等性
Producer 可以配置 重试次数,在发送失败时自动重试
幂等性 Producer(开启 enable.idempotence=true)能确保 不重复写入,避免因重试导致消息重复
6、批量发送
Producer 会将多条消息打包成批次,提高网络和磁盘效率
支持 压缩算法(gzip、snappy、lz4、zstd),降低带宽和存储开销
3、发送模式
1、同步发送
调用 send().get(),阻塞等待 Kafka 返回结果
简单,但性能差
2、异步发送
调用 send(),返回 Future,通过回调函数获取结果
高吞吐、低延迟,实际生产中常用
4、工作流程
1、Producer 从 Zookeeper(老版本)或 Broker(新版本)获取 元数据(Topic 分区信息)
2、选择分区(根据 key 或策略)
3、将消息加入缓冲区(可能会压缩、合并批次)
4、通过网络请求发送给对应分区的 Leader Broker
5、Leader 写入日志文件,并根据 acks 决定是否等待副本确认
6、Leader 返回确认,Producer 收到结果
5、消费者consumer
1、作用
1、从 Kafka Topic 的分区(Partition) 中读取消息
2、支持分布式消费和高并发处理
3、保障消息的有序性、可靠性
2、核心功能
1、Consumer Group(消费者组)
多个 Consumer 可以组成一个组(Group)
Kafka 会把一个 Topic 的分区分配给组内的不同消费者,一个分区只能被组内一个 Consumer 消费
同组内消费者:分工合作,保证每条消息只被消费一次
不同组之间:相互独立,每个组都能消费完整的 Topic 数据
2、分区分配策略
1、Range分配
按分区范围分配(可能不均衡)
2、RoundRobin分配
轮询分配,更均衡
3、Sticky分配
在尽量均衡的同时,减少分配变化,避免抖动
3、位移(Offset)管理
Offset 是 Consumer 消费进度的标记
早期版本:offset 存在 Zookeeper
现在(>=0.9):offset 存在 Kafka 内部的 __consumer_offsets 主题
自动提交
简单,但可能出现“消费到但没处理就提交 offset”的问题
手动提交
灵活,常用在生产环境
4、消费模式
1、点对点
同组的消费者共享消息
2、发布-订阅
不同组的消费者都能收到完整消息
3、消费流程
1、Consumer 启动,向 Kafka 集群请求 分区分配信息
2、根据 Consumer Group 协调器(Group Coordinator)分配到的分区,开始消费数据
3、读取消息时,Consumer 会不断拉取(pull)Leader Broker 中的数据
4、消费完成后,提交 offset(提交到 __consumer_offsets 主题)
5、如果 Consumer 宕机,新的 Consumer 会接管分区,从上次提交的 offset 继续消费
4、消费模型特性
1、消息顺序性
Kafka 只保证 分区内的消息有序
如果要全局有序,只能让一个分区对应一个消费者
2、再均衡
当 Consumer Group 成员发生变化(加入/退出/宕机),Kafka 会触发分区重新分配
Rebalance 期间,消费者会暂停消费 → 分区重新分配 → 恢复消费
3、容错与高可用
如果一个 Consumer 挂了,Kafka 会把它的分区交给组内其他 Consumer 继续消费
6、存储机制
Topic → Partition → Segment(日志段)
每个分区是一个有序、不可变的追加日志
7、高级特性
1、高性能
1、顺序写
Kafka 的日志文件是 append-only,避免随机写,顺序写性能极高
2、Page Cache(操作系统缓存)
Kafka 不自己管理缓存,而是依赖 OS 的 Page Cache。写入时写到 Page Cache,再刷盘(fsync)
3、零拷贝
Kafka 使用 sendfile 系统调用,直接将 Page Cache 数据发送到 socket,不需要复制到用户态
减少了 用户态 ↔ 内核态 切换,提升吞吐量
4、批量存储
Producer 将多条消息合并成 batch,一次写入,减少磁盘 I/O
5、压缩
Producer 可启用压缩(Gzip、Snappy、LZ4、Zstd)
批量压缩 + 批量解压,降低存储和传输开销
2、副本机制
每个分区有 1 个 Leader + N-1 个 Follower
Leader:负责读写请求
Follower:从 Leader 拉取数据(异步复制)
3、ISR(In-Sync Replica,同步副本集合)
ISR = 与 Leader 保持同步的副本集合
Producer 的 acks=all 时,必须等 ISR 全部确认才算成功
只有 ISR 中的副本才能参与 Leader 选举
4、选举流程
1、Controller 监控集群中所有 Broker
2、Leader 宕机 → Controller 通知 ISR 中的一个副本成为新的 Leader
3、其他 Follower 改为从新 Leader 拉取数据
如果 ISR 为空(极端情况),Kafka 可以配置 unclean.leader.election.enable
true:允许非 ISR 副本当 Leader(可能丢数据,但保证可用性)
false(默认):只允许 ISR 副本当 Leader(保证数据一致性)
8、消息可靠性保证
1、生产端
acks=all
retries 重试
enable.idempotence=true(幂等性)
2、broker端
副本数 replication.factor ≥ 3
min.insync.replicas 设置(确保至少有几个副本确认写入才成功)
3、消费端
手动提交 offset,避免消费后未处理就提交
3、RabbitMQ
1、基本概念
1、broker
服务端
2、producer
生产者
3、consumer
消费者
4、queue
队列,存储消息
5、exchange
交换机,决定消息如何路由
1、direct
精确匹配 routing key
2、tocpic
支持通配符 *(一个词)、#(多个词)
3、fanout
广播消息到所有绑定的队列,不管 routing key
4、headers
基于消息头属性匹配,使用少
6、binding
绑定规则,队列和交换机之间的关系
7、routing key
路由键,消息路由的关键字段
2、工作模式
1、Simple模式(简单队列)
2、Work Queue模式(工作队列)
3、Publish/Subscribe模式(发布订阅)
4、Routing模式(路由模式)
5、Topics模式(主题模式)
6、RPC模式(远程过程调用)
3、消息可靠性保障
1、生产者确认机制
1、两种模式
RabbitMQ 提供了两种确保消息成功投递到 Broker 的机制
1、事务模式
类似于数据库事务,通过 txSelect、txCommit、txRollback 实现,但性能较差,因为会同步阻塞
2、Confirm 模式
异步确认机制,生产者发送消息后,Broker 会异步返回确认信号(ack)或否定信号(nack)
2、工作流程
生产者开启 Confirm 模式,发送消息后继续处理其他任务,当收到 Broker 的 ack 表示消息已持久化到磁盘,nack 则表示需要重试
3、批量确认
支持批量消息的确认,提高性能,但某条消息失败时需要整个批次重发
2、消息持久化
1、交换机持久化
服务器重启后交换机依然存在,否则交换机会丢失
2、队列持久化
服务器重启后队列及其元数据仍然存在,但队列中的消息还需要单独持久化
3、消息持久化
通过设置消息的 delivery_mode 为 2,确保消息被写入磁盘
4、性能问题
持久化会显著影响性能,因为涉及磁盘 I/O,需要根据业务重要性进行权衡
5、注意事项
即使消息持久化,在写入磁盘前仍然有极短时间窗口可能丢失消息(页缓存未刷盘)
3、消费者确认机制
1、自动确认
消息发送给消费者后立即被认为已成功消费,风险是如果消费者处理失败,消息就丢失了
2、手动确认
消费者处理完业务逻辑后显式发送 ack,Broker 才会删除消息;如果处理失败可以发送 nack 让消息重新入队
3、批量确认
可以一次性确认多个消息,提高性能,但中间某个消息失败会影响整个批次
4、重试机制
通过 nack 和 requeue 参数控制消息是否重新放回队列,但要防止无限重试
5、死信队列(DLX)
当消息被拒绝且不重新入队,或达到最大重试次数时,可以转入死信队列进行特殊处理
4、高级特性
1、TTL(Time-To-Live)过期时间
1、队列级别 TTL
队列内所有消息的统一过期时间
2、消息级别 TTL
每条消息单独设置过期时间
3、应用场景
验证码过期、限时优惠、临时会话等有时效性要求的业务
结合 DLX 可以实现 延迟队列
2、死信队列(DLX)
1、死信
无法被正常消费的消息,包括被拒绝的消息、过期的消息、队列满时被丢弃的消息
2、死信交换机
专门处理死信的交换机,可以绑定到普通队列上
3、触发条件
消息被消费者拒绝且不重新入队(reject/ nack + requeue=false)
消息在队列中存活时间超过 TTL
队列达到最大长度限制,新消息无法进入
3、延迟队列
基于 TTL + DLX:消息过期后路由到死信队列,实现延迟
插件方式:rabbitmq-delayed-message-exchange,可直接设置延迟时间
4、优先级队列
高优先级的消息优先被消费,适用于VIP服务或紧急任务处理
5、惰性队列
消息直接存储到磁盘,减少内存压力,适合大容量低吞吐场景
4、RocketMQ
1、简介
1、定位
阿里巴巴开源的分布式消息中间件,低延迟、高并发、高可用
2、特点
支持事务消息、顺序消息、消息轨迹、海量消息堆积能力
3、应用场景
电商交易、金融支付、大数据处理、物联网数据采集
2、基本概念
1、NameServer
类似注册中心,存储 Broker 元数据
2、Broker
消息存储和服务节点
3、Producer
生产者,发送消息到 Broker
4、Consumer
消费者,订阅并消费消息
5、Topic
消息的逻辑分类
6、Queue
Topic 下的物理队列,用于并行消费
7、Message Key / Tag / DelayLevel
Key:消息唯一标识<br>
Tag:消息类型或标签,用于消费端过滤<br>
DelayLevel:延迟消息等级
3、核心机制
1、消息发送模式
1、同步发送
等待 Broker 返回确认,可靠性高
2、异步发送
回调通知发送结果,性能高
3、单向发送
不等待确认,吞吐最高,但可能丢消息
2、消息消费模式
1、push模式
Broker 推送消息到消费者
2、pull模式
消费者主动拉取消息
3、顺序消费
保证同一个 Queue 消息顺序
4、并发消费
不同 Queue 并行消费,提高吞吐
3、消息可靠性机制
1、消息存储
CommitLog + ConsumeQueue + IndexFile
2、重试机制
Consumer 消费失败 → 重新投递
延迟重试队列
3、消息幂等性
消费端实现幂等(避免重复消费)
Message Key 去重
4、消息顺序性保证
1、全局顺序
保证同一个 Topic 的消息严格顺序(单 Queue)
2、分区顺序
按某个 Key 哈希到同一个 Queue,实现顺序消费
5、延迟消息/定时消息
Broker 内置延迟等级(DelayLevel)
消息发送时设置 delayLevel
6、NameServer元数据管理
存储 Broker 地址和 Topic 元数据
Producer/Consumer 通过 NameServer 获取路由信息
NameServer 无状态,可水平扩展
4、高级特性
1、集群与高可用
1、主从同步
Master:主节点,提供读写
Slave:从节点,异步同步主节点数据
2、同步/异步复制
异步复制 → 高吞吐,但可能丢消息
同步复制 → 可靠,但延迟高
2、顺序消息实现
队列级别顺序消费
消费者按 Queue 分配消息消费线程
支持全局顺序与分区顺序两种模式
3、事务消息
1、Producer 先发送半消息(half message)到 Broker
2、本地事务执行后 → 提交或回滚消息
3、Broker 定期回查未决事务,确保最终一致性
4、消息过滤
Tag 过滤:轻量,Broker 端快速过滤
SQL92 过滤:Broker 端支持属性过滤,灵活但性能稍低
5、消息回溯 / 消费重放
Consumer 可指定从某个 Offset 或时间点重新消费
便于业务补偿或修复问题
6、流量控制与限速
1、Broker 限制消费速率
2、Consumer 端通过 Pull 队列数量控制处理能力
3、防止消费者堆积消息导致 OOM
11、Zookeeper
1、核心
1、ZNode(节点)
Persistent(持久节点)
服务器重启后仍存在
Ephemeral(临时节点)
会话结束或断开后删除
Sequential(顺序节点)
节点创建自动加序号,可与临时/持久组合
2、Session(会话)
客户端与 ZK 连接的状态
包含 sessionId、状态、超时时间
心跳维持会话,过期自动清理临时节点
3、Watch(监听器)
一次性事件通知机制
事件类型
NodeCreated:节点创建
NodeDeleted:节点删除
NodeDataChanged:节点数据变化
NodeChildrenChanged:子节点变化
4、数据存储
类似文件系统树形结构
ZNode 存储数据(最大 1MB)
支持顺序访问与节点版本管理
2、集群与协议
1、集群架构
Leader:处理写请求并广播事务
Follower:处理读请求并同步数据
Observer(可选):只读节点,不参与投票,降低延迟
2、Zab协议
保证强一致性
流程
1、Leader 选举<br>2、事务广播(Proposal) → Follower ACK<br>3、事务提交(Commit) → 多数派确认生效
Zxid:全局唯一事务 ID,用于保证顺序
3、leader选举
每个节点投票给自己或 zxid 最大节点
多数票节点成为 Leader
异常触发重新选举,保证集群可用
3、分布式应用
1、分布式锁
使用 临时顺序节点
客户端创建节点 → 序号最小者获取锁
监听前序节点变化 → 释放/重试
2、选举
使用临时顺序节点
序号最小节点成为主节点
3、配置管理/注册中心
使用 ZNode 存储服务信息、配置信息
Watch 实现数据变化通知 → 动态更新
4、命名服务
ZNode 树形结构实现服务注册与发现
支持动态变更与事件通知
4、高可用与容错
1、集群部署
奇数节点 → 避免平票<br>Leader 异常 → 触发重新选举<br>Follower 异步同步 Leader 状态
2、客户端容错
自动重连<br>Watch 一次性通知 → 需重新注册
3、事务顺序与一致性
使用 Zxid 保证事务线性顺序<br>多数节点 ACK 后提交 → 保证强一致性
5、高级特性
1、性能优化
避免单节点子节点过多 → 减少 ZooKeeper 读写压力<br>使用 Observer 节点 减轻 Leader 写负载<br>批量事务提交 → 减少同步开销<br>客户端会话超时设置合理<br>数据压缩减少网络和存储开销
2、选主优化
Leader Election 使用临时顺序节点保证公平<br>客户端/节点重连 → 重新参与选主
3、分布式协调模式
Barrier(屏障):控制多个节点同步启动<br>Queue(队列):分布式任务排队处理<br>Group Membership(组成员管理):动态加入/离开通知
4、ZK与分布式系统结合
与 Kafka、HBase、Dubbo 等框架集成<br>提供 选主、配置管理、服务发现、锁机制
6、面试题补充
1、ZK 的节点类型有哪些?
持久节点、临时节点、顺序节点,可组合如临时顺序节点
2、ZK 如何保证一致性?
使用 Zab 协议,Leader 提交事务,多数 Follower ACK 后生效
3、ZK Leader 如何选举?
每个节点投票给自己或 zxid 最大节点,多数票节点成为 Leader,Follower 同步数据
4、ZK Watch 是什么?
一次性事件监听,触发后需重新注册,可监听节点创建、删除、数据和子节点变化
5、ZK 如何实现分布式锁?
临时顺序节点实现公平锁,序号最小者获取锁,监听前序节点变化
6、如何保证高可用?
奇数节点集群,Leader 异常触发重新选举,客户端自动重连并重新注册 Watch
7、Zxid 是什么?
全局唯一事务 ID,用于保证事务顺序和线性一致性
8、ZK 性能优化方法?
Observer 节点减轻 Leader 压力、批量事务、避免大子节点、合理会话超时、数据压缩
12、Elasticsearch
1、核心概念
1、ES是什么
基于 Lucene 的分布式搜索引擎<br>支持 全文检索、结构化搜索、分析聚合<br>特点:分布式、高可用、近实时(Near Real-Time)
2、核心术语
Index(索引):逻辑上的数据库
Type(类型):ES 7.x 之后已废弃
Document(文档):JSON 格式存储数据
Field(字段):文档中的属性
Shard(分片):索引水平拆分的最小单元
Primary Shard(主分片)
Replica Shard(副本分片)
Cluster(集群):由多个节点组成,协同工作
Node(节点):集群中的单台服务器
Master Node:负责集群管理
Data Node:存储数据、执行查询
Ingest Node:数据预处理
Coordinating Node:路由请求、聚合结果
3、Document与Field数据类型
String/Text/Keyword:全文/精确匹配
Numeric:integer、float、long
Date/Boolean
Object/Nested
2、索引与查询
1、索引创建
指定 mapping(字段类型、分析器)<br>
指定 settings(分片数量、分词器等)
2、Mapping
定义字段类型和属性
支持 analyzer(分词器)
Nested 与 Object 区别:Nested 支持嵌套文档独立查询
3、Query DSL
Match Query:全文搜索
Term Query:精确匹配
Range Query:范围查询
Bool Query:组合查询(must, should, must_not)
Aggregations(聚合):统计分析
4、分页与排序
from/size 分页,深分页使用 scroll API 或 search_after
sort 排序,注意字段类型和多字段排序
3、分布式架构
1、分片机制
索引被拆分成若干 primary shard + replica shard
分片路由:通过文档 _id hash 映射到分片
副本分片:提高查询性能和高可用性
2、集群管理
Master 节点:负责分片分配、节点加入/退出
数据节点:存储和查询分片
协调节点:请求分发、聚合结果
3、高可用
副本分片保证节点故障时可用
Master 节点选举采用 Zen Discovery
4、文档更新机制
Elasticsearch 不可变存储
更新本质是 标记旧文档删除 + 新文档写入
删除操作为 lazy delete,通过 merge 合并段文件
4、高级应用
1、全文检索优化
分词器选择:ik_smart/ik_max_word
字段类型优化:Text + Keyword 区分全文和精确匹配<br>
倒排索引:高效存储和检索
2、嵌套对象查询
使用 nested query 保证嵌套文档查询正确
Object 类型在多值嵌套时可能出现笛卡尔积问题
3、聚合分析
Metrics Aggregation:sum, avg, min, max
Bucket Aggregation:terms, histogram, date_histogram
Pipeline Aggregation:bucket 之间的统计分析
4、性能优化
合理分片和副本数量
使用 doc_values 提升排序/聚合性能
避免深分页 → 使用 scroll API / search_after
Bulk 批量操作减少网络开销
设置合理 refresh_interval,减少 segment merge 影响
5、高可用与扩展
集群扩容:增加节点,自动重新分片
集群监控:使用 _cat/indices, _cluster/health
热数据 / 冷数据分层存储
6、实时性与延迟
Elasticsearch 为 Near Real-Time (NRT)
文档写入 → refresh → 可搜索
可通过手动 refresh 或降低 refresh_interval 提升实时性
5、面试题补充
1、ES 是什么?
基于 Lucene 的分布式搜索引擎,支持全文检索、分析和聚合,高可用、近实时。
2、主分片和副本分片区别?
主分片(Primary)负责写入,副本分片(Replica)用于高可用和查询分担读压力。
3、Elasticsearch 如何实现高可用?
通过副本分片、Master 选举、集群自动感知节点故障。
4、更新文档时发生了什么?
Elasticsearch 不可变存储,更新为 标记旧文档删除 + 新文档写入,段文件 merge 后回收空间。
5、分词器作用?
将文本拆分为词条,用于倒排索引,影响全文检索精度和效率。
6、深分页如何优化?
避免使用 large from/size,使用 scroll API 或 search_after。
7、Nested 与 Object 区别?
Object 会 flatten 导致笛卡尔积,Nested 独立存储嵌套文档,查询更精准。
8、聚合常见类型?
Metrics(sum/avg/min/max)、Bucket(terms/histogram/date_histogram)、Pipeline(bucket 间分析)。
9、如何提升性能?
合理分片、副本、批量写入、doc_values、调整 refresh_interval、避免深分页。
10、什么是倒排索引?
倒排索引是一种存储 词条 → 文档列表 的数据结构,用于快速检索包含指定关键词的文档。
11、倒排索引和正向索引区别?
正向索引存文档 → 词条,适合顺序读取;倒排索引存词条 → 文档列表,适合快速全文搜索。
13、Logstash
1、核心概念
1、Logstash 是什么?
Elastic Stack(ELK)的一部分,开源数据收集、处理、传输工具,支持从各种来源收集数据 → 统一解析、过滤 → 输出到 Elasticsearch、文件、Kafka 等
2、特点
统一采集:支持日志、事件、指标等多种来源
实时处理:Near real-time 数据处理
可扩展:通过插件处理各种数据格式
3、典型应用场景
日志采集与分析
系统监控指标收集
数据清洗、结构化处理
2、核心组件
1、Pipeline(三大核心阶段)
Input(输入)
读取数据源,例如 file、beats、jdbc、kafka
Filter(过滤/处理)
数据清洗和解析,例如 grok、mutate、date、geoip
Output(输出)
写入目的地,例如 Elasticsearch、Kafka、file、stdout
2、事件(Event)
Logstash 处理的最小数据单元
由 字段(Field)+元数据(Metadata)+时间戳(@timestamp) 组成
3、插件机制
Input Plugins:file、beats、kafka、jdbc 等
Filter Plugins:grok、mutate、date、json、ruby、geoip
Output Plugins:elasticsearch、file、kafka、stdout
3、配置
1、Pipeline 配置示例
input {<br> file { path => "/var/log/syslog" start_position => "beginning" }<br>}<br><br>filter {<br> grok { match => { "message" => "%{SYSLOGTIMESTAMP:timestamp} %{DATA:host} %{GREEDYDATA:msg}" } }<br> date { match => ["timestamp", "MMM d HH:mm:ss", "MMM dd HH:mm:ss"] }<br>}<br><br>output {<br> elasticsearch { hosts => ["localhost:9200"] index => "syslog-%{+YYYY.MM.dd}" }<br> stdout { codec => rubydebug }<br>}
2、Grok 模式
用于结构化解析日志文本
常见模式:%{IP}, %{NUMBER}, %{WORD}, %{GREEDYDATA}
支持自定义正则模式
3、Conditionals
通过 if/else 条件分支处理不同事件
if [status] == "error" {<br> mutate { add_tag => ["error"] }<br>}
4、高级功能
1、Pipeline 多线程
Logstash 支持多线程处理事件
pipeline.workers 控制 filter/output 并行线程数
2、持久队列(Persistent Queue)
避免 Logstash 宕机丢数据
事件写入磁盘 → 重启后可恢复处理
3、性能优化
合理配置 pipeline.workers → CPU 核心数
调整 batch.size → 每批处理事件数量
使用 codec 压缩/解码数据减少 CPU 消耗
使用 Persistent Queue 避免内存压力
4、条件路由
多 input → 多 output 分流
条件语句决定事件流向
5、高级 Filter 技术
Mutate:字段修改、重命名、删除
Ruby:复杂数据处理
KV:解析 key=value 格式日志
Aggregate:合并多条事件数据
5、面试题补充
1、Logstash 的作用?
数据收集、清洗、处理、传输到目标系统,如 Elasticsearch。
2、Pipeline 三大核心阶段?
Input → Filter → Output。
3、Grok 是什么?
用于日志文本解析,将非结构化日志转为结构化字段。
4、Logstash 如何处理高并发?
设置 pipeline.workers 多线程处理,调整 batch.size,使用 Persistent Queue。
5、Persistent Queue 有什么作用?
避免 Logstash 宕机或内存压力丢失事件,事件可持久化磁盘。
6、Filter 常用插件?
grok、mutate、date、geoip、kv、ruby、aggregate。
7、Logstash 与 Filebeat 区别?
Filebeat 轻量级日志采集 → 发送到 Logstash;Logstash 负责复杂解析和输出。
8、如何优化 Logstash 性能?
多线程 workers、调整 batch.size、使用 codec、持久队列、减少复杂正则。
13、SpringCloud
1、SpringCloud核心基础
1、微服务核心特性与设计原则
1、核心特性
单一职责:每个服务只负责一个模块
服务自治:独立开发、独立部署、独立运维、独立数据库
轻量级通信:基于HTTP/REST协议,无强依赖
其他:服务可发现、弹性伸缩、容错性、统一监控
2、设计原则
DDD领域驱动设计:按业务领域拆分服务,非技术层
服务无状态:避免会话存储,便于水平扩容
面向接口编程:降低服务间耦合,便于替换实现
服务隔离:避免单个服务故障扩散到整个架构
3、微服务 vs 单体架构
微服务架构优缺点,如何避免缺点
优点是解耦、易扩容、技术栈灵活、迭代快
缺点是分布式复杂性、排查故障难、运维成本高、服务依赖复杂
规避方案:注册中心+熔断降级+链路追踪+容器/K8S
2、分布式核心理论
1、CAP定理
定义
分布式系统中,一致性C、可用性A、分区容错性P三者不可兼得,必舍其一
分类
CP(强一致+分区容错,舍A)
AP(高可用+分区容错,舍C)
对应组件
CP
Consul、Zookeeper
AP
Nacos、Eureka
2、BASE定理
定义
放弃强一致性,追求最终一致性,是分布式系统实际落地方案
三要素
基本可用、软状态、最终一致性
3、SpringCloud与SpringBoot的关系
核心关系
SpringBoot是微服务开发基础,SpringCloud是微服务治理上层框架
通俗理解
SpringBoot造单个微服务,SpringCloud管理多个微服务
核心公式
Spring Cloud = SpringBoot + 微服务治理组件
2、核心组件
1、服务注册与发现
1、核心作用
解决微服务之间互相找到的问题,服务启动注册自身信息,调用方法动态发现,无需硬编码地址
2、主流组件对比
Nacos(Alibaba)
AP/CP可切,服务发现+动态配置双功能,心跳检测,集群高可用,国内首选
Eureka(Netflix)
AP模式,自我保护机制,客户端心跳,已停更,仅老项目维护
Consul(第三方)
CP模式,强一致性,支持服务网络、KV存储,少量金融场景用
Nacos和Eureka的核心区别
1、功能:Nacos双功能,Eureka仅服务发现
2、CAP:Nacos可切,Eureka仅AP
3、健康检查:Nacos服务端主动+客户端心跳,Eureka仅客户端心跳
4、持久化:Nacos支持MySQL,Eureka仅内存
5、现状:Nacos活跃,Eureka停更
3、Nacos核心原理
1、服务注册流程
1、客户端启动,通过SDK发送注册请求(携带服务名、IP等)
2、服务端InstanceController处理,存储到内存+Derby(集群用MySQL)
3、返回成功,客户端存储服务端地址
2、服务发现流程
1、客户端启动拉取目标服务实例列表
2、本地缓存+长连接监听变化
3、实例变化时服务主动推送,客户端更新缓存
4、调用时从本地缓存取实例
3、健康检查机制
1、客户端每5秒发心跳
2、15秒未收到标记不健康
3、30秒未收到剔除服务列表(可配置)
4、支持自定义检查(数据库、Redis连接)
4、AP/CP切换
1、配置nacos.core.server.mode切换
2、AP(默认,服务发现):基于Distro协议,高可用,舍强一致
3、CP(配置中心):基于Raft协议,强一致,舍部分可用
4、Eureka核心考点
自我保护机制
定义
15分钟内心跳失败率超85%,判断为网络波动,不提出任何实例,即使已下线
触发条件
15分钟内,心跳率失败>85%
作用
避免网络波动误删可用服务,保可用性(宁可留搓,不丢可用)
2、服务远程调用
1、核心作用
解决微服务之间调用接口,封装HTTP请求,替代RestTemplate硬编码,实现本地方法调用
2、主流组件对比
1、OpenFeign(官方+Alibaba,首选)
基于Feign封装,注解式开发,集成负载均衡、熔断,开发高效
2、Feign(Netflix)
原生,仅基础调用,需手动整合组件,已被OpenFeign替代
3、RestTemplate(Spring原生)
硬编码URL,繁琐,无负载均衡/熔断,不推荐生产使用
OpenFeign和RestTemplate的区别
1、开发效率:OpenFeign注解式,无需硬编码<br>2、功能:OpenFeign集成负载均衡、熔断<br>3、可维护性:OpenFeign接口化,易维护<br>4、扩展性:OpenFeign可自定义配置
3、OpenFeign核心原理
1、项目启动,Spring扫描@FeignClient接口;<br>2、为每个接口创建JDK动态代理<br>3、代理注入容器,供调用方依赖<br>4、调用方调用接口,代理解析注解获取请求信息<br>5、代理通过HTTP客户端发请求<br>6、响应反序列化返回<br>7、集成LoadBalancer,自动负载均衡
4、关键配置
1、超时配置
全局
feign.client.config.default.connect-timeout=5000、read-timeout=10000
单服务
feign.client.config.服务名.xxx
2、日志级别
NONE(默认)、BASIC(核心信息)、HEADERS(请求响应头)、FULL(全局);生产常用BASIC(兼顾性能和排查)
3、HTTP客户端替换
替换为OkHttp/HttpClient,提升性能
3、负载均衡
1、核心作用
均匀发送请求到多个服务实例,提升吞吐量和可用性,避免单点过载、故障
2、主流组件
1、Spring Cloud LoadBalancer(官方推荐,首选):轻量级,支持同步/异步,基于WebFlux,代替Ribbon
2、Netflix Ribbon:功能完善,已停更,仅老项目维护,原理与LoadBalancer一致
两者区别?为什么替换Ribbon?
1、现状:LoadBalancer持续维护,Ribbon停更;<br>2、架构:LoadBalancer基于WebFlux,支持异步;Ribbon基于Servlet,仅同步;<br>3、轻量级:LoadBalancer间接,Ribbon重量级<br>
3、核心原理
工作流程
1、客户端通过OpenFeign传入服务名<br>2、负载均衡组件从注册中心取可用实例列表<br>3、按预设策略选最优实例<br>4、转发请求<br>5、记录调用情况,为策略调整提供依据
4、核心负载均衡策略
1、轮询(RoundRobin,默认):按顺序依次分发,简单无状态,适实例性能相近场景
2、随机:随机选实例,适对均匀性要求不高场景
3、权重:按实例权重分发,性能高的权重高,适实例性能差异大场景
4、响应时间权重:响应时间越短权重越重,适对响应速度要求较高场景(秒杀、支付)
5、自定义:实现ReactorLoadBalancer接口,重写choose方法,按业务定制
4、服务容错
1、核心作用
解决服务雪崩,通过熔断、降级、限流、隔离等手段,宁损局部、不丢整体
2、核心概念
服务雪崩
一个服务故障,引发所有调用它的服务阻塞,连锁反应导致整个架构崩溃
熔断
异常率/慢调用率达阈值,暂时断开调用,拒绝请求,一段时间后尝试恢复,保护被调用方
降级
调用失败、超时、熔断或负载过高时,执行兜底逻辑,快速返回结果,保护调用方
限流
限制请求量(QPS/并发数),避免流量洪峰压垮服务,保基本可用
隔离
将不同服务调用隔离,避免单个服务故障占用过多资源,影响其他调用
3、主流组件对比
1、Sentinel(Alibaba,首选)
轻量级,流量驱动,支持熔断、降级、限流等,注解式开发,实时监控,国内首选
2、Hystrix(Netflix)
重量级,基于线程池隔离,仅支持熔断、降级,已停更,仅老项目维护
两者核心区别?为什么推荐Sentinel?
1、设计理念:Sentinel流量驱动,Hystrix故障隔离;<br>2、轻量级:Sentinel简洁,Hystrix重量级;<br>3、功能:Sentinel功能丰富(限流、热点等);<br>4、监控:Sentinel内置控制台,Hystrix需整合其他组件;<br>5、现状:Sentinel活跃,Hystrix停更
4、Sentinel核心内容
1、核心概念
资源(@SentinelResource标价,保护对象)、规则(5种核心规则,可动态配置)、插槽链(责任链模式,核心执行流程)
2、插槽链执行顺序
NodeSelectorSlot->ClusterBuilderSlot->LogSlot->StatisticSlot->AuthoritySlot->SystemSlot->FlowSlot->DegradeSlog->业务逻辑
3、核心功能
限流
限制QPS/并发数,模式(QPS、并发数),效果(快速失败、预热、排队等待),支持单机/集群限流
熔断
3种触发条件(慢调用比例、异常比例、异常数),状态流转(闭合->打开->半开)
降级
4种类型,通过@SentinelResource的fallback、blockHandler配置兜底方法
热点限流
对热点数据参数限流,适用于秒杀、支付场景
系统保护
监控CPU、负载等,系统级限流
4、注解式开发
@SentinelResource核心属性(value、fallback、blockHandler等)
5、熔断 vs 降级
两者区别及适用场景
1、目的:熔断保护被调用方,降级保护调用方;<br>2、触发条件:熔断由被调用方异常指标被动触发,降级主动+被动结合;<br>3、作用范围:熔断服务级,影响所有调用方;降级调用方局部,仅影响当前调用方;<br>适用场景:熔断适被调用方故障,降级适调用失败、负载过高、流量洪峰
5、分布式配置中心
1、核心作用
解决配置分散、修改需重启、不一致问题,实现统一管理、动态推送、环境隔离,提升运维效率
2、主流组件对比
1、Nacos Config(Alibaba,首选)
动态配置、环境隔离、加密、持久化,与注册中心一体化,推+拉模式
2、Spring Cloud Config(官方)
基于Git,仅拉模式,无动态推送,需重启/手动刷新,已被替代
3、Apollo(携程)
功能强大,部署复杂,适大型项目
Nacos Config和Spring Cloud Config区别?为什么推荐Nacos?
1、推送:Nacos推+拉,实时生效;Config仅拉,需要重启;<br>2、功能:Nacos支持隔离、加密等;Config无;<br>3、部署:Nacos一体化,Config需单独部署;<br>4、易用性:Nacos控制台简单,Config繁琐;<br>
3、Nacos config核心原理
1、核心流程(配置拉取+动态刷新)
1、客户端启动读bootstrap.yaml,获取Nacos配置
2、向服务端拉取对应配置
3、服务端返回配置,客户端加载并覆盖本地
4、建立长连接,监听配置变化
5、配置修改,服务端主动推送新配置
6、客户端自动刷新,无需重启
7、长连接断开,定时拉取兜底
2、配置隔离
命名空间(环境/集群)+分组(服务模块)+数据ID(配置文件标识),三级隔离
3、配置持久化
单机Derby,集群MySQL,支持版本管理,回滚
4、配置加密
内置AES,敏感配置加密存储,客户端自动解密
5、配置刷新(@RefreshScope):标记需刷新Bean,配置修改后重新创建Bean,加载新配置
6、分布式网关
1、核心作用
微服务统一入口,解决路由转发、统一鉴权、限流、跨域等问题,屏蔽后端细节,提升安全性、可维护性
2、主流组件对比
1、Spring Cloud Gateway(官方推荐,首选)
基于WebFlux、Netty,非阻塞异步,支持动态路由等,性能高
2、Zuul 1.x(Netflix)
基于Servlet,同步阻塞,性能低,已停更,仅老项目维护
3、Zuul 2.x
基于Netty,异步,未被官方集成,生态不完善,少用
Gateway和Zuul 1.x区别?为什么性能差距大?
核心区别在于底层框架和IO模型:1、架构:Gateway基于WebFlux、Netty;Zuul基于Servlet;2、IO模型:Gateway非阻塞异步,一个线程处理多个请求,吞吐量高;Zuul同步阻塞,一个请求一个线程,易资源耗尽;3、性能:Gateway时Zuul的3-5倍。
3、Gateway核心原理
1、核心三要素
路由(route,请求转发规则)、断言(Predicate,请求匹配条件)、过滤器(Filter,请求/响应拦截处理)
2、工作流程
1、客户端发请求到Gateway
2、Gateway遍历路由断言,匹配符合条件的路由(无匹配返回404)
3、请求进入前置过滤器,执行鉴权、限流等
4、转发请求到目标服务
5、服务返回响应
6、响应进入后置过滤器,执行包装、日志等
7、响应返回给客户端
3、核心特性
动态路由(从Nacos获取,无需重启)、断言类型(路径、请求头、IP等)、过滤器(全局+局部)、跨域处理、限流熔断
3、高可用架构设计
1、架构设计核心要点
1、中间件
Nacos/Gateway/Config集群搭建,避免单点
2、数据层
数据库主从+读写分离,Redis集群
3、部署
异地多活、容器隔离(Docker/K8S)
2、核心设计考点
1、高可用微服务架构设计(全组件)
2、微服务拆分依据(DDD/业务边界)
3、秒杀场景高并发/高可用设计、统一鉴权体系设计
4、多环境配置隔离、服务平滑升级/灰度发布
4、问题排查
1、常见故障排查
1、微服务调用超时
2、服务雪崩
3、网关请求堆积排查
2、排查核心思路
从网关->服务->数据层逐步定额我i,结合监控/链路工具(SkyWalking、Prometheus)
5、生产实战
1、组件使用场景+实际问题解决
2、Sentinel/Seata的项目配置与选型原因
3、微服务性能优化的实操+效果
4、Netflix迁移到Alibaba的项目改造点
6、高频面试题
1、为什么P必须保留
分布式系统中网络分区不可避免(网络故障、机房断电),舍弃P则沦为单体,所以P必留,只能在C和A中选择
2、BASE和CAP的关系
BASE是CAP的延伸补充,CAP指出三者不可兼得,BASE给出落地思路,舍弃强一致选AP,通过三要素实现最终一致性,适配多数业务
3、SC和SB两者的区别与联系
联系
SpringCloud基于SpringBoot开发,依赖其自动配置、依赖管理,无Boot则无Cloud
区别
Boot专注单个微服务快速开发,解决单体效率低问题;Cloud专注分布式治理,解决多服务协调问题,互补缺一不可
4、为什么Nacos能同时支持AP和CP?底层协议?
基于两种底层协议;<br>1、AP:Distro协议(分片存储+异步复制),高可用,舍强一致,适服务发现;<br>2、CP:Raft协议(主节点选取),强一致,舍部分可用,适配置中心;可自由切换
5、Nacos服务发现默认选AP的原因?
服务发现核心需求是高可用,而非强一致;<br>实例下线客户端短暂获取,可通过熔断降级规避故障;<br>若服务发现不可用,整个调用崩溃,影响更大
6、Nacos集群如何搭建?
1、至少准备3个节点(奇数,便于Raft选举);<br>2、同集群名称,不同节点ID;<br>3、配置MySQL持久化;<br>4、配置节点间通信地址,实现同步;<br>5、启动节点,自动组成集群,支持故障转移
7、OpenFeign的工作原理
1、扫描@FeignClient接口;<br>2、创建JDK动态代理;<br>3、代理注入容器;<br>4、代理解析注解获取请求头信息;<br>5、发HTTP请求;<br>6、响应反序列化;<br>7、集成LoadBalancer实现负载均衡
8、OpenFeign如何设置超时时间?几种方式?
1、全局配置,对所有远程调用生效<br>2、单个服务配置,仅对指定服务生效<br>生产建议核心服务单独配置,避免全局影响
9、OpenFeign如何实现负载均衡?底层依赖?
内置集成SpringCloud LoadBalancer(替代Ribbon);<br>流程:1、通过服务名从注册中心取实例列表;2、按负载均衡策略选实例;3、转发请求,完成负载均衡
10、负载均衡核心原理?
核心是请求分发+实例选择;<br>流程是取实例列表,按策略选实例,转发请求;<br>目的是均匀分配算法,提升吞吐量,避免单点过载
11、LoadBalancer默认策略及原理?
默认轮询;原理是取实例列表后,按顺序依次分发、循环往复,无状态,无需记录调用情况,通用场景首选
12、如何实现自定义负载均衡策略?
1、实现ReactorLoadBalancer<ServiceInstance>接口,重写choose方法(定义选择逻辑);<br>2、在choose方法中按业务规则选实例;<br>3、配置类注入容器,替换默认策略;<br>4、(可选)为指定服务单独配置
13、Sentinel的核心执行流程(插槽链顺序)?
责任链模式,顺序->构建调用链路->统计集群指标->日志记录->统计流量指标->权限控制->系统保护->限流->熔断降级->业务逻辑
14、Sentinel熔断触发条件及状态流转?
3种条件:1、慢调用比例:耗时超阈值比例>=阈值,请求数>=5;2、异常比例:异常率>=阈值,请求数>=5;3、异常数:异常数>=阈值,请求数>=5;<br>状态流转:闭合(正常)->打开(熔断,10秒)->半开(尝试恢复,成功闭合、失败打开)
15、@SentinelResource核心属性及fallback与blockHandler的区别?
核心属性:value(资源名,必填)、fallback(业务异常/超时/熔断兜底)、blockHandler(限流/系统保护兜底)等;<br>区别:<br>1、触发场景:fallBack对应业务异常等,blockHandler对应限流等;<br>2、异常类型:fallback接收Throwable,blockHandler接收BlockException
16、如何解决雪崩问题?
多层防护:<br>1、熔断:断开故障服务调用;<br>2、降级:配置兜底逻辑,快速返回;<br>3、限流:核心接口设阈值,避免洪峰;<br>4、隔离:信号量隔离,避免资源占用;<br>5、缓存:高频接口缓存,减少远程调用;<br>6、重试:非核心接口合理重试;<br>7、监控:实时监控,提前发现异常。
17、Nacos Config如何实现动态推送?
核心长连接+推模式,拉模式兜底;<br>流程:1、客户端与服务端建立长连接;<br>2、配置修改,服务端主动推送;<br>3、客户端自动刷新;<br>4、长连接断开,定时拉取,保证最终一致。
18、配置隔离方式及项目种如何使用?
三级隔离(命名空间+分组+数据ID):<br>1、命名空间:dev/test/prod,客户端配置指定;<br>2、分组:按服务模块分组,客户端配置指定;<br>3、数据ID:服务名-环境.yml,客户端配置指定;实现不同环境、服务配置隔离
19、@RefreshScope的作用及原理?
作用:标记需动态刷新Bean,配置修改后重新创建Bean,无需重启,加载新配置;原理:基于Spring自定义作用域,配置刷新时销毁旧Bean、创建新Bean,新Bean加载最新配置
20、配置优先级?
从高到低:<br>1、Nacos远程配置;<br>2、客户端bootstrap.yml;<br>3、客户端application.yml;<br>4、系统环境变量;<br>5、命令行参数;<br>核心配置放Nacos,本地放客户端特有配置(端口)
21、Gateway的核心组件及作用?
1、路由:基本单元,定义转发规则,由ID、URL、断言、过滤器组成,负责转发符合条件的请求;<br>2、断言:匹配请求条件,基于Spring EL表达式,只有符合条件才能转发;<br>3、过滤器:拦截处理请求/响应,分全局和局部,实现鉴权、限流,责任链模式执行。
22、Gateway的工作流程?
1、客户端发送请求;<br>2、匹配路由断言;<br>3、前置过滤器处理;<br>4、转发请求到目标服务器;<br>5、服务器返回响应;<br>6、后置过滤器处理;<br>7、返回响应给客户端。
23、如何实现Gateway动态路由?
核心从Nacos动态获取路由规则,无需重启;<br>1、搭建Nacos,配置JSON格式路由规则(ID、URL、断言、过滤器);<br>2、Gateway引入Nacos依赖,配置Nacos地址;<br>3、Gateway监听Nacos路由配置变化,自动更新路由规则;<br>4、无需重启Gateway,即可实现路由新增、修改、删除
14、分布式
1、分布式基础理论
1、核心定义与特征
分布式系统是将一个完整的业务拆分为多个子任务,部署在堕胎独立的网络节点上,通过网络协同完整计算的系统,核心解决集中式系统的单点瓶颈、扩展能力差、容错性弱问题
核心特征
分布性
节点物理分散,对等通信
无全局时钟
无法精准同步节点时间,时序控制难度大
故障独立性
单个节点故障不影响整体集群运行
并发性
多节点同时处理请求,存在共享资源竞争
网络不可靠
网络延迟、丢包、分区是常态,是所有分布式问题的根源
2、CAP定理
CAP是分布式系统的基础定理,任何分布式系统只能同时满足其中两项,核心结论:分区容错性P是必选项,只能在CP和AP之间权衡
CP 系统:牺牲可用性保证强一致性,典型案例:ZooKeeper、Etcd、HBase、分布式数据库
AP 系统:牺牲强一致性保证可用性,典型案例:Eureka、Nacos AP 模式、Cassandra、大部分互联网业务系统
面试误区:CAP 不是永久放弃某一项,而是网络分区发生时,在 C 和 A 之间做取舍,分区恢复后可通过数据同步恢复一致性
3、BASE理论
BASE 是对 CAP 中 AP 方案的补充,核心思想是牺牲强一致性,换取系统的高可用性和可扩展性,保证数据最终一致性,是大规模分布式微服务系统的首选准则。
BA 基本可用
系统出现故障时,允许损失部分可用性(比如响应时间变长、非核心功能降级),保证核心功能可用
S 软状态
允许系统中的数据存在中间状态,该状态不影响系统整体可用性,即允许不同节点之间的数据同步存在延迟
E 最终一致性
系统中所有数据副本,经过一段时间的同步后,最终能够达到一致的状态,不需要实时保证强一致性
最终一致性常见模型:读己之所写、会话一致性、单调读、单调写、因果一致性
核心对比:ACID 是数据库事务的强一致性准则,BASE 是分布式系统的柔性一致性准则,二者是对立统一的关系。
4、分布式系统核心挑战
网络不可靠、时钟不一致、节点故障、数据一致性、并发安全、负载均衡、服务治理、可扩展性、故障排查、容灾备份。
2、分布式一致性算法
一致性算法的本质:在不可靠的网络环境中,让多个节点对一个提案达成统一共识,同时保证故障节点不影响集群正确性,是分布式协调、分布式存储的核心底层。
1、基础核心概念
提案(Proposal)、提议者(Proposer)、接受者(Acceptor)、学习者(Learner)、法定人数(Quorum,过半节点)、任期号(Term,单调递增,解决脑裂)。
2、Paxos算法
核心角色
Proposer(发起提案)、Acceptor(投票表决)、Learner(同步已确认提案)
核心流程
两阶段提交
准备阶段(Prepare):Proposer 向过半 Acceptor 发送带编号的 Prepare 请求,Acceptor 只响应编号大于已处理过的请求,承诺不接受更小编号的提案。
接受阶段(Accept):Proposer 收到过半响应后,向 Acceptor 发送 Accept 请求,Acceptor 验证编号合法后接受提案,过半 Acceptor 接受则提案被选定。
核心变种
Basic Paxos(单提案)、Multi-Paxos(多提案优化,选主减少 Prepare 阶段,工程落地主流)
面试要点
理论严谨但工程落地难度大,存在活锁问题,面试一般只问核心原理,不会深入细节。
3、Raft算法
Raft 的设计目标是易理解、易实现,将一致性问题拆分为领导选举、日志复制、安全性三个子问题,是 Etcd、Consul、云原生组件的核心算法。
核心角色
Leader(处理所有写请求,同步日志)、Follower(处理读请求,投票响应)、Candidate(选举阶段的候选角色)
核心流程
领导选举:集群启动 / Leader 宕机时,Follower 超时后转为 Candidate,发起投票,获得过半节点投票当选 Leader;选举超时时间随机化,避免选票分裂。每个任期 Term 只有一个 Leader,Term 单调递增。
日志复制:客户端写请求到 Leader,Leader 将请求封装为日志条目,并行同步给 Follower;收到过半 Follower 的写入确认后,Leader 提交日志、应用到状态机,返回结果给客户端,之后通知 Follower 提交日志。
安全性:只有包含所有已提交日志的节点才能当选 Leader;日志只能从 Leader 流向 Follower;Term 机制保证旧 Leader 无法提交新日志,彻底解决脑裂问题。
4、ZAB协议
ZAB(ZooKeeper Atomic Broadcast)原子广播协议,专为 ZK 设计,核心保证集群事务的顺序一致性和崩溃恢复能力
核心阶段
崩溃恢复阶段:Leader 宕机时,集群选举新 Leader,完成数据同步,保证所有节点数据一致
消息广播阶段:正常运行时,基于两阶段提交实现原子广播,Leader 发起提案,Follower 过半 ACK 后 Leader 提交,保证事务顺序执行
选主规则
优先比较 ZXID(事务 ID,越大数据越新),ZXID 相同比较 myid(节点 ID,越大优先级越高),过半投票当选
3、分布式协调服务
分布式协调服务是分布式系统的核心基础设施,解决集群管理、服务注册发现、配置中心、分布式锁、选主、元数据管理等核心问题。
1、ZooKeeper
核心概念
ZNode:ZK 的最小数据单元,分为 4 类:持久节点、持久顺序节点、临时节点(生命周期与 Session 绑定,Session 断开自动删除,无法创建子节点)、临时顺序节点。
Session:客户端与服务端的会话,有超时机制,Session 断开则临时节点自动删除。
Watcher:事件监听器,客户端可在 ZNode 上注册 Watcher,当 ZNode 发生创建 / 删除 / 数据修改 / 子节点变更时,服务端推送事件通知客户端。
集群架构:Leader(处理写请求,集群选主)、Follower(处理读请求,参与选主和过半写)、Observer(处理读请求,不参与选主和投票,提升读性能不影响写性能)。
2、Etcd
核心定位
基于 Raft 协议的分布式键值存储,强一致性、高可用,云原生生态首选。
核心优势
支持 MVCC(多版本并发控制)、租约 Lease(替代 ZK 的 Session,更灵活)、持续 Watcher(非一次性,支持范围监听)、HTTP/2+gRPC 接口,跨语言友好,K8s 原生适配。
4、分布式锁
分布式锁的核心目标:在分布式环境下,保证同一时间只有一个节点的一个线程能获取锁,执行临界区代码,解决跨节点的并发资源竞争问题。
分布式锁核心必备特性:互斥性、防死锁、高可用、可重入性、锁释放安全性、超时续期能力。
1、基于 Redis 实现分布式锁
核心原理:利用 Redis 单线程特性,通过原子命令保证互斥性,是高并发场景首选。
正确基础实现:SET lock_key unique_value NX PX 30000
NX:仅 key 不存在时设置成功,保证互斥<br>PX:设置过期时间,防止客户端崩溃导致死锁<br>unique_value:客户端唯一标识,防止误删其他客户端的锁
安全释放锁
必须通过Lua 脚本实现,保证「判断锁归属 + 删除锁」的原子性,避免误删。
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
进阶生产级特性<br>
可重入性:基于 Hash 结构记录客户端 ID 和重入次数,Lua 脚本保证原子操作。
看门狗 WatchDog:客户端获取锁成功后,定时任务每隔锁过期时间的 1/3 检查锁持有状态,自动续期,解决业务未执行完锁提前释放的问题。
公平锁:基于 List 队列实现请求排队,先到先得,避免线程饥饿。
红锁 RedLock:解决主从架构锁丢失问题,向 N 个独立 Redis 主节点申请锁,过半节点获取成功且总耗时小于锁过期时间,才算锁获取成功。
缺点
单点场景存在锁丢失风险,红锁实现复杂,强依赖系统时钟。
优点
性能极高、实现简单、适配高并发场景;
生产级落地
Redisson 框架,开箱即用,封装了所有核心特性。
2、基于 ZooKeeper 实现分布式锁
核心原理
利用临时顺序节点 + Watcher 机制实现。
实现流程
客户端在锁父节点下创建临时顺序节点;
获取所有子节点,判断自身节点是否为序号最小的节点,是则获取锁成功;
否则监听前一个序号节点的删除事件,事件触发后再次检查,确认最小则获取锁;
释放锁:删除临时节点,客户端崩溃 Session 断开则节点自动删除。
优点
天然防死锁、天然公平锁、可靠性高、无锁过期续期问题;
缺点
性能低于 Redis,频繁创建删除节点对 ZK 集群压力大,选主期间集群不可用。
3、基于数据库实现分布式锁
悲观锁实现
select ... for update 排他锁,开启事务后锁定行,事务提交释放锁。
乐观锁实现
基于版本号 / 时间戳,通过update影响行数判断是否获取锁成功。
优点
实现简单,无需引入额外中间件
缺点
性能极差、易死锁、不可重入、存在单点故障风险,仅适合低并发简单场景。
4、基于 Etcd 实现分布式锁
核心原理
基于 Raft 协议 + 租约 Lease + 有序 KV+Watcher 机制实现,云原生场景首选。
优点
一致性强、可靠性高、支持持续 Watcher、租约机制灵活
缺点
性能低于 Redis,实现复杂度较高。
5、分布式事务
分布式事务的核心问题:一个事务操作跨越多个数据库 / 服务节点,如何保证原子性(要么全成功,要么全回滚)和数据一致性。
1、核心分类
分为刚性事务(强一致性) 和柔性事务(最终一致性) 两大类,互联网微服务场景以柔性事务为主。
2、刚性事务方案(强一致性)
2PC(两阶段提交)
核心角色
AP(应用程序)、TM(事务管理器)、RM(资源管理器,数据库)
核心流程
准备阶段:TM 向所有 RM 发送准备请求,RM 执行事务,写 Undo/Redo 日志,不提交,返回是否就绪。
提交 / 回滚阶段:所有 RM 返回就绪,TM 发送提交请求;任意 RM 返回失败,发送回滚请求,RM 通过 Undo 日志回滚。
缺点
同步阻塞、TM 单点故障、网络异常导致数据不一致、脑裂问题,不适合微服务长事务场景。
3PC(三阶段提交)
2PC 的优化方案,将准备阶段拆分为 CanCommit 和 PreCommit,增加超时机制,减少阻塞范围,依然存在数据不一致风险,实现复杂,生产环境极少使用。
3、柔性事务方案(最终一致性,互联网主流)
基于 BASE 理论,牺牲强一致性换取高可用和高性能,是微服务场景的核心选型。
TCC(Try-Confirm-Cancel)
业务层面的 2PC,侵入性强,灵活性高,适合核心交易场景。
三个阶段
Try 阶段:业务检查、资源预留 / 锁定(比如冻结库存,而非直接扣减),是整个事务的基础。
Confirm 阶段:确认执行业务,使用 Try 阶段预留的资源,提交事务,必须保证幂等性。
Cancel 阶段:取消执行,释放 Try 阶段预留的资源,回滚事务,必须保证幂等性和空回滚处理。
核心难点
空回滚
Cancel 在 Try 之前执行,需通过事务控制表标记,后续拒绝 Try 请求。
防悬挂
防止 Try 在 Cancel 之后执行,校验到已执行 Cancel 则直接拒绝 Try。
优点
性能高、无锁阻塞、准强一致性、适用场景广
缺点
业务侵入性极强,开发运维成本高。
SAGA 模式
长事务解决方案,核心是正向操作 + 逆向补偿,适合复杂长流程业务。
核心原理
将分布式事务拆分为多个本地事务,每个事务对应一个正向操作和一个补偿操作;所有正向操作成功则事务完成,任意步骤失败则按逆序执行补偿操作,回滚已完成的操作。<br>
两种实现方式
编排式(主流)
中心协调器统一调度所有服务的正向和补偿操作,逻辑清晰,易管控,耦合度低。
协同式
各服务通过事件通信,无中心协调器,实现简单,耦合度高,难排查。
优点
业务侵入性低于 TCC,无需资源预留,适合长事务
缺点
仅保证最终一致性,无隔离性,易出现脏读,补偿逻辑开发成本高。
事务消息(RocketMQ/Pulsar 原生支持)
本地消息表的优化版,无业务侵入,高并发场景首选。
核心原理
MQ 支持两阶段提交的事务消息,将本地消息表下沉到 MQ 服务端,实现业务与消息解耦。
核心流程
生产者发送半消息(Half Message)到 MQ,消息对消费者不可见;
MQ 响应半消息发送成功,生产者执行本地事务;
本地事务成功,发送 Commit 请求,消息对消费者可见;失败则发送 Rollback 请求,MQ 删除消息;
超时回查:MQ 未收到 Commit/Rollback 时,定时回查生产者本地事务状态,根据结果决定提交 / 回滚;
消费者消费消息,执行本地事务,必须保证幂等性。
优点
业务无侵入、性能高、实现简单、适配高并发场景;
缺点
依赖 MQ 的事务消息能力,仅保证最终一致性。
适用场景
电商订单、积分、优惠券、通知类等大部分互联网业务场景。
本地消息表 + 消息队列
经典最终一致性方案,实现简单,适合快速落地。
核心原理
将分布式事务拆分为「本地事务 + 消息发送」,通过本地数据库的原子性,保证业务数据和消息数据同时写入 / 回滚,再通过定时任务保证消息可靠投递。
优点
实现简单、低侵入、易运维
缺点
消息表与业务表耦合,不适合高并发场景。
最大努力通知
最低一致性要求的方案,核心是尽最大努力投递结果,接收方通过主动查询兜底,适合非核心通知类场景,比如支付回调、短信 / 邮件通知。
4、选型指南
<br>
6、分布式缓存
1、核心定位与架构
分布式缓存的核心作用是降低数据库访问压力、提升系统响应速度、支撑高并发流量,主流实现为 Redis,核心分布式架构包括:主从复制、哨兵 Sentinel、Redis Cluster(官方分片集群)、Codis(代理层分片)。
2、缓存三大核心问题
缓存穿透
问题定义
大量请求查询不存在的数据,请求直接穿透缓存打到数据库,导致数据库压力骤增甚至宕机,常见于恶意攻击、非法参数、爬虫。
解决方案
接口层参数校验,拦截非法请求
布隆过滤器:将所有存在的 key 存入布隆过滤器,请求先查过滤器,不存在直接返回
缓存空值 / 默认值:对不存在的 key 缓存空值,设置较短过期时间
网关限流,拦截恶意 IP 请求
缓存击穿
问题定义
热点 key 过期,瞬间大量并发请求同时打到数据库,导致数据库压力骤增
解决方案
热点 key 永不过期:物理不设过期时间,逻辑过期后后台异步更新缓存
互斥锁:缓存失效时,仅一个线程能查询数据库并更新缓存,其他线程等待重试
提前预热:大促前提前加载热点 key,快过期时提前异步更新
多级缓存:本地缓存 + 分布式缓存,热点 key 提前下沉到本地缓存
缓存雪崩
问题定义
大量缓存 key 同时过期,或缓存集群整体宕机,所有请求直接打到数据库,导致数据库雪崩式宕机
解决方案
过期时间打散:给 key 的过期时间增加随机值,避免同时过期
缓存集群高可用:主从 + 哨兵 / Cluster 集群,多机房部署,避免单点故障
多级缓存兜底:本地缓存 + 分布式缓存,缓存故障时本地缓存降级
熔断降级:缓存故障时,熔断缓存请求,返回兜底数据,保护数据库
全链路限流:网关、服务层多级限流,控制请求峰值
3、缓存与数据库一致性问题
核心矛盾
缓存和数据库是两个独立存储,双写无法保证原子性,只能根据业务场景平衡一致性和性能,互联网场景优先保证最终一致性
主流方案:Cache Aside Pattern(旁路缓存,读多写少首选)
读流程
先读缓存,命中直接返回;未命中则读数据库,将数据写入缓存后返回
写流程
先更新数据库,再删除缓存
核心问题
为什么是删除缓存,不是更新缓存?
更新缓存会导致并发写数据不一致,且频繁更新的缓存大概率不会被读取,浪费资源;删除缓存采用懒加载,下次读取时再加载,性能更优
为什么先更新数据库,再删除缓存?
先删缓存再更新数据库,更新过程中并发读会把旧数据加载到缓存,导致长期脏数据
异常处理:更新数据库成功,删除缓存失败怎么办?
通过消息队列异步重试,或订阅数据库 binlog(Canal/Debezium)异步删除缓存,保证最终一致性
其他方案
读写穿透
缓存代理封装数据库和缓存的读写,业务无感知,一致性更好但实现复杂
回写模式
写缓存,异步批量刷库,写性能极高但一致性差,数据丢失风险高,仅适合非核心数据场景
强一致性方案
通过分布式事务保证更新数据库和删除缓存的原子性,性能极差,仅金融核心场景慎用
7、分布式消息队列
1、核心作用与主流选型
核心作用
系统解耦、异步化、削峰填谷、流量控制、广播通信
2、主流 MQ 选型对比
<br>
2、四大核心面试必考点
1、消息的可靠性投递(不丢失)
消息丢失分为生产端、Broker 端、消费端三个环节,全链路保证方案:
生产端:同步发送等待 Broker ACK 确认、异步发送回调处理失败、超时重试机制、事务消息保证、消息唯一 ID 防重复。
Broker 端:开启同步刷盘机制、多副本同步机制、ACK 设置为 - 1/all(所有副本写入成功才返回)、开启消息持久化。
消费端:关闭自动 ACK,业务处理完成后手动提交 ACK、消费失败重试机制、死信队列兜底处理失败消息
2、消息的重复消费与幂等性
重复消费原因:网络重试、消费者 Rebalance、手动 ACK 失败,MQ 的 At Least Once 投递机制必然会导致重复投递,核心解决方案是保证消费端的幂等性。
主流幂等性实现方案
唯一键 + 去重表:消息携带唯一 MessageId,处理前先查去重表,存在则直接返回,不存在则处理并写入去重表,原子操作
数据库唯一约束:利用数据库唯一索引,重复插入直接报错,保证幂等
乐观锁:基于版本号实现,update时校验版本号,影响行数 > 0 才执行成功
分布式锁:以 MessageId 为锁 key,保证同一时间只有一个线程能处理该消息
状态机幂等:基于业务状态流转,比如订单只能从待支付到已支付,重复请求直接拒绝
3、消息的顺序性
核心需求
消息的消费顺序与发送顺序一致
解决方案
全局顺序:Topic 仅设置一个分区,单消费者单线程消费,严格保证顺序但吞吐量极低,几乎不用
局部顺序(生产主流):将需要保证顺序的消息,通过同一业务 key(如订单号)发送到同一个分区,同一个分区仅被一个消费者消费,单线程处理,兼顾顺序性和吞吐量
4、消息积压问题(线上故障高频场景题)
积压原因
消费速度远低于生产速度,常见于消费逻辑耗时过长、消费者实例数不足、突发流量、消费报错无限重试
解决方案
紧急扩容:先扩容 Topic 分区数,再扩容消费者实例数,快速提升消费能力
性能优化:优化消费逻辑,减少耗时(批量处理、异步化、去除慢 SQL / 慢 RPC)、禁止无限重试,达到重试阈值直接进入死信队列,避免阻塞消费
流量控制:生产端限流,控制消息发送速度,削峰填谷
极端场景兜底:积压量极大时,先停掉原消费者,新建 Topic 承接新消息,新消费者处理新流量;再启动专用消费者,异步处理积压的旧消息,处理完成后切回原 Topic
5、高频补充考点
死信队列 DLQ:消息消费失败达到最大重试次数后,被投递到死信队列,不再重复投递,避免阻塞消费流程,后续可人工排查处理
延迟消息:消息发送后延迟一段时间才能被消费,RocketMQ 原生支持,RabbitMQ 通过 TTL + 死信队列实现,Kafka 需通过时间轮算法自研
重试机制:消费失败后,MQ 自动重试,需设置合理的重试间隔和最大重试次数,避免无限重试
8、分布式高可用架构
1、高可用核心指标
可用性 SLA,核心衡量标准
99.9%:全年不可用时间≤8.76 小时
99.99%:全年不可用时间≤52.56 分钟
99.999%:全年不可用时间≤5.26 分钟(互联网核心系统顶级目标)
核心辅助指标
RPO(恢复点目标,故障后允许丢失的最大数据量)、RTO(恢复时间目标,故障后系统恢复可用的最长时间)
2、高可用核心实现手段
冗余备份与故障转移
无状态服务:多实例水平扩容,负载均衡,健康检查自动剔除故障实例,流量无感知切换
有状态服务:主从 / 主备、多副本、分片集群,自动故障转移,比如 MySQL 主从、Redis 集群、ZK 集群
机房级容灾:同城双活、两地三中心、异地多活,单机房故障时流量自动切换到其他机房
隔离设计(防止故障扩散,避免雪崩)
核心目标
将故障限制在最小范围,避免级联故障
主流方案
服务隔离:核心服务与非核心服务拆分部署,避免非核心故障影响核心
线程池隔离:不同业务 / 接口使用独立线程池,避免慢接口占满线程池导致整体服务不可用
资源隔离:CPU、内存、磁盘、网络资源隔离,通过 K8s 等实现资源限制
读写隔离:读写分离,读请求到从库,写请求到主库,避免写操作影响读性能
机房 / 可用区隔离:多可用区部署,单可用区故障不影响整体服务
限流、熔断、降级
限流
控制请求的并发量和 QPS,防止系统被突发流量打垮,保护系统稳定性
主流限流算法
令牌桶算法(生产主流):令牌以固定速率生成,请求需获取令牌才能通过,桶满则不再生成令牌,既能控制平均速率,又能应对突发流量,Guava RateLimiter、Sentinel 均采用
漏桶算法:请求进入漏桶,以固定速率流出,超出容量的请求被拒绝,强制平滑流量,无法应对突发流量
滑动窗口计数器:将固定窗口拆分为多个小窗口,滑动统计,解决固定窗口的临界流量翻倍问题
限流粒度
全局限流、接口级限流、用户级限流、IP 级限流
实现位置
网关层(Nginx、Spring Cloud Gateway)、服务层(Sentinel、Resilience4j)、分布式限流(Redis+Lua)
熔断
当依赖的服务出现大量故障,故障率达到阈值时,熔断器打开,直接拒绝请求,不再调用故障服务,防止故障扩散,保护当前系统
熔断器三大状态
关闭状态:正常调用,统计故障率 / 超时率
打开状态:故障率达到阈值,直接拒绝请求,快速失败
半开状态:打开一段时间后,尝试放行部分请求,验证服务是否恢复,成功则关闭熔断器,失败则继续打开
主流实现
Alibaba Sentinel、Resilience4j
降级
系统压力过大或出现故障时,关闭非核心功能,或返回兜底数据,释放系统资源,保证核心功能可用
降级类型
熔断降级、限流降级、人工降级(大促前关闭非核心功能)
降级粒度
服务级降级、接口级降级、功能级降级
故障自愈与监控告警
健康检查:自动检测服务 / 节点状态,剔除故障实例,流量自动切换
自动扩缩容:根据 CPU、内存、QPS 等指标,自动扩缩容服务实例,应对流量波动
可观测性体系:Metrics(指标)+ Logs(日志)+ Traces(链路)三大支柱,实现全链路监控,及时发现故障
分级告警:按故障严重程度分级,多渠道通知,告警降噪避免告警风暴
高频面试题
1、怎么解决脑裂?
通过 Term 任期号 + 过半投票机制,旧 Leader 无法获得过半投票,无法提交日志,发现新 Leader 的更大 Term 后自动降级为 Follower。
2、Raft和Paxos的区别?
Raft 强 Leader 设计,流程拆分清晰,易理解易实现;Paxos 无强 Leader,理论通用但落地难度大。
3、Watcher 机制的缺点?
一次性触发,触发后需要重新注册;事件通知可能丢失,需客户端兜底;无法递归监听子节点变更。
4、为什么 ZK 是 CP 系统?
Leader 宕机时,集群进入选主阶段,期间不对外提供写服务,甚至暂停读服务,牺牲可用性保证数据一致性。
5、为什么ZK不适合做海量数据存储?
全量数据存储在内存中,内存容量有限;设计目标是存储小体量元数据,不适合大文件;写性能随集群节点数增加而下降。
6、为什么不能用SETNX + EXPIRE两个命令?
两个命令非原子操作,SETNX 成功后客户端崩溃,EXPIRE 未执行,会导致锁永久不释放,引发死锁。
7、怎么解决锁的可重入问题?
通过唯一标识记录锁持有者和重入次数,获取锁时判断归属,重入则次数 + 1,释放时次数 - 1,归零才真正释放锁。
8、怎么防止误删别人的锁?
锁必须绑定客户端唯一标识,释放时先校验归属,且校验和删除必须是原子操作。
9、微服务场景为什么不推荐 2PC?
同步阻塞导致性能差、并发低;<br>TM 单点故障会导致整个系统不可用;<br>跨服务网络不可靠易引发数据不一致;<br>长事务会占用数据库连接,引发数据库性能瓶颈。
10、分布式事务的隔离性怎么保证?
柔性事务无法实现数据库级别的隔离性,需通过业务层面实现,比如乐观锁、悲观锁、资源预留、状态机控制、串行化执行等。
11、热点 key 问题怎么解决?
本地缓存兜底、热点 key 拆分多副本分摊压力、接口级限流、大促前提前预热
12、缓存污染怎么解决?
缓存污染是指冷数据占用缓存内存,导致热点数据被淘汰,解决方案:采用 LRU/LFU 淘汰算法、合理设置 TTL、过滤冷数据、避免缓存大对象
13、多级缓存架构是什么?
标准架构:浏览器缓存 → CDN → 网关缓存 → 服务本地缓存(Caffeine/Guava) → 分布式缓存(Redis) → 数据库,层层拦截流量,保护底层资源
14、什么是雪崩效应?怎么防止?
底层服务故障导致上层服务级联故障,整个系统不可用。防止手段:隔离、熔断、降级、限流、超时控制、合理的重试策略、多副本高可用、资源隔离
15、怎么设计一个高可用的分布式系统?
无状态服务水平扩容,多实例多机房部署
有状态服务多副本集群部署,自动故障转移
全链路隔离设计,防止故障扩散
完善的限流、熔断、降级机制
超时、重试、幂等性全链路控制
可观测性体系与分级告警,故障自愈能力
数据备份与灾备方案,保证数据不丢失
16、分布式全局唯一 ID 生成方案
<br>
17、雪花算法时钟回拨问题怎么解决?
记录上次生成 ID 的时间戳,时钟回拨时,要么等待时钟追上,要么抛出异常,要么通过序列号兜底、机器 ID 扩容,主流框架均已实现该优化
18、分布式接口幂等性保证方案
Token 机制(先获取 Token,请求时校验并删除 Token,保证仅一次请求有效)、唯一键 + 去重表、数据库唯一约束、乐观锁、分布式锁、状态机幂等
19、秒杀系统分布式设计核心要点
全链路限流:前端限流、网关限流、服务层限流、分布式限流
流量削峰:消息队列异步化下单,将瞬时流量削平
库存安全:Redis 预扣库存 + 数据库最终扣减,乐观锁 / 分布式锁保证不超卖
热点处理:热点 key 本地缓存兜底、库存提前预热、隔离设计
高可用保障:熔断降级、关闭非核心功能、多级缓存、多机房部署
一致性保障:事务消息 / TCC 保证订单与库存的最终一致性
20、线上接口超时全链路排查思路
先通过监控大盘,查看接口的错误率、耗时、流量、饱和度,确认是整体超时还是个别请求,是否有报错
通过分布式链路追踪,根据 TraceID 定位超时环节,确认是 RPC 调用、数据库、缓存、外部服务,还是服务本身的问题
查看对应服务的日志,排查异常、报错、慢 SQL、慢调用堆栈
查看服务器、中间件的指标,CPU、内存、磁盘 IO、网络、数据库连接池、缓存命中率、线程池状态
定位根因后针对性解决,比如优化慢 SQL、修复缓存击穿、扩容服务、降级非核心功能、修复依赖服务故障
15、K8S
1、K8S基础与核心概念
1、核心定义与价值
K8S是开源的容器编排引擎,用于自动化部署、扩缩容和管理容器化应用,核心目标是实现应用生命周期的声明式管理和自动化运维
自愈能力、弹性扩缩容、声明式API、基础设施无关、服务发现与负载均衡、滚动更新/回滚、资源隔离与调度优化
2、核心基础概念
Pod
K8S最小的部署单元,由一个或多个容器组成,共享网络命名空间、存储卷、PID命名空间
Node
集群的工作节点(物理机/虚拟机),分为控制平面节点和工作节点
Namespace
集群的逻辑隔离单元,用于划分资源、权限、隔离环境
Label
键值对形式的标签,附加到资源上,用于筛选、分组、关联资源
Annotation
键值对形式的注解,用于存储非标识性元数据,不用于筛选
Selector
标签选择器,用于筛选匹配Label的资源
2、K8S核心架构与组件
K8S采用声明式API的分布式架构,核心分为控制平面(Control Plane/Master 节点)和工作节点(Worker Node),所有组件均通过kube-apiserver通信,etcd作为唯一持久化存储
1、控制平面组件(集群大脑)
负责集群全局决策、调度、状态管理、故障响应
kube-apiserver
集群唯一入口,所有的组件通信中枢,提供RESTful API,负责认证、授权、准入控制,唯一可操作etcd的组件
etcd
分布式键值存储,集群唯一的数据存储,保存集群所有状态、配置、资源对象
kube-scheduler
调度器,监听未调度的Pod,按策略选择最优Node,完成Pod与节点的绑定
kube-controller-manager
控制器管理器,运行一系列控制器,持续监听资源状态,将当前状态向期望状态调节
cloud-controller-manager
云控制器管理器,对接云厂商API,分离K8S核心逻辑与云厂商相关逻辑
2、工作节点组件
负责运行容器化应用,管理Pod生命周期、网络、存储,上报节点和Pod状态
kubelet
节点核心代理,接收apiserver下发的Pod规范,确保容器按规范运行,上报节点和Pod状态
kube-proxy
节点网络代理,实现Service的负载均衡和网络转发,维护节点iptables/ipvs规则
容器运行时(CRI)
负责容器生命周期管理,遵循CRI规范,如containerd、CRI-O
3、K8S核心资源对象
1、工作负载资源
1、ReplicaSet(RS)
副本控制器,确保指定数量的Pod副本始终运行,通过LabelSelector关联Pod
一般不直接使用,由Deployment管理
2、Deployment
声明式管理Pod和RS,支持滚动更新、回滚、扩缩容,最常用的无状态工作负载
无状态应用,如Web服务、API服务、微服务
3、StatefulSet
管理有状态应用,为Pod提供稳定的网络标识、持久化存储、有序的部署/删除/更新
有状态应用,如数据库、Kafka、Redis集群、分布式存储
4、DaemonSet
确保所有/指定Node节点上都运行一个Pod副本,节点加入集群自动创建,移除自动销毁
节点级守护进程,如日志采集、监控代理、网络插件
5、Job
一次性任务,创建Pod直到指定数量的Pod成功完成,任务完成后Pod不再重启
一次性批处理任务、数据处理、备份、脚本执行
6、CronJob
基于时间调度的Job,按Cron表达式定时创建Job执行任务
定时备份、定时报表、定时数据清理
2、服务发现与负载均衡资源
解决Pod IP动态变化的问题,实现服务的固定访问入口、负载均衡与服务发现
1、Service
对一组Pod的抽象,提供固定的访问入口(ClusterIP),通过Label Selector关联后端Pod,屏蔽Pod的IP变化和生命周期变化
1、Cluster IP
默认类型,分配集群内唯一虚拟IP,仅集群内可访问
2、NodePort
在ClusterIP基础上,在每个Node开放固定端口(默认30000-32767),通过<节点IP>:<NodePort>访问,集群内+集群外(节点网络可达)
3、LoadBalancer
在NodePort基础上,对接云厂商负载均衡器,分配公网/私网LB IP,集群内+公网/私网
4、ExternalName
将集群内Service映射到集群外域名,通过CoreDNS实现CNAME转发,无需绑定后端Pod,集群内访问集群外服务,统一访问入口
2、Ingress
集群级资源,定义HTTP/HTTPS流量的转发规则,实现域名路由、路径路由、TLS终止、限流等能力,将集群外流量转发到集群内Service
3、Ingress Controller
Ingress规则的执行者,是反向代理服务(如Nginx、Traefik),持续监听Ingress资源,根据规则更新代理配置,实现流量转发
4、CoreDNS
集群内的DNS服务器,为Service和Pod提供域名解析服务,实现集群内服务发现
解析规则
Service域名
<service名称>.<namespace>.svc.cluster.local,解析为Service的Cluster IP;Headless Service解析为后端所有Pod的IP列表
Pod域名
<pod-ip用-分隔>.<namespace>.pod.cluster.local
3、配置与存储资源
配置资源
1、ConfigMap
存储非敏感配置信息,键值对形式,适用环境变量、配置文件、启动参数
2、Secret
存储敏感信息,base64编码,键值对形式,适用密码、证书、Token、镜像拉取凭证
存储资源
1、Volume
Pod级存储卷,生命周期与pod一致,支持emptyDir、hostPath、pvc等多种类型
emptyDir
临时存储,Pod创建时创建,销毁时删除,同Pod容器共享,适用于临时数据、缓存
hostPath
将节点主机目录挂载到Pod,生命周期独立于Pod,适用于需要访问节点文件的场景
2、PV
持久化卷,集群级存储资源,生命周期独立于Pod,对接NFS、Ceph、云存储等后端
3、PVC
持久化卷声明,用户对存储资源的申请,Namespace级资源,K8S为PVC匹配符合要求的PV并完成绑定
4、StorageClass
存储类,定义存储类型、共给者、参数,实现存储动态共给,无需管理员提前创建大量PV,用户创建PVC时自动创建对应PV
4、K8S核心原理
1、Pod全生命周期管理
生命周期阶段
Pending
已被 apiserver 接收,但未被调度到节点,或镜像未拉取完成
Running
已调度到节点,所有容器已创建,至少有一个容器正在运行 / 启动 / 重启
Succeeded
所有容器都成功执行完成,且不会重启,通常为 Job 的 Pod
Failed
所有容器都已退出,至少有一个容器以非 0 状态退出
Unknown
无法获取 Pod 状态,通常是节点通信故障
生命周期关键环节
Init 容器
主容器启动前执行的容器,必须全部执行成功主容器才会启动,多个 Init 容器串行执行。适用场景:初始化配置、等待依赖服务就绪、权限准备、数据预加载。
容器钩子
PostStart(容器创建后执行,用于初始化、注册服务)、PreStop(容器终止前执行,用于优雅关闭、保存数据)。
容器重启策略
Always(默认,容器退出总是重启,适用于长期运行服务)、OnFailure(非 0 退出时重启,适用于 Job)、Never(永不重启)
三大健康检查探针
StartupProbe(启动探针)
检测容器是否启动完成,保护慢启动应用
执行时机:容器启动后执行,首次成功后停止,由 Liveness/Readiness 接管
失败后重启容器
适用启动慢的应用(Java、数据库),避免被 Liveness 探针误杀
LivenessProbe(存活探针)
检测容器是否正常存活
执行时机:StartupProbe 成功后,持续周期性执行
失败后重启容器
适用检测应用死锁、崩溃、无响应,实现自愈
ReadinessProbe(就绪探针)
检测容器是否可以正常处理请求
执行时机:StartupProbe 成功后,持续周期性执行
失败后将 Pod 从 Service 后端 Endpoint 中移除,停止接入流量
适用检测应用启动完成、依赖就绪、负载过高,避免转发异常请求
探针检测方式:ExecAction(执行命令)、TCPSocketAction(TCP 端口检测)、HTTPGetAction(HTTP GET 请求)、GRPCAction(gRPC 健康检测)
2、调度原理
kube-scheduler 核心职责是为未调度的 Pod 选择最优节点,分为三个核心阶段:预选阶段→优选阶段→绑定阶段。<br>
核心阶段
1、监听阶段:通过 watch 机制持续监听未调度的 Pod(spec.nodeName 为空)。<br>
2、预选阶段:过滤掉不符合要求的节点(资源不足、端口冲突、污点不匹配、亲和性不满足等),筛选出可运行 Pod 的节点列表。<br>
3、优选阶段:对预选通过的节点按预设策略打分(0-100 分),得分最高的节点为最优节点。<br>
4、绑定阶段:调用 apiserver 将 Pod 的 spec.nodeName 设置为目标节点名称,完成绑定,目标节点 kubelet 接管 Pod 创建。<br>
影响调度的核心配置
1、nodeSelector:最简单的节点选择方式,通过 Label 硬限制 Pod 调度到匹配的节点。
2、节点亲和性(NodeAffinity):比 nodeSelector 更灵活,支持硬限制 / 软限制、多条件组合。<br>
3、Pod 亲和性 / 反亲和性:根据已运行 Pod 的 Label,决定当前 Pod 的调度位置,实现协同部署或隔离,提升高可用性。<br>
4、污点(Taint)与容忍(Toleration):污点是节点的排斥属性,容忍是 Pod 的适配属性,实现节点隔离、专用节点、故障驱逐。污点效果分为 NoSchedule(硬限制不调度)、PreferNoSchedule(软限制)、NoExecute(不调度 + 驱逐无容忍的运行中 Pod)。<br>
5、拓扑分布约束:控制 Pod 在节点、可用区等拓扑域中的均匀分布,避免单点故障,K8S 1.19 + 稳定。<br>
6、requests/limits:requests 是调度的核心依据,调度器仅会将 Pod 调度到剩余可用资源≥requests 的节点。<br>
3、K8S网络原理
K8S 网络模型核心是IP-per-Pod,每个 Pod 有独立 IP,Pod 之间直接通信,无需 NAT,网络扁平化。<br>
1、四大网络通信场景<br>
1、同节点 Pod 通信:通过 CNI 创建的虚拟网桥二层转发,Pod 的 veth pair 接入网桥,直接通信。<br>
2、跨节点 Pod 通信:通过 CNI 插件实现,分为两种主流方案:<br>
Overlay 网络:通过 VXLAN/IPIP 隧道封装,将 Pod 数据包封装到节点 IP 包中传输,兼容性好,有轻微性能损耗,代表插件 Flannel。<br>
Underlay 网络:通过 BGP 路由协议直接路由,无需隧道封装,性能接近原生网络,对底层网络有要求,代表插件 Calico。<br>
3、Pod 访问 Service:通过节点上 kube-proxy 维护的 iptables/ipvs 规则,将请求 DNAT 到后端 Pod,实现负载均衡。<br>
4、集群外访问集群内服务:主流方式为 NodePort、LoadBalancer、Ingress、专线 / VPN 接入。<br>
2、CNI 插件对比
1、Flannel
Overlay/Host-GW模式
轻量、简单、稳定,仅提供网络连通性,无网络策略能力
适用测试环境、简单集群
2、Calico
Overlay/Underlay(BGP)模式
功能丰富,支持网络策略、BGP 路由、ACL,性能好,生产环境主流
适用生产环境、需要网络隔离、对性能有要求的集群
3、Cilium
eBPF模式
基于 eBPF 实现,高性能,可替代 kube-proxy,支持高级网络功能、可观测性
大规模集群、对性能和可观测性要求高的场景
3、kube-proxy 三种工作模式
userspace
用户空间代理,请求经内核转发到用户空间 kube-proxy,再转发到 Pod
兼容性好
性能极差,已废弃
iptables
内核空间模式,kube-proxy 生成 iptables 规则,内核直接完成 DNAT 转发
性能优于 userspace,内核原生支持,默认模式
大规模集群下规则线性增长,匹配性能下降,仅支持轮询算法
ipvs
内核空间模式,基于内核 IPVS 模块实现负载均衡
性能极高,大规模集群下无性能衰减,支持多种负载均衡算法,规则更新无延迟
需要内核开启 IPVS 模块,兼容性略低于 iptables
4、资源管理与 QoS 等级
requests
容器申请的资源量
调度的核心依据,仅会调度到剩余资源≥requests 的节点
节点资源不足时,作为 QoS 等级和驱逐优先级的判断依据
limits
容器运行时的最大资源上限
不影响调度
超过 CPU limits 会被限流,超过内存 limits 会被 OOMKill
Pod 的 QoS 等级<br>
1、Guaranteed(有保证)
所有容器的 CPU 和内存的 requests=limits,且都不为 0
优先级最高
最后被驱逐
核心生产应用、数据库、中间件
2、Burstable(突发型)
至少有一个容器设置了 CPU / 内存的 requests 或 limits,不满足 Guaranteed 要求
优先级中等
中间被驱逐
普通业务应用
3、BestEffort(尽力而为)
所有容器都没有设置 CPU 和内存的 requests 和 limits
优先级最低
最先被驱逐
测试应用、非核心任务
5、K8S安全体系
K8S 安全体系遵循纵深防御原则,核心分为三大环节:认证(Authentication)→授权(Authorization)→准入控制(Admission Control),所有对 apiserver 的请求都必须经过这三个环节的校验。
1、认证(Authentication)
验证请求者的身份,确认 “你是谁”。K8S 不管理普通用户账户,仅管理 ServiceAccount(Pod 使用的服务账户)。<br>
常用认证方式:X509 客户端证书(管理员 / 组件常用)、ServiceAccount Token(Pod 访问 apiserver 使用)、OIDC(企业 SSO 对接)、Bearer Token、Webhook Token 认证。<br>
2、授权(Authorization)
验证已认证的用户是否有权限执行对应操作,确认 “你可以做什么”,RBAC(基于角色的访问控制) 是默认启用、最常用的模式。<br>
RBAC 核心 4 个资源对象<br>
Role
Namespace 级
定义 Namespace 内的权限集合
ClusterRole
集群级
定义集群级资源、跨 Namespace 资源的权限集合
RoleBinding
Namespace 级
将 Role/ClusterRole 绑定到用户 / ServiceAccount,仅在当前 Namespace 生效
ClusterRoleBinding
集群级
将 ClusterRole 绑定到用户 / ServiceAccount,全集群生效
3、准入控制(Admission Control)<br>
apiserver 的请求拦截器,在请求经过认证、授权之后,写入 etcd 之前执行,实现配置修改、合规校验、安全管控。<br>
Mutating Admission Webhook(变更型):修改请求内容,如注入 Sidecar、设置默认资源 requests/limits。<br>
Validating Admission Webhook(验证型):校验请求合法性,不符合规则则拒绝,如禁止 root 用户运行、禁止使用 latest 镜像。<br>
执行顺序:先执行所有 Mutating Webhook,再执行所有 Validating Webhook。<br>
4、其他核心安全能力<br>
网络策略(NetworkPolicy):Namespace 级资源,定义 Pod 之间的网络访问规则,实现三层 / 四层网络隔离,默认集群内 Pod 全通,可通过 NetworkPolicy 实现白名单管控。<br>
Pod 安全标准(PSS):替代已废弃的 PodSecurityPolicy(PSP),分为 Privileged(特权级)、Baseline(基线级)、Restricted(受限级)三个安全等级,实现 Pod 运行时的安全管控。<br>
Pod Security Context:Pod / 容器级安全配置,定义运行用户、权限、Capabilities、Seccomp 等,实现容器最小权限运行。<br>
镜像安全:私有镜像仓库、镜像签名验证、漏洞扫描、镜像拉取策略 Always。<br>
etcd 安全:TLS 加密通信、数据加密、访问控制、定期备份。<br>
6、K8S运维与排障
1、核心 kubectl 命令
1、排障核心命令
# 查看资源详细信息与事件,排障第一步<br>kubectl describe <资源类型> <资源名称> -n <命名空间><br># 查看Pod日志,-f实时跟踪,--previous查看上一次崩溃日志<br>kubectl logs <pod名称> -n <命名空间> -c <容器名称> --previous<br># 进入Pod容器内排查<br>kubectl exec -it <pod名称> -n <命名空间> -- /bin/bash<br># 查看节点/Pod资源使用率<br>kubectl top node/pod -A<br># 查看集群事件,定位异常原因<br>kubectl events -n <命名空间> --for <资源类型>/<资源名称>
2、运维核心命令
# 声明式创建/更新资源,生产环境推荐<br>kubectl apply -f <yaml文件><br># 管理滚动更新/回滚<br>kubectl rollout status/history/undo deployment/<名称>
2、集群部署与高可用
主流部署方式:kubeadm(官方推荐,生产 / 测试通用)、二进制部署(可控性高,学习用)、云厂商托管集群(ACK/TKE/EKS,生产环境主流)、minikube/kind(本地测试)。<br>
集群高可用核心<br>
1、控制平面高可用:至少 3 个控制平面节点,apiserver 前加负载均衡器。<br>
2、etcd 高可用:Raft 算法要求奇数节点,至少 3 个节点,分为堆叠式(与控制平面同机)和外部独立集群。<br>
3、工作节点高可用:多可用区部署,避免单点故障。<br>
etcd 备份与恢复
1、备份:etcdctl snapshot save <备份文件路径> --endpoints=<etcd地址> --cacert=<ca证书> --cert=<客户端证书> --key=<客户端密钥><br>
2、恢复:etcdctl snapshot restore <备份文件路径> --data-dir=<etcd数据目录>,恢复后需重启 etcd 集群和 apiserver。<br>
3、监控与可观测性<br>
监控体系:Prometheus + Grafana 是事实标准,核心组件:node-exporter(节点指标)、kube-state-metrics(K8S 资源指标)、metrics-server(资源指标 API,用于 HPA/kubectl top)、Alertmanager(告警管理)。<br>
日志体系:主流 EFK 栈(Elasticsearch + Fluent Bit + Kibana),生产环境常用 DaemonSet 方式采集节点容器日志。<br>
7、K8S进阶特性与云原生生态
1、自动扩缩容
HPA(水平 Pod 自动扩缩容):自动调整 Pod 副本数,基于 CPU / 内存、自定义业务指标(QPS、请求延迟)、外部指标实现自动扩缩容,是最常用的弹性方案。<br>
VPA(垂直 Pod 自动扩缩容):自动调整 Pod 的 CPU / 内存 requests/limits,适用于无法水平扩缩容的有状态应用。<br>
CA(集群自动扩缩容):自动调整集群节点数量,Pod 无法调度时自动扩容节点,节点利用率低时自动缩容,云环境常用。<br>
2、CRD 与 Operator
CRD(自定义资源定义):扩展 K8S API,自定义新的资源类型,让自定义资源和原生资源一样支持声明式管理、版本控制、RBAC 权限。<br>
Operator:封装了应用运维最佳实践的控制器,由CRD + 自定义控制器组成,持续监听自定义资源的状态,执行应用的部署、扩缩容、备份、升级、故障恢复等运维操作,实现复杂应用的全生命周期自动化管理。<br>
常用 Operator:Prometheus Operator、MySQL Operator、Redis Operator、Kafka Operator。<br>
3、容器运行时深度原理
CRI(容器运行时接口):K8S 定义的容器运行时标准接口,kubelet 通过 CRI 与容器运行时通信,解耦 kubelet 和容器运行时。<br>
OCI(开放容器倡议):定义容器镜像和运行时的行业标准,runc 是 OCI 运行时规范的参考实现。<br>
4、云原生生态核心组件
服务网格 Service Mesh:以 Istio 为代表,通过 Sidecar 模式代理服务流量,提供流量管理、安全、可观测性、限流、熔断、灰度发布等能力,无需修改业务代码。分为控制面(Istiod)和数据面(Envoy Sidecar)。<br>
GitOps:基于 Git 的持续交付模式,将集群期望状态存储在 Git 仓库,通过 Argo CD/Flux CD 自动同步到集群,实现声明式、可审计、可回滚的应用交付。<br>
集群管理平台:Rancher、KubeSphere,降低 K8S 使用门槛,提供多集群管理、可视化界面、DevOps、微服务治理等能力。<br>
8、高频面试题
1、为什么K8S选择Pod作为最小调度单元?
1、解决紧密耦合的容器协同问题,主容器+Sidecar可共享网络和存储,无需端口映射即可本地通信;<br>2、实现原子调度,避免跨节点调度导致的协同失败;<br>3、统一生命周期管理,同Pod容器同步创建、销毁。
2、Namespace的隔离能力边界?
Namespace是逻辑隔离而非物理隔离。可隔离:资源配额、RBAC权限、资源命名唯一性;不可隔离:节点、底层网络(默认跨Namespace Pod互通,需NetworkPolicy限制)、PV、集群级资源。
3、Pod从创建到运行的完整流程?
1、用户通过kubectl/API客户端提交Pod/Deployment创建请求到kube-apiserver;<br>2、apiserver执行认证->授权->准入控制,验证请求合法性,通过后将资源对象写入etcd;<br>3、apiserver返回创建成功给客户端,etcd完成数据持久化;<br>4、kube-scheduler通过watch机制,监听到未绑定节点的Pod;<br>5、scheduler执行调度流程:预选阶段过滤不符合要求的节点->优选阶段对剩余节点打分排序,选出最优节点->绑定阶段将Pod与目标节点绑定,信息写入apiserver并持久化到etcd;<br>6、目标节点的kubelet通过watch机制,监听到调度到本节点的Pod;<br>7、kubelet通过CRI接口调用容器运行时,拉取镜像、创建并启动容器,执行init容器->主容器的初始化流程;<br>8、kubelet执行Pod健康检查(StartupProb->LivenessProbe/ReadinessProbe),确认容器正常运行;<br>9、kubelet将Pod状态更新到apiserver,持久化到etcd;<br>10、若Pod关联了Service,EndPointSlice控制器更新对应EndpointSlice,kube-proxy根据规则更新节点转发规则,完成服务访问配置。
4、ConfigMap热更新原理?
ConfigMap通过volume挂载到Pod时,kubelet会定期同步ConfigMap内容,更新后自动更新挂在目录中的配置文件,应用监听文件变化即可实现热更新。不支持热更新的场景:环境变量挂载、subPath挂载、不可变ConfigMap
5、PV与PVC的生命周期?
共给(静态/动态)->绑定->使用->回收
6、PV回收策略?
Retain(保留,需管理员手动清理)、Delete(删除,PVC删除后PV和数据自动删除)、Recycle(已废弃)
7、PV访问模式?
RWO(单节点读写)、ROX(多节点只读)、RWX(多节点读写)、RWOP(单Pod读写,K8S 1.27+ 稳定)
8、普通用户与 ServiceAccount 的区别?
普通用户给人 / 外部客户端使用,全局跨 Namespace,由外部身份系统管理;ServiceAccount 给 Pod 内程序使用,Namespace 级,由 K8S 管理,通过 Token 认证,自动挂载到 Pod。
9、Pod 一直处于 Pending 状态,怎么排查?
1、第一步:执行kubectl describe pod <pod名称>查看 Events 字段,90% 的问题可在此定位。<br>2、调度失败常见原因:节点资源不足、亲和性 / NodeSelector 不匹配、污点与容忍不匹配、端口冲突、PVC 绑定失败。<br>3、镜像拉取失败常见原因:镜像名称 / 标签错误、私有仓库未配置 imagePullSecret、节点网络无法访问镜像仓库、拉取超时。<br>4、其他原因:Namespace 的 ResourceQuota 已满。
10、Pod 一直处于 CrashLoopBackOff 状态,怎么排查?
1、第一步:kubectl describe pod查看退出码、退出原因(如 OOMKilled)。<br>2、第二步:kubectl logs <pod名称> --previous查看上一次启动的日志,定位应用崩溃原因。<br>3、常见原因:OOMKilled(内存超过 limits)、启动命令 / 配置错误、健康检查失败、应用程序崩溃、容器启动后立即退出、权限不足。
11、Pod 处于 Running 状态,但服务访问不通,怎么排查?
1、第一步:进入 Pod 内本地访问,排除应用本身问题(如仅监听 127.0.0.1、端口配置错误)。<br>2、第二步:检查 ReadinessProbe 是否成功,失败会导致 Pod 被从 Service 后端移除。<br>3、第三步:检查 Service 的 Selector 是否匹配 Pod 的 Label,EndpointSlice 是否有对应的 Pod IP 和端口。<br>4、第四步:检查 Pod 网络连通性、NetworkPolicy 是否限制访问、kube-proxy 是否正常运行、iptables/ipvs 规则是否正确。<br>5、第五步:集群外访问需检查 NodePort/LoadBalancer/Ingress 配置、防火墙是否放行。
12、Node 节点处于 NotReady 状态,怎么排查?
1、第一步:登录节点,检查基础环境:磁盘是否满、内存 / CPU 是否耗尽、网络是否通、时间是否同步。<br>2、第二步:检查 kubelet 是否正常运行,查看 kubelet 日志journalctl -u kubelet -f定位报错。<br>3、第三步:检查容器运行时(containerd)是否正常运行,crictl ps是否可正常执行。<br>4、第四步:检查节点与 apiserver 的网络连通性、内核参数是否正确、Swap 是否关闭、是否有节点压力污点。
13、简述 K8S 的整体架构和各组件的作用?
K8S 分为控制平面和工作节点。<br>控制平面是集群大脑,包含:kube-apiserver(集群唯一入口,API 服务,认证授权)、etcd(集群唯一持久化存储,保存所有集群状态)、kube-scheduler(Pod 调度,选择最优节点)、kube-controller-manager(运行各类控制器,实现状态调谐)、cloud-controller-manager(对接云厂商基础设施)。<br>工作节点负责运行应用,包含:kubelet(节点代理,管理 Pod 生命周期,上报状态)、kube-proxy(实现 Service 的负载均衡和网络转发)、容器运行时(CRI,管理容器生命周期,如 containerd)。
14、Deployment 和 StatefulSet 的核心区别?
核心区别是 Deployment 用于无状态应用,StatefulSet 用于有状态应用,具体差异:<br>1. 网络标识:Deployment 的 Pod 名称、IP 随机;StatefulSet 的 Pod 名称固定,通过 Headless Service 提供固定 DNS 域名。<br>2. 存储:Deployment 的 Pod 共享存储;StatefulSet 通过 volumeClaimTemplates 为每个 Pod 创建独立 PVC,Pod 重建存储不变。<br>3. 部署更新:Deployment 并发创建 / 更新;StatefulSet 有序部署、有序删除、有序更新,支持分区更新。
15、三个探针(Liveness、Readiness、Startup)的区别和适用场景?
1. StartupProbe:检测容器是否启动完成,仅在启动时执行,成功后停止,失败重启容器,适用于慢启动应用,避免被 Liveness 误杀;<br>2. LivenessProbe:检测容器是否存活,持续执行,失败重启容器,实现自愈,适用于检测应用死锁、崩溃;<br>3. ReadinessProbe:检测容器是否可以处理请求,持续执行,失败则切断流量,适用于避免将流量转发到无法处理请求的 Pod。
16、K8S 的 RBAC 核心组件是什么?分别有什么作用?
RBAC 是基于角色的访问控制,核心 4 个组件:<br>1. Role:Namespace 级,定义 Namespace 内的权限集合;<br>2. ClusterRole:集群级,定义集群级 / 跨 Namespace 的权限集合;<br>3. RoleBinding:Namespace 级,将 Role/ClusterRole 绑定到用户 / ServiceAccount,仅在当前 Namespace 生效;<br>4. ClusterRoleBinding:集群级,将 ClusterRole 绑定到用户 / ServiceAccount,全集群生效。
17、什么是 Operator?和 CRD 的关系?
Operator 是封装了应用运维最佳实践的控制器,由 CRD + 自定义控制器组成。<br>CRD 是扩展 K8S API,自定义新的资源类型,定义应用的期望状态;<br>Operator 的自定义控制器持续监听 CRD 的状态,执行应用的部署、升级、备份、故障恢复等运维操作,实现应用的全生命周期自动化管理,将当前状态调谐到期望状态。
评论
0 条评论
下一页