上接cpu飙高问题,案例分析(一)
某定时任务job 收到cpu连续(配置的时间是180s)使用超过90%的报警;
复现操作很简单:
a.找一台机器,观察jvm相关监控;观察日志;
b.修改分片数量为1且指定分片容器ip为监控的容器ip,点击执行分片,查看分片确认成功后, 点击执行一次。通过观察步骤c1)成功复现。
340271 5312f Curator-TreeCache-18" #648 daemon prio=5 os_prio=0 tid=0x00007f10dc156000 nid=0x5312f waiting on condition [0x00007f1158bcb000]
对于2C4G优化方案:
parallelStream()原理:
//code1
WORDS.entrySet()
.parallelStream()
.sorted((a,b)->b.getValue().compareTo(a.getValue()))
.collect(Collectors.toList());
//code2
Set<String> words = new ConcurrentHashSet<>();
words1
.parallelStream()
.forEach(word -> words.add(word.getText()));
注意:
默认的如果使用默认的线程池执行的话,forkJoinPool会使用当前系统默认的cpu核心数量-1,但是主线程也会参与计算。
执行结果: 可以看出默认的ForkJoinPool线程池,除了worker线程参与运算 ,方法线程也会参与预算。
Runtime.getRuntime().availableProcessors() ;是jdk提供的获取当前系统的可用的核心数,本次踩坑在于,现在多数应用都是发布在容器中的,虽然应用部署的容器是2C4G的,但是ForkJoinPool创建的ForkJoinPool.commonPool-worker-线程却有几十个,登录容器所在的物理机查看机器配置如下:
实际为2个cpu每个cpu 32核 总共是64核,编写测试程序验证也是如此:
由此可知,默认的ForkJoinPool获取的是当前系统的核心数量,如果应用部署在docker容器中,那么就获取的是宿主机的CPU核心数。
容器明明分配的是2个逻辑核心数,为什么java获取的会是物理机的核心数呢?如何解决这个问题呢。
是否是容器构建的时候某些参数配置的原因导致的? 将问题反馈给运维,但是对方并未解决该问题。
那么java是否可以解决呢?毕竟如果我们自定义线程池设置线程数量也会使用
Runtime.getRuntime().availableProcessors()这个方法,这其实是JDK的一个问题,已经trace在 JDK-8140793 ,原因是获取CPU核数是通过读取两个环境变量,其中:
其中_SC_NPROCESSORS_CONF 就是我们需要容器真实的CPU数量。
获取CPU数量的源码
第一种办法是使用新版本的Jdku131以上的版本 :
容器内获取CPU核数的坑
另外一个办法是使用自编译上面的源代码,通过LD_PRLOAD的方式将修改后的so文件加载进去Mock掉CPU的核数。
jdk官方链接声明: Java SE support for Docker CPU and memory limits
jdk版本 jdk1.8.0_20 :返回cpu核心数28
jdk版本: jdk-1.8.0_192 返回cpu核心数2
尽量使用lambda表达式遍历数据,推荐使用常规的for、for-each模式
原因:
lambda内部有着一套复杂的处理机制(反射、类型转换、拷贝),性能开销要比常规for、for-eatch大的多。在普通业务场景下这种性能差异可以忽略不计,但是在某些高频场景下(N万次调用/秒)就不能忽略了。它虽然不会导致一次迭代卡顿,但是会持续增加cpu的消耗,以及增加GC的压力。
反例:
// lambda并没有起到简化代码的作用,反而会增加系统压力
List<ValidationResult> results = children.stream()
.map(e -> e.validate(context, nextCell, nextCoverCells, occupyAreas))
.collect(Collectors.toList());
List<ValidationResult> failureList =
results.stream().filter(ValidationResult::isFailure).collect(Collectors.toList());
List<ValidationResult> successList =
results.stream().filter(ValidationResult::isSuccess).collect(Collectors.toList());
正例:
// 优化后总cpu下降2%-4%左右。
// 备注:cpu下降如此明显的原因是该代码的调用频率非常高,真正的N万次/秒调用
List<ValidationResult> failureList = new ArrayList<>(4);
List<ValidationResult> successList = new ArrayList<>(4);
for (PathPreAllocationValidator child : children) {
ValidationResult result = child.validate(context, currCell, previousCell, nextCell);
if (result.isSuccess()) {
successList.add(result);
} else {
failureList.add(result);
}
}