CPU飙高问题排查过程(2)

✅什么是CPU利用率?怎么算的? 问题发现 本文整理自一个真实的案例,在一次大促之前的压测时发现了这个问题。 在每次大促之前,我们的测试人员都会对网站进行压力测试,这个时候会查看服务的cpu、内存、load、rt、qps等指标。 在一次压测过程中,测试人员发现我们的某一个接口,在qps上升到500以后,CPU使用率急剧升高。 CPU利用率,又称CPU使用率。顾名思义,CPU利用率是来描述CPU的使用情况的,表明了一段时间内CPU被占用的情况。使用率越高,说明你的机器在这个时间上运行了很多程序,反之较少。 问题定位 遇到这种问题,首先是登录到服务器,看一下具体情况。 定位进程 登录服务器,执行top命令,查看CPU占用情况: 1 2 3 $top PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1893 admin 20 0 7127m 2.6g 38m S 181.7 32.6 10:20.26 java top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。 通过以上命令,我们可以看到,进程ID为1893的Java进程的CPU占用率达到了181%,基本可以定位到是我们的Java应用导致整个服务器的CPU占用率飙升。 定位线程 我们知道,Java是单进程多线程的,那么,我们接下来看看PID=1893的这个Java进程中的各个线程的CPU使用情况,同样是用top命令: 1 2 3 $top -Hp 1893 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 4519 admin 20 0 7127m 2.6g 38m R 18.6 32.6 0:40.11 java 通过top -Hp 1893命令,我们可以发现,当前1893这个进程中,ID为4519的线程占用CPU最高。 ...

March 22, 2026 · 1 min · santu

Arthas统计方法耗时的原理是什么?

