对于一个并发访问量与大数据量的应用来说,在生产环境上很容易出现各种诡异的问题。那么为了分析与解决这些。我们需要用到各种不同的工具定位出问题。
利用日志,一个设计良好的应用必须要纪录部分项目在运行过程中所产生的关键业务信息(视具体业务场景而定)及所有异常信息,异常日志是分析定位问题最常用工具,必须将整个堆载信息及关键参数一同纪录下来。
一般普通的问题我们都可以同过分析日志而找到问题根源,对与一些性能问题,如,页面相应慢,偶尔打不开或一到高峰期就无法打开页面的问题通常只靠日志很难定位出问题所在。这时我们首先需要看一下服务器的连接数、网卡流量、cpu Load、IO是否具增,如果是集群情况下看下个集群节点的情况是否大概一致。如果差距较大,就需要分析下负载均衡工作是否正常(一般来说特定情况下出现负载不均衡的情况也很可能是程序bug或其他原因造成),服务器运行时状态都可以通过专用监控软件实现,如开源软件cacti等。
另外如果我们发现外部请求连接数比平常高出10几或甚至更高的倍数,或发现日志里抱出大量的socket连接异常,或没有日志输出的情况下我门需要使用jdk自带的jmap、jstat、jstack对jvm中的当前对象占用的堆栈空间、垃圾回收、栈中执行的线程分别进行分析,由于是生产环境一般我们无法使用如jconsol或jvisualvm等可视化工具进行分析。
首先使用jstat -gcutil pid 1000 100分析在旧生代被占用情况及对应的垃圾回收情况,如果旧生代的垃圾回收过于平凡,如每10秒甚至是5秒中回收一次,那么整个站点将异常缓慢,甚至无法访问,因为每次的垃圾回收将会挂起整个jvm,如果我们给jvm的新生代于旧生代都分配了1-2G的空间,那么造成这种情况的原因就在于每次用户请求时产生了大量的大对象,这些朝生夕死的对象本应该在新生代中分配及回收,往往由于大并发请求加大对象而导致新生代空间不够用,而直接将对象分配到了旧生代上来。这将是一种灾难,我们已经无法通过简单的对jvm调优来解决问题,唯一的办法就是找到疯狂生成大量大对象的代码并改进。这时我们可以使用jmap来查看当前jvm内存中哪些对象占用了大量内存,便于更快速的定位到造成问题的代码。对于一个设计良好的站点,jvm垃圾回收在新生代的间隔几分钟一次,旧生代则应隔4-5小时甚至更长的时间。
在同一时刻产生大量请求连接的情况,一般来说有两个造成问题的原因,一个是pv数或用户请求数大增,这种情况相对比较少,除非被攻击,另一个也是最有可能的情况是随着一天中的高峰期的到来,相应程序响应越来越缓慢造成。这个时候我们就可能通过jstack这个查看当前正在运行的堆栈进行分析,一般来说我们看到栈信息包括,自己写的程序产生处且处理RUNNING状态下线程、自己的程序处理BLOCKED状态、处于WAITTING状态,最严重的漠过于BLOCKED的线程,当我们发现相同的栈信息均在执行同一代码处处于该种状态时就意味着我们的代码没有处理好相对应的业务,使程序处于锁的争夺中,如果严重最后将导致我们服务器无法在对外响应,此时内存堆中的空间也会被消耗完并且无法回收的情况。一个例子是log4j在打印日志时,会在内部相同的地方进行锁定。有一种不同的情况是,有些BLOCKED是应用服务器本身产生如等待外部请求的到来,属于正常现象。对于同时处于RUNNING的N多线程都在执行相同的代码也同样意味着此处执行时间相对较长或存在着较严重性能问题或被执行次数非常频繁或者两者都是,此时有可能是在查询数据库,调用远程接口,IO,执行耗时数据转化或正则表达式等。处于WAITING状态很多的分析也与RUNNING的情况差不多,有差别的只是该状态的线程正在等待另一线程的notify通知,存在问题的也往往是notify执行较慢。
除了以上分析以外,对cpu load分析也较为重要,一般来说load在cpu总核数的1倍较好。
如果我们的服务器总体运行状况良好,却发现该服务器或连接这台服务器的其它服务器连接数很高或出现连接broden pipe的情况,此时就要看一下网络是否正常,通过ping一般就能发现问题。ping的时候一般要在1ms内返回,最好能在0.2-0.5ms内返回。