问题排查总结

生产线上一些问题排查方法,常用手段小结。

今天给大家分享一些个人一些线上问题的排查手段,方法。欢迎评论区交流。

收藏本站,三伏磨会不定时分享实战经验。

常见问题分类

业务问题

  • 日志排查
  • 代码逻辑排查
  • 配置排查

性能问题

  • 接口问题
  • JVM问题
  • Redis问题
  • MySQL问题
  • 系统问题

日志排查


# 本地日志可能非常大,所以正常需要根据需要来过滤数据
# 如果直接打开超大文件,可能会导致一些问题,比如: 假死# 指定显示行数
cat test.log | tail -n 20 # 显示20行

# 查找包括Exception字符串的行cat test.log | grep "Exception"
# 指定行数,避免过大
cat test.log | grep "Exception" | tail -n 20
# and 查找包括Exception与java:81
cat test.log | grep "Exception" | grep "java:81"
# or 查找包括Exception或RequestMappingHandlerAdapter
cat test.log | grep -E "Exception|RequestMappingHandlerAdapter"

# 统计相关
# 统计Exception数量
cat test.log | grep "Exception" | wc -l

代码逻辑排查

本地代码

  • 单元测试
  • 请求回放
  • 断点

线上代码


# arthas 下载&安装祥见附录1
# jad 参考: https://arthas.gitee.io/jad.html
# 功能:反编译代码。 可以直观的确认当前运行版本代码
# 解决:提交了,不知道为什么没有作用?
jad com.nascent.ecrpsaas.openapi.interceptor.APIHandlerInterceptor
# 将编译代码输出
jad com.nascent.ecrpsaas.openapi.interceptor.APIHandlerInterceptor > /tmp/APIHandlerInterceptor.java

配置排查


# arthas 下载&安装祥见附录1
# 解决:配置没有生效? 现在有配置是什么样的?

静态配置类&枚举类


# arthas 下载&安装祥见附录1
# ognl 参考: https://arthas.gitee.io/ognl.html
ognl "@xxx.GlobalsEnums@SYSTEM_ERROR.getMsg()"

获取SpringBean对象

比如项目中,配置了一个SpringBean对象


<!-- activeMq的连接池 -->
<bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory">
  <property name="connectionFactory" ref="targetConnectionFactory"/>
  <!--连接池的最大连接数-->
  <property name="maxConnections" value="${activemq.pool.max-connections}"/>
</bean>

获取对应的pooledConnectionFactory当前配置

# 获取任何一个请求对象,因为都会存在对应的SpringContext容器
tt -n 1 -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod
# 特别强调: 这里在正式环境中,添加-n来指定次数,且应该设置成1。
# 否则当你执行一个调用量不高的方法时可能你还能有足够的时间用 CTRL+C 中断 tt 命令记录的过程,但如果遇到调用量非常大的方法,瞬间就能将你的 JVM 内存撑爆。
# 通过tt的回放能力,获取容器内的Bean
# 随意请求一个地址,只需要被Spring拦截
tt使用  获取SpringBean对象

此时,如果使用ognl可以得到target(当前被调用对象)。因为这个对象是由SpringContext管理的,所以可以获取得容器信息。


tt -i 1003 -w 'target.getApplicationContext().getBean("pooledConnectionFactory")'
# 获取最大连接数
tt -i 1003 -w 'target.getApplicationContext().getBean("pooledConnectionFactory").maxConnections'

接口问题

响应时间


# arthas 下载&安装祥见附录1
# 解决:接口响应慢?哪个环节慢?
# trace 参考: https://arthas.gitee.io/trace.html
trace xxx.APIHandlerInterceptor preHandle -n 1
# 特别强调: 这里在正式环境中,添加-n来指定次数
arthas使用

# 根据方法用时过滤
trace xxx.APIHandlerInterceptor preHandle -n 1 '#cost>10'
# 该过滤是过滤整个方法的用时,而不是指过滤方法内的调用方法用时

trace 的问题在于,

