技术实践|线上故障分析及解决方法(下)

线上故障通常是指大规模的影响线上服务可用性的问题或者事件,而对于线上故障的处理不仅是一项技术活,更是对技术人员/技术团队应急反应能力的考验。本文主要从线上故障的分类、应对思路、出现原因等方面入手,总结一些经验和问题解决方法,以求与各位同仁探讨交流。

五、分析思路

■ 根据日志输出的异常信息定位问题,需要区分Tomcat中的catalina.out(标准输出和错误)和iocalhost.xx.log(应用初始化的日志,错误则无法启动)

■ 磁盘是否已满(df -h)?->删除多余日志

 流量是否有异常?->限流、降级、扩展服务节点、架构优化

 外部系统问题->数据库、搜索引擎、分布式缓存、消息队列的故障解决、性能优化、分区设计等

 应用的CPU、内存、IO

1. CPU分析

 使用top、vmstat、ps等命令定位CPU使用率高的线程:top -p [processId] -H

 jstack [pid]打印繁忙进程的堆栈信息。

 通过printf %0x [processId]转换进程id为16进制,在堆栈信息中查找对应的堆栈信息。

 JVM加入-xx:+PrintCompilation参数,查看是否是JIT编译引起的CPU飙高。

CPU繁忙:线程中有无限空循环、无阻塞、正则匹配或者单纯的计算;发生了频繁的GC;多线程的上下文切换;JIT编译

2. CPU分析Tips

 一个进程的CPU使用率是其所有线程之和(线程对应LWP),CPU使用率高可以配合mpstat具体分析,是否是单线程应用程序引起的。

 top的CPU使用率近似实时,ps则是平均使用率。

 top的CPU使用率默认是Iris Mode,为单CPU衡量的一个值,最大为100%。

可切换为Solaris Mode,此值在多处理器环境下,为占的总CPU的使用率,例如,4核CPU中总的CPU使用率最高为400%。

 jstack查看线程栈时需要注意:由于jstack dump实现机制每次只能转储出一个线程的栈信息,因此输出信息中可能会看到一些冲突的信息,如一个线程正在等待的锁并没有被其他线程持有,多个线程持有同一个锁等。

3. 内存分析

内存使用不当:频繁GC,响应缓慢;OOM,堆内存、永久代内存、本地线程内存

 堆外内存:JNI、Deflater/Inflater、DirectByteBuffer。通过vmstat、top、pidstat等查看swap和物理内存的消耗状况。通过Google-preftools来追踪JNI、Deflater这种调用的资源使用状况。

 堆内存:创建的对象、全局集合、缓存、ClassLoader、多线程

●查看JVM内存使用状况:jmap -heap

●查看JVM内存存活的对象:jmap -histo:live

●把heap里所有对象都dump下来,无论对象是死是活:jmap -dump:format=b,file=xxx.hprof

●先做一次Full GC,再dump,只包含仍然存活的对象信息:jmap -dump:format=b,live,file=xxx.hprof

●使用Eclipse MAT或者jhat打开堆dump的文件,根据内存中的具体对象使用情况分析。

●VJTools中的vjmap可以分代打印出堆内存的对象实例占用信息。

4. 网络IO分析

 netstat -anpt:查看网络连接状况。当TIME_WAIT或者CLOSE_WAIT连接过多时,会影响应用的响应速度。前者需要优化内核参数,后者一般是代码Bug没有释放网络连接。

 使用tcpdump来具体分析网络IO的数据。tcpdump出的文件直接打开是一堆二进制的数据,可以使用Wireshark查看具体的连接以及其中数据的内容。tcpdump -i eth0 -w tmp.cap -tnn dst port 8080。

 sar -n DEV,查看吞吐率和吞吐数据包数,判断是否超过网卡限制。

5. IO分析Tips

 %iowait在Linux的计算为CPU空闲、并且有仍未完成的IO请求的时间占总时间的比例。

 %iowait升高并不一定代表IO设备有瓶颈。需要结合其他指标来判断,如await(IO操作等待耗时)、svctm(IO操作服务耗时)等。

 avgqu-sz是按照单位时间的平均值,所以不能反映瞬间的IO洪水。

 

6. 在线代码分析

 远程Debug:Tomcat远程调试

 在线Trace:BTrace、HouseMD、Greys-atonomy、arthas

六、故障解决

1. 可能的问题分类

 代码Bug:fix

 性能问题:CPU、内存、IO使用优化

 JVM配置

