系统Cpu利用率降低改造之路

系统Cpu利用率降低改造之路

一.背景

1.1 系统背景

该系统是一个专门爬取第三方数据的高并发系统,该系统单台机器以每分钟400万的频次查询第三方数据,并回推给内部第三方系统。从应用类型上看属于IO密集型应用,为了提高系统的吞吐量和并发,我们引入了协程(Quasar框架)。由于业务的特殊性,高峰期的时候我们需要管理快300台机器,抛开单台机器每个月成本不算,光是通过后台管理系统更改快300台机器的配置和将机器出局Ip加入到第三方代理提供商的白名单中这一过程就比较繁琐,且容易出现问题。为了解决这一历史遗留问题,降低机器成本,以及提高运维效率,就有了下面的改造之路。

1.2 知识背景

1.2.1 什么是平均负载

平均负载是指单位时间内,处于可运行状态和不可中断状态的进程数。它不仅包括了正在使用 CPU 的进程,还包括等待 CPU等待 I/O 的进程。

1.2.2 什么是CPU使用率

CPU 使用率,是单位时间内 CPU 繁忙情况的统计。

1.2.3 平均负载和CPU使用率的关系

  • CPU 密集型进程,使用大量 CPU 会导致平均负载升高,此时这两者是一致的;
  • I/O 密集型进程,等待 I/O 也会导致平均负载升高,但 CPU 使用率不一定很高;
  • 大量等待 CPU 的进程调度也会导致平均负载升高,此时的 CPU 使用率也会比较高。

二.发现与猜想

2.1 发现问题

通过监控发现我们的系统单台机器(8C,16G)的CPU利用率竟然维持在80%-90%左右,负载接近过载

在这里插入图片描述
在这里插入图片描述
由于我们的系统是IO密集型应用,理论上CPU利用率不可能这么高,所以就需要分析为什么CPU利用率高,CPU利用率过高会间接的导致CPU负载过高,导致CPU队列中的任务调度不过来,从而出现吞吐量降低,扫描次数下降等问题。

2.2 猜想

根据经验我们猜测是不是代码里面出现了耗时计算,死循环,大量序列化反序列化等会导致CPU升高的操作。

三.分析问题

3.1 猜想一:并发过高导致日志打印过多

根据框架同事反馈,我们系统每分钟大概向CLog日志系统输出1G左右日志,经过分析发现是由于日志报文过大且量级较多导致。为此在不影响业务的情况下,我们采用了抽样的方式向日志系统输出日志。分析代码底层我们输出日志时存在序列化有可能导致CPU飘高,为此我们采用了直接调用对象的toString方法直接转换成String输入。但是发上线上后,CPU利用率整体只下降了1%~2%,效果不是很明显。

3.2 猜想二 :压缩回推业务数据导致CPU高

由于系统原先部署在公司外部,回推数据的报文较大且是外网和内网交互,当时带宽不足,可能会出现数据丢失现象,所以当时做了数据压缩功能。我们猜测是不是由于压缩数据存在计算逻辑而导致CPU过高。由于目前目前系统已迁入公司内部,内网之间交互无需考虑带宽的影响,经过验证我们发现可以关闭压缩功能,发上线上后,效果仍然不明显,CPU利用率整体只下降了3%~4%左右,效果仍然不是很明显。

3.3 猜想三: 代码中存在常量字符串频繁反序列化成对象操作

经过上述两个优化后,我们的CPU利用率降了4%~6%左右,但是远远没有达到我们的预期,为此我们开始分析业务代码,通过Arthas(Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率)分析占用CPU较高的堆栈信息。我们发现有些类中定义一个静态成员变量,每次调用方法都将该成员变量反序列化成一个对象,在并发高的场景下,频繁的反序列化,显而易见会产生性能问题。下面只列举其中一个,在每次调用test方法时,都将SPECIAL_SEAT_NAME_MAPPING这个常量反序列化成一个Map,并且每次都会将Mapvalue切割,底层采用的是正则很明显会有效率问题。

public class Test {
    private static final String SPECIAL_SEAT_NAME_MAPPING = "{\"3\": \"硬卧|二等卧\", \"J\": \"二等卧|硬卧\",\"4\": \"软卧|一等卧\", \"I\": \"一等卧|软卧\"}";
    