无法支持重载方法

无法直接定位方法下的方法

入参与响应结果跟踪


# arthas 下载&安装祥见附录1
# 解决:调用参数与响应结果跟踪。
# tt 参考: https://arthas.gitee.io/tt.html
# 表达式变量祥见附录2
tt -n 1 -t xxx.CustomerController getCustomerInfo4ThirdParty
# 请求参数
tt -i <tt-index> -w 'params'
# 响应结果
tt -i <tt-index> -w 'returnObj'

JVM问题

CPU

看CPU占用


top

如果CPU一直过高,并且不见回落。则需要排查进程要关线程的状态。

线程跟踪


# 查看进程内最耗费CPU的线程
top -Hp <pid>
查看进程内最耗费CPU的线程

如何排查对应的线程问题? 需要结合jstack

jstack


# 跟踪所有线程
jstack 1 | more
跟踪所有线程

该命令可以查看出当前JVM中的线程情况。

主要关注以下三个状态:


WAITING:进入等待状态    
  方式:wait/join/park方法进入无限等待,通过notify/notifyAll/unpark唤醒;•
TIMED_WAITING:与WAITING类似。   •
方式:a. 给定等待时间的wait/join/park方法;b. sleep方法;•
BLOCKED:被动进入等待状态  
    方式:进入Synchronized块;

线程状态数量统计


jstack 1 | grep "State: WAITING" | wc -l

查看特定线程的状态信息

查看线程Id=65的线程信息


# 转义进程Id为16进制
printf '%x\n' 65 # 41
jstack 1 | grep 41
查看特定线程的状态信息

统计某线程数量


cat /proc/<pid>/status | grep Threads

jstack <pid> | grep "Druid-ConnectionPool" | wc -l

三伏磨整理。 收藏本站,不定时分享实战经验。

内存

常见OOM发生原因

  • 堆溢出

java.lang.OutOfMemoryError: Java heap space

代码中可能存在大对象分配

可能存在内存泄露,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。

  • 永久代/元空间溢出

存放了被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等

java.lang.OutOfMemoryError: PermGen space

java.lang.OutOfMemoryError: Metaspace

运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载

应用长时间运行,没有重启

  • 方法栈溢出

java.lang.OutOfMemoryError : unable to create new native Thread

创建的了大量的线程导致的

JVM相关配置:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/dump.hprof

正常情况下,JVM需要配置相关的OOM时dump,防止OOM时没有较好的分析依据。

需要注意的是, 如果环境分配的内存较大,dump出来的文件大小会与内存大小一致。

如32G内存,dump出来的文件大小也要为32G。

在K8S容器中,需要注意文件的保存位置,防止保存在POD内,并且POD重启(或者策略驱逐)导致文件丢失。

知识点: OOM并不会导致容器直接无法服务。 发生OOM,说明该线程正在申请内存,受影响的线程局限于抛出异常的线程(daemon子线程除外)。而其他线程已经有足够内存,不需要再额外申请,所以不会受影响。且OOM后,受影响的线程因异常而退出,只被该线程所持有的资源不可达后,GC自动回收资源。

jmap


top
OOM问题定位

如果在观察的时候,%MEN一直往上增,但是基本不见回落(GC时无法回收),则表示此时,在大量的创建对象,并且对象一直被持有,无法被GC回收,有可能发生了内存泄露问题。

此时,需要结合jmap 来排查问题。


jps -l # 查看JAVA相关进程

查看堆空间


jmap -heap <pid>

统计存活对象

注意: 该操作会导致触发一次FullGC(STW),并暂停服务(STW)。非必要时勿操作!


jmap -histo:live <pid>
#或者,导出全部对象。该操作不会引发FullGC。但是也会暂停服务
jmap -histo <pid>

导出堆栈

注意: 该操作会导致触发一次FullGC,并暂停服务。非必要时勿操作!


jmap -dump:live,format=b,file=dump.hprof <pid>
#live 是指只导出存活对象 # 或者
jmap -dump:format=b,file=dump.hprof <pid>

