天天看点

一点项目查错经验之谈(转载)

知识点参考于 芋道源码文章笔记,对自身提升有帮助,所以手动写下来加深理解

核心内容:正确的处理一次在线故障

故障发生时:意料之外此的错误,无响应或者响应缓慢,但出于服务中,无法关闭影响用户体验,需要尽快修复

应对思路:

重点:第一时间上报给自己的直属领导和相关负责人,并再可能的情况下及时周知问题,影响范围,解决方案,预计恢复时间

有2个捷径

1团队中对相关问题有经验者,并能确定通过某种手段恢复系统的正常运行,那么第一时间恢复(回滚),但同时要保留现场,以备后续问题的定位和处理;如果没有经验,则根据错误程度,粗暴的采用定时重启,限流,降级等保证服务可用

2业务负责人,技术负责人,核心研发,架构,运维和运营对问题快速分析,主要考虑方向是系统近期的变化,分以下几个方面:

    1近期是否有新版本发布

    2是否有营运活动

    3是否有流量波动

    4业务量是否有提升

    5运营人员是否在系统上做变动

    6依赖的基础平台和资源(第三方接口或者maven的jar)是否有新发布

常见的可能原因:网路,CPU,内存,IO指标异常

 1.DNS,网路,CDN故障

 2.代码BUG,逻辑不严谨,连接未释放

 3.代码性能本身问题

 4.内存泄漏:本地缓存

 5.异常流量:DDOS

 6.业务提升导致QPS无法负载

 7.数据库,搜索引擎,分布式缓存,消息队等中间件的性能问题

必备准备工作知识(可多人聚合)

1计算机基础知识:网络,操作系统,网络链路

2java内存管理:垃圾回收算法,垃圾回收器,关键GC参数,JVM内存模型

3java代码规范,性能测试流程

4系统参数调优

5常见系统诊断工具,JDK自带的和其他的

6了解业务系统:总体架构,压力方向,容量,软件版本,模式和基础参数

CentOS自带的检测工具

1.uptime:系统负载的任务量

2.demesg|tail:查看系统日志

3.vmstat l :查看CPU的LOading状态

4.free -m 查看内存情况

5.top:系统全局指标信息

6.netstat -tanp:查看TCP网路连接状况

系统诊断工具-sysstat,指令如下

Mpstat -p all l:查询CPU使用情况(一个占用特别高的话可能就是单线程引起的)

Sar -n dev l:查询网络设备吞吐量的

Sar  -n  tcp,ETCP l:查看TCP连接状态,每秒发起的连接数目,可查网络谁都丢包

Iostat -xz l:查询磁盘的IO情况

JDK诊断工具

·jstack:java堆栈跟踪工具,主要打印指定java进程,核心文件或远程调试器的JAVA线程的堆栈跟踪信息

·jmap:java内存隐射工具,打印java进程,核心文件或远程调试服务器的共享对象内存

·jhat:java堆分析工具

·jinfo:JAVA配置信息根据

·jstat:JVM系统检测工具

.jcmd:java命令行

·visualvm(推荐可使用ideal集成),JVM线程,内存,类等信息

·jconsole:功能和visualvm类似,支持直接远程执行MBEan

·jmc:收费的软件,功能更佳

其他工具

1charies:HTTP协议抓包工具。可将自己设置成系统的网路访问代理服务器,实现网路数据包的截取和分析

2tpacketcapture:TCP协议抓包工具,主要用于android端获取TCP数据包,生成的文件可以通过wireshark分析

3greys-atonomy:在线诊断工具,可通过动态修改字节码达到无须重启JVM添加日志,监测方法耗时等动态增强代码的目的

4arthas(自己有学习,很不错的):阿里开源的java诊断工具箱,基于greys-atonomy而来,可在线诊断,反编译字节,查看最消耗资源的java进程

5jwebap:javaEE性能检测框架,支持http请求,JDBC连接,method的调用轨迹以及次数,耗时的统计

6awesome-scripts:封装了很多常用的诊断工具,脚本

步骤思路(在没有准确的知道原因的情况下)

1.DNS是否正常(ping)

2网路是否正常(ping,telnet)

3查看日志文件(1查看tomcat的cataliana.out初始化日志,然后在看项目日志,此步骤出问题最常见)

4检查磁盘是否满(删除多余日志)

5流量是否有异常?限流,降级,扩散服务节点,架构优化

6外部系统问题:数据库,搜索引擎,分布式缓存,消息队列,性能,分区设计

7应用的CPU,内存,IO

CPU常见分析

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

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

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

