天天看點

tomcat假死探索與解決,tomcat connector配置

上一篇部落格介紹了解決oom導緻的程式假死,這裡探索解決程式假死的問題。

排除oom

首先懷疑還是記憶體溢出導緻的,但是:

沒有有異常日志

沒有dump檔案生成

gc.log,證明jvm堆記憶體還多着呢,如下:

[PSYoungGen: 246097K->8179K(240640K)] 1093342K->869809K(4172800K), 0.0522768 secs] [Times: user=0.18 sys=0.02, real=0.05 secs] 

minorgc後,新生代200多M,用了8M;jvm整體4G記憶體,也就用了900M。

明顯說明tomcat挂掉的時候堆中還剩餘很多記憶體。排除jvm的oom錯誤,此時heap還有很大空間,排除oom的可能性。

這裡補充幾個檢視jvm狀态的指令

jmap -heap pid :檢視某程序堆狀态

jstat -gc pid : 檢視程序的gc統計

jstack pid >> head.log : 将目前程序中線程的狀态輸出到head.log檔案中。

gc.log的分析可參考:https://blog.csdn.net/h2604396739/article/details/87815823

第一反應是我修改代碼導緻的,挨個復原并測試,發現毫無用處。

猜想一:linux的oom-killer導緻

什麼是linux的oom-killer:

Linux的malloc配置設定記憶體,不是一次到位的真配置設定了指定大小的實體記憶體,而是先承諾你,實際用到的時候才去系統配置設定,如果剛好那個時候記憶體不夠了,就會觸發oom-killer。

oom_killer(out of memory killer)是Linux核心的一種記憶體管理機制,在系統可用記憶體較少的情況下,核心為保證系統還能夠繼續運作下去,會選擇殺掉一些程序釋放掉一些記憶體。通常oom_killer的觸發流程是:程序A想要配置設定實體記憶體(通常是當程序真正去讀寫一塊核心已經“配置設定”給它的記憶體)->觸發缺頁異常->核心去配置設定實體記憶體->實體記憶體不夠了,觸發OOM。

一句話說明oom_killer的功能:

當系統實體記憶體不足時,oom_killer周遊目前所有程序,根據程序的記憶體使用情況進行打分,平均消耗記憶體越高得分越高,然後從中選擇一個分數最高的程序,殺之取記憶體。

關于oom-killer導緻的程序死亡可以參考下面兩篇文章

https://blog.csdn.net/liu251/article/details/51181847

http://www.kuqin.com/linux/20120701/321430.html

[email protected] ~ $ dmesg | grep java

[13598416.310265] INFO: task java:2320 blocked for more than 120 seconds.

[13598416.313090] java            D ffffffff8168a850     0  2320   1265 0x00000080

[17958045.456044] java invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0

[17958045.457931] java cpuset=/ mems_allowed=0

[17958045.459905] CPU: 3 PID: 2100 Comm: java Not tainted 3.10.0-514.26.2.el7.x86_64 #1

[17958045.617487] [ 1703]     0  1703   629830    19224      97        0             0 java

[17958045.641509] [ 6262]  1001  6262  2677222  1870645    3883        0             0 java

[17958045.649750] Out of memory: Kill process 6262 (java) score 936 or sacrifice child

[17958045.651089] Killed process 6262 (java) total-vm:10708888kB, anon-rss:7482580kB, file-rss:0kB, shmem-rss:0kB

[email protected] ~ $ dmesg | egrep -i -B100 'killed process' 結果:

[17958045.649750] Out of memory: Kill process 6262 (java) score 936 or sacrifice child

[17958045.651089] Killed process 6262 (java) total-vm:10708888kB, anon-rss:7482580kB, file-rss:0kB, shmem-rss:0kB

運維人員執行 cat  /var/log/messages | grep oom-killer 發現linux最近并沒有進行oom的killer,最近的一次是一周前,也就是我優化之前,當時确實占用了6G記憶體。

确定不是linux oom killer,下面兩個角度也可以佐證:

1)如果是linux的oom killer,那麼tomcat程序應該會挂掉,tomcat程序應該不再存在,現在現象是程序還在,但是服務徹底停止,程式假死.

2)系統記憶體是8G,我的jvm的heap設定的4G,現在僅僅用了不到2G,是以如果除了我的服務還有别的服務,并且相比之下我的服務整體占用記憶體較多情況下,才有可能linux系統執行oomkiller,殺掉我的程序。但實際情況是該機器上僅僅有我的tomcat,并且小于系統的8G記憶體,是以排除oom-killer的可能性。

猜想二:資料庫連接配接池耗盡導緻的程式假死

借鑒:https://blog.csdn.net/chang_yushun/article/details/88929625

以前查一次,會将所有的資料儲存到記憶體中,是以不再需要connection,現在因為oom的問題,不再緩存,是以會采取每請求每連庫,這樣對資料庫連接配接池的要求就很高了,

但是配置的最大連接配接數還是以前的20,然後如果超過了連接配接池的限制,那麼所有的請求阻塞,然後會一直累積,然後進一步導緻程式假死。修改該邏輯的時候确實考慮到會增加mysql的壓力和連結,

也去找dba确認了庫的壓力,庫沒有壓力過大的問題,也考慮到連接配接的問題,是以找dba确認了連接配接數的問題,dba回報300百左右的連接配接都不是問題,實際連接配接都是低于100的。

檢查了一下代碼中庫連接配接的配置,将代碼中關于連接配接池的maxActive配置從20修改為100,也調大空閑連接配接數,然後測試效果:

測試發現還是會存在程式假死的情況,但是有所改善,以前一天能挂三次,現在一天也就挂一次,說明增大連接配接池有一定效果,但是肯定不是導緻tomcat假死的根本原因。

猜想三:大量并發請求阻塞導緻的假死

netstat -ano | grep TIME_WAIT | wc -l

這個正常的情況下的值是低于100的,但是模拟大量請求的時候發現,該值會飙升到3000左右。對linux來說,這個值到幾萬都沒有問題,但是對tomcat來說就不一定了。

是以猜測可能是大量請求阻塞導緻的tomcat假死,修改tomcat的請求連接配接配置。原本的server.xml中的配置就是預設的,如下

    <Connector port="8080" protocol="HTTP/1.1"

               connectionTimeout="20000"

               redirectPort="8443" />

修改後如下:

<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"  acceptorThreadCount="2" 

connectionTimeout="20000"  enableLookups="false"  

maxThreads="200"    acceptCount="1000"    maxConnections="10000"   

minSpareThreads="100"  tcpNoDelay="true"   />

修改io類型:protocol="org.apache.coyote.http11.Http11NioProtocol"  四種IO模型:BIO、NIO、NIO2、APR,預設bio,修改成nio,我項目是bi系統,是以存在大量的資料需要傳輸,使用nio模式,提高性能 

處理連接配接的線程配置:acceptorThreadCount:用于接收連接配接的線程的數量,預設值是1。一般這個值需要改動的時候是因為該伺服器是一個多核CPU,如果是多核CPU 一般配置為 2.

maxThreads:用于接收和處理client端請求的最大線程數,tomcat底層将采取線程池的方式來處理用戶端請求,此參數辨別這線程池的尺寸.maxThreads意味着tomcat能夠并發執行request的個數.此值預設為200事實上"200"個線程數,已經足夠大了.本人的線上環境為maxThreads=200.對于NIO模式下,maxThreads參數應該由CPU核心數和你的線程執行内容決定,如果是查詢資料庫或者需要進行磁盤的IO需要cpu進行等待,那麼可以适當調大該值;但是如果線程的執行内容就是進行計算或者是别的需要一直占用cpu的操作,太大的值,并不能提升NIO性能,反而會使性能下降,因為線程切換(CS)将會占據CPU的大量時間。

minSpareThreads:線程池中,保持活躍的線程的最小數量,預設為10。