JVM相关配置:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/dump.hprof

堆栈分析 – MAT

堆栈分析 - MAT

行为数据

  1. Histogram 直方图 – 类数量列表
  2. Dominator Tree 支配树 – 类引用关系
  3. Top Consumers 跟直方图相似 – 按包分组
  4. Duplicate Classes 重复类 – 被不同ClassLoader加载的类

报表数据

  1. Leak Suspects 可疑泄露
  2. Top Consumers 占用总堆1%以上的报表

Histogram 类个数直方图

展示某个特定类的对象个数和每个对象使用的内存

Histogram 类个数直方图

Shallow Heap

Shallow Heap是指对象本身占用的内存大小,不包括它的引用对象。 针对非数组类型的对象,它的大小就是对象与它所有的成员变量大小的总和。 针对数组类型 的对象,它的大小是数组元素对象的大小总和。

Retained Heap

Retained Heap = 当前对象大小 + 当前对象直接或间接引用的对象的大小总和。

相当于对象被GC之后,可以从Heap上释放的内存大小。

(注:实际释放的内存大小需要根据是否有被GCRoot引用,是否可回收影响)

Retained Heap大小有两种不同的计算方式。

  • Calculate Minimum Retained Size(quick approx..) 快速估算
  • Calculate Precise Retained Size 精确计算

Dominator tree 支配树,对象引用关系树

如果所有指向对象Y的路径都经过对象X,则认为对象X支配对象Y。

Dominator tree 支配树,对象引用关系树

Top Consumers 内存消耗排行

Top Consumers 内存消耗排行

Leak Suspects 可疑泄露报告

显示MAT发现的可能导致内存泄漏的地方,和用于分析这些发现的工具和图表的链接。

Immediate Dominators 查看类的支配树(直接引用)

在直方图Histogram中,可以查看特定类的支配树。

查看特定类的支配树

Path to GC Roots 查看GC Roots引用链

查看GC Roots引用链

with all references 查看所有的引用链

exclude weak references 过滤弱引用

exclude soft references 过滤软引用

exclude phantom references 过滤虚引用

exclude weak/soft references 过滤弱/软引用

exclude phantom/soft references 过滤虚/软引用

exclude phantom/weak references 过滤虚/弱引用

exclude phantom/weak/soft etc. references 过滤虚/弱/软引用

  • exclude custom fields… 自定义过滤

with outgoing references 我引用了谁

with incoming references 谁引用了我

GC

堆主要组成

堆主要组成

相关参数


-Xms #-Xms128M 堆初始大小,默认为物理内存的1/64(<1GB)
-Xmx #-Xmx128M 堆最大大小,默认如果空余堆大小大于70%(MaxHeapFreeRatio可以修改大小)时,JVM会自动减少堆直到
-Xms的最小限制-XX:NewSize #新生代空间初始大小-XX:MaxNewSize #新生代空间最大大小-Xmn #新生代空间大小(eden + 2 survivor space)-XX:MetaspaceSize #元空间初始大小-XX:MaxMetaspaceSize #元空间最大大小

注意,老年代的大小会根据新生代自动设定:

老年代初始大小=堆最大大小(-Xmx) – 新生代初始大小(-XX:NewSize)

老年代最大大小 = 堆最大大小(-Xmx) – 新生代最大大小(-XX:MaxNewSize)

从参数配置来看,在设置的时候,应该尽量的将堆-Xms与-Xmx设置大小一致,避免JVM一直扩容、缩容。

GC日志


# GC日志指令
-XX:+PrintGC -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:<filename>
# GC日志对性能的影响极小,在生产环境也可以开启# 触发条件: 1、自动 2、监控工具强制调用 3、jmap -histo:live pid

建议设置

-Xms -Xmx设置

三伏磨整理。 收藏本站,不定时分享实战经验。

看懂GC日志

看懂GC日志

日志分析 – GcViewer

GcViewer日志分析

GC统计信息

