ThreadLocal之强、弱、软、虚引用
ThreadLocal内存泄漏的核心原因是:当ThreadLocal对象失去强引用后,其对应的value值因线程存活而无法被及时回收
hreadLocal 的底层数据结构主要由 Thread 类内部维护的 ThreadLocalMap 实现,该结构基于数组的哈希表(Entry[] 数组)来存储键值对,其中每个键是 ThreadLocal 实例,值是线程私有的数据。12
核心数据结构详解
-
ThreadLocalMap 的数组基础:
ThreadLocalMap并未实现标准的Map接口,而是通过一个Entry数组(private Entry[] table)作为底层存储。数组的大小始终是 2 的幂次方(如 16、32 等),以优化哈希计算效率。13 -
Entry 的定义:
Entry是ThreadLocalMap的静态内部类,继承自WeakReference>,表示一个键值对:key:弱引用的ThreadLocal实例(用于标识数据归属)。value:实际存储的线程私有数据(如HashMap或自定义对象)。3
-
哈希冲突处理:当多个
ThreadLocal映射到数组同一索引时,采用开放地址法(线性探测)解决冲突:- 通过
ThreadLocal的threadLocalHashCode & (len - 1)计算初始索引。 - 若该位置已有数据,且
key不匹配,则依次检查下一个位置(i = nextIndex(i, len)),直到找到匹配的key或空位。13
- 通过
关键机制与流程
-
初始化与存储:
- 每个
Thread对象在首次调用ThreadLocal.set()时,会创建ThreadLocalMap(通过createMap()方法),初始容量为 16。23 set()操作会计算ThreadLocal的哈希值,定位数组下标,若位置为空则直接插入;若key已存在则覆盖value;若发生冲突则线性探测插入。12
- 每个
-
获取与查找:
get()方法通过相同哈希算法定位初始下标,遍历数组比较key是否匹配,若未找到则返回null。1
-
内存管理:
Entry.key使用弱引用,避免ThreadLocal实例无法被回收(即防止内存泄漏)。当ThreadLocal无强引用时,其对应的Entry会被垃圾回收,但value可能仍残留,需通过expungeStaleEntry()等机制清理
3.弱引用
弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。
使用场景
- Handler防止内存泄漏:使用WeakReference包装Context
- 异步任务:RxJava、Thread、AsyncTask中持有Context时使用弱引用
- 静态变量和单例模式:避免静态成员长期持有Activity引用
2.软引用
软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
对于只有软引用的对象来说,
当系统内存充足时它 不会 被回收,
当系统内存不足时它 会 被回收。
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!
使用场景
用较多内存的对象,例如图片、音视频等,也适合使用软引用。当应用需要该对象时,可以从软引用中获取该对象并使用,否则可以在稍后重新创建它
synchronized与Reentrantlock的区别
- synchronized是一个关键字,属于JVM层面的,Reentrantlock是一个类,是API层面的;
- synchronized是自动加锁、释放锁,Reentrantlock则需要手动加锁和释放;
- synchronized底层有一个锁升级的过程(偏向锁—轻量级锁----重量级锁)
当一个线程获取一个琐时,此时该锁为偏向锁,当第二个线程尝试获取锁时,该锁会升级为轻量级锁,底层通过自旋来实现(不会造成线程阻塞),当自旋次数过多,会升级为重量级锁,会造成线程阻塞。
ReentrantLock是Java并发编程中的重要锁机制,相比synchronized关键字功能更丰富、灵活性更高
核心特性
可重入性:同一个线程可以重复获取同一把锁,避免死锁
公平性选择:支持公平锁和非公平锁两种模式
- 公平锁:严格按照线程请求顺序分配锁资源,新线程会判断AQS队列是否有等待线程,如果有则加入队列尾部等待
- 非公平锁:允许插队抢占锁资源,不管队列是否有线程等待都会先尝试直接获取锁
高级功能:
- 等待可中断:线程在等待锁的过程中可以响应中断
- 锁绑定多个条件:一个ReentrantLock可以绑定多个Condition对象,实现精细的线程协作
- 超时获取锁:支持尝试获取锁并设置超时时间
与synchronized对比
| 特性 | ReentrantLock | synchronized |
|---|---|---|
| 实现层面 | JDK API级别 | JVM内置 |
| 锁释放 | 必须手动unlock() | 自动释放 |
| 公平性 | 可配置 | 只能非公平 |
| 条件变量 | 支持多个Condition | 单路通知 |
| 中断支持 | 支持 | 不支持 |
优先使用synchronized的情况:
- 简单的同步需求
- 低并发场景
- 代码简洁性优先
选择ReentrantLock的情况:
- 需要尝试非阻塞获取锁
- 要求响应中断或超时机制
- 复杂的并发控制场景
java怎么解决ABA问题
版本号是一种常见的方法,它允许你存储关于数据更改的额外信息。例如,可以使用AtomicStampedReference类。这个类不仅存储一个值,还存储一个标记(通常是整数),每次更新值时可以同时更新这个标记。
AtomicStampedReference通过引入版本戳(stamp)机制,在CAS操作中同时验证值和版本号,从而解决ABA问题。
核心原理
ABA问题是指线程A修改值后又改回原值,线程B误判为未变化。AtomicStampedReference为每个值附加一个版本号(stamp),每次修改时版本号递增。即使值相同,若版本号不同,CAS操作也会失败,避免误判。 12
关键实现
- Pair结构:内部使用
Pair存储值和版本号,保证不可变性。 - CAS方法:
compareAndSet需同时匹配预期值和版本号,若不匹配则更新失败。 - 版本号递增:每次修改自动增加版本号,确保状态变化可追踪
使用时间戳
使用时间戳来代替版本号。Java中的System.nanoTime()或System.currentTimeMillis()可以用来获取时间戳。这种方法类似于版本号,但它依赖于时间的流逝,可能在极端情况下(如系统时间被调整)出现问题
4. 重试机制与乐观锁
在某些情况下,简单地重试操作可能就足够了。如果操作失败(因为ABA问题),可以简单地重试直到成功。这通常用在读多写少的场景中
1. AQS基础概念
AQS(AbstractQueuedSynchronizer)是Java并发包(JUC)的核心框架,用于构建锁和同步器。它通过维护一个同步状态(state)和一个FIFO等待队列(CLH队列)来实现线程同步。AQS提供两种同步模式:独占模式(如ReentrantLock)和共享模式(如Semaphore)。
2. 核心机制
- 状态管理:通过
state变量(volatile修饰)标识资源占用状态,使用CAS操作保证原子性。 - 队列管理:未获取到锁的线程会被封装为
Node节点加入等待队列,通过LockSupport.park()阻塞线程。 - 模板方法:子类需实现
tryAcquire(获取锁)和tryRelease(释放锁)方法,AQS处理线程排队和唤醒逻辑。
3. 常见面试题解析
3.1 AQS全称与功能
- 全称:AbstractQueuedSynchronizer(抽象队列同步器)。
- 功能:提供锁的通用实现框架,支持公平/非公平锁、可重入锁等。
3.2 核心方法
acquire():获取锁,失败则加入等待队列。release():释放锁,唤醒后续线程。signal():唤醒等待线程。
3.3 实现原理
- 独占模式:以ReentrantLock为例,通过
state记录重入次数,线程独占资源。 - 共享模式:如Semaphore,
state表示可用许可数,允许多线程共享资源。
4. 应用场景
- 构建锁:ReentrantLock、ReadWriteLock。
- 同步工具:CountDownLatch、Semaphore。
- 线程通信:通过
Condition实现等待/唤醒机制
AQS中的state变量是同步状态的核心标识,其具体含义由不同的同步器实现决定
核心作用:
- 资源状态管理:通过
volatile int state表示资源占用情况,保证多线程间的可见性 - 原子操作:通过CAS(Compare-And-Swap)操作确保状态修改的线程安全
不同同步器中的具体应用:
- ReentrantLock:state表示锁的重入次数(0=空闲,1=占用,>1=重入)
- Semaphore:state表示当前可用的许可数量
- CountDownLatch:state表示计数器的当前值
工作流程:线程通过CAS尝试修改state,成功则获取资源,失败则进入等待队列
什么是高并发
高并发指的是系统在同一时间能够处理大量用户请求的能力。比如双十一购物节、12306抢票等场景,都会在短时间内产生海量访问请求。
核心特点:
- 同时性:大量用户在同一时刻访问系统
- 高负载:系统需要快速响应海量请求而不崩溃
- 技术支撑:需要专门的技术架构来保障系统稳定运行
实际应用场景:
• 电商促销活动(如双十一秒杀)
• 在线直播平台的大型赛事转播
• 多人在线游戏
• 社交网络热门话题讨论
技术实现手段:
通过负载均衡、缓存技术、异步处理等多种技术手段,确保系统在高流量时期仍能保持快速响应和服务稳定
高并发系统设计核心原则
高并发系统需满足三个条件:高性能(低延迟、高吞吐)、高可用性(99.9%以上稳定性)、可扩展性(水平扩容能力)。设计时需从架构分层、数据存储、流量控制等多维度综合优化。
关键技术实现方案
1. 负载均衡
- L4层:使用LVS、F5处理TCP流量,适合大流量场景
- L7层:Nginx、HAProxy实现HTTP请求分发,支持动态权重调整
- 云服务:阿里云SLB、AWS ELB提供自动扩缩容能力。
负载均衡4层F5面试题
1. 四层负载均衡基础
四层负载均衡基于IP+端口进行流量分发,工作在OSI模型的传输层(TCP/UDP),不解析应用层数据,因此具有低延迟、高吞吐的特点。F5的LTN(Local Traffic Manager)模块即支持四层负载均衡,适用于对性能要求高但无需应用层处理的场景(如数据库集群、游戏服务器)5。
2. F5四层负载均衡核心算法
- 轮询(Round Robin) :按顺序分配请求,简单但忽略服务器实际负载4。
- 最少连接(Least Connections) :将新请求分配给当前连接数最少的服务器,适合长连接场景12。
- 最短响应(Fastest Response) :通过探测服务器响应时间选择最优节点,需配合健康检查1。
3. 健康检查与会话保持
- 健康检查:F5通过ICMP、TCP或HTTP协议定期探测服务器状态,自动剔除故障节点。
- 会话保持:四层通常通过源IP哈希或TCP会话粘性实现,确保同一客户端请求始终指向同一服务器。
4. 四层与七层负载均衡对比
- 四层:性能高(如F5 BIG-IP可达200万QPS5),但功能单一,适用于纯流量分发。
- 七层:支持HTTP/HTTPS协议解析,可基于URL、Cookie等智能路由,但性能较低。
F5通过健康检查机制定期探测服务器状态,确保流量只分发到正常运行的服务器。主要使用以下几种探测方式:
🔍 健康检查类型
ICMP检查:通过发送ICMP回送请求报文检测服务器网络连通性1。这是最基础的检查方式,只能判断服务器是否可达。
TCP-HALF-OPEN检查:F5发送SYN包,收到服务器的SYN_ACK响应后,用RST包重置连接2。这种方式包数量少,效率高,默认探测间隔5秒,超时时间16秒。
TCP完整连接检查:通过发送FIN包正常中断连接,比TCP-HALF-OPEN更规范但开销稍大2。
⚙️ 配置与监控
在F5管理界面中,可以通过以下路径配置健康检查:
- Local Traffic → Pools → 选择池 → Health Monitors
- 支持自定义探测间隔、超时时间和重试次数
📊 状态查看
GUI方式:登录F5管理界面,在"Local Traffic→Nodes/Pools"中查看状态:
- 绿色:节点状态为"UP"
- 红色:节点状态为"Down"
- 灰色:节点被禁用
F5怎么判断最快响应的服务器
F5通过以下方式判断最快响应的服务器:
- 实时监测:持续统计各服务器的响应时间(如HTTP请求耗时或ICMP延迟)
- 动态选择:将新请求分配给当前平均响应时间最短的服务器
- 健康检查:自动排除故障服务器,仅对健康节点进行响应时间统计
5. 高频面试题示例
- Q:F5四层负载均衡如何选择服务器?
A:根据算法(如最少连接)或响应时间动态选择,需结合健康检查排除故障节点12。 - Q:四层负载均衡的优缺点?
A:优点为高性能、低延迟;缺点是无法处理应用层逻辑(如URL重写)。
F5通过以下机制判断连接最少的服务器:
-
健康检查基础
F5内置多种健康检查方式(如ICMP、TCP-HALF-OPEN、HTTP等),定期探测服务器状态。若服务器未通过检查,则标记为不可用,停止向其分配流量3。 -
最少连接算法
- 实时监控各服务器的活跃连接数。
- 将新请求自动分配给当前连接数最少的服务器,实现负载均衡
2. 缓存策略
- 多级缓存:浏览器缓存+CDN+本地缓存+Redis,减少数据库压力
- 更新机制:热点数据采用事件驱动更新(如库存变更实时同步)
3. 异步处理
- 消息队列:Kafka/RabbitMQ解耦耗时任务(如订单通知)。
- 无状态设计:JWT+Redis实现会话共享,支持横向扩展。
4. 数据库优化
- 读写分离:MySQL主从复制提升读性能。
- 分库分表:ShardingSphere按业务拆分数据,单表控制在千万级
典型架构案例
电商秒杀系统通常采用:
- 接入层:Nginx限流+WAF防护
- 应用层:Spring Cloud微服务拆分
- 数据层:Redis预减库存+MySQL异步落单
如何实现分布式锁以避免超卖?
在高并发系统中,通过分布式锁可以有效防止库存超卖问题。以下是几种主流实现方案:
Redis分布式锁(推荐方案)
基于Redis的SETNX命令实现分布式锁是最常用的方案4:
javaCopy Code
// 加锁:设置键值对并指定过期时间
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);
// 解锁:使用Lua脚本保证原子性
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
关键要点:
- 唯一标识:每个锁使用唯一requestId,避免误删其他线程的锁
- 自动过期:设置合理的过期时间,防止死锁
- 原子操作:使用Lua脚本确保检查与删除的原子性
java怎么保证不重复消费
在Java中保证消息不重复消费的核心思路是实现消息处理的幂等性,即无论同一条消息被消费多少次,最终结果都与消费一次相同12。
💡 主要解决方案
1. 数据库唯一约束
利用数据库的唯一索引来防止重复处理:
- 在消息表中为关键字段(如订单号、消息ID)建立唯一索引
- 处理消息时先尝试插入,如果插入失败说明已处理过
2. 唯一消息标识
为每条消息生成唯一ID,并在处理前进行校验25:
- 生产者发送消息时附加唯一标识(如UUID)
- 消费者维护已处理消息ID的缓存(Redis或内存Map)
- 处理前先查询该ID是否已存在
3. 业务状态检查
根据业务数据的当前状态判断是否需要处理2:
- 如订单超时未支付消息,消费前先检查订单是否已支付
- 如果已支付就跳过,否则执行取消操作
4. 分布式锁机制
使用分布式锁确保同一时刻只有一个消费者处理某条消息
- 以消息唯一标识作为锁的key
- 获取锁成功才处理消息,否则跳过
数据库分布式锁
悲观锁方案
sqlCopy Code
SELECT * FROM inventory WHERE product_id = 1 FOR UPDATE;
UPDATE inventory SET stock = stock - 1 WHERE product_id = 1;
乐观锁方案6
sqlCopy Code
UPDATE inventory
SET stock = stock - 1, version = version + 1
WHERE product_id = 1 AND version = old_version AND stock > 0;
消息队列串行化
通过内存队列将请求排队和串行化,降低事务并发操作3:
- 生产端:扣减内存库存初步过滤请求
- 消费端:固定速度消费队列,过滤超时请求后再扣减Redis库存
方案对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Redis锁 | 高频读写场景 | 性能高,实现简单 | 需处理网络分区问题 |
| 数据库悲观锁 | 强一致性要求 | 数据强一致 | 性能瓶颈明显 |
| 数据库乐观锁 | 低并发场景 | 无锁冲突 | 高并发下重试率高 |
| 消息队列 | 秒杀等高并发场景 | 削峰填谷,系统稳定 | 架构复杂,延迟较高 |
实践建议
💡 立即行动:从Redis分布式锁入手,结合Lua脚本确保原子性,这是平衡性能与复杂度的最佳起点。可以先在测试环境模拟高并发场景验证锁的有效性。
解释下什么是JVM 通过将Java字节码转换为特定平台的机器指令实现跨平台运行1。其核心功能包括类加载、内存管理(堆、栈等)和垃圾回收
QPS定义
QPS指服务器每秒能处理的查询次数,主要用于评估以查询为主的系统(如搜索引擎、数据库)的性能。
TPS定义
TPS指服务器每秒能完成的事务数,一个事务通常包括客户端请求、服务器处理和响应的完整过程。它更适用于涉及多步骤操作的场景(如金融交易、电商平台)。 23
核心区别
- 侧重点:QPS侧重查询效率,TPS侧重事务处理能力
什么是高并发
同一时间大量用户同时请求同一个API|URL地址,短时间内涌进啦大量用户,服务器感觉压力倍增,系统需要权利武装的去处理用户需求,但是服务器无法估量流量的体系,服务器down啦就完啦,而这种爆发式流程增长 简单粗暴的就是提升硬件能力,分布式系统,集群来处理这样的问题,把一台服务器的压力分摊到一堆服务器上,不同的业务模块分布到不同的服务器上或者把统一业务模块分成多个子业务模块,也就是解耦、消峰,解决高并发问题,还有缓存解决高并发问题,磁盘读取较慢,我们通常用读写速度更快的内存防止流量到达内存,静态资源(图片,音频),热门评论放在缓存服务器上,缓存有啦,如何快速响应,异步处理(发一千封邮件)极大的提高啦工作效率,还可以消峰限流,提高用户体验、更好的保护系统
高并发指系统在短时间内处理大量请求的能力
F5负载均衡器支持多种负载均衡策略,主要分为静态和动态两大类,旨在根据服务器状态、流量特征等智能分配请求。
静态负载均衡算法
静态算法在分配请求时不考虑服务器的实时状态,适用于服务器性能相近的场景。
- 轮询(Round Robin) :按顺序将请求依次分配给每台服务器,简单公平。13
- 比率(Ratio) :根据预设权重比例分配请求,权重高的服务器处理更多流量。1
- 优先权(Priority) :将服务器分组并设置优先级,优先将请求分配给高优先级组,组内采用轮询或比率算法。1
动态负载均衡算法
动态算法基于服务器的实时状态(如连接数、响应时间)进行流量分配,能更高效地利用资源。
- 最少连接(Least Connection) :将新连接分配给当前连接数最少的服务器,适合处理时间不均衡的场景。
- 最快响应(Fastest) :根据服务器的响应速度分配请求,将流量导向响应最快的服务器。
- 观察模式(Observed) :综合连接数和响应时间,选择性能最佳的服务器。
- 预测模式(Predictive) :利用历史性能数据预测服务器未来状态,提前分配请求
Nginx面试核心考点解析
Nginx作为高性能Web服务器和反向代理服务器,是运维和后端面试的高频考点。以下是关键知识点总结:
🔍 核心原理与架构
Master-Worker架构:采用多进程模型,Master进程管理Worker进程,Worker进程处理客户端请求3。这种设计支持热升级,单个Worker崩溃不会影响整体服务。
事件驱动模型:基于epoll(Linux)/kqueue(FreeBSD)的异步非阻塞机制,通过事件循环高效处理海量连接3。相比Apache的多线程模型,Nginx的内存消耗更低,能支持2-3万并发连接1。
⚡ 性能优势详解
高并发处理:Nginx采用事件驱动架构,适合IO密集型服务;Apache使用多进程/多线程,适合CPU密集型服务4。
内存管理高效:使用固定内存池分配资源,减少频繁内存申请释放的开销1。相比之下,Apache的进程模型容易导致内存碎片化。
静态文件处理:通过sendfile系统调用直接发送文件,减少内核态与用户态的数据拷贝1。
🔄 负载均衡策略
轮询法:默认方法,请求按时间顺序分配到不同后端服务器2。
权重模式:指定轮询几率,weight与访问率成正比,适用于后端服务器性能不均的场景
CDN加速核心方法
1. 引用公共CDN库
直接使用BootCDN、jsDelivr等免费CDN服务加载前端框架和库1。比如引入Vue.js:
htmlCopy Code
BootCDN支持近4000个开源项目,包括Bootstrap、jQuery、React等主流框架,版本更新及时且支持HTTPS1。
2. 配置自定义CDN域名
将静态资源部署到对象存储(如阿里云OSS),然后配置CDN加速域名3。
前端代码调整示例:
javascriptCopy Code
// 修改前 - 直接访问OSS
const imageUrl = 'https://your-bucket.oss-cn-hangzhou.aliyuncs.com/images/banner.jpg';
// 修改后 - 使用CDN加速域名
const imageUrl = 'https://cdn.yourdomain.com/images/banner.jpg';:ml-citation{ref="5" data="citationList"}
3. 字体文件CDN加速
使用Google Fonts、Adobe Fonts等CDN服务加载网页字体,通过离用户最近的服务器加速字体加载2。
⚡ 配置步骤详解
基础配置流程:
- 添加CDN加速域名 - 在CDN控制台配置加速域名和源站信息
- 配置CNAME解析 - 将加速域名指向CDN分配的CNAME地址
- 修改资源引用地址 - 将项目中的静态资源URL替换为CDN域名
apisix网关
Apache APISIX 是一款云原生、高性能的 API 网关,基于 Nginx 和 etcd 实现,支持动态路由、多协议代理(HTTP/gRPC/MQTT 等)及插件热加载5。其核心特性包括:
- 动态治理:实时配置更新,无需重启服务,支持灰度发布、限流熔断等。
- 多协议支持:统一管理 HTTP/WebSocket/TCP/UDP 流量,适配微服务与 IoT 场景。
- 插件扩展:提供 50+ 官方插件(如认证、限流),支持多语言开发自定义插件
tiktok授权
开发者账号申请
tiktok授权
JVM原理
什么是JVM
java程序的运行环境,java二进制字节码的运行环境
好处: 1:跨平台,一次编写 到处运行 windows电脑开发,linux环境上部署 上线 2、自动内存管理,垃圾回收机制 底层gc线程自动回收
1、栈内存
2、堆内存 :储存对象实例
3、方法区/元空间 :类信息,常量,静态变量 所有线程共享
4、程序计数器 :当前线程执行字节码指令的位置
5、垃圾回收
java源代码由JAVA-C编译.class字节码
JVM本质上就是运行在计算机上面的程序,他的职责是运行java字节码的文件
核心区别对比
栈内存由系统自动管理,分配和释放速度极快4。它存储函数的局部变量、参数和返回地址,生命周期与函数调用同步1。每个线程拥有独立的栈空间,默认大小通常只有几MB4。
堆内存需要手动申请和释放(如malloc/new)2,空间更大且可动态扩展5。它存储动态分配的对象,所有线程共享堆区,因此需要考虑线程安全问题1。
**
详细差异分析
管理机制
- 栈内存:编译器自动分配释放,函数调用时压栈,返回时弹栈
- 堆内存:程序员显式申请和释放,管理更复杂
性能特点
- 栈内存:仅需移动栈指针,分配时间复杂度为O(1)
- 堆内存:需搜索可用内存块,可能触发系统调用,分配较慢
线程安全
- 栈内存:线程私有,每个线程独立栈空间,无需同步
- 堆内存:线程共享,需通过锁等机制保证安全
容量限制
- 栈内存:空间有限,通常几MB,过多数据会导致栈溢出
- 堆内存:受系统可用内存限制,空间远大于栈
三个核心功能 :
1、解释和运行 对字节码的指令实时的解释成机器码,让计算器执行, 2、内存管理,自动为对象、方法 分配内存,自动垃圾回收机制,回收不在使用的对象 3、即时编译 对热点的代码进行优化 提升他的执行效率
垃圾回收机制(Garbage Collection, GC)是自动识别并回收不再使用的内存对象的机制,核心原理是通过追踪存活对象并回收未引用的内存空间,解决手动管理内存可能导致的泄漏、野指针等问题。
核心功能
- 自动识别与回收无效内存
- 防止内存泄漏、提升开发效率及程序稳定性
主要算法
-
标记-清除算法
- 标记阶段:从GC Roots(如栈帧、静态变量等)出发,遍历并标记所有存活对象。
- 清除阶段:回收未被标记的对象。缺点是会产生内存碎片。
标记整理
- 标记存活对象后,将其向内存一端移动并整理,最后清除边界外空间。
- 优点:消除碎片,空间利用率高;缺点:因对象移动导致效率较低
-
标记-复制算法
- 将内存分为两块区域,存活对象被复制到另一块区域后,原区域被清空。适用于年轻代(Young Generation)回收。
-
分代收集策略
- 将堆内存分为新生代(Young)、老年代(Old)和永久代(Perm),不同代采用不同回收算法(如新生代用复制算法,老年代用标记-清除或标记-整理)
为什么java年轻代采用复制
Java年轻代采用复制算法,主要是因为其内存分配和回收特点与新生代对象的生命周期高度匹配。
核心原因:高效处理"朝生夕死"的对象
年轻代中98%的对象都是短期存活的,很快就会变成垃圾。复制算法在这种场景下优势明显:
- 只扫描存活对象:复制时只需处理少数存活对象,效率高
- 无内存碎片:存活对象被完整复制到新空间,保持内存连续
- 回收彻底:一次性清空原使用区域,回收速度快
具体实现机制
年轻代被划分为Eden区和两个Survivor区(From和To),默认比例8:1:1:
- 新对象分配在Eden区
- Minor GC时:将Eden和From区中存活的对象复制到To区
- 角色交换:清空Eden和From区,然后From和To区互换角色
这种设计既保证了回收效率,又避免了原始复制算法浪费一半内存的问题。
①当.java文件被编译为.class文件时,.class文件会被加载到类加载子系统,然后由类加载子系统将文件加载到运行时数据区
②在运行时数据区中,类对象被加载到方法区中,便于后面new出来的实例对象可以通过这个类对象模板中创建新的对象。
③创建出来实例对象会被加载到堆中
④虚拟机栈:每个线程都会在虚拟机栈中开辟一个空间,每调用一个方法时,这个方法就会被压入栈中,也就是说每个栈中存放的是方法调用的层级
⑤本地方法栈:同样是每一个线程都会在本地方法栈中开辟一块内存,每次调用一个本地方法,这个本地方法就会被加载到本地方法栈中。
⑥程序计数器:记录当前线程所运行到的指令地址:由于考虑到java虚拟机在多线程模式下是通过线程轮流切换并分配时间片的方式进行的,因此当某个线程分配的时间片使用完但是当前线程并没有执行结束时,这时就需要使用程序计数器记录下当前线程所运行到的指令地址,当当前线程再度被分配到时间片时,从当前指令下继续执行。
标记-清除算法
先标记所有需要回收的对象,然后统一回收 缺点:会产生内存碎片,可能触发额外垃圾回收 标记-整理算法
标记过程相同,但回收后会整理内存区域 优点:避免内存碎片;缺点:效率较低 复制算法
将内存分为两块,每次只用一块,存活对象复制到另一块 优点:效率高、无碎片;缺点:内存利用率低
ZGC和CMS是两种设计理念完全不同的垃圾回收器,主要区别如下:
CMS G1 ZGC的区别
CMS、G1和ZGC是Java虚拟机中三种重要的垃圾回收器,它们在设计目标和适用场景上有明显差异。
核心区别对比
CMS(Concurrent Mark-Sweep)
- 工作机制:初始标记→并发标记→重新标记→并发清理
- 优势:低延迟,适合响应时间敏感的应用
- 劣势:会产生内存碎片,可能引发Full GC;已在JDK 17中被移除
- 适用场景:Web服务、交易系统等对延迟敏感的应用4
G1(Garbage-First) MCP_G1]^
- 工作机制:基于分区(Region)的回收策略,通过筛选回收优先处理高收益区域4
- 优势:可预测停顿时间,支持空间整合避免内存碎片4
- 特点:JDK 9起成为默认垃圾回收器,兼顾吞吐量和停顿时间控制3
- 适用场景:需要平衡吞吐量和延迟的大内存应用
ZGC(Z Garbage Collector) MCP_ZGC]^
- 目标:在处理大堆时实现亚毫秒级的停顿时间3
- 特点:可扩展的低延迟垃圾回收器3
- 适用场景:超大堆内存、对延迟要求极高的应用
关键差异总结
| 特性 | CMS | G1 | ZGC |
|---|---|---|---|
| 算法 | 标记-清除4 | 标记-整理4 | 并发标记-整理 |
| 内存碎片 | 容易产生4 | 空间整合4 | 无碎片 |
| 停顿时间 | 低延迟4 | 可预测4 | 亚毫秒级3 |
| JDK默认 | JDK 8 Parallel GC3 | JDK 9+默认3 | 可选 |
💡 建议:如果应用对延迟极其敏感且堆内存超大,考虑升级到JDK 11+并使用ZGC;若需要平衡吞吐量和延迟,G1是不错的选择。
G1和ZGC哪个更稳定?
稳定性对比结论
ZGC在低延迟和大内存场景下更稳定,G1在中小堆和兼容性要求高的场景更稳定。
关键差异
- 停顿时间:ZGC平均停顿8ms(G1为45ms),最大停顿12ms(G1为180ms)1
- 内存支持:ZGC支持TB级堆内存,G1超过64GB时性能下降6
- 兼容性:G1支持JDK 8+,ZGC需JDK 11+2
适用场景
- 选择ZGC:金融交易、实时系统等对延迟敏感,或堆内存>32GB的场景2
- 选择G1:批处理任务、中小堆(4-16GB)或需兼容老版本JDK的系统12
调优建议
- ZGC:固定堆大小(-Xmx/-Xms)提升稳定性2
- G1:需精细调整-XX:MaxGCPauseMillis和-XX:InitiatingHeapOccupancyPercent
Java里new一个对象时,发生了什么?
1. 类加载检查
当JVM遇到new指令时,首先检查这个类是否已经被加载2。如果没有加载,JVM会通过类加载器执行完整的类加载过程,包括加载、验证、准备、解析和初始化阶段。
2. 内存分配
类加载完成后,JVM在堆内存中为对象分配空间。分配方式主要有两种:
- 指针碰撞:适用于内存规整的情况,简单移动指针即可
- 空闲列表:适用于内存不规整的情况,需要维护可用内存块列表
3. 内存空间初始化
分配内存后,JVM将对象的成员变量初始化为默认值4:
- 数值类型:0或0.0
- 布尔类型:false
- 引用类型:null
4. 对象头设置
JVM设置对象头信息,包括哈希码、GC信息、锁状态和类元数据指针等
5. 执行初始化代码
这是最后也是最关键的步骤:
- 执行构造代码块(如果有)
- 调用类的构造方法进行最终初始化
- 将堆内存地址赋值给引用变量
Java里new一个对象时,发生了什么
在Java中使用new关键字创建对象时,JVM会执行一系列精密的操作流程:
1. 类加载检查
JVM首先检查目标类是否已加载。如果未加载,则触发类加载器执行完整的加载过程(加载→验证→准备→解析→初始化)。
2. 内存分配
在堆内存中为对象分配空间,分配策略包括:
- 指针碰撞:内存规整时简单移动指针
- 空闲列表:内存碎片化时维护可用内存块
3. 内存初始化
将对象的所有实例变量设置为默认值:
- 数值类型初始化为0
- boolean类型初始化为false
- 引用类型初始化为null
4. 对象头设置
配置对象头信息,包含:
- 哈希码
- GC分代年龄
- 锁状态标志
- 类元数据指针
5. 执行构造函数
按顺序执行初始化代码:
- 实例变量显式初始化
- 实例代码块执行
- 构造函数体执行
6. 引用关联
最后将堆内存地址赋值给引用变量,完成对象创建。
整个过程确保了对象的正确初始化和内存安全,是Java面向对象编程的基础机制。
Redis分布式锁的潜在问题及解决方案
1. 锁未被释放
问题:线程获取锁后因异常未释放,导致死锁。
解决:在finally块中强制释放锁,确保锁一定能被释放。
**
2. 锁被误删
问题:多线程使用相同锁值,导致A线程释放B线程的锁。
解决:使用唯一标识符(如UUID)作为锁值,释放时校验标识符。
3. 锁过期但业务未完成
问题:业务执行时间超过锁过期时间,引发并发问题。
解决:使用Redisson的自动续期机制(Watchdog),定期延长锁有效期5。
Redisson的自动续期机制(Watchdog)原理
核心工作原理
当线程成功获取锁后,Redisson会启动一个后台定时任务,定期检查锁的状态1。这个任务默认每隔10秒执行一次,检查当前线程是否仍持有该锁4。如果锁仍在被持有,系统会自动将锁的过期时间延长30秒2,确保锁的有效期与业务执行时间相匹配
4. 非原子性操作
问题:SETNX与EXPIRE非原子操作,加锁后服务崩溃导致死锁。
解决:使用SET key value NX PX命令原子性设置锁和过期时间45。】
SETNX命令与SET命令NX选项有何区别?
SETNX命令与SET命令的NX选项在功能上相似,但存在一些关键区别。
主要区别:
| 特性 | SETNX 命令 | SET key value NX |
|---|---|---|
| 原子性 | 是 | 是 |
| 支持设置过期时间 | ❌ 不支持 | ✅ 支持 EX/PX |
| 适合分布式锁 | 可以,但需配合EXPIRE | 更推荐 |
| 返回值 | 1 或 0 | OK 或 nil |
| 推荐场景 | 非过期操作、兼容老Redis版本 | ✅ 推荐用于分布式锁等场景 |
详细说明:
SETNX 是一个独立的命令,语法为 SETNX key value1。它仅在键不存在时设置值,返回1表示成功,0表示失败。
SET key value NX 是SET命令的一个可选参数,可以同时设置过期时间,例如 SET lock_key "lock_val" NX EX 10
推荐做法: 在实现分布式锁时,建议使用 SET key value NX PX milliseconds,这样既能保证原子性,又能自动设置过期时间防止死锁
5. 单点故障
问题:Redis单节点故障导致锁服务不可用。
解决:采用Redisson的RedLock算法,在多个Redis节点上获取锁。
Redis单节点故障导致锁服务不可用怎么解决
Redis单节点故障导致锁服务不可用的问题,可以通过RedLock红锁算法来解决。
RedLock红锁算法解决方案
核心原理:基于多节点投票机制,客户端需要向集群中多个独立的Redis节点发起加锁请求,只有当超过半数节点成功获取锁时,才认为加锁成功12。
具体实现步骤:
- 部署多个独立Redis节点(建议5个主节点,相互独立)
- 并发加锁:客户端依次向所有Redis节点发送加锁命令
- 统计成功数量:计算成功获得锁的节点数
- 判断成功条件:若多数节点(≥ N/2 +1)返回成功,则认为加锁成功3
6. 锁竞争
问题:高并发下锁竞争激烈,性能下降。
解决:使用Redisson的公平锁或优化锁粒度。
7. 性能开销
问题:频繁加锁释放锁增加Redis负载。
解决:使用连接池、批量操作及异步处理优化性能。
缓存一致性解决方案主要包括以下5种:
1. Cache-Aside模式(旁路缓存)
- 实现:读请求先查缓存,未命中则查数据库并回写;写请求先更新数据库,再删除缓存
- 优点:实现简单,天然防并发写冲突
- 缺点:存在短暂不一致窗口期
2. 双写事务控制
- 实现:通过本地事务同时更新数据库和缓存
- 优点:理论强一致性
- 缺点:性能损耗大,需处理事务回滚
3. 延迟双删策略
- 实现:先删缓存→更新数据库→延迟后二次删除缓存
- 优点:降低不一致时间窗口
- 缺点:延迟时间难设定,无法完全消除风险
4. 消息队列方案
- 实现:通过监听数据库binlog异步更新缓存
- 优点:解耦业务代码,支持失败重试
- 缺点:架构复杂度高,存在消息延迟
- 高并发读写分离场景
**
5. 特殊值标记法
- 实现:更新缓存为特殊值→修改数据库→读请求检测特殊值并重试
- 优点:实现简单
- 缺点:需处理重试逻辑,用户体验可能受影响
RDB和AOF怎么联合使用
Redis 提供了两种持久化方式:RDB(快照)和 AOF(日志),它们可以联合使用,形成互补。Redis 4.0 引入的混合持久化(RDB-AOF hybrid persistence)是生产环境的推荐配置3。
混合持久化的核心思想是:使用 RDB 作为全量备份,AOF 作为增量备份。这样既保留了 RDB 文件小、恢复快的优点,又获得了 AOF 数据安全性高的长处
配置方法(在 redis.conf 中设置):
- 开启 AOF:
appendonly yes - 启用混合持久化:
aof-use-rdb-preamble yes
工作原理:
- 定期创建 RDB 快照作为基础数据集
- 两次 RDB 快照之间的所有写操作,会以 AOF 日志的形式追加记录3。
- Redis 重启恢复时,会先加载 RDB 的基础数据,再重放 AOF 的增量命令,实现快速且安全的数据恢复
建议:在 redis.conf 中检查并设置 aof-use-rdb-preamble yes 来启用混合持久化,这是平衡性能和数据安全性的最佳实践
redis为什么Aof丢失数据概率低
AOF(Append Only File)丢失数据概率低的核心原因在于其日志式持久化机制和灵活的同步策略,通过以下设计实现高可靠性:
1. 日志追加机制
AOF通过记录所有写操作命令实现持久化,采用追加写入方式(不覆盖原文件),避免因文件损坏导致数据丢失15。即使系统崩溃,未完成的操作也不会影响已持久化的数据。
2. 三种同步策略
通过appendfsync配置可平衡安全性与性能:
- always:每个写命令立即同步到磁盘,数据零丢失,但性能影响大4
- everysec(默认):每秒同步一次,最多丢失1秒数据,兼顾性能与可靠性25
- no:依赖操作系统同步,性能最优但可能丢失更多数据4
3. 对比RDB的天然优势
RDB通过定时快照保存数据,两次快照间若崩溃会丢失数据;而AOF通过持续记录写操作,即使配置为每秒同步,丢失窗口也远小于RDB23。
4. 容错与恢复能力
- AOF文件为可读文本,支持通过重放命令恢复数据1
- 提供
redis-check-aof工具检测和修复损坏文件6
消息不丢失的三大保障环节
- 生产端:使用同步发送模式,确保Broker返回ACK确认消息已持久化。
- 存储端:Broker需将消息写入磁盘(如RocketMQ的CommitLog文件)并同步到从节点。
- 消费端:关闭自动提交,改为手动提交消费位点,处理完业务逻辑后再确认。
MQ确认机制 MQ确认机制是确保消息可靠传递的核心机制,主要分为生产者确认和消费者确认两部分。
📤 生产者确认机制
生产者确认机制用于保证消息成功发送到RabbitMQ服务器2。主要通过以下方式实现:
Confirm确认模式4:
-
开启方式:调用
channel.confirmSelect()开启确认模式 -
确认类型:
publisher-confirm:消息投递到交换机的确认publisher-return:消息从交换机路由到队列的确认
配置示例:
yamlCopy Code
spring:
rabbitmq:
publisher-confirm-type: correlated
publisher-returns: true
📥 消费者确认机制
消费者确认机制确保消息被正确处理后才从队列中移除5。
ACK模式类型3:
- AUTO_ACKNOWLEDGE:自动确认(默认)
- CLIENT_ACKNOWLEDGE:客户端手动确认
- INDIVIDUAL_ACKNOWLEDGE:单条消息确认
手动确认的优势:
- 防止消息处理异常时丢失
- 避免后续代码异常造成消息丢失
MQ确认机制有哪些常见问题?
MQ确认机制在实施中常见问题及解决方案:
一、生产者确认问题
- 网络超时导致重发:生产者未收到MQ确认时可能重复发送消息,需设置超时阈值和重试策略1。
MQ网络超时导致重发怎么解决
MQ网络超时导致消息重发是一个常见问题,可以通过以下几种核心方案来解决:
🔧 解决方案
1. 实现消息幂等性
为每条消息生成唯一ID,在消费者端进行去重处理4。当收到重复消息时,通过检查ID来判断是否已经处理过,避免重复消费。
2. 优化超时配置
调整MQ的超时时间设置,避免因网络波动导致的误判重发1。根据业务场景合理配置超时阈值,平衡可靠性和实时性。
3. 改进消息处理逻辑
如果消费者处理消息耗时过长导致超时,需要优化处理逻辑或拆分消息任务1。
4. 建立重发监控机制
通过消息状态追踪和定时任务扫描,及时发现和处理重复消息。
- 异步确认丢失:异步模式下回调未触发可能导致消息丢失,需结合同步确认或日志记录。
- 批量消息确认:批量发送时需确保所有消息成功投递,否则需回滚事务。
二、消费者确认问题
- 自动确认风险:消费者未处理完消息但已自动确认(AUTO_ACKNOWLEDGE),需改为手动确认(CLIENT_ACKNOWLEDGE)35。
- 确认延迟:网络延迟导致MQ误判消息未消费,需优化消费者处理逻辑或增加超时重试
- 事务模式冲突:事务性会话(Session.TRANSACTED)与自动确认模式混用可能导致消息状态混乱
三、幂等性设计
- 重复消费:需为消息生成唯一ID,消费端通过数据库唯一索引或分布式锁去重
- 重试机制:设置最大重试次数,超限后转入死信队列避免无限循环
RocketMQ默认最大重试16次,采用渐进式延迟策略,每次重试间隔时间逐渐增加,从1秒到最长2小时6。
ActiveMQ默认最大重试6次,超过后消息会进入死信队列2。
Spring-Kafka支持配置重试次数,达到配置次数后消息进入死信队列
RabbitMQ服务端有默认的重试机制,不支持在Consumer客户端重新配置1:
- 重试间隔:最小10秒,最大1800秒,默认60秒
- 最大重试次数:16次1
- 停止条件:重试期间任何一次消费成功,则立即停止重试
重试队列 :%RETRY%+CONSUMERGroup 死信队列 :%DLQ%+CONSUMERGroup
MQ五种模式
RabbitMQ的五种核心工作模式如下:
- 简单模式
单生产者、单消费者、单队列,消息被消费后自动删除。适用于基础消息传递场景23。 - 工作队列模式
多消费者共享同一队列,通过竞争机制处理消息,支持负载均衡。适用于任务分发场景(如数据库操作)。 - 发布/订阅模式
生产者将消息发送至交换机,所有绑定该交换机的队列均会收到消息。适用于广播通知场景26。 - 路由模式
通过路由键(Routing Key)将消息定向到特定队列,实现精准消息分发34。 - 主题模式
基于通配符(如*、#)匹配路由键,支持灵活的消息过滤和订阅
四、顺序性保障
- 并发消费乱序:单线程消费或按业务键分区(如订单ID)保证顺序
- 高并发场景:通过队列分区或限流控制生产/消费速率
五、持久化与可靠性
-
未持久化丢失:消息未落盘时MQ宕机导致丢失,需开启队列持久化
-
集群同步延迟:镜像队列或联邦插件需配置同步策略。
-
高吞吐场景(日志采集/实时计算)
→ 选择Kafka,其分区并行和批量处理机制可轻松应对海量数据 -
低延迟场景(即时通讯/交易系统)
→ 选择RabbitMQ,内存队列和直接推送机制实现微秒级响应 -
高可靠场景(金融交易/订单支付)
→ 选择RocketMQ,其事务消息和顺序写机制确保数据强一致CPU飙高怎么定位问题,如何处理
首先使用TOP
找到cpu高的PID top -H PID 找到线程id jstack pid > jstack.log 查看jstack.log grep线程id
栈和队列有什么区别
栈和队列是两种操作受限的线性表,核心区别如下:
操作规则
- 栈:后进先出(LIFO),仅允许在栈顶进行插入和删除操作
- 队列:先进先出(FIFO),在队尾插入元素,在队头删除元素
操作位置
- 栈:所有操作(入栈/出栈)都在栈顶完成
- 队列:插入在队尾,删除在队头
应用场景
- 栈:函数调用、括号匹配、浏览器历史记录
- 队列:任务调度、消息队列、缓冲区管理
Redis分布式锁解决什么问题,怎么实现的,业务代码没处理完,锁过期了怎么办
分布式系统中多个服务实例并发访问共享资源时的数据一致性问题
实现方式
1. 基础实现(SETNX方案)
通过Redis的SETNX(Set if Not eXists)命令实现:
javaCopy Code
// 获取锁(键不存在时设置成功)
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
// 释放锁(需验证持有者)
String script = "if redis.call('get', KEYS:ml-citation{ref="1" data="citationList"}) == ARGV:ml-citation{ref="1" data="citationList"} then return redis.call('del', KEYS:ml-citation{ref="1" data="citationList"}) else return 0 end";
redisTemplate.execute(script, Collections.singletonList(lockKey), clientId);
缺点:锁过期时间固定,业务执行超时会导致锁提前释放5。
2. 改进方案(Redisson)
Redisson通过看门狗机制自动续期锁:
javaCopy Code
RLock lock = redissonClient.getLock("inventory:" + productId);
try {
lock.lock(10, 10, TimeUnit.SECONDS); // 最多等待10秒,锁自动续期10秒
// 执行业务逻辑
} finally {
lock.unlock();
}
优势:支持可重入锁、自动续期、公平锁等特性
核心区别
-
设计目标
- CMS(Concurrent Mark Sweep):以最短回收停顿时间为目标,适合对响应时间敏感的应用 。 12
- ZGC(Z Garbage Collector):以极低停顿时间(≤10毫秒)和大堆支持为目标,适用于大内存(数十GB到TB级)场景 。 13
-
内存管理机制
- CMS:采用标记-清除算法,不进行内存压缩,可能导致内存碎片 。 13
- ZGC:使用并发标记-整理算法,通过染色指针和读屏障技术消除碎片,支持大堆 。 23
-
适用场景
- CMS:适合Web服务器、交互式应用等低延迟场景 。 2
- ZGC:适合实时数据处理、大型分布式系统等大内存高并发场景 。 3
其他差异
- 停顿时间:CMS停顿时间较短但不稳定,ZGC停顿时间始终≤10毫秒 。 13
- 内存碎片:CMS易产生碎片,ZGC通过整理消除碎片
ZGC(Z Garbage Collector)
- 特点:极低停顿时间(通常低于10毫秒),适用于大内存和低延迟场景。
- 原理:使用着色指针和读屏障技术,减少内存占用和访问开销。
- 适用场景:大规模数据处理、长时间运行的应用 。 12
CMS(Concurrent Mark-Sweep)
- 特点:并发标记和清除,减少停顿时间,但可能产生内存碎片。
- 原理:分四阶段(初始标记、并发标记、重新标记、并发清除),依赖CPU资源。
- 适用场景:低延迟要求但可接受内存碎片的场景 。 23
G1(Garbage-First)
- 特点:分代收集与区域化设计,可预测停顿时间。
- 原理:将堆划分为多个Region,按垃圾量优先回收。
- 适用场景:对吞吐量和停顿时间都有要求的场景
怎么保证MQ消费顺序 顺序消费的核心原理
顺序消费要求同一业务实体的消息(如同一订单)必须按发送顺序被处理。实现的关键是:
- 消息路由到同一队列:生产者发送消息时,根据业务键(如订单ID)计算哈希值,确保相同键的消息始终进入同一个队列或分区。34
- 单线程消费队列:消费者通过分布式锁或队列独占机制,保证同一队列在同一时刻仅被一个消费者线程处理,避免并发导致乱序
RocketMQ的顺序消费实现
RocketMQ通过以下机制保证顺序性:
-
生产端路由:使用
MessageQueueSelector接口,基于业务键(如订单ID)选择目标队列,确保相同键的消息进入同一队列。36javaCopy Code // 示例:基于订单ID哈希选择队列 MessageQueue queue = mqs.get(orderId.hashCode() % mqs.size());
2. 消费端模式:注册MessageListenerOrderly监听器,RocketMQ内部通过分布式锁和本地锁确保队列的独占消费。
* 锁机制:Broker端锁定队列,消费端通过`synchronized`锁保证线程安全。5
* 重试限制:顺序消息消费失败时,重试次数有限,避免阻塞后续消息
线程池参数,原理,如何设置参数
线程池通过复用线程、控制并发数量来提高系统性能并管理线程资源。了解其核心参数和工作原理,是进行合理配置的关键。4
⚙️ 线程池七大核心参数
线程池的配置主要依赖于 ThreadPoolExecutor 的七个参数:4
-
corePoolSize(核心线程数) :线程池中常驻的核心线程数量,即使空闲也不会被回收(除非设置
allowCoreThreadTimeOut)。 -
maximumPoolSize(最大线程数) :线程池允许创建的最大线程数量,用于应对任务突发高峰。
-
keepAliveTime(线程存活时间) :超出核心线程数的那些空闲线程,在等待新任务时的最长存活时间。
-
unit(存活时间单位) :
keepAliveTime的时间单位(如秒、毫秒)。 -
workQueue(任务队列) :用于保存已提交但尚未被执行任务的阻塞队列。
-
threadFactory(线程工厂) :用于创建新线程的工厂,可以自定义线程名、优先级等。
-
handler(拒绝策略) :当任务队列已满且线程数达到最大值时,如何处理新提交的任务。 // 灾难配置:无界队列 new LinkedBlockingQueue<>(); // 默认Integer.MAX_VALUE // 当任务产生速度 > 处理速度时: // 1. 队列不断堆积 // 2. 内存持续增长 // 3. 最终OOM,系统崩溃 // 生产环境配置: new LinkedBlockingQueue<>(100); // 设置合理的边界 new ArrayBlockingQueue<>(200); // 固定大小,快速响应
- 当有新任务提交时,线程池优先创建核心线程来处理。
- 如果核心线程已满,新任务会被放入任务队列中等待。
- 当任务队列也满了,线程池才会创建新的非核心线程(不超过
maximumPoolSize)来处理任务。 - 如果线程数已达到最大值且队列已满,则会触发指定的拒绝策略
- CPU密集型:配置过多线程会导致频繁的线程上下文切换,反而降低性能。多出的1个线程是为了在发生页缺失等意外暂停时,能有线程立即补上,保持CPU忙碌。
- IO密集型:线程在等待IO时会释放CPU,因此可以配置更多线程来充分利用CPU空闲时间4。
- 阻塞系数:指任务等待IO的时间占比,通常在0.8~0.9之间
字符串(String)
- 实现:基于SDS(简单动态字符串),支持文本和二进制数据,最大容量512MB。内部编码可为int(整数)、embstr(短字符串)或raw(长字符串) 。
- 场景:缓存对象(如用户信息)、计数(点赞/访问量)、分布式锁(SET命令的NX参数) 。
哈希表(Hash)
- 实现:底层使用ziplist(压缩列表)或hashtable,根据元素数量和大小动态切换 。
- 场景:存储对象属性(如用户信息拆分存储)、缓存结构化数据 。
列表(List)
- 实现:底层为linkedlist或ziplist,支持LRU缓存淘汰策略 。
- 场景:消息队列(BRPOP阻塞读取)、社交动态流(如微博热搜) 。
集合(Set)
- 实现:底层为intset(整数集合)或hashtable,支持交集/并集操作 。
- 场景:标签系统(如用户兴趣标签)、抽奖活动、社交关系图谱 。
有序集合(ZSet)
- 实现:基于zset结构,结合skip list和hashtable实现排序 。
- 场景:排行榜(按播放量、点赞数排序)、地理位置排序(Geo模块
缓存淘汰策略 有哪几种
FIFO(先进先出)
- 原理:淘汰最早进入缓存的数据。
- 优点:实现简单,开销小。
- 缺点:可能移除热门数据,保留无用数据。 12
LRU(最近最少使用)
- 原理:淘汰最久未被访问的数据。
- 优点:利用“时间局部性”,适用于热点数据场景。
- 缺点:大规模偶然访问可能导致热点数据被清除。 13
LFU(最不经常使用)
- 原理:淘汰访问频率最低的数据。
- 优点:保护高频数据,避免偶然扫描影响。
- 缺点:早期高频数据可能长期占用缓存。 12
MRU(最近最多使用)
- 原理:淘汰最近被访问的数据。
- 适用场景:特定顺序访问模式(如数据库全表扫描)。 13
RR(随机替换)
- 原理:随机选择数据淘汰。
- 优点:实现简单,决策快。
- 缺点:命中率低,不可预测。 13
Clock算法(时钟算法)
- 原理:LRU的高效近似实现,使用“使用位”和环形链表。 15
SLRU(分段式LRU)
- 原理:将缓存分为两部分,新数据先进入“试探段”,再次访问后移至“保护段”。 3
TTL(生存时间)
- 原理:淘汰即将过期的数据。 36
Two-Tiered Caching(两级缓存)
- 原理:分层存储数据,优先访问高频层。 3
Redis特有策略
- volatile-lru:仅淘汰设置了过期时间的数据中的最不常用项。
- allkeys-lru:淘汰所有数据中最不常用的项。
- volatile-random:随机淘汰设置了过期时间的数据。
- allkeys-random:随机淘汰所有数据。
- volatile-ttl:淘汰即将过期的数据。
- noeviction(默认):不淘汰数据,内存溢出时报错 JVM调优
MYSQL事务隔离级别
- 读未提交(READ UNCOMMITTED)
最低隔离级别,允许读取其他事务未提交的数据,可能导致脏读、不可重复读和幻读问题13。 - 读已提交(READ COMMITTED)
只能读取已提交的数据,避免脏读,但不可重复读和幻读问题仍存在24。 - 可重复读(REPEATABLE READ)
MySQL默认隔离级别,确保事务内多次读取结果一致,通过Next-Key Locking机制避免幻读34。 - 串行化(SERIALIZABLE)
最高隔离级别,强制事务串行执行,完全避免脏读、不可重复读和幻读,但性能最低
MVCC版本控制 在计算机科学中,MVCC(多版本并发控制)是一种用于数据库管理系统中的并发控制机制,旨在提高数据库操作的并发性,同时保持数据的一致性和隔离性。MVCC通过为每个查询操作创建数据的快照来实现这一点,而不是锁定整个数据库或数据行。这种方法允许多个事务同时读取同一数据,而不会相互干扰。
脏读 :读取到其他人未提交的数据
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问 这个数据,然后使用了这个数据
幻读是数据库事务中的一种并发问题,指一个事务在前后两次查询同一范围数据时,后一次查询结果中出现了前一次未读取到的新记录。这种现象通常由其他事务在查询间隙插入数据导致 幻读:同一个事务 两次查询同一范围的数据 结果集改变 不可重复读:同一事务 多次查询 数据不一样
📊 四种隔离级别及对应问题
1. 读未提交(READ UNCOMMITTED)
- 问题:脏读 - 能读取其他事务未提交的修改4
- 例子:事务A将余额从1000改为2000(未提交),事务B查询余额看到2000,但事务A随后回滚,事务B读到的就是无效数据
2. 读已提交(READ COMMITTED)
- 解决:脏读
- 问题:不可重复读 - 同一事务内多次读取结果不一致
- 例子:事务A第一次查询余额为1000,事务B将余额改为2000并提交,事务A再次查询余额变成2000
3. 可重复读(REPEATABLE READ)
- 解决:脏读、不可重复读
- 问题:幻读 - 同一事务内多次查询返回的行数不同
- 例子:事务A查询年龄=20的用户有3人,事务B新增1个年龄=20的用户并提交,事务A再次查询发现有4人
4. 串行化(SERIALIZABLE)
- 解决所有问题:脏读、不可重复读、幻读6
- 代价:完全串行执行,性能最低2
MVCC的基本原理和工作方式
-
版本控制:
- 每个数据项在数据库中都包含一个版本号或时间戳。
- 当事务读取数据时,它看到一个特定时间点的数据版本(即快照)。
-
写操作:
- 当事务要修改数据时,它实际上是将修改写入到新的数据版本中,而不是直接覆盖旧版本。
- 旧版本的数据仍然保留,直到没有事务再需要看到它们为止。
-
可见性控制:
- 事务只能看到在事务开始之前就已经存在的数据版本。
- 对于正在进行的事务,其他事务的修改在当前事务看来是不可见的,除非它们在事务开始之后被提交。
-
垃圾回收:
- 为了释放不再需要的旧版本数据
索引失效场景 :
单一列的NOT IN子句 索引生效
聚簇索引
聚集索引是将数据表中的行按照索引键的顺序进行物理排序的索引。一个表只能有一个聚集索引,因为数据表的物理存储顺序只能有一种。聚集索引的索引键决定了数据在表中的存储顺序。
1.2 工作原理
在聚集索引中,索引叶节点存储的是数据行本身。也就是说,聚集索引的叶节点就是数据页。由于数据行按照索引键排序,因此可以通过二分查找等高效算法快速定位到所需的数据行。
1.3 优缺点
优点
- 快速数据检索:由于数据行按照索引键排序,检索效率高,特别是在范围查询和排序操作中表现尤为突出。
- 节省存储空间:不需要额外的存储空间来存储索引叶节点,因为数据行本身就存储在叶节点中。
缺点
- 插入和更新成本高:由于数据行按照索引键排序,插入新数据或更新现有数据可能会导致数据页的重新排序,性能开销较大。
- 只支持一个聚集索引:一个表只能有一个聚集索引,因此在选择聚集索引键时需要慎重考虑。
1.4 使用场景
聚集索引适用于以下场景:
- 范围查询:需要频繁进行范围查询(例如查找某个时间段内的数据)的表。
- 排序操作:需要频繁进行排序操作的表。
- 主键:通常在主键上创建聚集索引,因为主键的唯一性和非空特性适合作为聚集索引键。
数据行与B+树索引存储在同一结构中,叶子节点直接包含完整数据行。每张表仅能有一个聚簇索引,通常由主键或第一个唯一非空索引充当
InnoDB 的数据文件.frm 和数据文件.ibd 其中.ibd(Innodb data)储存的数据和索引信息是存放在一起的 .frm(fram)表结构
.表的数据行都存在索引树的叶子节点中 .索引和数据存放在同一个B+树中 因此从聚集索引中获取数据比在非聚集索引快
myisam中存放的.frm frame 表结构 .MYD MyISAM Data 表数据 .MYI MyISAM Index 表索引 数据和索引不存放在一起 先找到指针 再根据地址找到数据
B树和B+树区别
存储结构 B树:所有叶子节点存放数据
B+树:非叶子节点存放索引,叶子节点存放完成数据
B+树:叶子连接成链 支持范围查询
B树 :无连接设计
查找方式:
查找直接结束
B+树:必须到叶子节点 查询路径稳定高效 减少IO 次数 适合大数据
当SQL执行UPDATE,INSERT时候,发生了什么
- 事务启动
- InnoDB引擎会分配事务ID并开启事务,记录Undo Log(用于回滚)和Redo Log(用于崩溃恢复)。
- INSERT操作会触发主键或聚簇索引的定位,若数据页未满则直接写入,否则可能触发页分裂。
- 两阶段提交
- Prepare阶段:Redo Log标记为“准备提交”,确保崩溃后可恢复到该状态。
- Commit阶段:Binlog记录逻辑变化,Redo Log更新为“提交状态”。
两段式提交和三段式提交的区别
两段式提交(2PC)和三段式提交(3PC)是分布式事务的两种协议,核心区别如下:
1. 阶段划分
- 2PC:分准备(Prepare)和提交(Commit)两阶段。协调者先询问参与者是否可提交,再根据反馈决定最终操作36。
- 3PC:增加预提交(PreCommit)阶段,形成CanCommit、PreCommit、DoCommit三阶段,减少阻塞风险47。
**
2. 核心差异
- 阻塞问题:2PC中参与者可能因协调者崩溃长期阻塞;3PC通过超时机制和预提交阶段降低阻塞概率7。
- 容错性:3PC在预提交阶段检测异常,避免2PC的脑裂问题(部分节点提交、部分回滚)47。
- 性能:3PC因多一个阶段,通信开销略高于2PC,但可用性更高
日志与索引更新
- Undo Log:记录插入行的“删除指针”,支持事务回滚。 1
- Redo Log:物理修改记录(如页号、偏移量),用于崩溃恢复。 1
- Binlog:逻辑日志(语句或行级变更),用于主从复制。 1
undolog和redolog的区别
Undo Log和Redo Log是MySQL InnoDB存储引擎中两种重要的日志机制,它们在功能和作用上有着本质区别。
核心区别对比
| 特性 | Undo Log (回滚日志) | Redo Log (重做日志) |
|---|---|---|
| 主要作用 | 事务回滚和MVCC实现5 | 崩溃恢复和数据持久性12 |
| 记录内容 | 逻辑日志,记录反向操作4 | 物理日志,记录数据页修改16 |
| 使用场景 | 事务回滚、一致性读取、MVCC5 | 系统崩溃后数据恢复26 |
| 存储位置 | InnoDB表空间内6 | 数据目录下的ib_logfile文件6 |
详细功能说明
Undo Log 主要用于保证事务的原子性5。当事务需要回滚时,通过Undo Log可以恢复到事务开始前的状态6。它记录的是逻辑操作,比如对INSERT记录DELETE操作,对UPDATE记录相反的UPDATE操作4。
Redo Log 则确保事务的持久性2。采用"预写式日志"策略,事务提交时先将修改写入Redo Log,即使系统崩溃也能通过Redo Log恢复数据6。
实际应用建议
在数据库设计和优化时,合理配置Undo Log和Redo Log的大小对于系统性能至关重要。建议根据业务的事务频率和数据修改量来调整相关参数,避免因日志空间不足导致的性能问题。
锁机制
- 行锁:UPDATE精准匹配主键时仅锁定单行,范围匹配时锁定间隙或区间。 2
- 共享锁:INSERT因唯一性冲突会加共享锁,可能引发死锁(如与SELECT FOR UPDATE冲突)
redis为什么快
单线程
Redis将所有数据存储在内存中,避免了传统磁盘数据库的I/O瓶颈。内存的读写速度远快于磁盘,使得Redis能够实现微秒级的延迟响应
单线程模型
Redis采用单线程处理命令执行,避免了多线程上下文切换和锁竞争的开销
redis是单线程还是多线程
1、无论什么版本 工作线程只有一个
2、6.X版本出现啦IO多线程
Elasticsearch分页有几种查询方式
Elasticsearch 主要支持三种分页查询方式,每种方式都有其特定的适用场景和优缺点。
📄 分页方式对比
1. From + Size 分页
- 工作原理:通过
from参数指定起始位置,size参数指定每页大小23 - 优点:实现简单,支持随机跳转分页3
- 缺点:存在深度分页问题,性能随
from值增大而显著下降45 - 限制:默认最大返回 10000 条数据(受
max_result_window参数限制)5
2. Scroll 分页
- 工作原理:创建搜索上下文快照,通过游标
scroll_id持续遍历数据14 - 优点:适合处理大量数据,性能稳定45
- 缺点:占用服务器资源,数据非实时(基于查询时的快照)45
3. Search After 分页
- 工作原理:基于排序字段,使用上一页最后一条记录的排序值来获取下一页4
- 优点:适合深度分页,性能良好,支持实时数据5
- 缺点:只能顺序翻页,不能随机跳转
mysql事物开启方式
MySQL提供了两种主要的事务开启方式:
1. 显式事务模式
使用 START TRANSACTION 或 BEGIN 命令显式开启事务1,然后通过 COMMIT 提交或 ROLLBACK 回滚5。
使用流程:
sqlCopy Code
START TRANSACTION; -- 或 BEGIN
-- 执行SQL操作...
COMMIT; -- 提交事务
-- 或 ROLLBACK; -- 回滚事务
这种方式只影响当前事务,事务结束后自动恢复原来的提交模式3。
2. 隐式事务模式
通过设置 autocommit 参数来控制事务提交方式1。
设置方法:
sqlCopy Code
SET autocommit = 0; -- 关闭自动提交,改为手动提交
-- 后续所有SQL操作都需要手动COMMIT
默认情况下 autocommit = 1,即自动提交模式,每条SQL语句都会立即提交
避免幻读 MySQL主要通过以下几种机制来避免幻读问题:
核心解决方案
1. 可重复读隔离级别 + MVCC
MySQL默认的可重复读隔离级别通过MVCC(多版本并发控制)实现快照读,在事务开始时创建一致性视图,后续读取都基于这个快照,避免看到其他事务新增的数据24。
2. Next-Key Lock锁机制
在可重复读级别下,InnoDB使用Next-Key Lock(行锁+间隙锁组合),锁定索引记录及其间隙,防止其他事务在范围内插入新记录56。
3. 间隙锁(Gap Lock)
专门锁定索引记录之间的间隙,确保事务执行期间索引范围不会被插入新记录6。
4. 串行化隔离级别
最高隔离级别,通过强制事务串行执行来完全避免幻读,但会显著降低并发性能3。
1.优先使用 REPEATABLE READ 隔离级别(MySQL 默认),通过 Next-Key Lock 机制: 2.对范围查询加锁(FOR UPDATE 或 LOCK IN SHARE MODE),触发间隙锁,阻止其他事务插入新记录。 3.普通查询(快照读)通过 MVCC 保证可重复读,无需担心幻读。 4.避免使用 SERIALIZABLE,除非业务对一致性要求极高且可接受性能损失
不同场景的应对策略
快照读场景:使用MVCC机制,读取历史版本快照45。
当前读场景:使用SELECT ... FOR UPDATE等加锁语句,配合Next-Key Lock防止幻读
@Bean作用
我理解您可能是想问关于Spring框架中@Bean注解的用法和作用。
@Bean是Spring框架中的一个方法级别注解,主要用于告诉Spring容器,被注解的方法会返回一个对象,这个对象应该被注册为Spring应用上下文中的一个bean。它通常用在@Configuration注解的类中,但也可以在@Component等可以被Spring容器扫描到的组件类中使用
主要特点:
- 用于自定义对象的创建和初始化过程
- 可以替代XML配置中的``标签
- 方法名默认作为bean的名称,也可以通过
name属性指定 - 允许设置对象的属性、调用构造方法等复杂配置
使用示例:
在配置类中定义一个bean:
javaCopy Code
@Configuration
public class AppConfig {
@Bean
public User user() {
User user = new User();
user.setName("张三");
return user;
}
}
建议: 在需要创建第三方库对象或复杂配置对象时使用@Bean注解,这样可以更灵活地控制对象的创建过程
@postconstruct
@PostConstruct 是 Java 标准注解(位于 javax.annotation 包下),用于标记一个方法,该方法会在 Bean 实例化完成且依赖注入完成后自动调用,通常用于执行初始化任务4。
主要特性与执行时机
- 执行顺序:构造方法 → 依赖注入(如
@Autowired)→@PostConstruct方法 - 调用次数:一个 Bean 的生命周期中,
@PostConstruct方法 只执行一次 - 方法规则:方法不能是
static的,但可以是final的;不应有参数(拦截器等特殊情况除外),通常返回void
典型使用场景
- 替代在构造方法中直接使用依赖组件,避免因依赖注入未完成而导致的空指针问题
- 加载配置信息、初始化缓存、启动后台线程等需要在 Bean 完全就绪后执行的逻辑
事务失效的场景
Spring事务失效主要有以下几类常见场景:
🔧 方法定义问题
- 非public方法:@Transactional只能用于public方法,私有方法上添加注解完全无效4
- final/static方法:被final或static修饰的方法无法被代理类重写,导致事务失效5
- 当前类未被Spring管理:缺少@Service等注解,类未被Spring扫描管理4
⚠️ 调用方式问题
- 同类方法内部调用:当类内部方法A调用本类事务方法B时,实际未经过代理对象
- 多线程调用:在异步多线程环境下,事务上下文无法传递6
🐛 异常处理问题
- 异常被捕获未抛出:在try-catch块中捕获异常但未重新抛出,事务管理器无法感知异常1
- 抛出受检异常:默认仅RuntimeException触发回滚,Exception等受检异常不会触发回滚1
🗄️ 配置与环境问题
- 数据库不支持事务:如MySQL的MyISAM引擎无事务能力4
- 传播行为配置不当:PROPAGATION_NOT_SUPPORTED等传播机制会导致事务不生效2
- Spring基础配置缺失:未启用@EnableTransactionManagement或未配置事务管理器
事务失效的场景
Spring事务失效主要有以下几类常见场景:
🔧 方法定义问题
- 非public方法:@Transactional只能用于public方法,私有方法上添加注解完全无效4
- final/static方法:被final或static修饰的方法无法被代理类重写,导致事务失效5
- 当前类未被Spring管理:缺少@Service等注解,类未被Spring扫描管理4
⚠️ 调用方式问题
- 同类方法内部调用:当类内部方法A调用本类事务方法B时,实际未经过代理对象
- 多线程调用:在异步多线程环境下,事务上下文无法传递6
🐛 异常处理问题
- 异常被捕获未抛出:在try-catch块中捕获异常但未重新抛出,事务管理器无法感知异常1
- 抛出受检异常:默认仅RuntimeException触发回滚,Exception等受检异常不会触发回滚1
🗄️ 配置与环境问题
- 数据库不支持事务:如MySQL的MyISAM引擎无事务能力4
- 传播行为配置不当:PROPAGATION_NOT_SUPPORTED等传播机制会导致事务不生效2
- Spring基础配置缺失:未启用@EnableTransactionManagement或未配置事务管理器4
💡 实用建议
立即检查你的代码:使用@Transactional时,确保方法是public的,避免在同类中直接调用事务方法,并针对业务异常正确配置rollbackFor属性。
事务的传播隔离级别
事务的传播隔离级别是Spring框架中管理事务行为的重要机制,主要包含传播行为和隔离级别两个核心概念。2
📊 事务隔离级别
隔离级别定义了事务在并发访问时的数据可见性规则,解决脏读、不可重复读和幻读问题:4
1. 读未提交(READ_UNCOMMITTED)
- 最低隔离级别,事务可以读取其他事务未提交的数据
- 可能发生脏读、不可重复读和幻读6
2. 读已提交(READ_COMMITTED)
- 只能读取其他事务已提交的数据
- 避免脏读,但可能出现不可重复读和幻读2
3. 可重复读(REPEATABLE_READ)
- 确保同一事务中多次读取相同数据结果一致1
- MySQL的InnoDB默认级别,通过MVCC机制实现1
4. 串行化(SERIALIZABLE)
- 最高隔离级别,完全串行执行事务2
- 解决所有并发问题,但性能开销最大2
5. 默认(DEFAULT)
- 使用数据库默认的隔离级别2
🔄 事务传播行为
传播行为定义了事务方法之间相互调用时的行为规则:
PROPAGATION_REQUIRED(默认)
- 当前方法必须在事务中运行,不存在则新建,存在则加入
PROPAGATION_REQUIRES_NEW
- 总是新建事务,如果当前存在事务则挂起
PROPAGATION_SUPPORTS
- 支持当前事务,不存在则以非事务方式执行
PROPAGATION_NOT_SUPPORTED
- 以非事务方式执行,存在事务则挂起
PROPAGATION_MANDATORY
- 必须在事务中运行,否则抛出异常
PROPAGATION_NEVER
- 必须在非事务环境下执行,否则抛出异常
PROPAGATION_NESTED
- 如果当前存在事务,则在嵌套事务内执行
💡 实践建议
立即配置检查:在关键业务方法上明确指定@Transactional的isolation和propagation属性,避免依赖默认配置带来的不确定性。比如资金操作使用REQUIRES_NEW传播行为,查询操作使用READ_COMMITTED隔离级别,平衡数据一致性和系统性能。
举例说明不同隔离级别的问题
不同事务隔离级别主要解决并发操作时的数据一致性问题,级别越高数据越安全,但并发性能越低。6
📊 四种隔离级别及对应问题
1. 读未提交(READ UNCOMMITTED)
- 问题:脏读 - 能读取其他事务未提交的修改4
- 例子:事务A将余额从1000改为2000(未提交),事务B查询余额看到2000,但事务A随后回滚,事务B读到的就是无效数据
2. 读已提交(READ COMMITTED)
- 解决:脏读
- 问题:不可重复读 - 同一事务内多次读取结果不一致
- 例子:事务A第一次查询余额为1000,事务B将余额改为2000并提交,事务A再次查询余额变成2000
3. 可重复读(REPEATABLE READ)
- 解决:脏读、不可重复读
- 问题:幻读 - 同一事务内多次查询返回的行数不同
- 例子:事务A查询年龄=20的用户有3人,事务B新增1个年龄=20的用户并提交,事务A再次查询发现有4人
4. 串行化(SERIALIZABLE)
- 解决所有问题:脏读、不可重复读、幻读6
- 代价:完全串行执行,性能最低2
💡 实用建议
立即检查你的数据库默认隔离级别,大多数业务场景使用读已提交或可重复读就能平衡数据一致性和系统性能
Spring解决循环依赖的原理:
- 构造器注入循环依赖问题:
Spring容器默认不支持通过构造器注入解决循环依赖的问题。这是因为构造器注入在Bean创建的早期阶段就需要完成,而此时容器还没有完全准备好所有需要的Bean实例。 - Setter方法注入与字段注入:
Spring通过使用setter方法或字段注入(从Spring 4.3开始支持)来处理循环依赖问题,因为它允许在对象实例化之后设置依赖关系。这意味着在创建Bean的过程中,可以先实例化Bean,然后再设置其依赖关系
Spring内部如何实现:
Spring使用三级缓存来解决单例Bean的循环依赖问题:
- 一级缓存(Singleton Objects) :存放完全初始化好的Bean。
- 二级缓存(Early Singleton Objects) :存放早期引用的对象(半成品对象)。当Bean还在创建过程中时,会被放入这个缓存。
- 三级缓存(Singleton Factories) :存放对象工厂。每个对象工厂负责创建对应的Bean实例。
java如何统计接口访问量
concurrentHashmap怎么实现计数 java代码
在Java中,ConcurrentHashMap 是 Java 5 引入的一个线程安全的哈希表实现。它通过分段锁(segmentation)的方式来减少锁的粒度,从而提高并发性能。但是,值得注意的是,自从 Java 8 以后,ConcurrentHashMap 的实现已经发生了重大变化,不再使用分段锁而是采用了更为复杂的同步策略(如基于 Node 数组的 CAS 操作和 synchronized 块)。
如果你想在 Java 中使用 ConcurrentHashMap 来实现一个计数器,你可以直接利用它的 get 和 put 方法来实现原子性操作。这里是如何使用 ConcurrentHashMap 来创建一个简单的计数器的示例代码:
示例代码
javaCopy Code
import java.util.concurrent.ConcurrentHashMap;
public class Counter {
private final ConcurrentHashMap counterMap;
public Counter() {
this.counterMap = new ConcurrentHashMap<>();
}
public void increment(String key) {
counterMap.merge(key, 1, Integer::sum);
}
public int getCount(String key) {
return counterMap.getOrDefault(key, 0);
}
public static void main(String[] args) {
Counter counter = new Counter();
counter.increment("event1");
counter.increment("event1");
counter.increment("event2");
System.out.println("Count for event1: " + counter.getCount("event1")); // 输出: Count for event1: 2
System.out.println("Count for event2: " + counter.getCount("event2")); // 输出: Count for event2: 1
}
}
解释
- 构造函数:初始化一个
ConcurrentHashMap实例。 - increment 方法:使用
ConcurrentHashMap的merge方法来原子地增加计数。merge方法接受三个参数:键、值(这里是1,表示每次增加的值)、以及一个合并函数(这里是Integer::sum,用于将现有的值和新的值相加)。 - getCount 方法:使用
getOrDefault方法来获取键对应的值,如果不存在则返回0。
注意事项
- 使用
ConcurrentHashMap的merge方法是进行原子性更新的推荐方式,因为它内部使用了乐观锁(基于 CAS 操作)和必要的同步机制来确保线程安全。 - 如果你的计数器只需要简单的增加操作,也可以直接使用
counterMap.compute(key, (k, v) -> (v == null) ? 1 : v + 1)来实现,但这通常不如merge方法直观。 - 对于更复杂的更新逻辑(例如,基于条件更新),
merge方法提供了极大的灵活性。
通过这种方式,你可以利用 ConcurrentHashMap 的特性来实现一个高效且线程安全的计数器。
mysql一次select执行过程
MySQL中一条SELECT语句的执行过程可以分为以下几个关键步骤:
连接阶段
- 连接器负责与客户端建立TCP连接,验证用户名和密码
- 获取用户权限信息并缓存,后续操作都基于此权限2
查询处理阶段
-
查询缓存(MySQL 8.0已移除)
- 检查是否执行过相同查询
- 命中缓存则直接返回结果25
-
分析器
- 词法分析:识别SQL关键字、表名、列名等13
- 语法分析:构建抽象语法树(AST),验证SQL语法正确性13
-
优化器
- 生成执行计划,选择最优索引23
- 决定多表连接顺序和查询策略23
-
执行器
- 检查表操作权限3
- 调用存储引擎接口执行查询23
数据获取阶段
- 存储引擎根据执行计划读取数据
- 使用索引或全表扫描定位数据行2
- 逐行判断WHERE条件,将满足条件的行加入结果集3
结果返回阶段
- 服务器将结果封装成网络数据包2
- 通过连接返回给客户端23
核心架构分层
整个过程涉及MySQL的两大核心层:
- Server层:连接器、分析器、优化器、执行器等核心组件35
- 存储引擎层:负责数据存储和提取,支持InnoDB、MyISAM等35
实用建议:要深入了解具体查询的执行细节,可以使用EXPLAIN命令分析SQL执行计划,查看索引使用情况和性能瓶颈4
MySQL 8.0已移除 为什么
MySQL 8.0已移除 为什么
MySQL 8.0移除查询缓存(Query Cache)的主要原因包括:
- 性能瓶颈与复杂性
查询缓存在高并发场景下会引发全局锁竞争,导致性能下降而非提升。其维护开销大,且缓存命中率低,频繁更新数据时需清除缓存,反而浪费资源。 - 数据一致性问题
高并发更新时,查询缓存易出现数据不一致问题,影响系统稳定性。 - 内存占用与扩展性
缓存大量数据占用内存,影响性能;且MySQL 8.0追求更高扩展性,查询缓存与现代架构格格不入。 - 替代方案更优
MySQL 8.0推荐使用应用层缓存(如Memcached/Redis)或优化InnoDB Buffer Pool,这些方案更高效、可扩展。 - 官方优化方向
MySQL团队认为查询缓存设计初衷已过时,移除后系统性能、可伸缩性和稳定性显著提升。
HashMap线程不安全场景
HashMap在多线程环境下主要存在以下几种线程不安全场景:
🚨 主要线程安全问题
1. 数据覆盖(最常见问题)
当多个线程同时执行put操作时,如果发生哈希冲突(计算出的插入下标相同),可能导致一个线程的写入被另一个线程覆盖。比如线程A和B同时向同一个桶位置插入数据,最终只有一个线程的数据会被保留。
2. 扩容导致的问题
- JDK 1.7及之前:扩容时可能产生死循环,链表形成环形结构3
- JDK 1.8:虽然解决了典型的死循环问题,但扩容过程中仍可能出现数据丢失或状态混乱36
3. size计数不准确
多个线程同时修改HashMap时,size的计数可能不准确,因为size++不是原子操作3。
4. 读取到不一致状态
一个线程正在更新HashMap时,另一个线程读取可能获得更新过程中的中间状态值,导致数据不一致4。
🔍 具体场景示例
并发put导致数据覆盖:
javaCopy Code
// 两个线程同时put key=1和key=9(假设哈希冲突)
// 最终可能只有一个值被成功存储,且size计数错误:ml-citation{ref="1" data="citationList"}
💡 立即行动建议
在多线程环境中,请使用 ConcurrentHashMap 替代 HashMap,它通过分段锁技术保证了线程安全且性能较好,只需将代码中的 HashMap 直接替换为 ConcurrentHashMap 即可解决上述所有问题。
redis场景题:与数据库的数据一致性、排行榜怎么做(大key、分片时因为排行榜导致的节点跳跃怎么处理)
我将为您详细解答Redis数据一致性和排行榜实现的相关问题。
Redis与数据库数据一致性
数据一致性问题的核心在于写操作的执行顺序。推荐使用"先更新数据库,再删除缓存"的策略13。
主要解决方案
1. 延迟双删策略
在更新数据库前后都执行缓存删除操作,第二次删除需要延迟几百毫秒3。这样可以处理并发读取导致的脏数据问题。
2. Cache-Aside模式(旁路缓存)
这是业务系统最常用的缓存策略:
- 读操作:先查缓存,未命中则查数据库并写入缓存
- 写操作:先更新数据库,再删除缓存
3. 最终一致性保证
对于大多数业务场景,保证最终一致性即可,不必追求强一致性。可以通过设置合理的缓存过期时间来自动修正数据不一致。
推荐实践
- 优先选择删除缓存而非更新缓存1
- 设置合理的缓存过期时间
- 对于删除失败的情况,实现重试机制3
Redis排行榜实现
Redis的有序集合(ZSet)是实现排行榜的理想选择45。
核心命令
bashCopy Code
ZADD key score member # 添加/更新成员分数
ZREVRANK key member # 获取成员排名(从高到低)
ZREVRANGE key start stop # 获取排名范围内的成员
ZSCORE key member # 获取成员分数
解决大Key问题
1. 数据分片
将大排行榜按时间维度拆分:
bashCopy Code
# 按天拆分
leaderboard:20251201
leaderboard:20251202
2. 冷热数据分离
- 热数据:最近活跃用户保留在排行榜
- 冷数据:历史数据归档到数据库
处理分片时的节点跳跃
问题分析:在Redis集群分片环境下,不同用户的分数可能分布在不同的节点上,导致全局排名计算困难。
解决方案:
1. 预分桶策略
将分数范围划分为多个桶,每个桶对应一个有序集合:
bashCopy Code
# 0-100分用户
leaderboard:bucket:1
# 101-200分用户
leaderboard:bucket:2
浏览器缓存是浏览器为加速网页加载而临时存储资源的技术,主要分为内存缓存、磁盘缓存和HTTP缓存(含强缓存与协商缓存)三类。
内存缓存
- 存储位置:浏览器内存中,生命周期短(关闭浏览器即清除)。
- 适用场景:临时存储当前会话的资源(如页面元素、脚本),访问速度快但不持久。 12
磁盘缓存
- 存储位置:本地磁盘,生命周期长(需手动或程序清除)。
- 适用场景:长期存储静态资源(如图片、CSS、JS文件),下次访问时可直接读取。 13
HTTP缓存
- 强缓存:通过Cache-Control或Expires字段控制,直接使用本地缓存无需向服务器请求。 34
- 协商缓存:通过Last-Modified/ETag验证资源是否更新,若未更新则返回304状态码。 34
其他浏览器存储机制
- LocalStorage:持久化存储键值对,关闭浏览器后仍保留。 6
- SessionStorage:临时存储键值对,关闭浏览器即清除。 6
- Application Cache(AppCache):缓存整个页面,适用于离线访问
jvm调优
- 年轻代(Young Generation) :主要用于存放新生的对象。通常,年轻代被分为一个Eden区和两个Survivor区(通常命名为from和to)。
- 老年代(Old Generation) :主要用于存放长期存活的对象。
2. 调整年轻代和老年代的大小
- **
-Xmn或-XX:NewSize和-XX:MaxNewSize**:用于设置年轻代的大小。例如,-Xmn512m将年轻代设置为512MB。 - **
-XX:SurvivorRatio**:用于设置Eden区与一个Survivor区的大小比例。例如,-XX:SurvivorRatio=8表示Eden区与一个Survivor区的大小比例为8:1。 - **
-XX:NewRatio**:用于设置年轻代与老年代的比例。例如,-XX:NewRatio=3表示年轻代占整个堆的1/4,老年代占3/4
老年代空间不足 内存泄露 年轻代内存不足 jmap 查看堆日志 jstack 查看线程日志
内存参数配置
JVM参数配置
JVM参数配置是优化Java应用性能的关键,主要包括内存管理参数和垃圾收集器参数。以下是核心配置说明:
1. 内存管理参数
-
堆内存设置
-Xms:初始堆大小(如-Xms512m)-Xmx:最大堆大小(如-Xmx2g)-Xmn:新生代大小(如-Xmn256m)-XX:NewRatio=:新生代与老年代比例(默认2,即1:2)-XX:SurvivorRatio=:Eden区与Survivor区比例(默认8,即8:1:1)
-
元空间设置
-XX:MetaspaceSize=:元空间初始大小(如-XX:MetaspaceSize=256m)34-XX:MaxMetaspaceSize=:元空间最大值(如-XX:MaxMetaspaceSize=512m)34
-
线程栈设置
-Xss:线程栈大小(如-Xss256k)
-
直接内存设置
-XX:MaxDirectMemorySize=:直接内存大小(如-XX:MaxDirectMemorySize=1g)
2. 垃圾收集器参数
-
串行回收器
-XX:+UseSerialGC:单线程串行回收器(适合小内存环境)34
-
并行回收器
-XX:+UseParallelGC:多线程并行回收器(吞吐量优先)34-XX:ParallelGCThreads=:设置并行线程数(如-XX:ParallelGCThreads=4)
-
CMS回收器
-XX:+UseConcMarkSweepGC:低延迟回收器(适合Web应用)34-XX:CMSInitiatingOccupancyFraction=:触发GC的阈值(如-XX:CMSInitiatingOccupancyFraction=75)34
-
G1回收器
-XX:+UseG1GC:平衡型回收器(JDK9+默认)34-XX:MaxGCPauseMillis=
热点数据问题
如果订单要是用雪花算法或字符串想做hash,怎么分库分表
使用雪花算法或字符串哈希进行分库分表时,关键是要解决数据分布均匀性和查询效率问题。
雪花算法分库分表方案
核心问题:直接使用雪花算法生成的ID进行分表时,在低并发场景下会出现数据倾斜3。因为雪花算法的12位序列号每毫秒都从0开始,低并发时sequence基本为0或很小数字,导致数据集中在前几个分表中。
优化方案:
- 美团Leaf优化:在雪花算法基础上进行改进,避免序列号总是从0开始
- 基因分片法:在订单号中嵌入用户ID哈希值,如订单号=时间戳+用户ID后4位+随机数,直接根据订单号定位库表
- 嵌入分片信息:在订单号头部添加分库分表路由码(如库序号+表序号),实现无索引直接查询
字符串哈希分库分表方案
常用哈希算法:
- CRC32散列:
crc32($keyword) >> 16 & 0xffff1 - 一致性哈希:减少节点增减时的数据迁移量
- 取模运算:
hash(keyword) % (库数量×表数量)2
实现示例:
phpCopy Code
function getHash(&$keyword,$n) {
$hash = crc32($keyword) >> 16 & 0xffff;
return sprintf("%02s",$hash % $n);
}
$table = 'userinfo_'.getHash($username,100);
$sql = "insert into {$table} values(....)";
订单号分片怎么保证数据分配均匀
订单号分片保证数据分配均匀的关键在于合理选择分片键和分片算法。以下是具体方案:
1. 分片键选择
- 高基数原则:订单号应具有高基数(值的范围大),避免热点集中。例如,使用订单ID(通常为自增ID)作为分片键,确保数据均匀分布5。
- 查询亲和性原则:订单号应与高频查询条件一致,减少跨节点查询。例如,若订单查询主要按用户维度进行,可结合用户ID和订单号进行复合分片5。
2. 分片算法
- 哈希分片:对订单号进行哈希计算(如MD5、CRC32),然后取模决定分片位置。优点是数据分布均匀,但扩容时需重新分布数据23。
- 一致性哈希:通过哈希环解决扩容问题,仅相邻节点数据迁移,减少整体迁移量36。
- 范围分片:按订单号连续范围分片(如按时间范围),适合范围查询但易形成热点2。
3. 实践建议
- 分库分表工具:使用ShardingSphere或MyCat等工具实现自动分片。
- 动态分片策略:根据数据量动态调整分片,避免单一分片过载1。
- 数据迁移:定期迁移数据,确保各分片数据量接近平衡
分片策略选择
按用户ID分片2:
- 适用场景:用户维度的查询频繁
- 实现:
user_id % 64分库,user_id % 1024分表 - 优点:同一用户数据集中存储,避免跨分片查询
按订单号分片:
- 适用场景:需均匀分散写入压力,避免热点
- 优点:数据分布均匀,支持高并发写入
订单号分片怎么保证数据分配均匀
订单号分片保证数据分配均匀的关键在于合理选择分片键和分片算法。以下是具体方案:
1. 分片键选择
- 高基数原则:订单号应具有高基数(值的范围大),避免热点集中。例如,使用订单ID(通常为自增ID)作为分片键,确保数据均匀分布5。
- 查询亲和性原则:订单号应与高频查询条件一致,减少跨节点查询。例如,若订单查询主要按用户维度进行,可结合用户ID和订单号进行复合分片5。
2. 分片算法
- 哈希分片:对订单号进行哈希计算(如MD5、CRC32),然后取模决定分片位置。优点是数据分布均匀,但扩容时需重新分布数据23。
- 一致性哈希:通过哈希环解决扩容问题,仅相邻节点数据迁移,减少整体迁移量36。
- 范围分片:按订单号连续范围分片(如按时间范围),适合范围查询但易形成热点2。
3. 实践建议
- 分库分表工具:使用ShardingSphere或MyCat等工具实现自动分片。
- 动态分片策略:根据数据量动态调整分片,避免单一分片过载1。
- 数据迁移:定期迁移数据,确保各分片数据量接近平衡
跨分片查询解决方案
索引表机制2:
- 建立订单号→用户ID映射表
- 查询时先查索引表获取用户ID,再路由到对应分片
数据冗余2:
- 多写异构:订单同时写入买家库和卖家库
- 字段冗余:订单表冗余商家名称、用户名称等高频字段
建议先评估业务的具体并发量和查询模式,选择适合的分片策略,并在测试环境中验证数据分布的均匀性,避免上线后出现严重的数据倾斜问题。
浏览器缓存有哪些 哪几种实现
浏览器缓存主要分为以下类型及实现方式:
一、按缓存层级分类
-
浏览器缓存
-
强缓存:通过
Expires(绝对时间)或Cache-Control: max-age(相对时间)控制,直接使用本地资源2。 -
协商缓存:通过
ETag或Last-Modified与服务器校验,未更新则返回3043。 -
存储位置:
- Memory Cache:内存缓存,适合频繁访问的短期资源(如当前页面的CSS/JS)。
- Disk Cache:磁盘缓存,适合大文件,关闭页面后仍有效。
-
-
CDN缓存
由网站管理员部署,通过网关服务器动态转发请求,缓存机制与浏览器缓存类似1。 -
代理服务器缓存
作为中间层缓存资源副本,减少源服务器请求1。 -
其他缓存
- 数据库缓存:提升复杂查询性能1。
- 应用层缓存:代码逻辑实现的缓存(如Redis)1。
二、按技术实现分类
-
HTTP缓存
- 强缓存:
Cache-Control(优先级高于Expires)3。 - 协商缓存:
ETag(唯一标识)和Last-Modified(最后修改时间)3。
- 强缓存:
-
本地存储
-
Cookie:存储用户身份信息,大小限制4KB4。
-
Web Storage:
localStorage:永久存储,大小约5MB4。sessionStorage:会话级存储,关闭页面后清除4。
-
IndexedDB:支持复杂查询的客户端数据库。
-
Web SQL:已废弃,部分浏览器仍支持。
-
concurrenthashmap1.7和1.8区别
ConcurrentHashMap 1.7 vs 1.8 主要区别
| 维度 | JDK 1.7 | JDK 1.8 |
|---|---|---|
| 数据结构 | Segment数组 + HashEntry链表 | Node数组 + 链表 + 红黑树 |
| 锁机制 | 分段锁(ReentrantLock) | 细粒度锁(synchronized + CAS) |
| 并发度 | 默认16个Segment,每个Segment独立锁 | 每个Node独立锁,无固定并发度上限 |
| 链表转红黑树 | 无此机制 | 链表长度>8时自动转红黑树(提高查询效率)且数据长度大于64; 小于64是扩容 链表是解决hash冲突的 hashCode& (length-1)重点 |
| 查询复杂度 | 链表O(n) | 红黑树O(log n) |
| 初始化机制 | Segment延迟初始化 | Node数组延迟初始化(CAS) |
| 扩容机制 | Segment级扩容 | Node级扩容(CAS) |
| 性能提升 | 通过分段锁降低锁竞争 | 通过细粒度锁和红黑树优化查询性能 |
关键区别解析
-
数据结构升级
JDK 1.7 使用Segment数组(类似多个小的HashMap)+HashEntry链表结构,而 JDK 1.8 直接采用Node数组(类似HashMap)+ 链表+红黑树结构。- 1.7:Segment 数组默认16个,每个Segment包含一个HashEntry数组和链表。
- 1.8:Node数组延迟初始化,链表长度超过8时自动转红黑树(提高查询效率)。
-
锁机制优化
JDK 1.7 使用分段锁(ReentrantLock)控制并发,而 JDK 1.8 采用细粒度锁(synchronized + CAS)。- 1.7:锁粒度较大,每个Segment独立锁(16个锁)。
- 1.8:锁粒度更细,每个Node独立锁(无固定上限),通过CAS减少锁竞争。
-
查询性能提升
JDK 1.8 引入红黑树优化链表查询,链表长度超过8时自动转红黑树(查询复杂度从O(n)降为O(log n))。- 1.7:链表查询O(n)。
- 1.8:链表长度>8时转红黑树,查询效率显著提升。
-
初始化与扩容机制
JDK 1.8 通过CAS实现Node数组延迟初始化和Node级扩容,减少锁竞争。- 1.7:Segment级扩容,锁竞争较大。
- 1.8:Node级扩容,锁竞争更小。
总结
JDK 1.8 通过数据结构升级(链表转红黑树)和锁机制优化(细粒度锁+CAS)显著提升了ConcurrentHashMap的并发性能和查询效率,是JDK 1.7的升级版
concurrentHashmap什么情况触发扩容
ConcurrentHashMap 主要在以下两种情况下会触发扩容45:
- 元素数量超过阈值:当执行插入操作后,如果整个Map中的元素总数超过了(当前数组长度 × 负载因子(默认0.75))计算得出的扩容阈值,就会触发扩容。
- 链表转红黑树前的检查:当某个桶中的链表长度达到8时,会尝试将链表转换为红黑树。但在转换之前,会先检查当前数组的长度是否小于64。如果数组长度小于64,则会优先进行数组扩容,而不是立即将链表树化。
核心机制说明:负载因子(默认0.75)是一个平衡性能和空间利用率的关键参数7。设置一个小于1的负载因子(如0.75),可以在哈希表变得过于拥挤之前就触发扩容,这有助于减少哈希冲突,保持较好的查询和插入性能。
💡 实用建议:
在初始化 ConcurrentHashMap 时,如果能预估大致的元素数量,建议使用带有初始容量的构造函数(例如 new ConcurrentHashMap<>(expectedSize))。这可以有效减少甚至避免运行期间的扩容操作,提升程序性能。
HASHMAP什么情况触发扩容
HashMap 主要在以下两种情况下会触发扩容:
1. 元素数量达到扩容阈值
当 HashMap 中的元素数量超过(当前数组长度 × 负载因子)时就会触发扩容。默认负载因子为0.75,初始容量为,所以当元素数量超过12个时就会开始扩容。
2. 链表转红黑树前的检查
当某个桶中的链表长度达到8时,准备将链表转换为红黑树之前,会先检查数组长度是否达到64。如果数组长度小于64,会优先进行数组扩容,而不是立即将链表树化。
💡 实用建议:在初始化 HashMap 时,如果能预估大致的元素数量,建议使用带有初始容量的构造函数(如 new HashMap<>(expectedSize))。这可以有效避免运行期间的频繁扩容操作,显著提升程序性能。
Redis大key怎么解决? 怎么预防? 怎么排查?
RedisInsight怎么设置阈值
RedisInsight阈值设置步骤
-
连接Redis实例
打开RedisInsight,通过IP/端口连接目标Redis集群,需确保管理端口(如9443)和数据库端口(如6379)畅通6。 -
进入告警配置界面
在左侧导航栏选择"Monitoring" → "Alerts",点击"Create Alert Rule"。 -
配置内存使用率阈值
- 指标类型:选择"Memory Usage"
- 条件:设置"used_memory"超过80%触发警告,超过90%触发严重告警3
- 持续时间:持续5分钟超过阈值则触发
-
配置连接数阈值
- 指标类型:选择"Connected Clients"
- 条件:设置超过1000个连接时触发告警
- 建议值:根据业务需求调整,通常不超过最大连接数的80%
-
配置慢查询数量阈值
- 指标类型:选择"Slow Queries"
- 条件:设置每分钟超过10条慢查询时触发告警
- 建议值:根据
slowlog-log-slower-than配置的阈值(如10ms)动态调整1
-
设置通知方式
选择邮件/短信/webhook通知,配置接收人信息,测试告警通知是否正常。
关键指标建议阈值
- 内存使用率:警告80%,严重90%
- 连接数:不超过最大连接数的80%
- 慢查询:每分钟>10条(阈值建议10ms)1
- 内存碎片率:保持、<)
- index:全索引扫描
- ALL:全表扫描(需优化)
📊 关键性能指标
- possible_keys:可能使用的索引
- key:实际使用的索引6
- rows:预估需要读取的行数(越小越好)4
- filtered:条件过滤后返回的行百分比2
- Extra:额外信息,如"Using index"表示覆盖索引5
💡 优化建议
立即检查你的慢查询:在SQL语句前加上EXPLAIN关键字,重点关注type为ALL(全表扫描)和rows值较大的查询,这些是首要优化目标。
es脑裂怎么解决
ES脑裂问题解决方案
脑裂是Elasticsearch集群中由于网络分区导致出现多个主节点的现象。以下是核心解决方案:
主要预防措施 45
- 设置最小主节点数:配置
discovery.zen.minimum_master_nodes为(master_eligible_nodes / 2) + 1 - 使用奇数个主节点:建议集群包含3、5、7等奇数个符合主节点条件的节点
- 合理网络规划:确保集群节点间的网络延迟和稳定性
具体配置方案 5
对于3节点集群,设置:
yamlCopy Code
discovery.zen.minimum_master_nodes: 2
解决方案优势 6
- 确保只有获得超过半数投票的节点才能成为主节点
- 有效防止网络分区时出现多个主节点
- 在ES 7.0+版本中已成为默认配置
建议立即检查:登录您的ES集群,确认discovery.zen.minimum_master_nodes参数是否已正确配置为(节点数/2)+1,这是防止脑裂最直接有效的方法。
es为什么出现多个主节点会导致数据不一致问题
Elasticsearch出现多个主节点(脑裂)导致数据不一致的原因如下:
- 网络分区引发双主竞争
当集群因网络问题被隔离为两部分时,每部分可能误判对方节点失效并选举新主节点,形成两个独立的主节点。此时两个主节点会同时处理写请求,导致相同索引的数据在不同分区出现冲突 - 主节点角色与数据分片冲突
主节点负责管理集群状态(如分片分配),若同时承担数据节点角色,高负载或内存回收可能导致响应延迟。其他节点因超时误判主节点失效,触发新选举,但原主节点恢复后仍继续处理请求,造成分片状态混乱13。 - 分片同步机制失效
写操作需主分片与副本分片同步完成(默认quorum一致性级别)。脑裂时,不同主节点可能对同一分片执行冲突的写操作,且因网络隔离无法完成同步,最终导致数据版本冲突或丢失
根本原因:Elasticsearch依赖主节点统一协调集群状态,多个主节点破坏了这一协调机制,使分片状态、数据版本等关键信息无法达成一致15。
需要具体调优参数清单吗?可以帮你整理discovery.zen.ping_timeout等关键配置的推荐值,快速预防脑裂。
什么是倒排索引
倒排索引是一种通过词项(单词)快速定位到包含该词项的文档的数据结构,主要用于全文检索系统。其核心思想是将文档中的词项与文档的映射关系反转,形成“词项→文档列表”的索引方式3。
核心组成
- 词项词典(Term Dictionary) :存储所有唯一词项,并指向倒排列表的入口5。
- 倒排列表(Posting List) :记录包含该词项的文档ID、词频及位置信息56。
**
与普通索引的区别
- 普通索引:直接按字段值排序,适合精确匹配(如
WHERE name = 'xxx')1。 - 倒排索引:通过分词、去重后建立词项到文档的映射,支持模糊查询和全文检索15。
**
典型应用
现代搜索引擎(如Elasticsearch)依赖倒排索引实现高效检索,例如输入“人工智能”可快速返回相关文档46。
**
想了解倒排索引在Elasticsearch中的具体实现细节吗?我可以快速梳理它的分词、压缩和查询优化流程。
给我一个倒排索引的简单示例
倒排索引是一种通过关键词快速找到包含该关键词的文档的索引方法。下面用一个简单例子说明:
假设我们有3个文档:
- 文档1:"我爱编程"
- 文档2:"编程很有趣"
- 文档3:"我爱学习"
建立倒排索引的过程就是:
- 分词:将每个文档内容拆分成单个词语
- 建立映射:记录每个词语出现在哪些文档中
**
最终得到的倒排索引表如下:
| 关键词 | 出现的文档 |
|---|---|
| 我 | 文档1, 文档3 |
| 爱 | 文档1, 文档3 |
| 编程 | 文档1, 文档2 |
| 很 | 文档2 |
| 有趣 | 文档2 |
| 学习 | 文档3 |
搜索过程:
- 搜索"编程" → 立即找到文档1和文档2
- 搜索"我爱" → 找到"我"和"爱"的交集:文档1和文档3
核心优势:5不再需要扫描所有文档,直接通过关键词就能定位到相关文档,大大提升检索效率。
动手建议:想要更直观理解?可以尝试用Python写个简单的倒排索引实现,只需要几十行代码就能体验完整的建索引和搜索流程。
怎么监控脑裂
脑裂监控的核心方法
- 心跳机制监控
通过Zabbix自定义监控项检测节点间心跳信号,若主备节点长时间未收到对方心跳(如超过3次超时),则触发报警25。
**
- 虚拟IP(VIP)状态检测
配置Zabbix定期检查VIP绑定状态,若发现同一VIP出现在多个节点(如主备节点同时持有10.0.0.12),立即判定为脑裂25。
**
00:04 脑裂现象的定义
00:59 脑裂现象产生的原因
01:44 脑裂现象的解决办法
02:31 引入第三方机构
-
网络与配置验证
- 使用脚本双向Ping检测节点间连通性(如
ping -c 2 -W 3)3 - 检查Keepalived配置一致性(如
virtual_router_id)
- 使用脚本双向Ping检测节点间连通性(如
实施步骤
-
环境准备
- 主备节点安装Zabbix Agent和Keepalived
- 配置Zabbix Server与Agent的通信密钥4
-
监控项配置示例
bashCopy Code # Zabbix自定义监控项:检查VIP状态 UserParameter=keepalived.vip.check, ip addr grep 10.0.0.12 | wc -l -
报警规则设置
- 触发条件:VIP数量≠1 或 心跳超时
- 动作:发送邮件/短信通知,并自动触发故障转移脚本56
预防措施
- 部署双心跳线(如串口+以太网)降低单点故障风险13
- 启用仲裁机制(如通过网关IP判断网络分区)16
- 定期测试故障切换流程
慢sq查询排查
慢SQL排查主要从定位慢查询、分析执行计划、优化SQL语句和调整系统配置几个方面入手。
🔍 快速定位慢SQL
开启慢查询日志是最直接的方法4:
- MySQL:设置
slow_query_log=1,long_query_time=1(超过1秒的查询) - 查看当前运行查询:
SHOW FULL PROCESSLIST4
📊 分析执行计划
使用 EXPLAIN 命令分析SQL执行过程,重点关注23:
- 是否命中索引 - 避免全表扫描
- 扫描行数 - 数据量过大即使走索引性能也不好
- 排序/临时表 - 是否产生额外开销
- 预估与实际行数偏差 - 统计信息是否准确
🛠️ 常见优化方案
索引优化34:
- 为查询条件添加合适索引
- 避免隐式类型转换导致索引失效
- 减少函数操作对索引的影响
SQL语句优化3:
- 减少不必要的列查询
- 使用覆盖索引
- 复杂SQL拆分为简单查询
- 大数据量考虑分表操作
哪些场景会导致接口超时
接口超时通常由网络问题、服务器负载、代码逻辑或配置不当引起。以下是常见场景:
网络异常
- 网络抖动或丢包:偶发的网络波动可能导致数据包丢失,引发超时。 12
- 带宽不足:高并发请求超出服务器带宽上限时,数据传输延迟增加。 12
- 服务器不可达:目标IP/端口错误、服务器宕机或跨地域链路故障。 2
服务器资源瓶颈
- 线程池满:异步任务排队等待处理,导致响应延迟。 13
- 数据库死锁:批量操作与事务冲突,阻塞接口执行。 13
- 服务过载:高并发下服务器资源(如CPU、内存)耗尽。 24
代码或配置问题
- 参数过多:批量查询传入大量ID,导致数据库全表扫描。 13
- 超时时间过短:未根据业务场景合理设置超时阈值。 25
- SQL未走索引:查询条件未命中索引,执行效率低下。 3
其他因素
- 安全策略限制:服务器限制最大连接数或请求速率。 4
- 协议层错误:HTTP头配置异常(如Content-Length不匹配)。 2
- HTTPS证书问题:证书过期或双向认证失败。 2
数据库死锁产生的原因及解决方案
数据库死锁是指两个或多个事务在执行过程中,因争夺资源而陷入相互等待的状态,导致所有事务都无法继续执行4。
🔍 死锁产生的主要原因
1. 资源竞争
- 多个事务同时修改同一行数据4
- 事务A锁定某行进行修改,事务B也试图修改同一行
2. 锁升级冲突
- 共享锁(读锁)升级为排他锁(写锁)时产生冲突4
3. 事务顺序不当4
- 事务A锁定资源1后请求资源2
- 事务B锁定资源2后请求资源1
- 形成循环等待:A等B释放资源2,B等A释放资源12
4. 全表扫描风险
- 无索引条件的查询会引发全表扫描2
- 行级锁可能升级为表级锁,增加死锁概率2
💡 有效解决方案
预防措施:
- 统一操作顺序:多表操作时按相同顺序处理2
- 避免长事务:减少锁的持有时间4
- 合理使用索引:避免全表扫描2
技术手段:
- 一次封锁法:事务一次性锁定所有需要的数据
- 顺序封锁法:预先规定数据对象的封锁顺序
死锁处理:
- MySQL会自动检测死锁并回滚其中一个事务5
- 使用
SHOW ENGINE INNODB STATUS查看死锁详情5 - 分析死锁日志定位业务逻辑冲突6
🛠️ 立即行动建议
检查你的SQL语句,确保多表操作按固定顺序执行,并为常用查询字段添加合适索引,这能显著降低死锁发生概率。
服务过载:高并发下服务器资源(如CPU、内存)耗尽
高并发环境下服务器资源耗尽是系统崩溃的主要原因,主要表现为CPU使用率持续超过80%、内存使用率接近90%、系统负载急剧上升等4。这种情况通常由资源竞争、线程配置不当、请求队列堆积等因素触发1。
🔍 资源耗尽的核心表现
CPU瓶颈
- CPU使用率持续超80%,系统负载过高
- 大量复杂计算占用CPU资源
- 线程/进程数量过多导致上下文切换频繁4
内存瓶颈
- 内存使用率接近90%,频繁进行内存交换
- 缓存设计缺陷或内存泄漏导致内存逐渐耗尽4
连锁反应风险
当某个关键程序资源耗尽时,会引发依赖链上的其他程序相继崩溃,最终导致整个系统瘫痪5。
🛠️ 立即优化方案
线程池配置优化
合理设置核心线程数、最大线程数和队列容量,避免任务堆积导致资源耗尽。对于8核服务器,建议核心线程数设置为CPU核数,最大线程数可适当增加。
容量设计策略
根据业务预估TPS和QPS制定程序最大容量,在业务和成本承受范围内选择合适规格5。
异步编程应用
考虑使用协程或异步框架替代传统线程模型,减少CPU和内存的资源消耗5。
游标分页与Offset/Limit分页的对比分析
| 特性 | 游标分页(基于唯一ID)26 | Offset/Limit分页13 |
|---|---|---|
| 实现原理 | 通过WHERE条件锚定上一页最后一条记录的ID(如id > last_id)26 | 通过LIMIT限制返回记录数,OFFSET跳过指定行数13 |
| 性能表现 | - 线性时间复杂度:查询时间与数据量无关,仅依赖当前页记录数26 - 高效处理大数据集:避免全表扫描,支持千万级数据67 | - 随着页码增加,性能急剧下降:OFFSET值大时需跳过大量记录34 - 深度分页时耗时飙升:如OFFSET 99990时性能差10倍6 |
| 数据一致性 | - 通过游标锚定,数据变动不影响分页结果27 | - 容易出现偏移问题:数据插入/删除可能导致页数错乱35 |
| 可扩展性 | - 支持任意排序字段:不限制排序方式27 | - 仅支持固定排序:需按ORDER BY字段排序15 |
| 资源消耗 | - 低内存占用:仅处理当前页数据6 | - 高内存消耗:需扫描大量记录34 |
| 适用场景 | - 大数据集(如千万级数据)67 | - 小数据集或低频深度分页1 |
| SQL示例 | sql<br>SELECT * FROM users<br>WHERE id > 1000<br>ORDER BY id DESC<br>LIMIT 10;2 | sql<br>SELECT * FROM users<br>ORDER BY id DESC<br>LIMIT 10 OFFSET 20;1 |
关键优势总结
-
游标分页:
- 性能优势:通过游标锚定,查询时间稳定,不受页码影响26。
- 数据一致性:避免偏移问题,数据变动不影响分页结果7。
- 可扩展性:支持任意排序字段,适用于分布式环境27。
-
Offset/Limit分页:
- 实现简单:语法直观,适合小数据集或低频深度分页1。
- 性能劣势:深度分页时性能急剧下降,需扫描大量记录34。
适用场景建议
- 大数据集(如千万级数据):优先使用游标分页67。
- 小数据集或低频深度分页:可使用Offset/Limit分页1。
推荐:在大数据量场景中,优先采用游标分页(基于唯一ID的分页)
基于公开资料,2PC(两阶段提交)和3PC(三阶段提交)是用于保证分布式系统中数据一致性的协议。它们适用于需要跨多个资源(如数据库、服务)执行原子性操作的场景,但各有适用条件和局限性。以下结合公开资料中的信息,对使用场景进行说明。
分布式事务适用场景概述
-
2PC适用场景:
2PC适用于对数据强一致性要求高、且网络环境相对稳定的分布式系统。例如:- 银行转账:当资金从账户A转移到账户B时,需要确保扣款和入账操作要么全部成功,要么全部失败,避免数据不一致。
- 订单与库存管理:在电商系统中,创建订单时需同时更新订单表和库存表,若任一操作失败,需回滚整个事务。
- 核心业务事务:如金融、支付等关键领域,这些场景通常容忍较低的吞吐量,但必须保证原子性。3
-
3PC适用场景:
3PC主要针对2PC的缺陷(如阻塞和单点故障)进行优化,适用于对容错性和可用性要求更高的场景:- 高可用系统:当协调者可能故障时,3PC通过超时机制减少阻塞风险,例如分布式数据库或微服务架构中。
- 网络分区常见环境:在可能存在网络延迟或分区的场景(如跨地域部署),3PC的预提交阶段能提前释放资源,降低数据不一致概率。
协议特点与选择建议
-
2PC的优势与局限:
- 优势:协议简单,易于实现,能提供强一致性。
- 局限:存在同步阻塞(参与者资源锁定期间其他请求被阻塞)、协调者单点故障风险,以及网络分区可能导致数据不一致。
- 因此,2PC更适合内部系统或故障率低的环境,需通过改进(如主从协调者)缓解缺陷。
-
3PC的优势与局限:
- 优势:通过增加“询问阶段”和超时机制,减少了阻塞时间,并增强了容错性(如协调者故障时参与者可自行回滚)。
- 局限:实现复杂度较高,且仍无法完全避免数据不一致(如网络分区时部分参与者提交而其他回滚)。
- 3PC更适用于对可用性要求高的分布式系统,但需权衡开发成本。
总结
- 优先使用2PC的场景:业务逻辑简单、网络稳定、且能容忍潜在阻塞的强一致性需求(如核心金融交易)。
- 优先使用3PC的场景:系统分布较广、网络不可靠风险高,或需要更高容错性的场景(如大型分布式数据库)。 实际选择时,还需结合系统负载、一致性要求及运维能力综合考虑。若公开资料未覆盖特定场景,建议参考最新架构实践
2pc和3pc的区别
2PC和3PC的核心区别
-
阶段设计
- 2PC:两阶段(准备→提交/回滚),参与者需锁定资源并阻塞等待协调者指令26。
- 3PC:三阶段(CanCommit→PreCommit→DoCommit),新增预提交阶段,参与者仅校验资源而不锁定,减少阻塞34。
**
-
超时机制
- 2PC仅协调者有超时机制,参与者可能无限等待17。
- 3PC双方均设超时,参与者超时可自动提交或回滚,避免资源长期占用34。
-
资源占用
- 2PC准备阶段即锁定资源,易导致浪费5。
- 3PC在CanCommit阶段仅校验资源,降低无效占用14。
-
适用场景
- 2PC:对一致性要求高、网络稳定的场景(如金融交易)6。
- 3PC:需高可用性、容忍短暂不一致的场景(如电商库存扣减)
volatile 与 synchronized 的核心区别
| 维度 | volatile | synchronized |
|---|---|---|
| 修饰范围 | 仅能修饰变量 | 可修饰方法、代码块(本质关联对象锁) |
| 原子性 | 不保证(仅变量读写原子,复合操作不行) | 保证(同步块内所有操作原子化) |
| 可见性 | 保证(通过内存屏障刷新主内存) | 保证(加解锁时刷新主内存) |
| 有序性 | 禁止指令重排序(局部有序) | 保证执行顺序有序(全局有序,因互斥执行) |
| 性能开销 | 轻量级(无锁,仅内存屏障开销) | 重量级(JDK1.6 后优化,仍高于 volatile) |
| 使用场景 | 状态标记位(如 boolean flag)、DCL 单例 | 临界区代码(如复合操作、多线程共享资源修改) |
| 线程阻塞 | 无(不会导致线程阻塞) | 可能(重量级锁下竞争失败会阻塞) |
四、总结
volatile是轻量级同步,适合解决变量的可见性和指令重排序问题,无法处理复合操作的原子性;synchronized是通用同步机制,可解决原子性、可见性、有序性所有并发问题,但开销更高(JDK1.6 的锁优化已大幅缩小差距);- 实际开发中,
volatile常用于简单状态标记,而synchronized或java.util.concurrent包(如Atomic类、Lock接口)用于复杂并发控制。
三色标记算法是垃圾回收(GC) 中用于并发标记阶段的一种对象标记算法,主要解决传统标记 - 清除算法中需要 STW(Stop-The-World,停止所有用户线程)才能完成标记的问题,是现代垃圾收集器(如 CMS、G1、ZGC、Shenandoah)实现并发标记的核心基础。
一、三色标记的核心定义
算法将内存中的对象分为三种颜色,分别代表不同的标记状态:
- 白色(White) :初始状态下所有对象都是白色,表示未被垃圾收集器访问过。若标记结束后对象仍为白色,则判定为垃圾对象,将被回收。
- 灰色(Gray) :表示对象已被垃圾收集器访问过,但它的子对象(引用的对象)尚未被全部标记。灰色对象会被放入一个 “灰色队列” 中,等待后续处理其子对象。
- 黑色(Black) :表示对象已被垃圾收集器访问过,且其所有子对象都已完成标记。黑色对象是 “安全” 的,标记阶段不会再被重复处理;同时,黑色对象不会指向白色对象(理想状态下,若出现则可能导致漏标)。
多标问题的解决
具体解决方法
-
增量更新(Incremental Update) :
- 原理:当黑色对象(已标记完成)新增一个指向白色对象(未标记)的引用时,写屏障会记录这个新引用。在并发标记结束后,GC会重新扫描这些记录的黑色对象,将它们变回灰色并重新标记其引用链。34
- 效果:这破坏了“黑色对象直接指向白色对象”的条件,避免了漏标,但可能引入少量多标(浮动垃圾),因为新引用可能指向本应回收的对象。34
- 示例:如果对象D在标记后新增引用指向白色对象G,写屏障会记录D,后续以D为根重新扫描G,确保G被正确标记为存活。34
-
原始快照(SATB) :
- 原理:当灰色对象(正在标记)删除对白色对象的引用时,写屏障会记录删除前的引用关系(即原始快照)。在并发标记结束后,GC会以这些记录的灰色对象为根,重新扫描白色对象,确保它们不会被误回收。35
- 效果:这破坏了“灰色对象断开引用”的条件,避免了漏标,但同样可能产生浮动垃圾,因为删除操作可能只是临时的。35
- 示例:如果对象E删除对白色对象G的引用,写屏障会记录G,后续以E为根重新扫描G,保证G在标记视图中仍被视为存活
公司整体架构图
说下问题的解决思路
四个小点 问题定义 分析拆解 提出方案并解决 复盘分析 项目中有个小伙伴手动投产导致服务不可用啦,丢失啦8000躲避订单,然后我根据日志排查确认是服务不可用啦 然后找上下游和产品沟通 下单调用改成异步MQ的发送形式 并且投产有投产检查单-检查确认点-保证投产不出问题 最后解决啦这个问题
离职理由
集团下发指标 业务线调整 领导也找我沟通过让我去杭州负责业务,但是和我的职业发展有冲突
关于期望薪资?如果给不到,会考虑我们吗?
“薪资确实是我考量的重要因素之一,但绝不是唯一因素。我选择机会最看重的是平台的发展前景、岗位的匹配度以及团队的化学反应。贵公司一直是我的首选,如果我们在薪资上有些差距,我也非常愿意了解公司的整体薪酬福利包(如奖金、培训、晋升机制等),并希望我们能基于我的综合能力,找到双方都满意的方案。
你对自己的未来有哪些规划
短期(1-2年): 我希望能够快速融入团队,深入理解业务,确保在[具体岗位职责]上成为可靠的主力,为团队和公司创造切实的价值。 长期(3-5年): 我希望在专业领域深耕,成为能够独当一面的专家/在管理能力上有所提升(根据岗位性质选择),能够带领小型团队或负责更核心的项目,为公司更长远的目标贡献更多力量。我了解到贵公司有清晰的职业发展通道和培训体系,相信这里正是我实现规划的理想平台。”
AOP实现方式
接口 JDK动态代理 基于接口实现 java.lang.reflect.Proxy类和InvocationHandler接口生成代理对象
普通类 CGLIB动态代理基于继承实现,通过字节码技术为目标类创建子类,并在子类中拦截方法调用,从而实现代理,因此不要求目标类必须实现接口 混合使用
常见面试题
一.spring如何解决循环依赖 通过三级缓存机制,结合bean的生命周期(实例化和初始化分离)来实现
一级缓存:存储完全初始化完成的单例bean,一般直接使用
二级缓存:存储实例化但是未完成初始化的单例bean
三级缓存:储存bean工厂对象,用于在需要时生成早起对象 解决aop代理场景的循环依赖
在bean实例化后提前暴漏引用,结合依赖注入的顺序调整,最终解决了单例bean之间的循环依赖。需要通过setter 注入或者字段注入避免
二.java如何统计接口访问量
1.使用concurrentHashmap(线程安全)创建统计工具类
2.使用@webFilter创建过滤器,启动类需要加上@servletComponentScan开启扫描
3.使用Aop统计,使用 @Aspect和@Component注解
4.结合redis实现分布式
三.查询生产redis前置固定key
一般使用 user:info 或者用环境区分test:,prod,
1.使用keys{prefix}*(比较慢,会阻塞服务器直到所有匹配的健都被返回),
2.或者scan都可以(用于迭代当前数据库中的数据库键,不会阻塞并切可以分批处理),、
3.用zrangebylex(针对有序集合)
四.spring bean初始化成功之后发送消息通知
1.实现initializingBean接口的afterPropertiesSet()方法,该方法会在bean的属性初始化完成后调用 方法记得加啥@Cpmpoent注解
2.使用@postConstruct注解,这个方法会在bean构造器执行后,初始化完成前调用,本质是jsr-250规范 spring会自动识别触发
3.自定义初始化方法,通过@Bean的initMethod方法
4.通过监听spring容器事件(适用于全局通知) 实现ApplicationListener
如果需要异步就加上@Async避免bean初始化流程
五.springcloud服务之间调用方式
1.使用spring提供的RestTemplate工具类
2.基于openFeign注解
3.使用dubbo的rpc调用 ,基于tcp协议,性能优于http
六.AQS 抽象队列同步器
1.基于一个vilatile修饰的int类型状态变量(state),通过cas操作修改状态,并维护一个fifo等待队列用于存放阻塞线程
2、指出 独占模式(基于reentrantLock)和共享模式(semaphore)
七.hashmap和ConcurrentHashMap
1.HashMap:非线程安全,数据结构:数组 + 链表 + 红黑树 扩容机制:当元素数量(size)超过负载因子(默认 0.75)× 数组长度时,
触发扩容(数组长度翻倍),需重新计算所有元素的哈希值并迁移(耗时操作)。允许键和值为 null。
2.ConcurrentHashMap:线程安全,数据结构:与 HashMap 类似(数组 + 链表 + 红黑树)。摒弃 JDK 1.7 的分段锁(Segment),
改用 CAS + synchronized 实现更细粒度的同步:不允许键或值为 null(避免在并发场景下,null 无法区分 “键不存在” 和 “值为 null”)。
八.redis Zset跳表
当 ZSet 元素数量超过 128 个 或单个元素长度超过 64 字节 时,跳表自动替代压缩列表(ziplist)作为底层实现。
跳表本质上是一种随机化链表,通过在每个节点增加多级索引指针,实现快速定位
九.@postconstruct 完成依赖注入后自动调用的初始化方法
主要用于执行初始化操作,例如加载配置文件、建立数据库连接、预热缓存等,这些操作依赖于注入的组件 十. 1.@Component:作用于类 标记该类为Spring组件(如@Service、@Repository均为其派生注解),由容器自动扫描并实例化
2.@Bean:作用于方法(通常在@Configuration类中),通过方法返回值显式定义Bean,适用于无法修改源码的第三方类或需动态控制的场景
Java 领域模型
是业务领域的 “代码化映射”,通过实体、值对象、聚合等组件封装业务规则和行为,是 DDD 落地的核心。好的领域模型能使代码更贴近业务、易于理解和维护,尤其适合复杂业务系统(如电商、金融、ERP 等)的开发。
mysql事物开启方式和避免幻读
1.start transaction;
2.commit;
1.优先使用 REPEATABLE READ 隔离级别(MySQL 默认),通过 Next-Key Lock 机制:
2.对范围查询加锁(FOR UPDATE 或 LOCK IN SHARE MODE),触发间隙锁,阻止其他事务插入新记录。
3.普通查询(快照读)通过 MVCC 保证可重复读,无需担心幻读。
4.避免使用 SERIALIZABLE,除非业务对一致性要求极高且可接受性能损失。
十一.线程池 核心参数:核心线程数,最大线程数,工作队列,最大线程空闲时间,线程工厂,拒绝策略,线程工厂
事务实现方式
事务实现方式主要有两种:编程式事务和声明式事务。
编程式事务需要手动编写代码管理事务的开启、提交和回滚,灵活性高但代码侵入性强,维护成本较高。
声明式事务则通过注解或XML配置将事务管理与业务代码解耦,使用更简便。它又细分为:
- 基于
TransactionProxyFactoryBean的方式,需为每个事务类单独配置。 - 基于AspectJ的XML方式,配置集中,无需修改类。
- 基于注解的方式(如
@Transactional),配置简单,直接在业务方法上添加注解即可。
编程式事务实现原理
编程式事务实现原理
编程式事务通过手动编写代码控制事务的开启、提交和回滚,实现更细粒度的事务管理。其核心原理如下:
1. 事务管理器(PlatformTransactionManager)
- 定义:Spring事务管理的核心接口,负责事务的创建、提交和回滚。
- 实现:常见的实现类包括
DataSourceTransactionManager(用于JDBC)、HibernateTransactionManager(用于Hibernate)等。 - 作用:通过
getTransaction、commit和rollback方法管理事务生命周期。
2. 事务定义(TransactionDefinition)
- 定义:描述事务的属性,如传播行为(REQUIRED、REQUIRES_NEW等)、隔离级别(READ_COMMITTED、REPEATABLE_READ等)。
- 实现:通过
DefaultTransactionDefinition类配置事务属性。 - 作用:定义事务的行为规则,如是否需要新事务、事务的隔离级别等。
3. 事务状态(TransactionStatus)
- 定义:表示事务的当前状态,如是否已提交、是否回滚等。
- 作用:用于事务的控制和状态检查。
4. 事务控制流程
javaCopy Code
// 1. 获取事务管理器
PlatformTransactionManager transactionManager = ...;
// 2. 定义事务属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 3. 开启事务
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 4. 执行业务操作
// ...
// 5. 提交事务
transactionManager.commit(status);
} catch (Exception e) {
// 6. 回滚事务
transactionManager.rollback(status);
}
5. 核心机制
- ThreadLocal:通过
ThreadLocal存储事务上下文,确保事务在当前线程中的一致性。 - 事务传播:根据传播行为决定事务的创建或加入现有事务。
- 异常处理:捕获异常后自动回滚事务,确保数据一致性。
6. 优缺点
- 优点:灵活控制事务边界,适用于复杂场景。
- 缺点:代码侵入性强,维护成本高,与业务逻辑耦合。
注意:编程式事务需手动管理事务边界,适用于需要精确控制的场景,如嵌套事务或跨服务调
分库分表
声明式事务实现原理
声明式事务实现原理
声明式事务通过注解(如@Transactional)或XML配置自动管理事务,实现零侵入式事务控制。其核心原理如下:
1. AOP(面向切面编程)机制
- 代理对象生成:Spring容器启动时,通过
AbstractAutoProxyCreator扫描@Transactional注解,为标记类生成动态代理对象(JDK代理或CGLIB代理)。 - 拦截器链执行:代理对象在方法调用时触发
TransactionInterceptor拦截器,获取事务属性(传播行为、隔离级别等)。
2. 事务管理器(PlatformTransactionManager)
- 事务控制:
TransactionInterceptor通过PlatformTransactionManager(如DataSourceTransactionManager)与数据库交互,实现事务的开启、提交和回滚。 - 异常处理:捕获方法执行异常后自动回滚事务,确保数据一致性。
3. 核心流程
javaCopy Code
// 1. 代理对象调用方法
proxyObject.someMethod() {
// 2. TransactionInterceptor拦截
TransactionStatus status = transactionManager.getTransaction();
try {
// 3. 执行原始方法
targetObject.someMethod();
// 4. 提交事务
transactionManager.commit(status);
} catch (Exception e) {
// 5. 回滚事务
transactionManager.rollback(status);
}
}
4. 配置方式
- 注解方式:在类或方法上添加
@Transactional注解,Spring自动解析并生成事务代理。 - XML配置:通过
<tx:advice>和<aop:config>定义事务切面,实现事务织入。
5. 核心机制
- 动态代理:通过JDK代理(实现接口)或CGLIB代理(继承类)实现方法拦截。
- 事务传播:根据传播行为(如
REQUIRED、REQUIRES_NEW)决定事务的创建或加入现有事务。 - 隔离级别:通过
TransactionDefinition设置事务的隔离级别(如READ_COMMITTED、REPEATABLE_READ)。
6. 优缺点
- 优点:代码侵入性低,易于维护,适用于大多数场景。
- 缺点:自调用失效(方法内部调用自身时事务失效)。 数据计算
1. 贝壳
一面
时间 07-03 14:00~14:50 问题 1. 介绍去哪儿业务
- mysql索引:概念、类型、数据结构、mvcc
- 大促场景:库存扣减(redis原子操作)、同步mysql(异步批量)
- mq消息不丢失
- 天眼查 一面 时间 07-03 19:55~20:40 问题 1. 介绍业务
- im重构收益、长短连接优劣、稳定性怎么处理、线上故障如何处理
- 线程池拒绝策略
- 服务启动失败如何排查
- mysql连接失败如何排查
- 反转链表
- 美团 一面 二面 时间 07-03 13:55~15:35 07-08 14:00~15:05 问题 1. 架构
- java线程池
- mq消息保证
- netty组件
- mysql联合索引
- 算法:正反转列表求和进位 1. IM项目
- 工单复盘设计状态机
- 算法题,两数求和算进位
- 百意图 一面 二面(面试官迟到) 时间 07-03 20:00~21:00 07-09 11:10~11:40 问题 1. 去哪儿项目
- netty相关
- mysql rr下双事务并行 1. 工作经历
- 场景题:流如何去重
- 蘑菇车联 一面 时间 07-08 16:00~16:40 问题
- 上通科技 一面 时间 07-09 14:00~ 问题
- 小赢科技 一面 时间 07-10 14:00~ 问题
- 乐信圣文(四轮面试,两轮技术,两轮hr) 一面(业务leader) 时间 07-16 11:00~11:40 问题 1. 介绍两个项目,过程中碰到的难点
- 二分查找,最右匹配
- 滴滴 一面(业务) 时间 07-16 14:00~15:10 问题 1. dubbo服务注册和发现怎么做的?
- dubbo在cap里更侧重哪些?
- kafka高效的原因
- mysql索引
- mysql一次select执行过程
- 二维数组merge,将有包含区间的子数组做merge
- 桔子数科 一面(业务leader) 时间 07-17 14:00~14:50 问题 1. 介绍im项目
- 介绍普强项目
- tcp三次握手四次挥手
- 汽车之家 一面(业务leader) 时间 07-22 14:00~14:45 问题 1. 介绍im项目,消息如何保证不丢失
- netty粘包和拆包
- 小米流的推送,基于时间推送
- im消息持久化
- redis实现ip地址段和物理地址映射
- 未岚大陆 一面(业务) 时间 07-21 17:00~18:00 问题 1. im架构
- kafka消费者重平衡时机,解决方案
- 服务治理怎么治理
- 算法:两个链表,一个表示火车入站时间(排好序),另一个表示火车出站时间,求最多需要多少个站台
- 转转 一面(偏向招领导?) 时间 07-22 11:00~11:50 问题 1. 前端请求耗时如何检测是哪个阶段耗时
- 优惠券发放和核销系统设计
- 滴滴(交易平台) 一面(聚合支付,出账和入账) 二面 三面 时间 07-23 14:00~15:00 07-28 14:00~15:00 问题 1. im系统,redis挂了怎么办,消息如何不丢失,排队分配策略
- 场景题:设计一个feed流系统,考虑大v用户,如一个人发朋友圈,有100个好友和50000个好友的差别
- kafka和rocketmq如何选型
- kafka为什么那么快
- redis架构
- mysql 隔离级别 1. im系统。 a. kafka故障相应的降级方案 b. 切流过程 c. mysql库表设计,离线查询场景
- netty核心
- 算法:线程循环打印abc
- 易宝支付 一面(业务) 时间 07-23 10:00 问题 1. 项目经历
- im业务相关问题
- 简述工单系统
- mysql acid、mvcc
- mq 消息如何不丢失
- 轻松健康 一面(业务) 时间 07-24 15:00~15:40 问题 1.
- 奇富科技(360):技术一共二到三面 一面(业务leader) 时间 07-25 19:30~20:45 问题 1. 项目经历
- 普强项目详解
- 栈实现队列:两个栈+锁优化 未通过原因 反馈说对做过的项目不是很熟悉
- 美团(闪购-供应链-物流) 一面(业务) 时间 07-29 15:00~1 问题 1. 介绍项目,im项目,问询长城的相应物流和供应链
- 算法:数组合并,合并到第一个数组里
- 马上消费 一面(业务) 时间 08-01 10:00~1 问题 1.
- 滴滴-国际化治理 一面(业务) 二面 时间 08-12 14:00~15:10 08-13 20:00~20:45 问题 1. 单例双重检测 + synchronized在不同版本的优化 + 如何打破
- 字符串中找出最长不重复子串
- 三个线程循环打印abc,每次输出10次
- 线程池工作原理,核心线程和非核心线程是如何区分的,线程池状态
- mysql单表最大数据量,索引覆盖、最左匹配原则
- 小米内容接入架构 1. 去哪儿主要工作内容
- 地铁线路场景题:功能设计上有哪些功能模块
- 算法:从整数数组中获取指定和的两个元素下表
- 中企云链 - 业务架构 一面(业务) 时间 08-13 14:00~1 问题 1.
- 转转 一面(业务) 时间 08-15 15:00~1 问题 1.
- 美团 - 酒旅 一面(业务) 时间 08-15 10:30~1 问题 1.
- 高德-评测 一面(业务) 时间 08-20 15:00~16:00 问题 1. 小米feed流项目
- im项目
- 实现一个安全容器,既要支持map特性,也要支持list特性
- 京东-硬件对话系统(还有二到三轮面试) 一面(业务) 二面 三面(hrbp) 时间 08-25 17:00~17:40 08-28 17:00~17:30 09-03 15:30~16:00 问题 1. im项目
- 如果车联网关项目
- 树的深度优先遍历 1. 介绍im项目
- 介绍普强项目
- 介绍车联网关项目 1. 了解简历信息
- 对ai的认知
- 合并三面结果评估
- 马上消费(枭龙云-墨西哥业务-客服系统) 两轮技术面 九点打卡,晚上九点十点,周五六点,偶尔出差重庆 一面(业务) 二面 时间 08-25 20:00~20:45 08-27 14:00~ 问题 1. 单例模式
- volatile、synchronized
- 普强项目 1. 职业发展
- 普强项目
- 去哪儿工单状态机
- 快手 一面(业务) 二面 时间 08-28 11:00~12:00 09-03 19:00~19:30 问题 1. 去哪儿项目:工单状态机,数据存储
- 车联网关数据量
- mysql事务特性
- 反转列表 1. 介绍普强项目,一个特别的点介绍
- 海尔优加 一面(业务) 二面 三面 最终结果 时间 08-28 20:00~21:05 09-02 19:00~20:30 09-03 16:00~16:30 月薪 33000 每月700补助 正常绩效15薪 问题 1. im项目
- 车联网关项目 咨询人员稳定性相关的问题 咨询简历信息 索要薪资流水
- 智书企飞(北京-合同(从字节的业务拆分出来的),郑州-低代码,上海-ai)(早上九点半,晚上九点十一点十二点,周末可能加一天班) 一面(业务) 二面 时间 09-01 14:00~14:50 问题 1. 介绍普强项目
- 写令牌桶限流
- 滴滴 一面(业务) 二面 时间 09-03 14:00~15:30 09-08 20:00 问题 1. LFUCache实现
- HashMap线程不安全场景
- ConcurrentHashMap在不同jdk版本的变化
- 选择redis的原因
- redis场景题:与数据库的数据一致性、排行榜怎么做(大key、分片时因为排行榜导致的节点跳跃怎么处理) 1. 经历及跳槽原因
- im系统从整体层级上来讲背景、目标、挑战、如何解决
- 根据前序遍历和中序遍历构建二叉树
- 美团 一面(业务) 二面 时间 09-04 19:00~20:30 09-08 15:00 问题 1. 介绍车联网关系统
- 介绍im系统
- 列式数据库差异点,比如hbase、clickhouse,hbase rowkey怎么设计
- 将两个有序数组合并到第一个数组里
- 微财 一面(业务) 二面 时间 09-08 17:00~ 问题 1.
- web3.x 一面(业务) 二面 时间 09-08 11:00~ 问题 1.
总结
1、底层原理能说明白是亮点,
2、项目架构 项目业务横向 竖向必须说明白 现在招聘都看匹配度 这个是重点
3、场景 问题回答并且举列子 比如ThreadLocal 内存泄露问题 怎么解决 实际场景 什么情况用 这个亮点
别白嫖 分享 点赞 谢谢