关键词:版本|发现问题|机器|项目|增高|定位问题信息|构造|也可以|版本|研发人员|低配版101|14

网上高CPU网络服务器清查

文章内容文件目录

一、发现问题二、剖析及定位问题1、测试2、拆换项目中较为初期版本的软件及线程池3、刚开始对于网上高CPU网络服务器清查4、对于堆信息内容查询5、资询巨头,提议根据火焰图精准定位某一环节实行全过程的耗费状况6、运行命令转化成火陷图7、结果三、认证难题四、解决方法五、填补

一、发现问题

项目在发布以后,运作二十四小时以后CPU忽然增高,造成迫不得已重新启动机器。

二、剖析及定位问题

项目在发布前是历经稳定性测试,一开始精准定位是QPS过大,根据提升机器。但結果并并不是非常理想化,自始至终会出现多台机器增高。

项目自然环境

测试机 1c3g

1、检测

根据jmeter对该机器插口开展稳定性测试,qps为90,cpu增高到40%不断不久降低到10%,一直不断平稳。仍未造成CPU过高的状况,该机器配备比宣布自然环境要低4倍,第一步得到的结果应当没有插口上

2、拆换项目中较为初期版本的软件及线程池

再次发布项目以后,运作1天以后,仍然有2台机器的CPU不断增高,而且时间越长提升越大

3、刚开始对于网上高CPU网络服务器清查

#查询java的pidtop  #查看pid下的占有高的tid top -Hp pid  #复印tid16进制printf "%x\n" tid  #查询栈 輸出到xx.logjstack pid|grep -A 2000 tid的16进制 > xx.log​#查询gc 复印每2000Ms輸出一次,共10次jstat -gcutil pid 2000 10  

结果:栈信息内容基础全是RUN或TIME_WATING 并沒有有关的死链接的进程,可是根据gc发觉很多的YGC不断的增高,此刻充分考虑将会堆的信息内容有出现异常

4、对于堆信息内容查询

#查询堆jmap pid  jmap -heap pid  jmap -histo:live pid  ...​#常见的是最后一个 加一个more 避免过多內容霸屏 jmap -histo:live pid|more ​

实行数次最后一个指令,发觉一个序列在不断的增高,几百几百的提升并无随后降低的状况

