• java 线上java应用CPU过高排查


    昨天去了grafa 线上的监控看了看项目 突然发现cpu达到了70% (好久没关注) 然后开启了排查
    现象图如下
    效果图
    这个项目没有其他的很耗时的操作 没有大量计算 而且接口数量 以及并发数量均没有很大 怀疑有异常 本来想着从grafa 监控中看下最近几个月 CPU使用率的变化 可惜因为数据量太大 线上没有保留这么长时间

    出问题后临时的解决方案

    • 加大堆内存 之前1g 后来加到2g 问题并未解决 反而出来内存占用大于90%的告警
    • 之前怀疑是skywalking 引起的CPU过高 (后面才上线的)新建 临时服务节点 下掉了skywalking CPU过高问题还是存在

    开始进行深度代码排查

    • 申请线上跳板机只读权限 略
    • 进行操作
    #1.获取到pods  
    kubectl get pods 
    #2.进入到容器 
    kubectl exec -it 容器名 bash
    
    • 1
    • 2
    • 3
    • 4
    • 执行top 然后 M
      效果图

    • 占用CPU最大的进程pid为 然后 查询进程中消耗CPU最大线程

    • top -H -p 1

    效果图

    • 可以看到进程中占用CPU最大的线程ID为49
    • 将线程ID转化为16进制 (0x开头)
    • printf “0x%x\n” 49
      输入的值
    • 16进制的线程ID 0x31
    • 查询线程的栈信息 并匹配该线程ID 同时向下打印30行 并将匹配到的值 染色
    • jstack 1 | grep 0x31 -A 30 --color
      问题图片
      可以看到直接定位到了关键的代码行数 我们来看业务代码
    //等待被更新的缓存map
    private static final Map<Long, Integer> userCacheMap = new ConcurrentHashMap<>();
    //可用线程数
    private static final Integer availableProcessors = Runtime.getRuntime().availableProcessors();
    //任务执行线程池
    private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(availableProcessors, availableProcessors + 1, 3, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(1024), new ThreadFactory() {
    		@Override
    		public Thread newThread(Runnable r) {
    			return new Thread(r, "定时任务执行线程");
    		}
    	});
    	
    private class TaskThread extends Thread implements Runnable {
    	@Override
    	public void run() {
    		for (; ; ) {
    			//死循环 遍历map 存在数据进行处理 每处理一个数据 remove
    			Iterator<Map.Entry<Long, Integer>> iterator = userCacheMap.entrySet().iterator();
    			while (iterator.hasNext()) {
    				Map.Entry<Long, Integer> next = iterator.next();
    				Long userId = next.getKey();
    				Integer expireTime = next.getValue();
    				//丢入任务到线程池子
    				EXECUTOR.execute(new Task(userId, expireTime));
    				//任务数量
    				long taskCount = EXECUTOR.getTaskCount();
    				//完成任务数量
    				long completedTaskCount = EXECUTOR.getCompletedTaskCount();
    				//存活数量
    				int activeCount = EXECUTOR.getActiveCount();
    				LogUtil.info(log, LogUtil.LogType.INFO, "CacheUserServiceImpl.EXECUTOR",
    						"存活数量: {},任务数量: {},完成任务数量: {}", activeCount, taskCount, completedTaskCount);
    				iterator.remove();
    			}
    
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    复盘下 当时的设计方案

    • 其他的业务调用包装的获取缓存的方法
    • 缓存方法中 判断是否存在缓存 不存在进行获取 存在的话 进行 检测是否业务时间已经失效 失效的话 存入map 等待被更新
    • 同时启动中会开启一个线程 死循环 使用iterator 方式遍历map 如果存在等待被更新数据 进行遍历
    • 将每个遍历的结果包装成Task 丢入到线程池中 等待被调度 去重建缓存 同时remove掉
    • static修饰的线程池 进行执行任务

    替换方案

    • 去掉死循环迭代map的方式 当缓存业务失效时 直接包装成Task 丢到线程池 异步执行完成
    • for( ; ; ) 循环中循环不到数据 睡眠一会 几秒钟 这样就不会一直查询

    总结: for(; ; ) 会占用大量CPU 尤其是for(; ; ) 里面遍历map 慎用

  • 相关阅读:
    Java中生成指定字体的印章
    一站式开源持续测试平台 MerterSphere 之测试跟踪操作详解
    怎么利用大厂的API将大段音频转成文本
    Azure AD Domain Service(二)为域服务中的机器配置 Azure File Share 磁盘共享
    C语言程序设计教程(第三版)李凤霞 第九章课后习题答案
    蓝牙耳机开发与车载音响开发的工作对比
    zookeeper的安装使用
    RocketMQ 消费端如何监听消息?
    java double 保留两位小数
    1-k8s1.23.6-底座搭建-基于docker
  • 原文地址:https://blog.csdn.net/a15835774652/article/details/125507328