GC统计信息查看

内存统计信息

gc内存统计信息

暂停信息

gc暂停信息

配置信息

主要关注JVM的相关信息。

# jinfo <pid>
jinfo 1
# 会输超多信息
# 虚拟机信息
JVM version is 25.252-b09
# 系统配置属性
Java System Properties:
java.vendor = Oracle Corporation
sun.java.launcher = SUN_STANDARD
catalina.base = /usr/fffmo.com/tomcat
...
java.vm.name = OpenJDK 64-Bit Server VM
ignore.endorsed.dirs =
file.encoding = UTF-8
java.specification.version = 1.8
# JVM配置
Non-default VM flags: -XX:CICompilerCount=2 
-XX:InitialHeapSize=3145728000 
-XX:MaxHeapSize=3145728000 
-XX:MaxNewSize=1048576000 
-XX:MinHeapDeltaBytes=524288 
-XX:NewSize=1048576000 
-XX:OldSize=2097152000 
-XX:-OmitStackTraceInFastThrow 
-XX:+UseCompressedClassPointers 
-XX:+UseCompressedOops 
-XX:+UseParallelGC
Command line:  -javaagent:/home/admin/.opt/ArmsAgent/arms-bootstrap-1.7.0-SNAPSHOT.jar 
-Darms.licenseKey=cmmsmf4y87@9634a50d2fa7317-Darms.appName=ts-ecrp-open 
-Darms.agent.env=ACSK8S -Darms.agent.args= 
-Djava.util.logging.config.file=/usr/fffmo.com/tomcat/conf/logging.properties 
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager 
-XX:-OmitStackTraceInFastThrow -Xms3000m -Xmx3000m -Djdk.tls.ephemeralDHKeySize=2048 
-Djava.protocol.handler.pkgs=org.apache.catalina.webresources 
-Dorg.apache.catalina.security.SecurityListener.UMASK=0027 
-Dignore.endorsed.dirs= -Dcatalina.base=/usr/fffmo.com/tomcat 
-Dcatalina.home=/usr/fffmo.com/tomcat -Djava.io.tmpdir=/usr/fffmo.com/tomcat/temp

或者查看特定JVM参数


jinfo -flags <pid> # 查看所有的JVM flag
jinfo -flag NewSize <pid> # 查看JVM -XX:NewSize 的数据
# 如上,会输出如: -XX:NewSize=1048576000

jstat

对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。


# 查看GC情况
jstat -gc 1 3000
# jstat -options 查看所有支持的option
-class # 显示ClassLoad的相关信息
-compiler # 显示JIT编译的相关信息
-gc # 显示和gc相关的堆信息
-gccapacity # 显示各个代的容量以及使用情况
-gccause # 显示垃圾回收的相关信息(同-gcutil),同时显示最后一次或当前正在发生的垃圾回收的诱因
-gcmetacapacity # 显示metaspace的大小
-gcnew # 显示新生代信息
-gcnewcapacity # 显示新生代大小和使用情况
-gcold # 显示老年代和永久代的信息
-gcoldcapacity # 显示老年代的大小
-gcutil # 显示垃圾收集信息
-printcompilation # 输出JIT编译的方法信息

Redis常见问题排查

参考: https://redis.io

中文参考: http://doc.redisfans.com/index.html

性能相关的数据指标


# 连接到Redis中
# 参考: https://redis.io/commands/info
info

info命令输出的数据可分为10个类别,分别是:

  • server 一般 Redis 服务器信息
  • clients 已连接客户端信息
  • memory 内存信息
  • persistence RDB 和 AOF 的相关信息
  • stats 一般统计信息
  • replication 主/从复制信息
  • cpu CPU 计算量统计信息
  • commandstats Redis 命令统计信息
  • cluster Redis 集群信息
  • keyspace 数据库相关的统计信息
# 查看内存使用
info memory


used_memory_rss:从操作系统上显示已经分配的内存总量。

mem_fragmentation_ratio: 内存碎片率。

