一 简介
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
arthas详细官网介绍说明文档:简介 | arthas
Arthas实现的原理:
-
利用Instrumentation实现Java字节码的动态修改和增强,以实现方法拦截等功能。(Instrumentation是JDK1.5后新增的一个特性,它允许在程序运行时动态地修改或者增强Java类的行为,而不需要修改原始的字节码文件。Java Instrumentation API允许开发者通过编写一个代理程序(Agent)来监控和修改Java程序的运行状态)。
-
利用Java Debug接口实现线程的调试和监控,包括线程的挂起、恢复和修改等操作。
-
利用Java Attach机制或者Java Debug Wire Protocol(JDWP)协议实现对远程Java进程的连接和调试。 具体来说,Arthas的实现原理分为三个部分: Agent:Arthas作为Java Agent,通过代理方式加载到目标Java进程中,与目标进程建立通信管道,通过该管道向目标进程发送指令,并接收目标进程的响应数据。 Transformer:通过Instrumentation API,Arthas可以修改目标Java进程中的字节码,实现方法拦截和增强等功能。
a
背景
通常,本地开发环境无法访问生产环境。如果在生产环境中遇到问题,则无法使用 IDE 远程调试。更糟糕的是,在生产环境中调试是不可接受的,因为它会暂停所有线程,导致服务暂停。
开发人员可以尝试在测试环境或者预发环境中复现生产环境中的问题。但是,某些问题无法在不同的环境中轻松复现,甚至在重新启动后就消失了。
如果您正在考虑在代码中添加一些日志以帮助解决问题,您将必须经历以下阶段:测试、预发,然后生产。这种方法效率低下,更糟糕的是,该问题可能无法解决,因为一旦 JVM 重新启动,它可能无法复现,如上文所述。
Arthas 旨在解决这些问题。开发人员可以在线解决生产问题。无需 JVM 重启,无需代码更改。 Arthas 作为观察者永远不会暂停正在运行的线程。
1.Arthas(阿尔萨斯)能为你做什么?
Arthas
是 Alibaba 开源的 Java 诊断工具,深受开发者喜爱。
当你遇到以下类似问题而束手无策时,Arthas
可以帮助你解决:
-
这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
-
我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
-
遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
-
线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
-
是否有一个全局视角来查看系统的运行状况?
-
有什么办法可以监控到 JVM 的实时运行状态?
-
怎么快速定位应用的热点,生成火焰图?
-
怎样直接从 JVM 内查找某个类的实例?
Arthas
支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab
自动补全功能,进一步方便进行问题的定位和诊断。
2. 启动 arthas
默认在公司的容器平台k8s中,arthas访问目录:/tmp/arthas-boot.jar
公司对于arthas使用说明:https://wiki.mokahr.com/pages/viewpage.action?pageId=116730746
示例:https://k8s.mokahr.com/kubernetes/Staging/console/staging-1/ai-search-platform-76f7775cfd-tr4j4?container=ai-search-platform&k8sToken&shell=bash
在命令行下面执行(使用和目标进程一致的用户启动,否则可能 attach 失败,):
curl -O https://arthas.aliyun.com/arthas-boot.jar(下载arthas) java -jar arthas-boot.jar
-
执行该程序的用户需要和目标进程具有相同的权限。比如以
admin
用户来执行:sudo su admin && java -jar arthas-boot.jar
或sudo -u admin -EH java -jar arthas-boot.jar
。 -
如果 attach 不上目标进程,可以查看
~/logs/arthas/
目录下的日志。 -
如果下载速度比较慢,可以使用 aliyun 的镜像:
java -jar arthas-boot.jar --repo-mirror aliyun --use-http
-
java -jar arthas-boot.jar -h
打印更多参数信息。
选择应用 java 进程:
$ $ java -jar arthas-boot.jar * [1]: 35542[2]: 71560 math-game.jar
math-game
进程是第 2 个,则输入 2,再输入回车/enter
。Arthas 会 attach 到目标进程上,并输出日志:
[INFO] Try to attach process 71560 [INFO] Attach process 71560 success. [INFO] arthas-client connect 127.0.0.1 3658,---. ,------. ,--------.,--. ,--. ,---. ,---./ O \ | .--. ''--. .--'| '--' | / O \ ' .-' | .-. || '--'.' | | | .--. || .-. |`. `-. | | | || |\ \ | | | | | || | | |.-' | `--' `--'`--' '--' `--' `--' `--'`--' `--'`-----' wiki: https://arthas.aliyun.com/doc version: 3.0.5.20181127201536 pid: 71560 time: 2018-11-28 19:16:24 $
3.查看dashboard
如果你不知道arthas所支持的命令和功能,输入help可以看到所有的命令以及命令的详细说明
arthas@7]$ helpNAME DESCRIPTION help Display Arthas Help auth Authenticates the current session keymap Display all the available keymap for the specified connection. sc Search all the classes loaded by JVM sm Search the method of classes loaded by JVM classloader Show classloader info jad Decompile class getstatic Show the static field of a class monitor Monitor method execution statistics, e.g. total/success/failure count, average rt, fail rate, etc. stack Display the stack trace for the specified class and method thread Display thread info, thread stack trace Trace the execution time of specified method invocation. watch Display the input/output parameter, return object, and thrown exception of specified method invocation tt Time Tunnel jvm Display the target JVM information memory Display jvm memory info. perfcounter Display the perf counter information. ognl Execute ognl expression. mc Memory compiler, compiles java files into bytecode and class files in memory. redefine Redefine classes. @see Instrumentation#redefineClasses(ClassDefinition...) retransform Retransform classes. @see Instrumentation#retransformClasses(Class...) dashboard Overview of target jvm's thread, memory, gc, vm, tomcat info. dump Dump class byte array from JVM heapdump Heap dump options View and change various Arthas options cls Clear the screen reset Reset all the enhanced classes version Display Arthas version session Display current session information sysprop Display and change the system properties. sysenv Display the system env.
输入dashboard,按回车/enter
,会展示当前进程的信息,按ctrl+c
可以中断执行。
$ dashboard ID NAME GROUP PRIORI STATE %CPU TIME INTERRU DAEMON 17 pool-2-thread-1 system 5 WAITIN 67 0:0 false false 27 Timer-for-arthas-dashb system 10 RUNNAB 32 0:0 false true 11 AsyncAppender-Worker-a system 9 WAITIN 0 0:0 false true 9 Attach Listener system 9 RUNNAB 0 0:0 false true 3 Finalizer system 8 WAITIN 0 0:0 false true 2 Reference Handler system 10 WAITIN 0 0:0 false true 4 Signal Dispatcher system 9 RUNNAB 0 0:0 false true 26 as-command-execute-dae system 10 TIMED_ 0 0:0 false true 13 job-timeout system 9 TIMED_ 0 0:0 false true 1 main main 5 TIMED_ 0 0:0 false false 14 nioEventLoopGroup-2-1 system 10 RUNNAB 0 0:0 false false 18 nioEventLoopGroup-2-2 system 10 RUNNAB 0 0:0 false false 23 nioEventLoopGroup-2-3 system 10 RUNNAB 0 0:0 false false 15 nioEventLoopGroup-3-1 system 10 RUNNAB 0 0:0 false false Memory used total max usage GC heap 32M 155M 1820M 1.77% gc.ps_scavenge.count 4 ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(m 166 ps_survivor_space 4M 5M 5M s) ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.count 0 nonheap 20M 23M -1 gc.ps_marksweep.time( 0 code_cache 3M 5M 240M 1.32% ms) Runtime os.name Mac OS X os.version 10.13.4 java.version 1.8.0_162 java.home /Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home/jre
#4. 通过 thread 命令来获取到math-game
进程的 Main Class
thread 1
会打印线程 ID 1 的栈,通常是 main 函数的线程。
$ thread 1 | grep 'main('at demo.MathGame.main(MathGame.java:17)
#5. 通过 jad 来反编译 Main Class
$ jad demo.MathGame ClassLoader: +-sun.misc.Launcher$AppClassLoader@3d4eac69+-sun.misc.Launcher$ExtClassLoader@66350f69 Location: /tmp/math-game.jar /** Decompiled with CFR 0_132.*/ package demo; import java.io.PrintStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; public class MathGame {private static Random random = new Random();private int illegalArgumentCount = 0; public static void main(String[] args) throws InterruptedException {MathGame game = new MathGame();do {game.run();TimeUnit.SECONDS.sleep(1L);} while (true);} public void run() throws InterruptedException {try {int number = random.nextInt();List<Integer> primeFactors = this.primeFactors(number);MathGame.print(number, primeFactors);}catch (Exception e) {System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage());}} public static void print(int number, List<Integer> primeFactors) {StringBuffer sb = new StringBuffer("" + number + "=");Iterator<Integer> iterator = primeFactors.iterator();while (iterator.hasNext()) {int factor = iterator.next();sb.append(factor).append('*');}if (sb.charAt(sb.length() - 1) == '*') {sb.deleteCharAt(sb.length() - 1);}System.out.println(sb);} public List<Integer> primeFactors(int number) {if (number < 2) {++this.illegalArgumentCount;throw new IllegalArgumentException("number is: " + number + ", need >= 2");}ArrayList<Integer> result = new ArrayList<Integer>();int i = 2;while (i <= number) {if (number % i == 0) {result.add(i);number /= i;i = 2;continue;}++i;}return result;} } Affect(row-cnt:1) cost in 970 ms.
#6. watch
通过watch命令来查看demo.MathGame#primeFactors
函数的返回值:
# ai-search-platform使用示例 # $ watch demo.MathGame primeFactors returnObj Press Ctrl+C to abort. Affect(class-cnt:1 , method-cnt:1) cost in 107 ms. ts=2018-11-28 19:22:30; [cost=1.715367ms] result=null ts=2018-11-28 19:22:31; [cost=0.185203ms] result=null ts=2018-11-28 19:22:32; [cost=19.012416ms] result=@ArrayList[@Integer[5],@Integer[47],@Integer[2675531], ] ts=2018-11-28 19:22:33; [cost=0.311395ms] result=@ArrayList[@Integer[2],@Integer[5],@Integer[317],@Integer[503],@Integer[887], ] ts=2018-11-28 19:22:34; [cost=10.136007ms] result=@ArrayList[@Integer[2],@Integer[2],@Integer[3],@Integer[3],@Integer[31],@Integer[717593], ] ts=2018-11-28 19:22:35; [cost=29.969732ms] result=@ArrayList[@Integer[5],@Integer[29],@Integer[7651739], ]
更多的功能可以查看进阶使用。
#7. 退出 arthas
如果只是退出当前的连接,可以用quit
或者exit
命令。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时可以直接连接上。
如果想完全退出 arthas,可以执行stop
命令。
二.安装与卸载
可以直接通过rpm的方式进行安装:
sudo rpm -i arthas*.rpm
卸载通过直接删除文件的方式
-
在 Linux/Unix/Mac 平台
删除下面文件:
rm -rf ~/.arthas/ rm -rf ~/logs/arthas
-
Windows 平台直接删除 user home 下面的
.arthas
和logs/arthas
目录
三 进阶使用
通过web console来直接访问(默认端口8563),由于k8s容器中对外暴露端口被严格限制,只能在本机进行访问。如果你的服务部署在ecs服务器上,则可以直接通过web浏览器的方式来进行访问
1. 使用&在后台执行任务
比如希望执行后台执行 trace 命令,那么调用下面命令
trace Test t &
这时命令在后台执行,可以在 console 中继续执行其他命令。
2. 通过 jobs 查看任务
如果希望查看当前有哪些 arthas 任务在执行,可以执行 jobs 命令,执行结果如下
$ jobs [10]*Stopped watch com.taobao.container.Test test "params[0].{? #this.name == null }" -x 2execution count : 19start time : Fri Sep 22 09:59:55 CST 2017timeout date : Sat Sep 23 09:59:55 CST 2017session : 3648e874-5e69-473f-9eed-7f89660b079b (current)
可以看到目前有一个后台任务在执行。
-
job id 是 10,
*
表示此 job 是当前 session 创建 -
状态是 Stopped
-
execution count 是执行次数,从启动开始已经执行了 19 次
-
timeout date 是超时的时间,到这个时间,任务将会自动超时退出
3. 任务暂停和取消
当任务正在前台执行,比如直接调用命令trace Test t
或者调用后台执行命令trace Test t &
后又通过fg
命令将任务转到前台。这时 console 中无法继续执行命令,但是可以接收并处理以下事件:
-
‘ctrl + z’:将任务暂停。通过
jbos
查看任务状态将会变为 Stopped,通过bg <job-id>
或者fg <job-id>
可让任务重新开始执行 -
‘ctrl + c’:停止任务
-
‘ctrl + d’:按照 linux 语义应当是退出终端,目前 arthas 中是空实现,不处理
4. 任务输出重定向
可通过>
或者>>
将任务输出结果输出到指定的文件中,可以和&
一起使用,实现 arthas 命令的后台异步任务。比如:
$ trace Test t >> test.out &
这时 trace 命令会在后台执行,并且把结果输出到应用工作目录
下面的test.out
文件。可继续执行其他命令。并可查看文件中的命令执行结果。可以执行pwd
命令查看当前应用的工作目录
。
$ cat test.out
四 命令使用详解
1.查看类的静态属性值:
getstatic class_name field_name
示例
[arthas@7]$ getstatic com.moka.search.api.core.utils.Constants TYPE_DELETE field: TYPE_DELETE @String[1] Affect(row-cnt:1) cost in 13 ms. [arthas@7]$
示例对应的java代码
public class Constants { public static final String TYPE_DELETE = "1";public static final String TYPE_SAVE_OR_UPDATE = "0";}
2.查看类的类加载器
查看指定类的加载器
sc -d 对应类
Eg:
[arthas@7]$ sc -d com.moka.search.api.core.utils.Constantsclass-info com.moka.search.api.core.utils.Constants code-source file:/java_app.jar!/BOOT-INF/classes!/ name com.moka.search.api.core.utils.Constants isInterface false isAnnotation false isEnum false isAnonymousClass false isArray false isLocalClass false isMemberClass false isPrimitive false isSynthetic false simple-name Constants modifier public annotation interfaces super-class +-java.lang.Object class-loader +-org.springframework.boot.loader.LaunchedURLClassLoader@1d56ce6a +-sun.misc.Launcher$AppClassLoader@33909752 +-sun.misc.Launcher$ExtClassLoader@762c01c1
复习一下类加载器:
什么是类加载器,类加载器有哪些? 实现通过类的全限定名获取该类的二进制字节流的代码块叫做类加载器。 主要有一下四种类加载器: 1. 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。 2. 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。 3. 系统类加载器(system class loader,也称为 AppClassLoader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。 4. 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。
自定义类加载器可以动态加载来自于网络、磁盘、数据库等来源的class类二进制文件以下为一个自定义类
ublic class MyClassLoader extends ClassLoader {private String classPath;public MyClassLoader(String classPath) {this.classPath = classPath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] classData = getClassData(name);if (classData == null) {throw new ClassNotFoundException();} else {return defineClass(name, classData, 0, classData.length);}}private byte[] getClassData(String name) {// 从指定路径中读取class文件// ...} }
自定义类加载器作用:
自定义类加载器的作用主要有以下几个方面:
-
实现类隔离:自定义类加载器可以实现不同的类加载器加载不同路径下的类文件,从而实现类隔离。这在一些需要动态加载插件、使用不同版本的库等场景下非常有用。
-
加载加密的类:自定义类加载器可以在加载类的过程中进行解密,从而保护类的安全性。
-
修改字节码:自定义类加载器可以在加载类的过程中对字节码进行修改,从而实现一些特殊的需求,如AOP框架等。
-
热部署:自定义类加载器可以支持类的热部署,即在应用程序运行过程中,通过修改类文件并重新加载,实现动态更新应用程序的功能。 总之,自定义类加载器可以实现更加灵活、安全、高效的类加载方式,同时也为一些特殊需求提供了解决方案。
3.查看指定的classloader加载了哪些jar
使用classloader -t 以树的形式展示类加载器
[arthas@7]$ classloader -t +-BootstrapClassLoader +-sun.misc.Launcher$ExtClassLoader@762c01c1 +-com.taobao.arthas.agent.ArthasClassloader@14a26eaf +-sun.misc.Launcher$AppClassLoader@33909752 +-org.springframework.boot.loader.LaunchedURLClassLoader@1d56ce6a +-com.alibaba.fastjson2.util.DynamicClassLoader@f231925
由于我们的ai-search-platform为spring项目,我们查看
org.springframework.boot.loader.LaunchedURLClassLoader@1d56ce6a
使用命令: classloader -c classloader对应的hash值
arthas@7]$ classloader -c 1d56ce6a jar:file:/java_app.jar!/BOOT-INF/classes!/ jar:file:/java_app.jar!/BOOT-INF/lib/ratelimiter-2.0.8-RELEASE.jar!/ jar:file:/java_app.jar!/BOOT-INF/lib/spring-boot-starter-2.2.0.RELEASE.jar!/ jar:file:/java_app.jar!/BOOT-INF/lib/spring-boot-2.2.0.RELEASE.jar!/ jar:file:/java_app.jar!/BOOT-INF/lib/spring-boot-autoconfigure-2.2.0.RELEASE.jar!/ jar:file:/java_app.jar!/BOOT-INF/lib/spring-boot-starter-logging-2.2.0.RELEASE.jar!/ jar:file:/java_app.jar!/BOOT-INF/lib/logback-classic-1.2.3.jar!/ jar:file:/java_app.jar!/BOOT-INF/lib/logback-core-1.2.3.jar!/ jar:file:/java_app.jar!/BOOT-INF/lib/log4j-to-slf4j-2.12.1.jar!/ jar:file:/java_app.jar!/BOOT-INF/lib/jul-to-slf4j-1.7.28.jar!/ jar:file:/java_app.jar!/BOOT-INF/lib/jakarta.annotation-api-1.3.5.jar!/ jar:file:/java_app.jar!/BOOT-INF/lib/commons-pool2-2.7.0.jar!/ jar:file:/java_app.jar!/BOOT-INF/lib/spring-boot-starter-aop-2.2.0.RELEASE.jar!/
4.thread
thread命令可以查看当前占用cpu最多的县城,并且打印对应的堆栈信息。
参数说明
参数名称 | 参数说明 |
---|---|
id | 线程 id |
[n:] | 指定最忙的前 N 个线程并打印堆栈 |
[b] | 找出当前阻塞其他线程的线程 |
[i <value> ] | 指定 cpu 使用率统计的采样间隔,单位为毫秒,默认值为 200 |
[--all] | 显示所有匹配的线程 |
cpu-使用率是如何统计出来的
这里的 cpu 使用率与 linux 命令top -H -p <pid>
的线程%CPU
类似,一段采样间隔时间内,当前 JVM 里各个线程的增量 cpu 时间与采样间隔时间的比例。
工作原理说明:
-
首先第一次采样,获取所有线程的 CPU 时间(调用的是
java.lang.management.ThreadMXBean#getThreadCpuTime()
及sun.management.HotspotThreadMBean.getInternalThreadCpuTimes()
接口) -
然后睡眠等待一个间隔时间(默认为 200ms,可以通过
-i
指定间隔时间) -
再次第二次采样,获取所有线程的 CPU 时间,对比两次采样数据,计算出每个线程的增量 CPU 时间
-
线程 CPU 使用率 = 线程增量 CPU 时间 / 采样间隔时间 * 100%
支持一键展示当前最忙的前 N 个线程并打印堆栈:
$ thread -n 3 "C1 CompilerThread0" [Internal] cpuUsage=1.63% deltaTime=3ms time=1170ms "arthas-command-execute" Id=23 cpuUsage=0.11% deltaTime=0ms time=401ms RUNNABLEat java.management@11.0.7/sun.management.ThreadImpl.dumpThreads0(Native Method)at java.management@11.0.7/sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:466)at com.taobao.arthas.core.command.monitor200.ThreadCommand.processTopBusyThreads(ThreadCommand.java:199)at com.taobao.arthas.core.command.monitor200.ThreadCommand.process(ThreadCommand.java:122)at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:82)at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:18)at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:111)at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:108)at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:385)at java.base@11.0.7/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)at java.base@11.0.7/java.util.concurrent.FutureTask.run(FutureTask.java:264)at java.base@11.0.7/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)at java.base@11.0.7/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)at java.base@11.0.7/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)at java.base@11.0.7/java.lang.Thread.run(Thread.java:834) "VM Periodic Task Thread" [Internal] cpuUsage=0.07% deltaTime=0ms time=584ms
-
没有线程 ID,包含
[Internal]
表示为 JVM 内部线程,参考dashboard命令的介绍。 -
cpuUsage
为采样间隔时间内线程的 CPU 使用率,与dashboard命令的数据一致。 -
deltaTime
为采样间隔时间内线程的增量 CPU 时间,小于 1ms 时被取整显示为 0ms。 -
time
线程运行总 CPU 时间。
5.heapdump
heapdump类似于java dump jvm的堆栈信息。只 dump live 对象
[arthas@58205]$ heapdump --live /tmp/dump.hprof Dumping heap to /tmp/dump.hprof ... Heap dump file created
6.jvm相关命令
memory命令查看各个分区对应占用的内存大小和比例
$ memory Memory used total max usage heap 1066M 2880M 2880M 37.03% par_eden_space 998M 1152M 1152M 86.67% par_survivor_space 68M 192M 192M 35.47% cms_old_gen 0K 1572864K 1572864K 0.00% nonheap 154M 625M 1016M 15.18% code_cache 46M 512M 512M 9.14% metaspace 95M 100M 256M 37.30% compressed_class_space 11M 13M 248M 4.83% direct 5M 5M - 100.00% mapped 0K 0K - 0.00%
7.查看jvm相关参数:
jvm
MACHINE-NAME 7@ai-search-platform-76f7775cfd-tr4j4 JVM-START-TIME 2023-05-17 14:29:57 MANAGEMENT-SPEC-VERSION 1.2 SPEC-NAME Java Virtual Machine Specification SPEC-VENDOR Oracle Corporation SPEC-VERSION 1.8 VM-NAME Java HotSpot(TM) 64-Bit Server VM VM-VENDOR Oracle Corporation VM-VERSION 25.201-b09 INPUT-ARGUMENTS -Xmx3072m -Xms3072m -XX:-OmitStackTraceInFastThrow -XX:+PrintGC -XX:+PrintGCDateStamps -Xloggc:/data/logs/ai-search-platform/gc-%t.log -XX:HeapDumpPath=/data/logs/ai-search-platform/java.hprof -XX:-UseAdaptiveSizePolicy -XX:+PrintAdaptiveSizePolicy -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=256m -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=128m -XX:+HeapDumpOnOutOfMemoryError -XX:SurvivorRatio=6 -XX:NewRatio=1 -XX:ReservedCodeCacheSize=512m -XX:InitialCodeCacheSize=512m -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSParallelInitialMarkEnabled -XX:PrintCMSStatistics=1 -XX:+CMSScavengeBeforeRemark -XX:+UseConcMarkSweepGC -XX:+PrintGCApplicationStoppedTime -Dcluster=staging-1 -Dcloud=ali
五 线上实操案例
1.tagram项目中出现了线上导入数据太慢,要求进行性能优化。
如果不使用arthas,需要修改代码,将各个步骤的耗时通过日志打印出来,然后重新发布至线上,不仅耗时费力,而且对代码产生侵入性。而使用arthas则而使用arthas trace命令,可以在线上直接定位指定方法的各个步骤执行的耗时。从而定位问题是获取候选人标签速度过慢导致,通过优化该接口解决问题 :
2.使用arthas模拟排查堆内存溢出问题
[arthas@58205]$ heapdump --live /tmp/dump.hprof Dumping heap to /tmp/dump.hprof ... Heap dump file created
将堆文件dump后,可在k8s管理页面中下载:
下载完成后,可以用jprofile工具将其打开:
看到这里,大对象的类型,内容可以确定。如果仅靠类型、内容无法确认大对象的产生原因的话,我们还可以继续通过调用链路来进行分析:
右击选择selected objects
在biggest objects中选择对象,右键show grah
查看对象调用链路:
可以看到对象引用链路,从而确认对象的生成原因。一般对象太大导致内存溢出,都是由于内存没有及时释放导致:
3.使用火焰图协助排查线上es升级后,CPU负载变高问题
$profiler start #经过一段时间后,获取已经采样的数量 $ profiler getSamples 150 $profiler stop --format svg --file /Users/leixingbang/Downloads/profile.html
将鼠标点上具体的矩形方块后,能够看到具体执行的类看,以及其对cpu的占用情况。
一般来说,一家公司对es集群只维护一个大版本,便于快速定位和排查问题(eg.6.x版本只维护6.6.2版本,7.x版本只维护7.17版本,8.x版本只维护8.5版本)。
1.对于7版本(大版本)集群希望只维护一个版本,最终选择7.17,对同大版本的7.5版本集群进行升级
2.根据官方描述,_id放到堆外性能损失非常小可以忽略,且对BKD进行了优化
3.升级完成,一段时间之后,收到用户报障
发现CPU占用率提高,并且发现查询平均延迟增大,需要定位问题产生原因。
由于已经确认是由于CPU负载升高导致,我们可以使用es中自带的命令,但是由于缺乏采样,需要持续观察,经过观察一段时间后,发现是由于时不时出现的update相关函数,导致消耗的CPU过多。
GET /_nodes/hot_threads
为了进一步定位问题,我们使用arthas进行采样,查看其火焰图:
从图中可以看到Bulk请求执行过程中的getDocID方法占有大量CPU。
集群写多读少,使用的是niofs( 是一种文件存储类型,它使用 Java NIO(New I/O)库来读写磁盘上的文件)。可知,7.5版本的FST是在堆外,但是_id字段是在堆内。升级到7.17版本后,默认FST在堆外,该字段也放到了堆外(官方版本应该是7.9就开始放到堆外了,具体为什么要放到堆外内存详细原因参考:https://www.cnblogs.com/muzhongjiang/p/13930195.html)。fst、bkd放到堆外内存的原因是,受限于寻址空间,堆内存的最大32G(一个指针占用8字节),放到堆外内存,由于堆外内存
数据放到堆外,其实也就是文件放到磁盘,读一次之后放到pagecache。
这样也就可以解释了,在upsert类请求多的时候会频繁查询docId,此时如果_id字段没有进入pageCache或者被踢出pageCache,那么就会出现响应慢,并且CPU高、IO高的情况。 将7.17版本的bkd、fst改为堆内存存储,elasticsearch中添加以下设置(详细参考:FST Off Heap 内存优化_indices.segment_memory.off_heap.enable_weixin_42414008的博客-CSDN博客):
curl -H "Content-Type:application/json" -XPUT http://localhost:9200/_cluster/settings -d '{"persistent" : {"indices.segment_memory.off_heap.enable" : false} }'
因此:update、upsert、get等请求如果十分频繁,_id使用offheap将不会是个好的选择,除非给足够的堆外内存,并且保证尽可能常驻内存。.不同的业务场景下使用ES的同一版本也会有不一样的效果。对于日志场景的话,由于数据只有index请求,没有update是十分合适的,但是对于搜索的业务场景,就不建议这么设置了。