首先明确一下連接配接與請求的關系:連接配接是TCP層面的(傳輸層),對應socket;請求是HTTP層面的(應用層),必須依賴于TCP的連接配接實作;一個TCP連接配接中可能傳輸多個HTTP請求。

有關連接配接(三次握手四次揮手就是一個完整的連接配接)數的配置:

maxConnections: "10000",tomcat并發處理最大tcp連接配接數

acceptCount:acceptCount即是此隊列的容量,如果隊列已滿,此後所有的建立連結的請求(accept),都将被拒絕。預設為100。在高并發/短連結較多的環境中,可以适當增大此值;當長連結較多的場景中,可以将此值設定為0.

單個連接配接的能接受的請求個數(單個連接配接中能接受的最大請求數):

maxKeepAliveRequests: 每個TCP連接配接接受最大的Http請求數目,當處理一個keep alive請求達到這個最大值,Tomcat關閉這個連接配接,設定1為失效任何keep alive請求,對于BIO高并發,四層負載平衡和NoSSL情況需要失效;對于SSL APR/NIO 7層負載平衡需要激活,設定為-1是不限制,預設為100。

如果想要真正了解tomcat connector參數的含義,需要了解tomcat的通信模式:reactor:https://blog.csdn.net/h2604396739/article/details/81123143

修改後的效果:目前已經穩定運作一周時間,沒有再出現tomcat假死的場景。

假死原因分析見下篇文章:https://blog.csdn.net/h2604396739/article/details/91377054

2019.09.20補充:上面的預設配置protocol="HTTP/1.1",tomcat對應bio的說法不正确,筆者用的tomcat版本已經是8.5.而tomcat從8.0開始,protocol="HTTP/1.1"  預設IO為nio了。

那下面哪個參數發生了實際作用?

enableLookups="false"   與并發請求量無關

maxThreads="200"  預設200 未修改

acceptCount="1000"  預設100--》1000

maxConnections="10000"   nio預設10000

minSpareThreads="100"  default 10

tcpNoDelay="true" default true

acceptorThreadCount="2"  1--》2

maxThreads:tomcat起動的最大線程數,即同時處理的任務個數,預設值為200

acceptCount:當tomcat起動的線程數達到最大時,接受排隊的請求個數,預設值為100

maxConnections :最大連接配接數,bio下等于maxThreads,nio下預設10000

到底怎麼接收請求的?

bio下,因為每連接配接每線程,是以此時先達到maxthreads,然後緩存請求,最大接收請求數為maxThreads+acceptCount

nio下,先達到maxConnections,然後緩存acceptCount,超過拒絕

問題:

tomcat 7: protocol="HTTP/1.1"  預設IO為Bio,是以此時maxConnection的大小為預設的maxthreads 200,很好解釋上面的程式假死現象?

tomcat 8: protocol="HTTP/1.1"  預設IO為nio,那麼此時maxConnection的大小為10000還是maxthreads 200,按照定義”對于NIO和NIO2,預設值為10000“,應該是10000,但是protocol沒有顯式聲明nio,是以也有可能是等于maxthreads200

我用的tomcat版本8.5

如果是200也可以解釋很好解釋上面的程式假死現象。

如果是10000,那不應該出現假死,而且即使出現了假死,上面的配置也僅僅是 acceptCount="1000"  預設100--》1000發生了作用,這個跟maxConnections(10000)相比也是杯水車薪,為什麼能解決假死?

希望知道的大神幫忙解答。。。

參考:

bio nio 和aio差別可以參考文章;可以參考https://blog.csdn.net/h2604396739/article/details/82534253

http://tomcat.apache.org/tomcat-7.0-doc/config/http.html---tomcat connector配置官網說明

https://blog.csdn.net/m0_37797416/article/details/83505663----tomcat中server.xml中Connector各個參數的意義

https://www.cnblogs.com/kismetv/p/7806063.html#t2--詳解tomcat的連接配接數與線程池

https://blog.csdn.net/binglong_world/article/details/80748520----杜絕假死,Tomcat容器設定最大連接配接數

繼續閱讀