used_memory_lua: Lua脚本引擎所使用的内存大小。

mem_allocator: 在编译时指定的Redis使用的内存分配器,可以是libc、jemalloc、tcmalloc。

Linux服务器导致的性能问题(不常见)

因内存交换引起的性能问题

内存使用率是Redis服务最关键的一部分。如果一个Redis实例的内存使用率超过可用最大内存 (used_memory > 可用最大内存),那么操作系统开始进行内存与swap空间交换,把内存中旧的或不再使用的内容写入硬盘上(硬盘上的这块空间叫Swap分区),以便腾出新的物理内存给新页或活动页(page)使用。

在硬盘上进行读写操作要比在内存上进行读写操作,时间上慢了近5个数量级,内存是0.1μs单位、而硬盘是10ms。如果Redis进程上发生内存交换,那么Redis和依赖Redis上数据的应用会受到严重的性能影响。 通过查看used_memory指标可知道Redis正在使用的内存情况,如果used_memory>可用最大内存,那就说明Redis实例正在进行内存交换或者已经内存交换完毕。管理员根据这个情况,执行相对应的应急措施。

响应延迟

# 延迟时间
redis-cli --latency -h 127.0.0.1 -p 6379
# 以毫秒为单位测量Redis的响应延迟时间,正常的延迟是0.3左右


连接数量过多排查

client list


大Key

redis-cli --bigkeys


慢日志

# 对执行时间大于多少微秒(microsecond,1秒 = 1,000,000 微秒)的查询进行记录
# 执行以下命令将让 slow log 记录所有查询时间大于等于 100 微秒的查询
CONFIG SET slowlog-log-slower-than 100
# slow log 最多能保存多少条日志
# 让 slow log 最多保存 1000 条日志:
CONFIG SET slowlog-max-len 1000
# 查看 slow log
slowlog get
# 指定数量 
slowlog get 100
# 清空日志
slowlog reset

案例

# 一次Redis调用需要用时30ms+
# 慢日志
slowlog get

….

1985 1594086857 17038 KEYS

….

# 初步怀疑是Keys导致了Redis响应缓慢
# 排查发现Keys是商城后台在特定情况下使用的,使用较少,应该基本不影响
redis-cli --latency -h <host> -p <port>
# 发现延迟达到10ms+
# 通过监控平台,发现CPU接近100%,每秒并发操作12W+
info clients
# 连接数只有1200+左右
info commandstats
# 命令统计, 发现Ping次数达到
# cmdstat_ping:calls=878105367911,usec=818766996940,usec_per_call=0.93
# 再次统计
info commandstats
# 将二个数据相加,可以大概得到每秒的相差数量。
# 当初的问题ping每分钟达到400W+

三伏磨整理。 收藏本站,不定时分享实战经验。

排查项目Redis相关使用

项目中并没有直接PING命令

<property name="testOnBorrow" value="true" />
<property name="testOnReturn" value="true" />
<property name="testWhileIdle" value="true" />


发现问题

如果设置了testOnBorrow则从连接池拿出连接都都会执行一次PingPong

同理,设置了testOnReturn时,归还连接时也会执行一次PingPong

解决:

<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />


MySQL常见问题排查

慢SQL – Explain

字段意义
• id SELECT识别符。这是SELECT查询序列号。这个不重要,查询序号即为sql语句执行的顺序
• select_type select类型
    • SIMPLE 进行不需要Union操作或不含子查询的简单select查询时,
    响应查询语句的select_type 即为simple,无论查询语句是多么复杂,
    执行计划中select_type为simple的单位查询一定只有一个
    • PRIMARY 一个需要Union操作或含子查询的select查询执行计划中,
    位于最外层的select_type即为primary。
    与simple一样,select_type为primary的单位select查询也只存在1个
    • union 由union操作联合而成的单位select查询中,除第一个外,
    第二个以后的所有单位select查询的select_type都为union。
    union的第一个单位select的select_type不是union,而是DERIVED。
    它是一个临时表,用于存储联合(Union)后的查询结果
    • DEPENDENT UNION dependent 与UNION select_type一样,
    dependent union出现在union或union all 形成的集合查询中。
    此处的dependent表示union或union all联合而成的单位查询受外部影响
    • union result union result为包含union结果的数据表
