cpu飙高问题,案例分析(二)——批处理数据过大引起的应用服务CPU飙高

上接cpu飙高问题,案例分析(一)

一、批处理数据过大引起的应用服务CPU飙高

1.1 问题场景

某定时任务job 收到cpu连续(配置的时间是180s)使用超过90%的报警;

1.2 问题定位

  1. 观察报警中的jvm监控,发现周期性出现即每天8:00,cpu从5%-99%大约持续3分钟左右然后恢复正常,平时cpu使用率较为平稳(排除因为应用发布导致的cpu升高);
  2. 任务系统周期性出现很可能是定时执行了大量运算导致的,查看任务系统页面,确实存在一个8点执行的定时任务;
  3. 分析代码梳理该任务的业务逻辑为:一个兜底的定时的job任务;其中涉及大量复杂运算,现在猜测基本是改任务导致的,那么可以复现一下,确认下;

复现操作很简单:
a.找一台机器,观察jvm相关监控;观察日志;
b.修改分片数量为1且指定分片容器ip为监控的容器ip,点击执行分片,查看分片确认成功后, 点击执行一次。通过观察步骤c1)成功复现。

  1. 至此基本方向确定是这个任务导致的CPU升高,接下来分析为何CPU升高,以及如何优化问题;

1.3 问题分析

  1. a)也可能是程序运行过程创建大量对象触发GC,GC线程占用CPU过高导致;b)CPU升高原因可能是程序大量运算导致;
  2. 不管是情况a) 还是情况b) 都需要复现问题,观察使用cpu高的线程有哪些,使用jstack命令导出线程栈的信息进行观察,观察每个线程CPU使用率;

1.4 操作步骤

  1. 预发环境触发任务执行,复现场景。注意:此步骤需要谨慎操作,由于预发和线上是相同的数据库,所以预发环境部署代码需要把相关的操作屏蔽掉了,比如发送MQ,更新数据库等。避免影响线上数据和MQ等;
  2. 登录堡垒机使用top命令查看目前的进程信息:
    在这里插入图片描述
  3. 发现java进程33907 使用的cpu已经达到200%,使用命令 top-H -p 33907 命令查看该进程下的哪个线程使用的资源最多
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

340271 5312f Curator-TreeCache-18" #648 daemon prio=5 os_prio=0 tid=0x00007f10dc156000 nid=0x5312f waiting on condition [0x00007f1158bcb000]

1.5 结论

  1. ThreadPoolTaskExecutor为该任务显示创建,核心线程数为2,最大为6,队列大小为300,根据线程栈信息可以看出与程序配置一致,根据实际配置可以发现此部分正常配置符合预期;
  2. 程序中没有创建FrokJoinPool,但是程序使用了大量的 parallelStream();由于parallelStream默认使用的是公共的forkJoinPool线程池,且该线程池的线程数量配置为系统的 Runtime.getRuntime().availableProcessors() - 1; 现在对于parallelStream的使用等于在多线程中,嵌套了多线程;
  3. Curator-TreeCache 线程的来源,通过发现线程池的初始化参数可知:线程的的拒绝策略为CallerRunsPolicy(),该策略为:如果线程池队列和核心线程数满了后,继续提交到线程池的任务会通过方法线程即调用线程池的那个方法来执行,可以理解为主线程;

1.6 优化方案

  1. 修改所有的parallelStream() 为stream();4c8G配置的机器cpu使用率稳定在50%左右;
  2. 2C4G的机器执行,CPU使用率仍然高达90%,通过分析线程栈的dump文件发现主要是业务线程占用资源过高,其中不足2%的还有部分是由于并行流导致的,在job执行时候引用的第三方jar包中,此部分暂时不处理;

对于2C4G优化方案:

  1. 由于这个任务是兜底的,不需要立即执行完成,且执行频率为1天1次可以将线程池调整为1个线程;
  2. 申请容器升级为4c*8G.;

1.7 问题引申

parallelStream()原理:

  1. parallerlStream是jdk8的特性,是在Stream的基础上实现的并行流式操作;旨在简化并行编码,提升运算效率;
  2. 并行流的底层是基于ForkJoinPool实现的,其中ForkJoinPool采用的思想类似MapReduce的思想,将一个大的运算任务拆分为子任务(fork的过程);然后执行所有的子任务运算后的结果合并在一起(join过程); 通过分治的方式完成一个计算;

1.8 parallelStream()最佳实践

1.8.1 并行流使用问题分析

  1. 根据parallelSteam的原理我们知道底层是使用的ForkJoinPool;那么我们程序通常会有如下代码:
//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()));
  1. 那么我们并没有创建ForkJoinPool,且不同的集合都在调用parallerStream(), 那么最终用的是哪个线程池呢?很显然既然没有报错,就说明jdk应该会给一个默认的ForkJoinPool。源码如下:

注意:
默认的如果使用默认的线程池执行的话,forkJoinPool会使用当前系统默认的cpu核心数量-1,但是主线程也会参与计算。

执行结果: 可以看出默认的ForkJoinPool线程池,除了worker线程参与运算 ,方法线程也会参与预算。

1.9 最佳实践总结:

  1. 并行流如果使用,最好使用自定义的线程池,避免使用默认的线程池即线程池隔离思想,造成阻塞或者资源竞争等问题;
  2. parallelStream 适用的场景是CPU密集型的,假如本身电脑CPU的负载很大,那还到处用并行流,那并不能起到作用,切记不要再paralelSreram操作是中使用IO流;
  3. 不要在多线程中使用parallelStream,如本次案例,大家都抢着CPU是没有提升效果,反而还会加大线程切换开销;

1.10 踩坑记录:

  Runtime.getRuntime().availableProcessors() ;是jdk提供的获取当前系统的可用的核心数,本次踩坑在于,现在多数应用都是发布在容器中的,虽然应用部署的容器是2C4G的,但是ForkJoinPool创建的ForkJoinPool.commonPool-worker-线程却有几十个,登录容器所在的物理机查看机器配置如下:
在这里插入图片描述
实际为2个cpu每个cpu 32核 总共是64核,编写测试程序验证也是如此:
在这里插入图片描述
  由此可知,默认的ForkJoinPool获取的是当前系统的核心数量,如果应用部署在docker容器中,那么就获取的是宿主机的CPU核心数。

1.11 Runtime.getRuntime().availableProcessors()问题

 容器明明分配的是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

1.11.1 使用方法一测试:测试环境容器验证,验证通过,结果如下:

jdk版本 jdk1.8.0_20 :返回cpu核心数28
在这里插入图片描述
jdk版本: jdk-1.8.0_192 返回cpu核心数2
在这里插入图片描述

1.12 建议

尽量使用lambda表达式遍历数据,推荐使用常规的for、for-each模式

原因:

  1. 性能比传统foreach低;

lambda内部有着一套复杂的处理机制(反射、类型转换、拷贝),性能开销要比常规for、for-eatch大的多。在普通业务场景下这种性能差异可以忽略不计,但是在某些高频场景下(N万次调用/秒)就不能忽略了。它虽然不会导致一次迭代卡顿,但是会持续增加cpu的消耗,以及增加GC的压力。

  1. 不便于代码调试;

反例:

// 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);
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/195788.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软件测试测试文档编写

在软件测试中的流程中&#xff0c;测试文档也是一个重要的流程&#xff0c;所以测试人员也需要学习测试文档的编写和阅读。 一、定义&#xff1a;   测试文档&#xff08;Testing Documentation&#xff09;记录和描述了整个测试流程&#xff0c;它是整个测试活动中非常重要…

基于若依的ruoyi-nbcio流程管理系统增加流程节点配置(二)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 上一节把数据库与相关基础数据字典准备好&#xff0c;下面就来实现相应的功能&#xff0c;目前先针对自定义…

大学招聘平台既然存在逻辑漏

找到一个学校的就业信息网&#xff0c; 随便点击一个招聘会&#xff0c;并且抓包查看返回包 注意返回包中的dwmc参数&#xff0c;这个是公司名称&#xff0c;zplxr参数这个是招聘人员姓名&#xff0c;lxdh参数是电话号码&#xff0c;这几个参数后面有用 在第一张图点击单位登…

Python 爬虫 案例 之 豆瓣Top250电影数据

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 课程亮点&#xff1a; 1、动态数据抓包演示 2、csv文件保存 3、requests模块的使用 4、parsel解析数据的使用 环境介绍&#xff1a; python 3.8 pycharm 模块…

一种方便、优美的使用Python调用fofa API的方法

免责声明&#xff1a;由于传播或利用此文所提供的信息、技术或方法而造成的任何直接或间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c; 文章作者不为此承担任何责任。 学习网络安全的过程中&#xff0c;绕不开fofa搜索&#xff0c;我的需求是使用fofa获取互联网所…

C++不同平台下的RTTI实现

给定一个含有虚函数的对象的地址&#xff0c;找到对应的类名&#xff0c;不同平台下方法也不同&#xff0c;这是由于RTTI实现并没有统一的标准。 Linux&#xff1a; #include <iostream> #include <typeinfo>class Person { public:virtual void func(){std::cout…

[算法总结] - 蓄水池采样算法

问题描述 在长度为N的数组中&#xff0c;随机等概率选取K个元素&#xff0c;如何实现这个随机算法。 思路很简单&#xff0c;生成一个[0, N]的随机数index&#xff0c;然后返回index上的数值即可。 但是&#xff0c;如果输入是一个长度未知的数组比如stream&#xff0c;先遍历…

