SpringBoot線上服務(wù)假死,CPU內(nèi)存正常,什么情況?
背景
開發(fā)小伙伴都知道線上服務(wù)掛掉,基本都是因為cpu或者內(nèi)存不足,出現(xiàn)GC頻繁O(jiān)OM之類的情況。本篇文章區(qū)別以上的情況給小伙伴們帶來不一樣的服務(wù)掛掉。
還記得嗶哩嗶哩713事故中那場詭計多端的0嗎?

對就是這個0,和本次事故沒關(guān)系,但深受啟發(fā)。
問題排查
老規(guī)矩在集群環(huán)境中同一個服務(wù)幾個節(jié)點無響應(yīng)。如不及時解決會可能形成雪崩效應(yīng)。
優(yōu)先查看服務(wù)日志是否有報錯,禮貌習(xí)慣性查看服務(wù)cpu及內(nèi)存情況。先復(fù)習(xí)下,若服務(wù)無報錯。cpu或內(nèi)存出現(xiàn)異常,按如下步驟排查。
常規(guī)排查
1、查看服務(wù)進程中線程情況
top?-H?-p?pid
或
ps?-mp?pid?-o?THREAD,tid,time
2、查看系統(tǒng)異常線程16進制
printf?“%x\n”?nid
3、查看異常線程堆棧信息
jstack?pid?|?grep?number
查看占用最大內(nèi)存對象前一百
jmap?-histo?pid|head?-100
導(dǎo)出到文件
jstack?-l?PID?>>?a.log
或dump信息使用工具Mat或JProfiler查看
jmap?-dump:live,format=b,file=/dump.bin?pid
經(jīng)過上面一通手法操作,足以解決此類常規(guī)報錯了,通常大多是原因各種循環(huán)遞歸、或數(shù)據(jù)庫慢查詢等。
Mat使用
在MAT中,會有兩種大小表示:
Shallow Size:表示對象自身占用的內(nèi)存大小,不包括它引用的對象。Retained size:當前對象內(nèi)存大小+當前對象直接或間接引用的對象大小,全部的總和,簡單理解,就是當前對象被GC后,總共能釋放的內(nèi)存大小。
Histogram視圖
以Class Name為維度,分別展示各個類的對象數(shù)量。它默認是以byte為單位的,

要顯示讓單位展示出來,點擊Window->Preferences選擇最后一項,點擊Apply and Close
再重新打開Histogram視圖,就會生效了。