    private void test(SeatInventory seat, String seatTypes) {
        if (StringUtils.isNotBlank(seatTypes)) {
            HashSet<String> seatTypeList = Sets.newHashSet(seatTypes.split(""));
            Map<String, Object> specialSeatNameMap = JacksonUtils.toMap(SPECIAL_SEAT_NAME_MAPPING);
            for (String seatCode : seatTypeList) {
                String special_name = String.valueOf(specialSeatNameMap.get(seatCode));
                String oldName = seat.getSeat_name();
                if (StringUtils.contains(special_name, oldName) && !StringUtils.startsWith(special_name, oldName)) {
                  seat.setSeat_name(Splitter.onPattern("\\|").splitToList(special_name).get(0));
                }
            }
        }

    }
}

更改后代码如下:将反序列化操作前置,避免每次都反序列化对象,并将其中对value进行切割的操作也前置,避免每次都切割,以此来减少性能开销。经过此次更改CPU利用率竟然下降了**8%~12%**左右。

public class Test {
    private static final String SPECIAL_SEAT_NAME_MAPPING = "{\"3\": \"硬卧|二等卧\", \"J\": \"二等卧|硬卧\",\"4\": \"软卧|一等卧\", \"I\": \"一等卧|软卧\"}";
    public static final Map<String, List<String>> specialSeatNameMap = new ConcurrentHashMap<>();

    static {
        Map<String, Object> result = JacksonUtils.toMap(SPECIAL_SEAT_NAME_MAPPING);
        for (Map.Entry<String, Object> entry : result.entrySet()) {
            String specialName = String.valueOf(entry.getValue());
            List<String> specialNameList = Splitter.onPattern("\\|").splitToList(specialName);
            specialSeatNameMap.put(entry.getKey(), specialNameList);
        }
    }

    private void test2(SeatInventory seat, String seatTypes) {
        if (StringUtils.isNotBlank(seatTypes)) {
            HashSet<String> seatTypeList = Sets.newHashSet(seatTypes.split(""));
            for (String seatCode : seatTypeList) {
                List<String> specialNameList = specialSeatNameMap.get(seatCode);
                String oldName = seat.getSeat_name();
                if (CollectionUtils.isNotEmpty(specialNameList) && specialNameList.contains(oldName) && !Objects.equals(specialNameList.get(0),oldName)) {
                    seat.setSeat_name(specialNameList.get(0));
                }
            }
        }
    }
}

3.4 猜想四: 系统中存在大量深拷贝

通过Arthas命令我们发现,系统内存在大量JacksonUtils.toBeanJacksonUtils.toList调用来实现深拷贝功能,查看代码我们发现由于数据只有一份,会对这一份数据进行过滤并更改数据中的某些字段的值,所以需要拷贝两份完全一样的数据,来达到更改数据互不影响对方的属性值。由于采用的是序列化和反序列化的方式来生成新的对象,这种方式在并发高的系统中是及其消耗CPU的。下图是Arthas打印的Cpu利用率占用较高的堆栈信息。
在这里插入图片描述
在这里插入图片描述

为此我们决定在不影响业务的前提下寻找一种能实现深拷贝且不怎么占用CPU资源的方式来替代目前这种方式。经过调研几种能实现拷贝的工具Spring的BeanUtils.copyPropertiesApache BeanUtils.copyProperties发现都存在很多坑。对于List的拷贝是属于浅拷贝,需要自己写兼容逻辑才能实现深拷贝,并且底层采用反射的方式从一定程度上来讲,采用反射的方式在并发高的情况下也会出现性能问题,不仅不能解决目前的问题,可能还要引发其他问题。对此我们采用了最原始的方式,采用new 对象的方式来实现深拷贝。经过这次的优化CPU利用率直降20%~25%

四.结果

经过上述优化我们系统的CPU使用率从原先的80%-90%左右直接下降到了30%左右,负载也由原来的接近过载降到了原先的三分之一,我们发现原先我们单台机器每分钟查询8W次会出现瓶颈,按照优化之后理论上我们可以单台机器可以每分钟查询24W次,这样原先需要3台机器才能满足的查询次数现在只要1台机器就可以满足。

4.1 优化前后CPU利用率对比图和优化后负载情况

在这里插入图片描述

4.2 增大并发后CPU利用率和负载情况