【Jmeter】什么是BeanShell?

一、什么是BeanShell&#xff1f; BeanShell是用Java写成的,一个小型的、免费的、可以下载的、嵌入式的Java源代码解释器&#xff0c;JMeter性能测试工具也充分接纳了BeanShell解释器&#xff0c;封装成了可配置的BeanShell前置和后置处理器&#xff0c;分别是 BeanShell Prep…

中科大蒋彬课题组开发 FIREANN,分析原子对外界场的响应

内容一览&#xff1a; 使用传统方法分析化学系统与外场的相互作用&#xff0c;具有效率低、成本高等劣势。中国科学技术大学的蒋彬课题组&#xff0c;在原子环境的描述中引入了场相关特征&#xff0c;开发了 FIREANN&#xff0c;借助机器学习对系统的场相关性进行了很好的描述。…

盘点72个Android系统源码安卓爱好者不容错过

盘点72个Android系统源码安卓爱好者不容错过 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 链接&#xff1a;https://pan.baidu.com/s/1qiWeLjF2i4dlgmTYgPPSvw?pwd8888 提取码&#xff1a;8888 项目名称 A keyboardlisten…

【链接MySQL】教你用VBA链接MySQL数据库

hi&#xff0c;大家好呀&#xff01; 之前呢&#xff0c;给大家分享过一个自制链接表管理器的文章&#xff0c;文章中有链接SQL Server数据库的代码&#xff0c;大家对这一段代码比较有兴趣&#xff0c;既然大家有兴趣&#xff0c;那我们今天就来讲一下链接数据库的代码。 这…

Vue路由嵌套和携带参数的几种方法

1、路由嵌套 路由嵌套逻辑&#xff1a; router.index.js中使用children嵌套子路由 //该文件专门用于创建整个文件的路由器 import VueRouter from vue-routerimport About from "/pages/About"; import Home from "/pages/Home"; import News from "…

基础课12——深度学习

深度学习技术是机器学习领域中的一个新的研究方向&#xff0c;它被引入机器学习使其更接近于最初的目标——人工智能。深度学习的最终目标是让机器能够像人一样具有分析学习能力&#xff0c;能够识别文字、图像和声音等数据。 深度学习的核心思想是通过学习样本数据的内在规律…

控价是什么意思

对价格进行控制&#xff0c;使其在一个目标范围内的行为被称为控价&#xff0c;那为什么要做控价&#xff0c;控价的前提是价格乱了&#xff0c;而品牌会对渠道中的低价进行控制&#xff0c;这就是品牌进行控价的目标&#xff0c;控制低价。 品牌可以选择自己去控价&#xff0c…

python计算概率分布

目录 1、泊松分布 2、卡方分布 3、正态分布 4、t分布 5、F分布 1、泊松分布 泊松分布是一种离散概率分布&#xff0c;描述了在固定时间或空间范围内&#xff0c;某个事件发生的次数的概率分布。该分布以法国数学家西蒙德尼泊松的名字命名&#xff0c;他在19世纪早期对这种…

深入了解MySQL数据库管理与应用

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 当涉及MySQL数据库管理与应用时&#xff0c;深…

智慧工地管理系统加快推进工程建设项目全生命周期数字化

智慧工地管系统是一种利用人工智能和物联网技术来监测和管理建筑工地的系统。它可以通过感知设备、数据处理和分析、智能控制等技术手段&#xff0c;实现对工地施工、设备状态、人员安全等方面的实时监控和管理。 智慧工地以物联网、移动互联网技术为基础&#xff0c;充分应用大…

AIGC系列之:DDPM原理解读(简单易懂版)

目录 DDPM基本原理 DDPM中的Unet模块 Unet模块介绍 Unet流程示意图 DownBlock和UpBlock MiddleBlock 文生图模型的一般公式 总结 本文部分内容参考文章&#xff1a;https://juejin.cn/post/7251391372394053691&#xff0c;https://zhuanlan.zhihu.com/p/563661713&…

假定采用带头结点的单链表保存单词,当两个单词有相同的后缀时,可共享相同的后缀存储空间,例如,“loading”,“being”的存储映像如下图所示。

假定采用带头结点的单链表保存单词&#xff0c;当两个单词有相同的后缀时&#xff0c;可共享相同的后缀存储空间&#xff0c;例如&#xff0c;“loading”,“being”的存储映像如下图所示。 设str1和str2分别指向两个单词所在单链表的头结点&#xff0c;链表结点结构为 data ne…

网络篇---第五篇

系列文章目录 文章目录 系列文章目录前言一、如何实现跨域?二、TCP 为什么要三次握手,两次不行吗?为什么?三、说一下 TCP 粘包是怎么产生的?怎么解决粘包问题的?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站…