Leak Suspects
報表很直觀地展現(xiàn)了一個餅圖,圖中顏色深的部分表示可能存在內(nèi)存泄漏的嫌疑。
通過這個指標可以快速定位內(nèi)存泄漏地方出現(xiàn)在哪個類方法里的哪行代碼。
本次問題排查
1、 信息收集分析
因服務(wù)健康監(jiān)測無響應(yīng),cpu及內(nèi)存情況正常,直接查看堆棧信息,看看線程都在干什么
jstack?-l?PID?>>?a.log
Jstack的輸出中,Java線程狀態(tài)主要是以下幾種:
RUNNABLE線程運行中或I/O等待BLOCKED線程在等待monitor鎖(synchronized關(guān)鍵字)TIMED_WAITING線程在等待喚醒,但設(shè)置了時限WAITING線程在無限等待喚醒
發(fā)現(xiàn)都是WAITING線程。
"http-nio-8888-exec-6666"?#8833?daemon?prio=5?os_prio=0?tid=0x00001f2f0016e100?nid=0x667d?waiting?on?condition?[0x00002f1de3c5200]
java.lang.Thread.State:?WAITING?(parking)
at?sun.misc.Unsafe.park(Native?Method)
-?parking?to?wait?for??<0x00000007156a29c8>?(a?java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at?java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at?java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at?com.alibaba.druid.pool.DruidDataSource.takeLast(DruidDataSource.java:1897)
at?com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1458)
at?com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1253)
at?com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:4619)
at?com.alibaba.druid.filter.stat.StatFilter.dataSource_getConnection(StatFilter.java:680)
at?com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:4615)
at?com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1231)
at?com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1223)
at?com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:90)
at?com.baomidou.dynamic.datasource.ds.ItemDataSource.getConnection(ItemDataSource.java:56)
at?com.baomidou.dynamic.datasource.ds.AbstractRoutingDataSource.getConnection(AbstractRoutingDataSource.java:48)
at?org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)
at?org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77)
at?org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:82)
at?org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction.java:68)
at?org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor.java:336)
at?org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:84)
at?org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:62)
at?org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)
at?org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
at?org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
at?com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:143)
at?org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
at?com.sun.proxy.$Proxy571.query(Unknown?Source)
2、定位關(guān)鍵信息,追蹤源代碼
??at?java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
??at?com.alibaba.druid.pool.DruidDataSource.takeLast(DruidDataSource.java:1897)
DruidConnectionHolder?takeLast()?throws?InterruptedException,?SQLException?{
try?{
while?(poolingCount?==?0)?{
emptySignal();?//?send?signal?to?CreateThread?create?connection
??????????????if?(failFast?&&?isFailContinuous())?{
??????????????????throw?new?DataSourceNotAvailableException(createError);
??????????????}
??????????????notEmptyWaitThreadCount++;
??????????????if?(notEmptyWaitThreadCount?>?notEmptyWaitThreadPeak)?{
??????????????????notEmptyWaitThreadPeak?=?notEmptyWaitThreadCount;
??????????????}
??????????????try?{
??????????????????//?數(shù)據(jù)庫的連接都沒有釋放且被占用,連接池中無可用連接,導(dǎo)致請求被阻塞
??????????????????notEmpty.await();?//?signal?by?recycle?or?creator
??????????????}?finally?{
??????????????????notEmptyWaitThreadCount--;
??????????????}
??????????????notEmptyWaitCount++;
??????????????if?(!enable)?{
??????????????????connectErrorCountUpdater.incrementAndGet(this);
??????????????????throw?new?DataSourceDisableException();
??????????????}
??????????}
??????}?catch?(InterruptedException?ie)?{
??????????notEmpty.signal();?//?propagate?to?non-interrupted?thread
??????????notEmptySignalCount++;
??????????throw?ie;
??????}
??????decrementPoolingCount();
??????DruidConnectionHolder?last?=?connections[poolingCount];
??????connections[poolingCount]?=?null;
??????return?last;
}
結(jié)合日志報錯定位到問題代碼。因報錯可用連接沒有正常釋放,導(dǎo)致一直await卡死。
問題代碼如下:
try?{
??SqlSession?sqlSession?=?sqlSessionFactory.openSession(ExecutorType.BATCH);
??TestMapper?mapper?=?sqlSession.getMapper(TestMapper.class);
??mapper.insetList(list);
??sqlSession.flushStatements();
}?catch?(Exception?e)?{
???e.printStackTrace();
}
問題復(fù)現(xiàn)
按照以上信息在多活環(huán)境復(fù)現(xiàn)。因線程被打滿且都在等待導(dǎo)致監(jiān)控檢查無響應(yīng)。
tomcat線程被打滿:

tomcat默認參數(shù):
最大工作線程數(shù),默認200。
server.tomcat.max-threads=200
最大連接數(shù)默認是10000
server.tomcat.max-connections=10000
等待隊列長度,默認100。
server.tomcat.accept-count=100
最小工作空閑線程數(shù),默認10。
server.tomcat.min-spare-threads=100
Druid連接池的默認參數(shù)如下:

Druid連接池的配置參數(shù)如下:


解決
1、Druid連接池的配置超時參數(shù)
spring:?
??redis:
????host:?localhost
????port:?6379
????password:?
??datasource:
????druid:
??????stat-view-servlet:
????????enabled:?true
????????loginUsername:?admin
????????loginPassword:?123456
????dynamic:
??????druid:
????????initial-size:?5
????????min-idle:?5
????????maxActive:?20
????????maxWait:?60000
????????timeBetweenEvictionRunsMillis:?60000
????????minEvictableIdleTimeMillis:?300000
????????validationQuery:?SELECT?1?FROM?DUAL
????????testWhileIdle:?true
????????testOnBorrow:?false
????????testOnReturn:?false
????????poolPreparedStatements:?true
????????maxPoolPreparedStatementPerConnectionSize:?20
????????filters:?stat,slf4j,wall
????????connectionProperties:?druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
2、異常及時關(guān)閉連接
sqlSession.close();
