CPU飙高生产事故分析

  1. 事故背景
    晚上六点左右CPU告警,从日志量判断是在这段时间进行了大量的业务处理,追踪到是有人触发了订单推送的方法,时间跨度22天,订单量40w左右。

  2. CPU高使用率的本质
    表面现象:当线程池的线程数接近或超过CPU核心数时,操作系统的线程调度器需要频繁在不同线程之间切换。此时CPU的利用率较高,但不代表有效计算时间的占比高。
    实际原因:主要是调度开销。两个线程在同一个CPU核心上交替执行时,每次切换都需要保存当前线程的寄存器状态、内存信息等。这种切换需要耗费CPU时间。

  3. 配置线程数理论
    CPU密集型任务:建议线程数=CPU核心数+1,避免过多线程争夺计算资源。
    I/O密集型任务:公式为 核心数 * (1 + I/O等待时间/计算时间)。例如,若I/O耗时占比50%,则线程数可设为 2×核心数。

  4. 定位原因

stationIds.parallelStream().forEach(stationId -> {
       try {                
			ContextHolder.set(data); // threadlocal存储上下文配置
	        doSomething();
	   } catch (Exception e) {
	        log.error("error", e);
	   } finally {
	        ContextHolder.remove(); // 清理上下文配置
	   }
});

原因分析:
a. 当业务高峰期遇上并发流,形成多个线程在CPU交替执行的局面,上下文切换导致CPU飙高。
b. 多核 CPU 的缓存一致性协议(如 MESI)会因内存屏障触发缓存行(Cache Line)的同步。若多个线程频繁修改各自的 ThreadLocalMap(例如在同一个线程中反复 set/remove),可能导致缓存行频繁失效,增加 CPU 核心间的数据同步开销。
虽然 ThreadLocal 变量本身是线程隔离的,但底层 ThreadLocalMap 的读写操作仍需要维护线程内部的可见性。

parallelStream并发流的注意事项:
parallelStream()底层采用 ForkJoinPool 线程池,默认线程数与 CPU 核心数相同。
适合数学运算、复杂过滤等CPU密集型任务,而非I/O阻塞操作。
数据量小于1万时,线程调度开销可能抵消并行收益
I/O、网络请求会阻塞ForkJoinPool线程,影响其他并行任务,需改用独立线程池

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值