Jstat  -gcutil [pid] ,查看GC的情况是否正常,是否GC引起CPU过高

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

出现原因:线程中有空循环,无阻塞,正则匹配或者单纯的计算,频繁的GC,多线程的上下文切换,JIT编译

小知识

1个进程的CPU使用率是所有线程之和

2top的CPU使用率近似实时,PS则是平均

3top的CPU默认使用率是irix mode,单cpu最大值是100%,多处理器环境下,类如4核%CPU最大值是400%

4jstack查看线程栈要注意:由于jstack dump实现机制每次只能转存一个线程的栈信息,所有会看到一些冲突信息,如一个线程正在等待的锁并没有被其他线程持有,多个线程

持有同一个锁等

内存分析(频繁GC,响应缓慢,oom,堆内存,永久代内存,本地线程内存)

1堆外内存:JNI,Deflater/iinflater,directbyteBuffer.通过vmstat,top,pidstat等查看swaP和物理内存的消耗状况

2堆内存:创建的对象,全局集合,缓存,classloader,多线程

 -检查JVM内存使用状况:jmap -heap <pid>

 -查看JVM内存存活的对象:jmap -histo:live <pid>

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

磁盘IO分析(大量的随机读写,设备慢,文件太大)

·iostat -xz l 查看磁盘IO情况

·r/s,w/s,rkb/s,wkb/s等指标过大引起的问题

·await过大,硬件设备遇到了瓶颈或出现故障,一次IO操作一般超过20ms就说明磁盘压力过大

·avgqu -sz大于1,可能是硬件设备已经饱和

·%util越大表示磁盘越繁忙,%100表示已经饱和

网路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,查看吞吐率和吞吐数据包数,是否超过网卡限制

IO分析小知识

·%iowait在linux的计算机为CPU空闲,并且有仍未完成的IO请求的时间占总时间的比例

·%iowait升高不一定代表IO设备有瓶颈,需要结合其他指标来判断,如await,svctm等

·avgqu -sz是按照单位时间的平均值,不能反应瞬时的IO洪水

在线代码分析(重点需要掌握的)

1远程DEBUG:TOMCAT远程调试

2在线Trace:Btrace,houseMD,Greys-atonomy,arthas

故障解决

1代码BUG:FIX

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

3JVM配置

CPU使用优化方法

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

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

3避免正则表达式匹配,过多的计算。类如,避免使用String的format,split,replace方法,避免使用正则判断邮箱格式,避免序列化和反序列化

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

5多线程对锁的竞争考虑减少锁的粒度,拆分锁(类似ConcurrentHashMmap分bucket上锁),或者使用CAS,threadlocal,不可变对象等无锁技术,多线程最好使用JDK提供的并发包,EXECUTORS框架,此外Disruptor和actor在合适的场景也能使用

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

内存优化方法

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

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

·避免改变数据结构大小。避免改变数组集合的大小,对象构建最好指定合适的大小。改变大小导致不必要的对象分配,可能导致JAVA堆碎片。

·避免保存重复的string对象,同时也需要小心String.substring()与string.intern()的使用,中间过程会产生不少字符串

·尽量不适用finalizer

·释放不必要的引用:ThreadLocal使用完记得释放以防止内存泄露,各种stram使用完记得close

·适用对象池避免无节制的创建对象,造成GC,也不要随便使用对象池,除非像线程池,连接池等初始化/创建资源消耗较大的场景

·缓存失效算法

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

·打印日时不要输出文件名,行号。因为一般日志框架是通过打印线程堆栈实现,生成大量的String,另外,应对打印日志级别进行区分选择合适的

IO优化方法

1多考虑使用异步写入代替同步写入,参考redis的AOF机制

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

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

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

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

6合理选择IO流

JVM配置优化方法

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

2选择合适的GC策略

3老年优先使用Parallel GC(-xx:+UseParallel[Old] gc),可以保证最大的吞吐量。

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

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

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

7打开GC日志并读懂GC日志,以便排查问题,GC日志可以使用GC hIStogram(gchisto)生成图表和表格

代码性能建议

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

·优先考虑使用返回值而不是异常表示错误,虽然JVM已做了大量优化工作,如果只关注性能,在捕获异常时,直接采用fillinstackTrace()方法为空方法,不拷贝信息

·查看自己的代码是否对内联是友好的,方法的大小不超过35个字节,非虚方法

·个人建议:使用阿里插件,会对一般错误信息不同颜色区分,绿色(可忽略),黄色(不规范),红色(错误的代码),保证代码基本正确