编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

生产级线上基础问题排查常用手段小结

wxchong 2024-08-22 03:43:34 开源技术 14 ℃ 0 评论

今天给公司小伙伴分享了一些个人一些线上问题的排查手段,在这里也分享给大家。

(去除了一些针对公司内部的问题实例,请理解)

常见问题分类

业务问题

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

性能问题

  • 接口问题
  • 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拦截

此时,如果使用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来指定次数
# 根据方法用时过滤
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>


如何排查对应的线程问题? 需要结合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

如果在观察的时候,%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

  • 行为数据
    1. Histogram 直方图 - 类数量列表
    2. Dominator Tree 支配树 - 类引用关系
    3. Top Consumers 跟直方图相似 - 按包分组
    4. Duplicate Classes 重复类 - 被不同ClassLoader加载的类
  • 报表数据
    1. Leak Suspects 可疑泄露
    2. Top Consumers 占用总堆1%以上的报表

    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。


    Top Consumers 内存消耗排行


    Leak Suspects 可疑泄露报告

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


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

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


    Path to 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

    建议设置

    看懂GC日志



    日志分析 - GcViewer


    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/local/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/local/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/local/tomcat 
    -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/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/arthas && \
    unzip /tmp/arthas.zip -d /opt/arthas && \
    rm /tmp/arthas.zip
    # 启动
    java -jar /opt/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


    其他参考

    「JVM」GC——调优介绍

    [JVM] MAT进阶使用

    [MySQL] InnoDB 内部实现机制

    Tags:

    本文暂时没有评论,来添加一个吧(●'◡'●)

    欢迎 发表评论:

    最近发表
    标签列表