2. CPU使用优化

 不要存在一直运行的线程(无限循环),可以使用sleep休眠一段时间。这种情况普遍存在于一些pull方式消费数据的场景下,当一次pull没有拿到数据的时候建议sleep一下,再做下一次pull。

 轮询的时候可以使用wait/notify机制代替循环。

 避免正则表达式匹配、过多的计算。例如,避免使用String的format、split、replace方法;避免使用正则去判断邮箱格式(有时候会造成死循环);避免序列/反序列化。

 使用线程池,减少线程数以及线程的切换。

 多线程对于锁的竞争可以考虑减小锁的粒度(使用ReentrantLock)、拆分锁(类似ConcurrentHashMap分bucket上锁),或者使用CAS、ThreadLocal、不可变对象等无锁技术。同时,多线程代码的编写最好使用JDK提供的并发包、Executors框架以及ForkJoin等,此外Disruptor和Actor在合适的场景也可以使用。

 结合JVM和代码一起进行分析,避免产生频繁的GC,尤其是Full GC。

3. 内存使用优化

 使用基本数据类型而不是其包装类型能够节省内存。

 尽量避免分配大对象。大对象分配的代价以及初始化的代价很大,不同大小的大对象可能导致Java堆碎片,尤其是CMS。

 避免改变数据结构大小。如避免改变数组或array backed collections/containers的大小;对象构建(初始化)时最好显式批量定数组大小;改变大小导致不必要的对象分配,可能导致Java堆碎片。

 避免保存重复的String对象,同时也需要小心String.subString与String.intern的使用,中间过程会生成不少字符串。

 尽量不要使用finalizer。

 释放不必要的引用:ThreadLocal使用完记得释放以防内存泄漏,各种stream使用完也记得close。

 使用对象池避免无节制创建对象,造成频繁GC。但也不要随便使用对象池,除非像连接池、线程池这种初始化/创建资源较大的场景。

 缓存失效算法,可以考虑使用SoftReference、WeakReference保存缓存对象。

 谨慎热部署/加载的使用,尤其是动态加载类等。

 打印日志时,不要输出文件名、行号,因为日志框架一般都是通过打印线程堆栈实现,生成大量String。此外,打印日志时,先判断对应级别的日志是否打开再做操作,否则也会生成大量String。

4. IO使用优化

 考虑使用异步写入代替同步写入,可以借鉴Redis的AOF机制。

 利用预读取或者缓存,减少随机读。

 尽量批量写入,减少IO次数和寻址。

 使用数据库代替文件存储。

 使用异步IO、多路复用IO/事件驱动IO代替同步阻塞IO。

 使用协程提高网络IO性能:Quasar。

5. JVM配置

 合理设置各个代的大小。新生代尽量设置的大,但不能过大(会产生碎片),同样也要避免Survivor设置过大和过小。

 选则合适的GC策略。需要根据不同的场景选择合适的GC策略。这里需要说的是,CMS并非全能的。除非特别需要再设置,毕竟CMS的新生代回收策略ParNew并非最快的,且会产生碎片。此外,G1直到JDK8的出现也并没有得到广泛应用,并不建议使用。

 老年代优先使用Parallel GC (-XX:+UseParallel[Old]GC),可以保证最大的吞吐量。由于CMS会产生碎片,确实有必要才改成CMS或G1。

 注意内存墙(严重阻碍处理器性能发挥的内存瓶颈),一般讲单点应用堆内存设置为4G到5G即可,依靠可扩展性提高并发能力。

 设置JVM内存大小有一个经验法则:完成Full GC后,应该释放出70%的内存。

 配置堆内存和永久代/元空间内存之和小于32GB,从而可以使用压缩指针节省对象指针的占用。

 打开GC日志并读懂GC日志,以便于排查问题。GC日志文件可以使用GC Histogram(gchisto)生成图表和表格。

6. 代码性能建议

 算法、逻辑上是程序性能的首要,遇到性能问题,应该首先优化程序的逻辑处理。

 优先考虑使用返回值而不是异常表示错误。虽然现代JVM已经做了大量优化工作,但毕竟异常是有代价的,需要在合适的地方使用。一般用错误码返回值处理可能会发生的事情,用异常捕捉处理不期望发生的事情。如果使用异常并且比较关注性能,可以通过覆盖掉异常类的fillInStackTrace方法为空方法,使其不拷贝栈信息。

 查看自己的代码是否对内联是友好的。内联友好指的方法的大小不超过35字节(默认的内联阈值,不建议修改)、非虚方法(虚方法指的是在运行期才能确定执行对象的方法,最新的JVM对非虚方法会通过CHA类层次分析来判断是否可以内联)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值