在这里插入图片描述
从上图可知,当我们将并发增大至原先的三倍,CPU利用率甚至比原来的还要低,负载也比原先的低,说明我们的优化后的结果是符合我们的预期的查询第三方系统的次数从原先单台机器的每分钟8W多次变成了每分钟24W多次,平峰期我们日常需要维护90台机器可以直接缩减成30台,高峰期维护的300台机器可以直接缩减成100台,不仅每年可以直接为公司省去机器成本几十万,在机器的管理上也更加方便。

五.总结

在日常编码中,我们不仅要注重业务代码的实现,还需要考虑到系统的性能问题,要学会发现问题,并解决问题。对个人能力来说不仅是一个很大的提升,在一定程度上也能为公司带来效益。

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

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

相关文章

解决电脑睡眠后,主机ping不通VMware虚拟机

文章目录 问题解决方法方法一方法二注意 问题 原因&#xff1a;电脑休眠一段时间&#xff0c;再次打开电脑就ping不通VMware虚拟机。 解决方法 方法一 重启电脑即可&#xff0c;凡是遇到电脑有毛病&#xff0c;重启能解决90%问题。但是重启电脑比较慢&#xff0c;而且重启…

system函数和popen函数

system函数 #include <stdlib.h> *int system(const char command); system函数在linux中的源代码&#xff1a; int system(const char * cmdstring) {pid_t pid;int status;if(cmdstring NULL){return (1);}if((pid fork())<0){status -1;}else if(pid 0){ //子…

Spring MVC(三) 参数传递

1 Controller到View的参数传递 在Spring MVC中&#xff0c;把值从Controller传递到View共有5中操作方法&#xff0c;分别是。 使用HttpServletRequest或HttpSession。使用ModelAndView。使用Map集合使用Model使用ModelMap 使用HttpServletRequest或HttpSession传值 使用HttpSe…

Vue-Cli脚手架项目的搭建【新手快速入手】

目录 一、Vue CLI脚手架简介☺ 1.Node.js前置环境的安装 2.安装npm管理器 3.安装淘宝镜像(cnpm) 二、安装vue-cli 1. 版本号查看 2.旧版本卸载 3.新版本安装 4.检查 三、Vue项目的搭建 &#x1f4cc;进入Vue项目管理器 ★命令方式创建 若localhost拒绝访问怎么办&…

深度剖析:SSD能否全面取代HDD?-2

近日&#xff0c;希捷针对SSD即将全面取代HDD的市场预言也提出站在HDD厂商角度不同的观点。 这些观点出自希捷的一份演示文稿&#xff0c;实质上是对Pure Storage首席执行官Charlie Giancarlo所称“五年内不会再有新的磁盘系统出售”这一论断的回应&#xff0c;意味着到2028年底…

(十六)Servlet教程——Servlet文件下载

Servlet文件下载 文件下载是将服务器上的资源下载到本地&#xff0c;可以通过两种方式来下载服务器上的资源。第一种是使用超链接来下载&#xff0c;第二种是通过代码来下载。 超链接下载 在HTML或者JSP页面中使用超链接时&#xff0c;可以实现页面之间的跳转&#xff0c;但是…

深入理解卷积函数torch.nn.Conv2d的各个参数以及计算公式(看完写模型就很简单了)

代码解释帮助理解&#xff1a; torch.randn(10, 3, 32, 32)&#xff0c;初始数据&#xff1a;(10, 3, 32, 32)代表有10张图片&#xff0c;每张图片的像素点用三个数表示&#xff0c;每张图片大小为32x32。&#xff08;重点理解这个下面就好理解了&#xff09; nn.Conv2d(3, 64…

python自动打卡的代码

好的&#xff0c;以下是一个简单的Python自动打卡程序代码&#xff0c;用于在特定时间自动打卡&#xff1a; python import datetime import time # 设置打卡时间和打卡间隔 check_in_time datetime.datetime(2023, 3, 1, 9, 30) check_out_time datetime.datetime(2023, 3, …

苹果电脑免费第三方软件CleanMyMac X2025电脑版垃圾清理软件神器

Mac电脑用户在长时间使用电脑之后&#xff0c;时常会看到“暂存盘已满”的提示&#xff0c;这无疑会给后续的电脑使用带来烦恼&#xff0c;那么苹果电脑暂存盘已满怎么清理呢&#xff0c;下面将给大家带来一些干货帮你更好地解决这个问题。 CleanMyMac X2024全新版下载如下: h…

