網站首頁 編程語言 正文
生產事故記錄
- 背景
- 排查過程
- 1、Grafana監控報表
- 2、存活探針相關Yaml
- 3、tomcat線程池與數據庫連接池
- 4、堆棧信息與Jprofile的使用
背景
由于項目要求99.9%可用性,而此次事故導致宕機了五個小時,以后每個季度只能有一個小時的不可用時間。正好問題的排查一直都屬于程序員的寶貴經驗,因此就在這記錄下來。
發生的現象:
上午10點鐘左右,開始出現頁面訪問異常情況,檢查發現某個服務每間隔一段時間就會重啟一會。后續觀察是每固定20分鐘就會被k8s重啟一次。
出現了異常重啟的情況,首先就要查明異常的原因,直接原因比如CPU、內存等原因,間接原因就是看是哪里引起的每20分鐘重啟。
排查過程
1、Grafana監控報表
在Grafana上看到,基本在重啟前的邊緣上的情況:
- CPU比較正常,一直都是處于較低的使用率水平。
- JVM內存在有幾次重啟前都接近滿了,可能能夠被回收,但還沒有觸發Major GC,是個值得排查的地方,同時得去看看日志中有沒有out of memory的報錯日志。
- Tomcat連接池的線程數都滿了,達到最大的使用數量了,因為我們這里使用的默認的配置,200個就已經滿了。這是最大的嫌疑。
在上面三點基本看來最應該排查的就是Tomcat連接池。
2、存活探針相關Yaml
可能有人會覺得,就算連接池滿了,或者JVM發生out of memory了,都不應該會直接導致服務的重啟,這里因為我們用的是k8s,配置了服務的就緒探針和存活探針,每隔10秒鐘調用一次服務的接口來確保服務存活。當失敗超過3次以后就會認為這個服務已經死亡,就會開始重新啟動一個Pod,并remove掉原來的Pod。所以才出現了這樣的情況。
livenessProbe:
failureThreshold: 3
httpGet:
path: /actuator/health
port: 9902
scheme: HTTP
initialDelaySeconds: 60
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
首先我們是先調大了重啟的服務的Tomcat并發連接數到500,但是發現了一點,即使連接數從200上升到了500,服務依舊在不斷重啟當中。
那么就再來排查是什么占用了如此多的連接數,每間隔20分鐘出現一次,基本上能夠確定是某個定時任務引起的,再查看Grafana上相關http請求調度頻率,最終定位到了問題,同時看到那邊的代碼寫法也是有問題的。
3、tomcat線程池與數據庫連接池
本來是一個要批量獲取數據的功能,由于之前就存在了feignClient的查詢單個數據的接口,于是這里為了圖方便,便直接使用了這個接口,而不是新增一個批量查詢數據的接口。正好又是因為多次的查詢比較慢,所以該同事使用了多線程異步的方式去獲取數據,每有一個id,都會開啟一個線程去查詢,當有幾百上千條數據的時候,就會有大量的線程同時請求過去,從而造成了目標服務的Tomcat連接數不足的問題。
業務代碼類似下面示例:
for (int i = 0; i < list.size(); i++) {
CompletableFuture<String> future=CompletableFuture.runAsync(()->
//調用重啟服務的接口
);
}
但是有一個問題就是,Tomcat并發連接數提高到了500了,一個接口訪問不到1秒鐘的時間,最多也不過幾百上千的請求次數,按理來說在幾秒之內處理完這些請求,不應該會再出現這樣的情況才對,然而k8s在連續的三次存活探測中依然都直接超時了。那么除了Tomcat并發連接數,還有什么會拖慢請求呢?
所以就排查到了數據庫最大連接數,目前數據庫的最大連接數被設置成了20,不論你Tomcat連接數設置的多大,系統的性能值取決于最短板的地方,而這個就是數據庫最大連接數,由于我們的系統是TOB服務,這個服務是查詢和業務對接的,頁面對接的信息,并不是處理數據的服務,因此大家之前也沒有對這個服務的配置有過太多關注,才導致了這種情況。那么首先要做的就是提高數據庫連接池最大連接數到100,同時將那個異步多次調用接口的定時任務進行優化,就解決這個問題了。
4、堆棧信息與Jprofile的使用
除了對連接池等做了一些排查之外,同時我們還對JVM堆內存進行了分析,排除JVM的問題。
其實一般情況下當發生內存無法正常回收的情況,是能夠直接在服務日志中看到 java.lang.OutOfMemoryError: Java heap space的對內存溢出的日志的,因此只需要打開JVM heap dump on out of memory的參數即可,那么在發生堆內存溢出時,就會將當時的快照保存下來。
不過我們那暫時沒有出現內存溢出的錯誤日志。一個就是考慮到發生內存溢出,我記得是在多少次GC之內回收率小于某個固定閾值之后才會拋出異常,它并不是一瞬間就會發生的,所以那么會不會在這之前k8s就將Pod重啟了呢也說不準。
我們選取了POD重啟前夕的情況作為快照手動導了出來進行分析。使用JProfiler工具進行分析。
基本上只需要看兩個東西就行了。
一個是占用內存和數量最多的是哪些類,尤其是關注其中和我們業務相關的類:
還有一個就是關注大對象,這也是最值得關注的:
可以看到,上述圖片基本顯示了這些對象基本沒有和我們業務相關的類,如果這是heap dump on out of memory拿出來的快照,而某些業務相關類占了很大內存和數量,那這就是我們極為需要關注的。
再就是Biggest Objects這張圖,產生內存溢出的原因,基本就直接在這里面了,因為導致內存溢出的直接原因就是因為有大的對象不能夠即使回收,現在基本對象回收都是可達性分析法,上面的最大類基本也就是大量對象的根引用所在,下面掛了一大群因此而不能回收的對象,我們可以直接點擊一直往下,排查到與業務代碼中相關的原因。
因為我這里并沒有發生內存溢出,就拿spring下的DefaultListableBeanFactory簡單舉個例子
如DefaultListableBeanFactory下大部分的內存占用就是這個beanDefinitionMap的HashMap所占用的內存,這個Map下又是由他的Node節點占用了大部分內存。
我們可以直接在Jprofiler中點擊+號一直往下找,就很容易從根節點一直循著引用鏈找到與我們業務代碼相關的問題點在哪了。
原文鏈接:https://blog.csdn.net/weixin_44228698/article/details/124160526
- 上一篇:Synchronized鎖優化
- 下一篇:pg分區表的實踐
相關推薦
- 2022-04-04 react報錯‘react-scripts‘ 不是內部或外部命令,也不是可運行的程序
- 2022-12-23 Android入門之SubMenu的實現詳解_Android
- 2022-10-23 Android用于加載xml的LayoutInflater源碼超詳細分析_Android
- 2022-06-30 Oracle中游標Cursor的用法詳解_oracle
- 2022-02-20 給定一個數組,讓數組的每一項都乘以2幾種實現方法
- 2022-03-01 ivew 表格中列中使用tooltip文字提示,并且文字過長顯示...
- 2022-12-24 c++重載運算符時返回值為類的對象或者返回對象的引用問題_C 語言
- 2022-10-01 Python類和對象基礎入門介紹_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支