• table 表名
• type 连接类型,有多个参数,先从最佳类型到最差类型介绍 也是本篇的重点
    • const,表最多有一个匹配行,const用于比较primary key 或者unique索引。
    因为只匹配一行数据,所以很快,也可以理解为最优化的索引,常数查找
    • eq_ref 对于eq_ref的解释,mysql手册是这样说的:”对于每个来自于前面的表的行组合,从该表中读取一行。
    除了const类型,这可能是最好的联接类型”
    • ref 对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取。
    如果联接只使用键的最左边的前缀,或如果键不是UNIQUE或PRIMARY KEY
    (换句话说,如果联接不能基于关键字选择单个行的话),则使用ref。
    如果使用的键仅仅匹配少量行,该联接类型是不错的
    • ref_or_null 该联接类型如同ref,但是添加了MySQL可以专门搜索包含NULL值的行。
    在解决子查询中经常使用该联接类型的优化
    • index_merge 该联接类型表示使用了索引合并优化方法。
    在这种情况下,key列包含了使用的索引的清单,key_len包含了使用的索引的最长的关键元素
    • unique_subquery
    • index_subquery
    • range 给定范围内的检索,使用一个索引来检查行
    • index 该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小。
    (也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)
    • ALL 对于每个来自于先前的表的行组合,进行完整的表扫描。
    如果表是第一个没标记const的表,这通常不好,并且通常在它情况下很差。
    通常可以增加更多的索引而不要使用ALL,使得行能基于前面的表中的常数值或列值被检索出
• possible_keys 提示使用哪个索引会在该表中找到行,不太重要
• keys 指明MYSQL查询使用的索引
• key_len MYSQL使用的索引长度
• ref 显示使用哪个列或常数与key一起从表中选择行
• rows 显示MYSQL执行查询的行数,数值越大越不好,说明没有用好索引
• Extra 该列包含MySQL解决查询的详细信息


美团技术团队分享 https://www.jb51.net/article/75438.htm

索引优化

覆盖索引

‘Using Index’的意思是“覆盖索引”。

一个包含查询所需字段的索引称为“覆盖索引”

MySQL只需要通过索引就可以返回查询所需要的数据,而不必在查到索引之后进行回表操作,减少IO,提高了效率。

# 索引
# AK_out_nick_platform  out_nick, platform  UNIQUE
SELECT SQL_NO_CACHE es_party_time, out_nick, platform 
from kd_all_customer order by id desc limit 1000

SELECT SQL_NO_CACHE out_nick, platform 
from kd_all_customer order by id desc limit 1000 


索引选择性

# 索引选择性=索引基数/数据总数
# 索引基数
# show index from 表名 # cardinality
# 索引选择性平均数值组越接近1就越有可能利用索引


索引不优

明明有索引,但是不走索引?

两个同样结构的语句一个没有用到索引的问题?

原因: 二叉树索引本来最适合的就是点查询,和小范围的range查询, 当预估返回的数据量超过一定比例( 大概在预估的查询量达到总量的30%,没找到实际文档 )的时候, 再根据索引一条一条去查就慢了,反而不如全表扫描快了。

索引的最左原则

例如联合索引有三个索引字段(A,B,C)

查询条件:

(A,,)— 使用索引

(A,B,)— 使用索引

(A,B,C)— 使用索引

(,B,C)— 不会使用索引

(,,C)— 不会使用索引

三伏磨整理。 收藏本站,不定时分享实战经验。

系统问题

内存SWAP

当应用程序要请求新的内存页的时候,如果已经没有足够的物理内存,就会把目前物理内存中的一部分空间释放出来,以供当前运行的程序使用。