典型回答 Arthas在统计方法耗时时,使用了字节码插桩技术。它会在需要监控的方法中插入代码,在方法执行前记录开始时间,在方法执行完毕后记录结束时间,并计算两者的差值得到方法执行时间。这个过程就是字节码插桩的经典应用之一。 相比传统的基于AspectJ等框架实现AOP的方式,Arthas的动态插桩能力更强,支持无侵入式的监控。 扩展知识 字节码插桩 Java字节码插桩技术是指在编译期或运行期,通过修改Java字节码的方式,向代码中插入额外的代码,它可以在不改变Java源代码的情况下,对Java应用程序的运行时行为进行监控、调试、分析和优化等。例如实现性能监控、代码覆盖率检测、代码安全扫描等。 字节码插桩技术通常包括以下几个步骤: 生成目标类的字节码,这可以通过Java编译器(如javac)或其他工具(如AspectJ)完成。 解析字节码,识别需要插桩的代码区域(如方法、循环、异常处理等)。 插入额外的字节码,这些字节码通常是通过编写Java代码来实现的,并通过字节码生成库(如ASM、Javassist等)生成对应的字节码。 将修改后的字节码重新写回到磁盘或内存中,以便后续使用。 假设我们需要对一个Java方法进行性能监控,我们可以在方法的入口和出口处分别插入计时器,来统计方法的执行时间。这可以通过以下代码实现: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Monitor { public static void start() { long startTime = System.nanoTime(); // 将起始时间记录到ThreadLocal中,以便在方法返回时进行计算 ThreadLocalHolder.set("startTime", startTime); } public static void end() { long endTime = System.nanoTime(); // 获取起始时间 long startTime = (long) ThreadLocalHolder.get("startTime"); // 计算方法执行时间 long elapsedTime = endTime - startTime; System.out.println("Method execution time: " + elapsedTime + "ns"); } } public class Example { public void method() { Monitor.start(); // 执行方法逻辑 Monitor.end(); } } 但是,如果需要对多个方法进行性能监控,就需要在每个方法中分别插入Monitor.start()和Monitor.end(),这样会导致代码重复,可读性差,并且容易漏掉一些方法。这时,我们就可以使用字节码插桩技术,在编译期或者运行期,自动向每个方法的入口和出口处插入Monitor.start()和Monitor.end(),来实现代码的统一性和可维护性。 ...

March 22, 2026 · 2 min · santu

Sort aborted问题排查过程

问题发现 我们有一个定时任务,是做扫表的,但是最近经常出现定时任务扫表处理失败的报警,登录到机器上之后,发现有数据库层面的报错: 1 2 Caused by: com.taobao.tddl.common.exception.TddlRuntimeException: ERR-CODE: [TDDL-4614][ERR_EXECUTE_ON_MYSQL] Error occurs when execute on GROUP 'FIN_RISK_XXX_GROUP' ATOM 'cn-zhangjiakou_i-xxxxx_fin_risk_xxx_3028': Sort aborted: Query execution was interrupted More: [http://xxx.alibaba-inc.com/faq/faqByFaqCode.ht ml?faqCode=XXX-4614] 以上日志我做了简单的脱敏,其中最重要的就是这句: 1 Sort aborted: Query execution was interrupted 这是一个数据库查询执行过程中的错误信息,通常在数据库系统中会出现。这个错误消息表示数据库查询中的排序操作被中断或终止了。 问题排查 这个问题的发生,一般来说有几个原因: 1、慢SQL导致查询超时,这时候就会为了避免数据库链接被长时间占用而中断此次查询 2、查询被手动终止,数据库管理员手动中止正在执行的查询操作也会出现这个异常 3、资源不足,查询排序操作可能需要大量的计算和内存资源,如果数据库服务器的资源不足以执行排序操作,那么查询可能会被中断。这可能发生在高负载或资源不足的环境中。 主要就是以上这三个原因,接下来我们分析了一下导致失败的SQL 语句,这个语句在上面的ERROR日志打印的同时就已经给打印出来了,我隐藏了一些无关紧要的内容,大致SQL如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ### The error occurred while setting parameters### SQL: select business_type_enum, product_type_enum, subject_id, subject_id_enum, GROUP_CONCAT(distinct (number) SEPARATOR ',') as risk_case_numbers, GROUP_CONCAT(distinct (risk_level_enum) SEPARATOR ',') as risk_level_enums, from fraud_risk_case WHERE product_type_enum = ? and risk_case_status_enum = 'DRAFT' and subject_id like "23%" group by subject_id_enum, subject_id limit ?, ? 大致就是基于product_type_enum,risk_case_status_enum,subject_id做了条件查询,并且基于 subject_id_enum、subject_id两个字段做了一下分组。 ...

March 22, 2026 · 1 min · santu

服务发布分10批,第一批发完后负载很高后面恢复正常,如何处理?

典型回答 在服务发布过程中,应用刚刚启动好之后负载很高,过一会就恢复了,因为负载高之后很快就恢复了,那么一般来说可以排除代码问题。再根据观察看看是不是每台机器都有这种情况,以及是不是每一批启动的时候也都存在,那么就可以排除网络问题以及流量问题。 因为如果是网络和流量问题,要么就是会持续一会,要么就是所有机器都会存在。不太会说只有刚刚发布的机器才有这个问题。 那么,这个问题一般来说可能是以下几个原因。 缓存冷启动:第一批请求会触发缓存的冷启动,导致缓存未命中率高。 JIT优化:刚上线的代码会进行JIT优化,进行热点代码检测以及字节码生成。 ✅Load飙高问题排查过程 上面的两个问题,是比较常见的会导致这个问题的情况。那么如何解决呢? 1、缓存预热,可以在应用发布过程中,在Spring启动时就对缓存进行预热。避免启动后预热导致的资源抢占和缓存击穿等问题。 2、小流量切流,在应用发布的时候,可以针对刚刚启动的机器进行小流量切流,比如先给10%流量,让他进行缓存预热以及JIT优化的处理,当持续一段时间后,再逐步放开流量。 3、分更多的批次,和上面的情况类似,只不过是通过分更多批次让每次启动的机器更少一点,让流量更加平均一些,减少缓存击穿、JIT优化带来的影响。 4、提前扩容,有的公司也会这么干,比如我有50台机器,分10批,每一次就是5台。那么他可以先根据分批情况,扩容出来5台,就是一共55台,这样就能保证发布过程中线上还是至少同时有50台机器在对外提供服务的。也能减少服务器的压力。 5、限流,也可以在应用发布过中,针对这些刚刚启动的应用做限流。让流量不要那么大。

March 22, 2026 · 1 min · santu

Java进程突然挂了,可能是什么原因?

典型回答 Java进程突然挂了,**可能是真挂了,也有可能是假死。**我们先要确定是不是假死。 假死 Java进程的假死是指Java应用程序看似仍在运行,但实际上没有任何实际的工作进行,也不响应用户请求或其他外部输入。假死状态通常是由于某些问题导致应用程序进入了无限等待或阻塞状态。很多时候,我们的应用不响应任何请求,那不一定是真挂了,可能是假死。 以下是一些可能导致Java进程假死的常见原因: 死锁:多个线程互相等待对方持有的资源,从而无法继续执行。进入了无限等待,程序就可能会进入假死状态。 活锁:多个线程频繁变换状态或重复执行某些操作,但没有任何进展。比如while true不断执行。 无限等待:线程在等待某些条件满足,但这些条件永远不会发生(如等待输入或外部系统响应)。 I/O阻塞:线程在进行I/O操作时被阻塞,如网络通信、文件读写等,导致系统无法继续执行其他任务。 长时间的GC暂停:如果垃圾回收(GC)时间过长,可能导致应用程序挂起进入假死状态。 挂了 Java进程突然挂掉的原因页可能有很多,比如: 内存问题,如OOM:Java进程可能会因为内存泄漏或内存分配不足而崩溃。检查是否有OutOfMemoryError异常。 解决方法:增加JVM的堆内存(使用-Xmx参数),或者优化代码以减少内存使用。 ✅Java发生了OOM一定会导致JVM 退出吗? 系统资源不足,如CPU、磁盘被打满:如果系统资源不足,Java进程可能会挂起。 解决方法:监控系统资源使用情况,优化代码或增加硬件资源。 宿主机挂了:有的时候,我们的应用程序是部署在容器或者虚拟机上,有的时候可能是容器或者虚拟机所在的宿主机发生了异常,比如挂了,那么这个容器和虚拟机也会随之挂掉。 解决方法:检查宿主机问题,重启。 进程被 kill 了:也可能是被别人等到服务器之后执行了 kill命令,尤其是 kill -9。

March 22, 2026 · 1 min · santu

程序运行期发生ClassNotFoundException 可能是什么原因?

典型回答 找不到类,一般是编译器可能会出现的问题,就是编译的时候找不到对应的类,这时候IDE 会爆红,无法编译通过。 如果编译正常了,说明类都是存在的,那么为啥在运行期还会可能报错提示ClassNotFoundException呢? ClassNotFoundException是一个受检异常(checked exception)。他通常在运行时,在类加载阶段尝试加载类的过程中,找不到类的定义时触发。 一般来说,有以下几种可能,一般都是和类加载有关的。 ✅Java中类加载的过程是怎么样的? 动态加载类 在 Java 中,提供了很多可以在运行期动态的加载类的方法,而这些运行期动态加载的类,不会在编译器进行编译检查,那么运行期如果无法加载,那么就会抛出 ClassNotFoundException。 在Java中,有几种方式可以进行动态加载类: 使用 Class.forName() 方法: 1 2 3 Class<?> clazz = Class.forName("com.example.HollisTest"); Object obj = clazz.newInstance(); // 创建类的实例 // 可以通过 clazz 进行更多操作,如调用方法等 - `Class.forName(String className)`方法可以根据类的全限定名动态加载类。它会返回一个 Class 对象,可以通过该对象创建实例或者调用类的静态方法。 使用反射机制: 1 2 3 4 5 Class<?> clazz = Class.forName("com.example.HollisTest"); Object obj = clazz.getDeclaredConstructor().newInstance(); // 创建类的实例 Method method = clazz.getMethod("methodName", parameterTypes); Object result = method.invoke(obj, args); // 调用方法 // 其它操作... - Java 的反射机制(`java.lang.reflect`包)可以在运行时动态地获取类的信息、调用类的方法、访问字段等。虽然不是直接加载类,但可以在已知类名的情况下,动态地操作类的成员和行为。 使用 ClassLoader.loadClass() 方法: 1 2 3 4 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); Class<?> clazz = classLoader.loadClass("com.example.HollisTest"); Object obj = clazz.newInstance(); // 创建类的实例 // 可以通过 clazz 进行更多操作,如调用方法等 - `ClassLoader.loadClass(String className)`方法也可以用来动态加载类。与`Class.forName()`不同的是,`loadClass()`是通过类加载器来加载类的,可以更加灵活地控制加载的过程。 使用 ClassLoader.defineClass() 方法: 1 2 3 4 byte[] classData = // 从某处获取类的字节码数据 ClassLoader classLoader = new MyClassLoader(); // 自定义类加载器 Class<?> clazz = classLoader.defineClass("com.example.HollisTest", classData, 0, classData.length); Object obj = clazz.newInstance(); // 创建类的实例 - `ClassLoader.defineClass(String name, byte[] b, int off, int len)`方法可以通过字节数组形式定义类,然后加载它。通常用于自定义类加载器的实现。 版本兼容性问题 如果你的程序依赖的某个类发生了版本变化,并且新的版本在运行时无法被找到,也会导致 ClassNotFoundException。这一版出现在 jar 包冲突的情况下,比如我们在代码中多个地方依赖了 apache commons 这个包,但是依赖的 jar 包的版本不一样,那么在编译期可能都可以正常编译,但是最终会被 maven 给仲裁成其中的某一个版本,假如仲裁的结果是一个1.0.0的版本,这样就会导致某些在新版本1.0.1中新的 Class 就无法被找到。 ...

March 22, 2026 · 1 min · santu

服务器突然 ssh 连不上了,可能是什么问题?

典型回答 SSH是一种通过加密方式在网络中安全传输数据的协议,我们用的最多的就是通过 SSH,在本地计算机上远程登录到服务器。 ssh hollis@192.168.0.1 SSH 默认使用 TCP 端口22进行连接。一旦连接建立,SSH 可以创建多个加密通道(channels),用于并发执行多个命令或者传输多个数据流。 如果,服务器突然无法通过 SSH 连接可能由多种问题引起,以下是一些常见的可能性: SSH 端口号没开(一般很少发生) SSH 默认使用 TCP 端口22进行连接。如果服务器或者本地客户端的22端口没开,就无法连接。 网络问题(重要) 网络连接问题,如果本地客户端和远程服务器之间网络不通,那么就也会出现ssh 连不上的现象。 磁盘空间不足(重要) 如果服务器的磁盘空间耗尽,可能会导致 SSH 服务无法写入日志文件或临时文件,从而影响服务的正常运行。 服务器 CPU 被打满(重要) 当服务器的 CPU 使用率达到100%时,服务器将无法有效地处理新的连接请求和SSH会话请求。SSH服务本身需要CPU资源来处理加密和解密数据,以及客户端和服务器之间的会话管理。如果CPU已经过载,新的SSH连接请求可能会被服务器忽略或者延迟处理,导致连接超时或者连接失败。 防火墙设置(一般很少发生) 如果服务器的防火墙设置发生了变化,可能会导致 SSH 端口(默认是22)被阻止或者未正确开放。特别是在更新系统或安全策略时,防火墙规则可能会影响到 SSH 的访问。 SSH 配置错误(一般很少发生) SSH 服务的配置文件(通常是/etc/ssh/sshd_config)中的错误配置可能导致服务无法启动或者无法正常运行。例如,错误的端口设置、无效的密钥配置或安全设置限制。 安全策略限制(一般很少发生) 安全策略的更改或者更新可能会导致 SSH 连接被限制或者禁止,例如使用 SELinux 或 AppArmor 等安全增强工具时可能会限制 SSH 的操作。

March 22, 2026 · 1 min · santu

线上服务器如果磁盘满了,你会如何处理?

典型回答 当线上服务器磁盘满了时,这是一个紧急问题,可能会导致服务不可用、数据无法写入甚至系统崩溃。 首先要做的就是快速止血,尽快把空间释放了,避免情况进一步恶化。首先是登陆到机器上(如果都无法登录了,那就只能置换机器了。) 查看磁盘使用情况 1 2 df -h du -sh /* 找出哪个分区、哪个目录占用了最多空间。 df(disk free)用于显示文件系统的磁盘空间占用情况,包括每个挂载点的总空间、已用、可用和挂载路径。 1 2 3 4 5 df -h Filesystem Size Used Avail Use% Mounted on /dev/sda1 40G 30G 8.0G 80% / tmpfs 1.9G 0 1.9G 0% /dev/shm du(disk usage)用于显示指定目录或文件占用了多少磁盘空间。常用于查找哪个目录或文件占用空间大。 1 2 3 4 5 du -sh /* 2.1G /home 1.2G /var 300M /usr du -h --max-depth=1 /var 显示 /var 下一级目录占用空间大小。 清理临时文件 - 清空系统临时文件: 1 rm -rf /tmp/* 清理日志文件 ...

March 22, 2026 · 1 min · santu

端口冲突问题如何定位和解决

典型回答 端口冲突问题,其实就是两个程序同时想占用同一个端口,结果后启动的那个 bind 失败。Java应用如果端口冲突会在启动时报错:java.net.BindException: Address already in useNginx/Redis/MySQL 等会直接提示 port already in use。会导致服务启动失败。 如果发现冲突了,首先看下这个端口别谁用了,比如8080端口冲突了,可以用以下方式查看。 1 2 3 4 5 6 7 8 # 方法 1:lsof lsof -i:8080 # 方法 2:netstat netstat -tulnp | grep 8080 # 方法 3:ss ss -tulnp | grep 8080 输出结果会告诉你哪个进程在占用这个端口。 定位到冲突之后,想要解决冲突有两个办法: 1、杀掉占用端口的那个进程 如 kill ,或者kill -9 2、修改端口号重新启动 Java Spring Boot通过配置项可以修改端口号 server.port=9090 改端口要注意: 避免 0–1023(需要 root 权限的知名端口) 建议使用1024–49151 之内的端口号 49152–65535 是动态端口(系统临时分配,避免长期监听用它)

March 22, 2026 · 1 min · santu

留言给博主