一、故障基本信息
发生时间 |
消除时间 |
故障历时 |
故障类别 |
影响 |
2018-5-17 18:14:30 |
2018-05-18 08:58:15 |
16小时 |
应用故障 |
业务瘫痪,用户投诉 |
二、故障现象
APP无法登陆,导致大面积投诉。
三、故障分析
1.查看系统2018-5-17 17:23:47时catalina日志显示 ORA-00060: 等待资源时检测到死锁,且2018-5-17 18:11后无日志生成。
2.查看数据库显示与2018-05-17 17:23:12分时产生死锁现象,生成数据报告,分析如下:
3.tomcat应用日志2018.5.17号存在疑似内存泄漏告警
五月 17, 2018 4:56:51 下午 org.apache.catalina.loader.WebappClassLoader clearReferencesJdbc
严重: The web application [/manage] registered the JDBC driver [oracle.jdbc.OracleDriver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered. –内存泄漏告警
五月 17, 2018 4:56:51 下午 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
严重: The web application [/manage] appears to have started a thread named [Dispatcher-Thread-3] but has failed to stop it. This is very likely to create a memory leak.
五月 17, 2018 4:56:51 下午 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
严重: The web application [/manage] appears to have started a thread named [oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser] but has failed to stop it. This is very likely to create a memory leak.
五月 17, 2018 4:56:51 下午 org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
严重: The web application [/manage] created a ThreadLocal with key of type [net.sf.json.AbstractJSON.CycleSet] (value [net.sf.json.AbstractJSON$CycleSet@73c791f]) and a value of type [java.lang.ref.SoftReference] (value [java.lang.ref.SoftReference@51484b24]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
五月 17, 2018 4:56:51 下午 org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
注:Java不需要管理内存的分配和释放,一切由JVM自己来进行处理,当Java对象不再被应用时,等到堆内存不够用时JVM会进行GC处理,清除这些对象占用的堆内存空间,但是如果对象一直被应用,那么JVM是无法对其进行GC处理的,那么我们创建新的对象时,JVM就没有办法从堆中获取足够的内存分配给此对象,这时就会导致内存溢出(OOM)。出现OOM原因,一般都是因为不断的往容器里存放对象,然而容器没有相应的大小限制或清除机制,这样就容易导致OOM。
4.oracle awr性能报告(SQL部份截取)
1) 上面是sql语句执行时间排序,主要是更新商家信息账户信息表及账户系统账户信息表余额,分别excutions执行了300多次,Elapsed Time per Exec (s)平均一个update执行时间(单位:秒),即每次update 5分钟左右。
2) 第1个sql:update PAY_CST_ACC set PCA_TOTALBANLANCE =( PCA_TOTALBANLANCE-:1 ), PCA_AVABANLANCE =(PCA_AVABANLANCE-:2 ) where PCA_CSTNO =:3 and PCA_USERACC=:4,花了325秒,即5分钟左右,此时已经不正常。
因为oracle对数据增删改查的操作机制,都需要将数据从磁盘调到内存操作,操作完commit提交后再根据一定规则刷到磁盘(如果该记录原先已被其它会话操作调在内存,则直接从内存读取)。连接会话操作数据都要占内存,如果应用众多并发会话连接没释放,则到后面的连接内存不够用了,等待前面的语句操作完释放,如果出现多个并发操作同一记录并锁住其它连接要用的记录等资源,则出现死锁。具体处理建议请见第五章。
四、故障处理
临时处理:分别Kill掉服务器tomcat进程,会话连接断开,重启应用问题解决。
五、故障预防建议
系统tomcat应用连库配置路径:xx/conf/context.xml,代码如下:
<Resource name="jdbc/ebank_ds" type="javax.sql.DataSource"
driverClassName="oracle.jdbc.OracleDriver"
url="jdbc:oracle:thin:@xxxx:1521/实例名"
username="数据库用户名"
password="密码"
maxActive="2000"
maxIdle="30"
maxWait="9000"/>
综合上述应用日志提示内存泄漏告警以及和oracle数据库日志提示检测到等待资源死锁,以及oracle awr性能报告。建议优化tomcat连接池参数,修改前建议先在测试和验证环境模拟生产环境高并发连接测试,验证有效后再修改生产配置。
增加和修改连接池参数如下:
<!--maxActive: 最大连接数量-->
<property name="maxActive" value="2000"/>
<!--minIdle: 最小空闲连接-->
<property name="minIdle" value="5"/>
<!--maxIdle: 最大空闲连接-->
<property name="maxIdle" value="30"/>
<!--initialSize: 初始化连接-->
<property name="initialSize" value="20"/>
<!-- 连接被泄露时是否打印 -->
<property name="logAbandoned" value="true"/>
<!--removeAbandoned: 是否自动回收超时连接-->
<property name="removeAbandoned" value="true"/>
<!--removeAbandonedTimeout: 超时时间(以秒数为单位)-->
<property name="removeAbandonedTimeout" value="10"/>
<!--maxWait: 超时等待时间以毫秒为单位-->
<property name="maxWait" value="10000"/>
<!-- 在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位. -->
<property name="timeBetweenEvictionRunsMillis" value="10000"/>
<!-- 在每次空闲连接回收器线程(如果有)运行时检查的连接数量 -->
<property name="numTestsPerEvictionRun" value="10"/>
<!-- 1000 * 60 * 30 连接在池中保持空闲而不被空闲连接回收器线程-->
<property name="minEvictableIdleTimeMillis" value="10000"/>
<property name="validationQuery" value=" select 1 from dual "/>
站点修改应用tomcat连接池配置路径:xx/ applicationContext.xml
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@xx:1521:实例名"/>
<property name="username" value="数据库用户名" />
<property name="password" value="密码" />
<property name="initialSize" value="5" />
<property name="maxActive" value="2000" />
<property name="defaultAutoCommit" value="true" />
修改成与账户系统一致的连接参数。
具体连接参数说明请参考tomcat 7官网链接说明:https://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html
附:连接参数的几点说明
maxActive="100"
表示并发情况下最大可从连接池中获取的连接数。如果数据库不是单独,供一个应用使用,通过设置maxActive参数可以避免某个应用无限制的获取连接对其他应用造成影响,如果一个数据库只是用来支持一个应用那么maxActive理论上可以设置成该数据库可以支撑的最大连接数。maxActive只是表示通过连接池可以并发的获取的最大连接数。连接的获取与释放是双向,当应用程序并发请求连接池时,连接池就需要从数据库获取连接,那么但应用程序使用完连接并将连接归还给连接池时,连接池是否也同时将连接归还给数据库呢?很显然答案是否定的,如果那样的话连接池就变得多此一举,不但不能提高性能,反而会降低性能,那么但应用成归还连接后,连接池如何处理呢?
maxIdle="30"
如果在并发时达到了maxActive=100,那么连接池就必须从数据库中获取100个连接来供应用程序使用,当应用程序关闭连接后,由于maxIdle=30,因此并不是所有的连接都会归还给数据库,将会有30个连接保持在连接池种中,状态为空闲。
minIdle=”2”
最小默认情况下并不生效,它的含义是当连接池中的连接少有minIdle,系统监控线程将启动补充功能,一般情况下我们并不启动补充线程。
问题:如何设置maxActive和maxIdle?
理论上讲maxActive应该设置成应用的最大并发数,这样一来即便是在最大并发的情况下,应用依然能够从连接池中获取连接,但是困难时的是我们很难准确估计到最大并发数,设置成最大并发数是一种最优的服务质量保证,事实上,如果某个用户登录提示系统繁忙,那么在他再次登录时,可能系统资源已经充足,对于xx系统我们建议将maxActive设置为系统注册人数的十分之一到二十分之一之间。例如系统的注册人数为1000,那么设置成50-100靠近100的数字,例如85或90。
maxIdle对应的连接,实际上是连接池保持的长连接,这也是连接池发挥优势的部分,理论上讲保持较多的长连接,在应用请求时可以更快的响应,但是过多的连接保持,反而会消耗数据库大量的资源,因此maxIdle也并不是越大越好,同上例我们建议将 maxIdle设置成50-100中靠近50的数字,例如55。这样就能在兼顾最大并发同时,保持较少的数据库连接,而且在绝大多情况,能够为应用程序提供最快的相应速度。
removeAbandoned="true"
removeAbandonedTimeout="60"
logAbandoned="true"
有时粗心的程序编写者在从连接池中获取连接使用后忘记了连接的关闭,这样连池的连接就会逐渐达到maxActive直至连接池无法提供服务。现代连接池一般提供一种“智能”的检查,但设置了removeAbandoned="true"时,当连接池连接数到达(getNumIdle() < 2) and (getNumActive() > getMaxActive() - 3)时便会启动连接回收,那种活动时间超过removeAbandonedTimeout="60"的连接将会被回收,同时如果logAbandoned="true"设置为true,程序在回收连接的同时会打印日志。removeAbandoned是连接池的高级功能,理论上这中配置不应该出现在实际的生产环境,因为有时应用程序执行长事务,可能这种情况下,会被连接池误回收,该种配置一般在程序测试阶段,为了定位连接泄漏的具体代码位置,被开启,生产环境中连接的关闭应该靠程序自己保证。