1:        111885      139385304 [Ljava.lang.Object;   3:         10515       15412904 [I   4:        142407       13450056 [C   5:         13892        4170928 [B   6:        135968        3263232 java.lang.String   .....  34:          6423         308304 java.util.HashMap  35:         12459         299016 java.util.concurrent.ConcurrentLinkedQueue$Node

5、资询巨头,提议根据火焰图精准定位某一环节实行全过程的耗费状况

最终一行便是发觉提高过快的序列,到此算作发觉了一个较为有效的信息内容,回过头就要剖析编码。但仍然没什么结果,编码逻辑性并不繁杂也仍未应用到该序列。

实行数次最后一个指令,发觉一个序列在不断的增高,几百几百的提升并无随后降低的状况

#刚开始安裝火陷图软件#实际安裝软件的全过程,大伙儿自主检索,文中不实际叙述如何安装火陷图

6、运行命令转化成火陷图

根据图中,大家能形象化的见到在MimeTypeUtils方式 中,应用来到过多的这一序列,随后就立即去看看源代码了。现阶段官方网早已修补了一版在2.2.6版本中(可是很不好运,并沒有彻底修补)

下边是2.2.6版本修补一版的编码,去除开以前的一些没有意义分辨,MimeTypeUtils.java文件

​ private static class ConcurrentLruCache {​ private final int maxSize;​ private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>();​ ....​ public V get(K key) { this.lock.readLock().lock(); try { if (this.queue.size() < this.maxSize / 2) { V cached = this.cache.get(key); if (cached != null) { return cached; } } else if (this.queue.remove(key)) { this.queue.add(key); return this.cache.get(key); } } finally { this.lock.readLock().unlock(); } this.lock.writeLock().lock(); try { // retrying in case of concurrent reads on the same key if (this.queue.remove(key)) { this.queue.add(key); return this.cache.get(key); } if (this.queue.size() == this.maxSize) { K leastUsed = this.queue.poll(); if (leastUsed != null) { this.cache.remove(leastUsed); } } V value = this.generator.apply(key); this.queue.add(key); this.cache.put(key, value); return value; } finally { this.lock.writeLock().unlock(); } } }

单纯性的阅读文章编码,并没什么BUG,可是我们可以去关心序列自身的难题。

查询ConcurrentLinkedQueue remove 源代码

public boolean remove(Object o) {    if (o != null) {      Node next, pred = null;      for (Node p = first(); p != null; pred = p, p = next) {        boolean removed = false;        E item = p.item;        if (item != null) {          if (!o.equals(item)) {            next = succ(p);            continue;         }          removed = p.casItem(item, null);       }​        next = succ(p);        if (pred != null && next != null) // unlink          pred.casNext(p, next);        if (removed)          return true;     }   }    return false;}

假如存有好几个则删掉第一个,并回到true,否者回到false,比如:好几个进程另外要获得到同一个要删掉的原素,则只删掉一个,别的回到false,再融合MimeTypeUtils方式 ,会再去实行add,这就造成会对序列出現無限的提高【将会】(非百分之百)。

7、结果

导致CPU特性过高,是由于序列长短太长,remove方式 必须解析xml全部序列內容。序列太长的缘故是由于remove 高并发状况下回到false,开发设计全过程中将会仍未关心到remove会回到false,造成無限的实行add方式 的将会。

三、认证难题

根据debug发觉spring boot在实行全过程中会对于客户恳求的Accept和回到的Content-Type都是启用该方式 。此刻实际上就可以故意结构Accept去恳求某一api,Accept中每一个用分号切分都是过一次方式 ,造成很多特性耗费。当地根据结构好几个Accpet值,发觉在MimeTypeUtils中的确能够超过自身对序列的长短设定,造成迟缓提高。

1、根据官方网github-issues搜索有关难题,发觉早已有些人在近期提及过该难题,并早已被close。

2、根据再度回应官方网研发人员,并出示大量的有关信息证实2.2.6版本修补以后仍然存有该难题

https://github.com/spring-projects/spring-framework/issues/24671#issuecomment-611427157

3、在这段时间又有些人得出根据MediaType 提交种类构造的Accept

https://github.com/spring-projects/spring-framework/issues/24767

4、实际构造认证

找一台低配版的服务项目 1c3g

应用jmeter,设定进程组,不用非常高50个进程,永久性推送

设定header的Accpet,能够先应用內容以下:

application/stream x-jackson-smile, application/vnd.spring-boot.actuator.v3 json, application/vnd.spring-boot.actuator.v2 json, application/json, multipart/form-data; boundary=----WebKitFormBoundaryVHfecvFDYeDEjhu4, multipart/form-data; boundary=----WebKitFormBoundarymKzwdDkWNDNzQFP0, multipart/form-data; boundary=----WebKitFormBoundaryiWpMXOUbWwBwq2AX, application/x-www-form-urlencoded, text/html;charset=UTF-8, application/octet-stream, application/vnd.ms-excel;charset=utf8, application/msword, multipart/form-data; boundary=----WebKitFormBoundaryGF2AJ2ZdPqbWOyEO, multipart/form-data; boundary=----WebKitFormBoundaryTZLPpyBs2F0ycmkB, multipart/form-data; boundary=----WebKitFormBoundaryBUClXdZPA3oxpUpx, image/jpeg;charset=UTF-8, multipart/form-data; boundary=----WebKitFormBoundarysODcdeMwzfHwEjtw, multipart/form-data; boundary=----WebKitFormBoundary26i2en6YQUSXUBzs, multipart/form-data; boundary=----WebKitFormBoundaryxUUWAyZnZjwlM1oy, multipart/form-data; boundary=----WebKitFormBoundarysVMYk11吨VTTsXuEB, multipart/form-data; boundary=----WebKitFormBoundaryXsI4dpNsVTCWWrRo, multipart/form-data; boundary=----WebKitFormBoundaryiV1owCGwTHyQzja0, multipart/form-data; boundary=----WebKitFormBoundarygf1XpLmgasAQU9fi, multipart/form-data; boundary=----WebKitFormBoundaryBNaQtUvpQ2VV7YYA, multipart/form-data; boundary=----WebKitFormBoundaryW1rdrg4AbJ5Jn3Po, multipart/form-data; boundary=----WebKitFormBoundaryoBwFj2ABM5LflDmW, multipart/form-data; boundary=----WebKitFormBoundary40xI2TxryjbkSCtO, multipart/form-data; boundary=----WebKitFormBoundarytaCC9B6g8u4urnLF, multipart/form-data; boundary=----WebKitFormBoundaryOrhplGKYP9ozLkCs, multipart/form-data; boundary=----WebKitFormBoundaryvEUouFAr3R3YJYBh, multipart/form-data; boundary=----WebKitFormBoundaryuQ9tEKtn59w5hPLY, multipart/form-data; boundary=----WebKitFormBoundaryRGvPXUBAuZ6xJ95u, application/vnd.openxmlformats-officedocument.wordprocessingml.document, multipart/form-data; boundary=----WebKitFormBoundary7jpljZi4k61KhCNN, multipart/form-data; boundary=----WebKitFormBoundary7GVKDTHVuBABvjGB, multipart/form-data; boundary=----WebKitFormBoundaryZbNBPl3T4VZ44q6B, audio/mp3, multipart/form-data; boundary=----WebKitFormBoundaryI6rUM76YvxrIEcqv, multipart/form-data; boundary=----WebKitFormBoundaryag4BDWrzifHRdDiR, multipart/form-data; boundary=----WebKitFormBoundary1YRsWAdVqDin8g8p, multipart/form-data; boundary=----WebKitFormBoundaryDaatlrV3KAyZu7wA, multipart/form-data; boundary=----WebKitFormBoundaryyhvikZJdRGH1AjQq, multipart/form-data; boundary=----WebKitFormBoundary2z4s店JhqeEx5XtVj4, multipart/form-data; boundary=----WebKitFormBoundaryeDLd1MTvuhmcmzNe, multipart/form-data; boundary=----WebKitFormBoundarybKizrvRESfhxHAMQ, multipart/form-data; boundary=----WebKitFormBoundary24U8tmsOluZqcRXX, multipart/form-data; boundary=----WebKitFormBoundarye4j6KdQyBjY4FqSk, multipart/form-data; boundary=----WebKitFormBoundaryjPmgLdzMcMYYB3yS, multipart/form-data; boundary=----WebKitFormBoundaryxzBZ9w6Je3IJ53NM, multipart/form-data; boundary=----WebKitFormBoundaryScy0j73cvx3iCFyY, multipart/form-data; boundary=----WebKitFormBoundaryTBoS8s4YWwmBGTDA, image/*, multipart/form-data; boundary=----WebKitFormBoundaryRUutFo3RXlNPgoBS, text/html;charset=utf-8, multipart/form-data; boundary=----WebKitFormBoundarykLObBi1tJMf159kt, multipart/form-data; boundary=----WebKitFormBoundary8M8MfCWBEFcsxnBU

提示:实际上构造非所述的accept也可以达到效果

不断压测,随后再开启另三个jemter,做一样的恳求实际操作,将header的Accept各自设定以下三种状况,也可以大量:

#第一种text/html,application/xhtml xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9#第二种text/css,*/*;q=0.1#第三种application/json

大家再根据复印堆信息能够发觉ConcurrentLinkedQueue序列刚开始提升限定忽然提高,又忽然降低,此刻能够把第一个jmeter恳求先中止。随后再不断观查堆信息

[[email protected] ~]$ jmap -histo:live 10114|grep java.util.concurrent.ConcurrentLinkedQueue  33:          4809         115416 java.util.concurrent.ConcurrentLinkedQueue$Node 768:            36            864 java.util.concurrent.ConcurrentLinkedQueue[[email protected] ~]$ jmap -histo:live 10114|grep java.util.concurrent.ConcurrentLinkedQueue  30:          5530         132720 java.util.concurrent.ConcurrentLinkedQueue$Node 768:            36            864 java.util.concurrent.ConcurrentLinkedQueue[[email protected] ~]$ jmap -histo:live 10114|grep java.util.concurrent.ConcurrentLinkedQueue  30:          5530         132720 java.util.concurrent.ConcurrentLinkedQueue$Node 767:            36            864 java.util.concurrent.ConcurrentLinkedQueue[[email protected] ~]$ jmap -histo:live 10114|grep java.util.concurrent.ConcurrentLinkedQueue  29:          6994         167856 java.util.concurrent.ConcurrentLinkedQueue$Node 768:            36            864 java.util.concurrent.ConcurrentLinkedQueue[[email protected] ~]$ jmap -histo:live 10114|grep java.util.concurrent.ConcurrentLinkedQueue  29:          7262         174288 java.util.concurrent.ConcurrentLinkedQueue$Node 768:            36            864 java.util.concurrent.ConcurrentLinkedQueue[[email protected] ~]$ jmap -histo:live 10114|grep java.util.concurrent.ConcurrentLinkedQueue  26:          9829         235896 java.util.concurrent.ConcurrentLinkedQueue$Node 777:            36            864 java.util.concurrent.ConcurrentLinkedQueue

显著能够发觉ConcurrentLinkedQueue在提高。到此对于SpringBoot在2.2.6版本号中cpu不断提高状况早已能够彻底的重现,重现全过程将会会存有失败,能够多试几回。

之上三个进程能够设定每一个为30,永久性。

刚开始开展恳求,随后大家再根据网络服务器中对于堆信息内容查询指令,查询 ConcurrentLinkedQueue序列提高状况

四、解决方法

1、现阶段发觉在多核CPU的状况下提高较为迟缓,可是到一定的长短以后也会加快增加CPU的耗费,因此高配备可能是一个解决方法

2、退级计划方案,现阶段根据比照。SpringFramework在5.1.x版本号无很大危害。

3、等候升级 现阶段master再度修补一版,预估4.27公布,官方网也将MimeTypeUtils列入5.3.x版本号重新构建之一

#修补计划方案从 ConcurrentLinkedQueue 序列转换来到 ConcurrentLinkedDeque 序列

五、填补

认证 ConcurrentLinkedQueue 序列,出現false状况

​import java.util.concurrent.ConcurrentLinkedQueue;import java.util.concurrent.LinkedBlockingQueue;​public class Main {    private static ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>();​    public static void main(String[] args) {        for (int i = 0; i < 1000; i ) {            Thread thread1 = new QueueThread(String.valueOf(i));            thread1.start();       }        try {            Thread.sleep(5000);       } catch (InterruptedException e) {            e.printStackTrace();       }        System.out.println("end");   }​    static class QueueThread extends Thread {        private int value = 0;​        private String name;​        public QueueThread(String name) {            this.name = name;            queue.add(value);       }​        @Override        public void run() {            for (int i = 1; i < 1000; i ) {                try {                    boolean flag = queue.remove(value);                    System.out.println("remove: " value " " flag);                    queue.add(value);                    value ;               } catch (Exception e) {                    System.out.println(e);               }           }       }   }}

文中创作者:Mx.Hao , Mx.Cx,转截请标明来源于FreeBuf.COM

猜你喜欢