这部分被释放的空间可能属于某一个程序,并且所谓的释放,是把这部分内存页存放到SWAP空间。

如果这个程序是活跃的,那么当它的内存页被存放到SWAP之后,下一刻它又要用到这一部分,那么就又要把这一部分换入内存中,这个时候,系统就要把其它程序的内存页换出到SWAP中,腾出空间给它。

反复如此,就会造成性能的问题。

所以如果频繁使用到SWAP来换出换入内存,那么就意味着负载过高,或者物理内存不够。

文件句柄数

并发调优时,往往需要预先调优linux参数,其中修改linux最大文件句柄数是最常修改的参数之一。

linux最大文件句柄数为1024个。当你的服务器在大并发达到极限时,就会报出“too many open files”。


最终排查手册

日志、经验、怀疑!!

附录1: arthas 安装&启动

官方文档: https://arthas.gitee.io/
 下载安装
 wget -qO /tmp/arthas.zip "https://maven.aliyun.com/repository/public/com/taobao/arthas/arthas-packaging/3.2.0/arthas-packaging-3.2.0-bin.zip" && \
 mkdir -p /opt/fffmo.com/arthas && \
 unzip /tmp/arthas.zip -d /opt/fffmo.com/arthas && \
 rm /tmp/arthas.zip
 启动
 java -jar /opt/fffmo.com/arthas/arthas-boot.jar

附录2:arthas ognl表达式核心变量

# 官方文档: https://arthas.gitee.io/
# 参考: https://arthas.gitee.io/advice-class.html



附录3:arthas 常用功能

# 官方文档: https://arthas.gitee.io/


trace-性能排查

# 参考: https://arthas.gitee.io/trace.html
trace -n 1 com.nascent.ecrpsaas.openapi.interceptor.APIHandlerInterceptor preHandle
# 强调: 在生产环境时, 必须先把-n带上


比较有问题的是,如果方法复杂度过高,会导致无法Agent进去。同时,目前无法支持重载方法。

tt – 时空隧道

记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测

# 参考: https://arthas.gitee.io/tt.html
tt -n 3 -t com.nascent.ecrpsaas.openapi.interceptor.APIHandlerInterceptor preHandle
# 强调: 在生产环境时, 必须先把-n带上

上文中的 arthas获取SpringContext的Bean信息就是利用tt的调用信息,然后使用ognl表达式获取的。

查看请求信息

#tt -i <index>
tt -i 1000

获取对象中的配置信息

tt -i 1000 -w 'target'

调用

tt -i 1000 -w 'target.requestNonce'


jad – 反编译指定已加载类的源码 解决:我改了呀,但是不知道为什么没有作用?

jad --source-only com.nascent.ecrpsaas.openapi.interceptor.APIHandlerInterceptor > /tmp/APIHandlerInterceptor.java
# 直接看
# jad --source-only com.nascent.ecrpsaas.openapi.interceptor.APIHandlerInterceptor

反编译之后的源码:

mc & redefine – 动态更新代码

不支持vi处理: apt-get install vim -y

编辑类,并保存成功之后,启动arthas

# 查找类对应的加载器
sc -d *APIHandlerInterceptor | grep classLoaderHash
# 输出:  classLoaderHash   726a17c4
# 内存编译代码
mc -c 726a17c4 /tmp/APIHandlerInterceptor.java -d /tmp
# 需要注意的是,JAVA的泛型在编译之后会被擦除。所以,最合适的方法就是将本地代码上传。
# 而不是基于反编译之后的代码来修改。
# 输出: Memory compiler output:
#      /tmp/com/nascent/ecrpsaas/openapi/interceptor/APIHandlerInterceptor.class
# 更行热替换
redefine /tmp/com/nascent/ecrpsaas/openapi/interceptor/APIHandlerInterceptor.class
# 输出: redefine success, size: 1
赞(4) 打赏
特别声明:除特殊标注,本站文章均为原创,遵循CC BY-NC 3.0,转载请注明出处。三伏磨 » 问题排查总结

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