springboot之统一异常封装

一&#xff1a;统一返回实体对象 JsonInclude(Include.NON_NULL) public class ResponseObject implements Serializable {private static final long serialVersionUID 1L;private Integer code 0;private String message "success";private Long time System.…

新版文件同步工具(Python编写,其中同时加入了多进程计算MD5、多线程复制大文件、多协程复制小文件、彩色输出消息、日志功能)

两个月前&#xff0c;接到一个粉丝的要求&#xff0c;说希望在我之前编写的一个python编写的文件同步脚本(Python编写的简易文件同步工具(已解决大文件同步时内存溢出问题)https://blog.csdn.net/donglxd/article/details/131225175)上加入多线程复制文件的功能&#xff0c;前段…

flutter中固定底部按钮,防止键盘弹出时按钮跟随上移

当我们想要将底部按钮固定在底部&#xff0c;我们只需在Widget中的Scaffold里面加一句 resizeToAvoidBottomInset: false, // 设置为false&#xff0c;固定页面不会因为键盘弹出而移动 效果图如下

CSCWD 2024会议最后一天 女高音惊艳全场,相声笑破肚皮

会议之眼 快讯 今天是第27届国际计算机协同计算与设计大会&#xff08;CSCWD 2024&#xff09;举办的最后一天&#xff01;会议依然热络&#xff0c;紧张而充实&#xff01;各个技术分论坛持续展开&#xff0c;学者们的热情不减&#xff0c;对技术领域的热爱和探索精神令人赞叹…

国产开源物联网操作系统

软件介绍 RT-Thread是一个开源、中立、社区化发展的物联网操作系统&#xff0c;采用C语言编写&#xff0c;具有易移植的特性。该项目提供完整版和Nano版以满足不同设备的资源需求。 功能特点 1.内核层 RT-Thread内核包括多线程调度、信号量、邮箱、消息队列、内存管理、定时器…

VS配置三方依赖

1.配置include 1.1.打开属性 1.2.打开“配置属性”-"C/C"-"常规" 2.配置lib 2.1.配置lib目录 打开"配置属性"-“链接器”-“常规”。 2.2.配置具体的lib 打开"配置属性"-"链接器"-“输入”。 也可以通过代码方式加入&…

差速机器人模型LQR 控制仿真(c++ opencv显示)

1 差速机器人状态方程构建 1.1差速机器人运动学模型 1.2模型线性化 1.3模型离散化 2离散LQR迭代计算 注意1&#xff1a;P值的初值为Q。见链接中的&#xff1a; 注意2&#xff1a;Q, R参数调节 注意3&#xff1a;LQR一般只做横向控制&#xff0c;不做纵向控制。LQR输出的速度…

明火检测实时识别报警:视觉算法助力安全生产管理

背景与现状 在各种工作、生产环境下&#xff0c;明火的存在往往是潜在的安全隐患。无论是加油站、化工园区、仓储场所还是校园&#xff0c;明火一旦失控就会引发火灾&#xff0c;造成严重的人员伤亡和财产损失。传统的明火检查手段主要依赖于人工巡查和定期的消防检查&#xf…

拯救被勒索病毒加密的文件

无意间打开了勒索病毒的告知文件&#xff0c;几年前很多人很熟悉这个文件。 --- Welcome. Again. --- [] Whats Happen? [] Your files are encrypted, and currently unavailable. You can check it: all files on your computer has extension u347q678t1. By the way, e…

安装nvm切换多个nodejs

今天实习&#xff0c;用到了公司的老项目vue2的&#xff0c;需要更换nodejs版本 我想直接安装一个16版本的&#xff0c;然后自己在webstrom中配置一下exe文件就可以了。 然而第一步就不行&#xff0c;在安装另一版本中显示 然后博主在这里介绍一下怎么使用nvm可以快速切换node…

动手学深度学习——多层感知机

1. 感知机 感知机本质上是一个二分类问题。给定输入x、权重w、偏置b&#xff0c;感知机输出&#xff1a; 以猫和狗的分类问题为例&#xff0c;它本质上就是找到下面这条黑色的分割线&#xff0c;使得所有的猫和狗都能被正确的分类。 与线性回归和softmax的不同点&#xff1…