记一次处理一起Java服务器生产集群CPU 100%问题

业余草

共 6823字,需浏览 14分钟

 · 2024-03-25

来源:juejin.cn/post/7263294595740139575

推荐:https ://t.zsxq.com/16yoFC1Fk

背景

收到一生产环境报警消息,一个集群的CPU利用率直接飙升到了100%。

268017812bf2a70b5fdc9e7e56478c39.webp

老板说让我处理,但是我很纳闷,最近没啥发布呀。。。

4dfe912744fd2fe3b376aff0c7da7c89.webp

原来还是同事埋的锅,真是坑呀。

e0cc27dd0d90d6fe87279683f370b6d8.webp

废话不多说,根据上图中的几种可能,下面我们一起找找问题吧!

看监控

先看下监控看下能不能看到有什么发现,发现这个集群消费 MQ 消息的数量突增,有几个尖刺状的流量已经达到了每分钟 8W+。

8ca71ef6df11e228d1927fc2cd8e4091.webp

很有可能是尖刺流量造成的这个问题,但是在8:30之后流量已经趋于缓和,但是CPU还是一直100%并没有降下去。

查看jvmGC状况

于是登录服务器看下jvm的运行状况。通过 jstat -gcutil命令查看服务的GC情况,发现已经进行了500多次的FullGC。关键点在于图中的 534 -> 535次的FullGC内存一点都没有被回收下去,这就很可怕了。

53b0c75b46da55f921b2317e5af78e07.webp

arthas查看服务整体状态

于是想用arthas查看下是否是内存分配有问题到这的内存无法回收。

c8375c8e6a16e6627b9b15745898bdba.webp

如上图,发现jvm的非堆内存的利用率已经满了,那么很有可能是这个问题导致的。

于是想办法调整下这个内存的大小,打开项目中配置的启动命令发现已经设置了非堆内存的大小,但是这里并没有生效nohup java -Xms2g -Xmx2g -Xmn1g -Xss1024K -XX:PermSize=512m -XX:MaxPermSize=512m,启动参数长这个样子,其中 -XX:PermSize=512m就是设置非堆内存大小的。但是没有生效。。。。为什么没生效呢???

jdk7->jdk8内存结构的调整

我们都知道7到8的改动很大,其中一块改动就是jdk8将以前的永久代。也就是说**_-XX:PermSize_**这个参数在jdk8中已经没有用了,但是这个陈旧的项目并没有人去修改这个参数。 先调整下这个参数试试。

关于永久代和元空间的详细内容,参见:https://t.zsxq.com/16uukniDDhttps://t.zsxq.com/16nrloyTIhttps://t.zsxq.com/16TJMKyM2

分析堆内存

内存无法回收一般是由内存泄漏,或者有大对象无法回收造成的,虽然上面找到一处问题,但是并不能很好的解释为什么内存无法被回收。分析下堆内存,

  • 使用 jmap -dump:live,format=b,file=heap.hprof 命令将堆内存的快照导出,下载下来。
  • 使用 jvisualvmjdk自带的工具分析下内存情况
8b028baf73e9fd747a0ab803ab2b6873.webp

类的分布情况,最大的 cahr[] string这个没什么问题,第三名和第四名就有问题了, 这两个,一个是阻塞队列、一个是业务代码中的一个内部类。一个内部类怎么会占用这么大的比重,看下代码。

      
      ExecutorService executorPool = Executors.newFixedThreadPool(numberOfCore);
RateLimiter handleLimit = RateLimiter.create(2010, TimeUnit.SECONDS);

ESBClient client = new ESBClient(key_esb);
client.setReceiveSubject(new ESBSubject(INFO_DEL_SUBJECTID, INFO_DEL_CLIENTID,SubMode.PULL));
client.setReceiveHandler(new ESBReceiveHandler() {

    @Override
    public void messageReceived(final ESBMessage msg) {
        try {
            final String msgStr = new String(msg.getBody(), "UTF8");
            executorPool.execute(new Runnable(){
                public void run() {
                    try {
                        handleLimit.acquire();
                        handleDelMsg(msgStr);
                    } catch (Exception e) {
                        e.printStackTrace();        
                    }
                }
            });

        } catch (Exception e) {
        }

    }
});

大家一起看下这端代码有没有什么问题。

  1. 第一点,这里使用了一个一定不能使用的线程池创建方法,Executors.newFixedThreadPool这个方法创建出来的线程池里面的任务队列是个无界队列。
  2. 第二点这里用到了限流,RateLimiter这里应该是防止流量过大下游扛不住。

但是为什么要在任务里面限流呢?在线程任务里面限流这个任务放到线程池中,线程在执行任务的时候就会等待,拖慢了整个线程池的运行速度,并发量高的时候大量的任务被放到队列中,而队列又是个无界队列,一下就会把jvm的内存打满,并且这些任务都是被线程池引用的状态无法回收。

这样jvm就会一直进行GC,已经在队列中的任务拿不到CPU资源无法执行,GC又无法回收内存导致了整个集群挂掉。

问题修改

除了修改元空间的启动参数,这里还需要修改代码如下。

      
      private static final ExecutorService executorPool = new ThreadPoolExecutor(numberOfCore, numberOfCore * 210, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1024), new DefaultThreadFactory("custom"), new ThreadPoolExecutor.CallerRunsPolicy());
RateLimiter handleLimit = RateLimiter.create(2010, TimeUnit.SECONDS);

ESBClient client = new ESBClient(key_esb);
client.setReceiveSubject(new ESBSubject(INFO_DEL_SUBJECTID, INFO_DEL_CLIENTID,SubMode.PULL));
client.setReceiveHandler(new ESBReceiveHandler() {

    @Override
    public void messageReceived(final ESBMessage msg) {
        try {
            handleLimit.acquire();
            final String msgStr = new String(msg.getBody(), "UTF8");
            executorPool.execute(new Runnable(){
                public void run() {
                    try {
                        handleDelMsg(msgStr);
                    } catch (Exception e) {
                        e.printStackTrace();        
                    }
                }
            });

        } catch (Exception e) {
        }

    }
});
  1. 首先将限流器移到提交任务的时候限流。
  2. 修改线程池的参数,有界队列、当队列满了调用者执行任务。由于我们这里消费MQ使用的是PULL模式,所有当任务消费不过来的时候就不会去拉消息了,所以采用这个 CallerRunsPolicy策略

后续关注

内存和GC恢复正常。

f1d19bb23f1913e5a269a34ba23b5cf9.webp 2b012b95b6016767fc5fbcf78d339265.webp

软件是交付给用户,并由用户体验的产品;代码则是对软件正确且详细的描述,所以代码质量关系到软件产品的质量。虽然软件质量不等于代码质量,但是代码上的缺陷会严重的影响到软件产品的质量。因此,为提高代码质量的投入是值得的。

浏览 10
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报