性能文章>高CPU消耗,这次又是lettuce的锅>

高CPU消耗,这次又是lettuce的锅原创

304417

起源于Thread.sleep

最近在系统调优过程中遇到一个比较有意思的高CPU消耗的问题(当时CPU使用率已经到90%左右),先上图感受一下。
34248825806163af22c04f2.png

是的,就是Thread.sleep这个方法,消耗了大概34%的CPU,而且久居不下。其实第一眼看到这玩意儿我是懵的,啥玩意儿。
9563388106163b20931123.png

心里暗骂了开发xxx遍,这nm是哪个没脑子的开发sleep的时候用的纳秒,事实证明我错了(其实也是因为自己平时撸代码比较少,对lettuce不是很熟,因为当时并不知道是lettuce导致的),向我们勤勤恳恳的dev道歉,瑞思拜。

开始找“BUG”

OK,言归正传,问题还是要好好分析的。终于在线程dump中发现了突破口,找到了这个线程:
41651375666163b384594a2.png

然后通过这个线程我找到今天的主角,有请lettuce闪亮登场。
29815399796163b3fe75890.png

到这里,知道是lettuce搞的鬼,但我对lettuce并不熟啊,老老实实复习英语。
lettuce官方文档(https://lettuce.io/core/5.3.7.RELEASE/reference/index.html)

在官方文档中发现了这个:
27698118306163b6040f125.png

lettuce的延时监控功能默认是开启的,在内存dump中,也可以看到相关的属性为true:
24917442506163c4fe59a4e.png

简单介绍一下延时跟踪功能,具体参见官方文档:

  • 依赖LatencyUtils模块
  • 可以统计执行次数
  • 第一次响应的延迟(min, max, percentiles)
  • 命令执行完的延迟(min, max, percentiles)
  • 命令延迟统计可以1、按主机和端口或套接字路径区分(不区分命令),2、按命令类型(GET、SET、…)跟踪
  • 延时监控可以通过配置进行关闭,官方文档中有示例如下:
ClientResources res = DefaultClientResources
        .builder()
        .commandLatencyCollectorOptions( DefaultCommandLatencyCollectorOptions.disabled())
        .build();

RedisClient client = RedisClient.create(res);

到这边基本可以给优化建议:在非必要的情况下,直接关闭该功能。
除了这个方法,暂时想不到其他解决办法。

根据之前的描述,Time.sleep()是在LatencyUtils模块下的调用到的,为了满足自己的好奇心,直接看了一下LatencyUtils相关的源码。
源码地址:https://github.com/LatencyUtils/LatencyUtils/blob/master/src/main/java/org/LatencyUtils/SimplePauseDetector.java
可以看到默认sleep的时间是1毫秒
36301116546163c6f8f36a0.png

线程sleep为什么消耗CPU

这边说明一下,其实挂起的线程是不会消耗CPU资源的,消耗资源的是频繁的唤醒和sleep。sleep会导致线程上下文切换和额外的系统消耗,相似的其实还有LockSupport.park()。下面是sleep的demo(关于park的小伙伴可以自己整一个玩玩),感受下散热风扇的咆哮(线程数量越多,CPU消耗越多):

public class HighCPU {

    public static void main(String[] args) {
        int threadCount = 100;
        final List<Thread> list = new ArrayList<>(threadCount);
        for(int i =0; i<threadCount; i++){
            Thread thread = new Thread(()->{
                while(true){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.setName("cpuThread" + i);
            list.add(thread);
            thread.start();
        }

    }
}

打完收工,纪念第一篇正儿八经的博文。

点赞收藏
开翻挖掘机
请先登录,查看1条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

【译】一个Java UUID生成导致的性能问题和解决方案

【译】一个Java UUID生成导致的性能问题和解决方案

介绍8个获取线程dump文件的方法

介绍8个获取线程dump文件的方法

【译】一次交易类场景CPU飙升100%的故障排除案例

【译】一次交易类场景CPU飙升100%的故障排除案例

怒啃 24 小时,终于搞懂上下文切换!

怒啃 24 小时,终于搞懂上下文切换!

【译】如何用Chronicle 的开源Pauser,平衡低延迟和CPU使用率?

【译】如何用Chronicle 的开源Pauser,平衡低延迟和CPU使用率?

【译】处理阻塞调用的两种方法:线程并发与网络异步

【译】处理阻塞调用的两种方法:线程并发与网络异步

7
1