偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

記一次線程池引發(fā)的故障 排查下來是誰的鍋

網(wǎng)絡(luò) 通信技術(shù)
敖丙之前在工作中遇到一個問題,我定義了一個線程池來執(zhí)行任務(wù),但是程序執(zhí)行結(jié)束后任務(wù)沒有全部執(zhí)行完,當(dāng)時心態(tài)就差點崩了。

本文轉(zhuǎn)載自微信公眾號「 三太子敖丙」,轉(zhuǎn)載本文請聯(lián)系 三太子敖丙公眾號。

[[329971]]

 背景

敖丙之前在工作中遇到一個問題,我定義了一個線程池來執(zhí)行任務(wù),但是程序執(zhí)行結(jié)束后任務(wù)沒有全部執(zhí)行完,當(dāng)時心態(tài)就差點崩了。

 

業(yè)務(wù)場景是這樣的:由于統(tǒng)計業(yè)務(wù)需要,訂單信息需要從主庫中經(jīng)過統(tǒng)計業(yè)務(wù)代碼寫入統(tǒng)計庫(中間需要邏輯處理所以不能走binlog)。

由于代碼質(zhì)量及歷史原因,目前的重新統(tǒng)計接口是單線程的,粗略算了算一共有100萬條訂單信息,每100條的處理大約是10秒,所以理論上處理完全部信息需要28個小時,這還不算因為 mysql 中 limit 分頁導(dǎo)致的后期查詢時間以及可能出現(xiàn)的內(nèi)存溢出導(dǎo)致中止統(tǒng)計的情況。

基于上述的原因,以及最重要的一點:統(tǒng)計業(yè)務(wù)是根據(jù)訂單所屬的中心進(jìn)行的,各個中心同時統(tǒng)計不會導(dǎo)致臟數(shù)據(jù)。

所以,我計劃使用線程池,為每一個中心分配一條線程去執(zhí)行統(tǒng)計業(yè)務(wù)。

業(yè)務(wù)實現(xiàn)

  1. // 線程工廠,用于為線程池中的每條線程命名 
  2. ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("stats-pool-%d").build(); 
  3.  
  4. // 創(chuàng)建線程池,使用有界阻塞隊列防止內(nèi)存溢出 
  5. ExecutorService statsThreadPool = new ThreadPoolExecutor(5, 10, 
  6.                 0L, TimeUnit.MILLISECONDS, 
  7.                 new LinkedBlockingQueue<>(100), namedThreadFactory); 
  8. // 遍歷所有中心,為每一個centerId提交一條任務(wù)到線程池 
  9. statsThreadPool.submit(new StatsJob(centerId)); 

在創(chuàng)建完線程池后,為每一個 centerId 提交一條任務(wù)到線程池,在我的預(yù)想中,由于線程池的核心線程數(shù)為5,最多5個中心同時進(jìn)行統(tǒng)計業(yè)務(wù),將大大縮短100萬條數(shù)據(jù)的總統(tǒng)計時間,于是萬分興奮的我開始執(zhí)行重新統(tǒng)計業(yè)務(wù)了。

問題

在跑了很久之后,當(dāng)我查看統(tǒng)計進(jìn)度時,我發(fā)現(xiàn)了一個十分詭異的問題(如下圖)。

藍(lán)框標(biāo)出的這條線程是 WAIT 狀態(tài),表明這條線程是空閑狀態(tài),但是從日志中我看到這條線程并沒有完成它的任務(wù),因為這個中心的數(shù)據(jù)有10萬條,但是日志顯示它只跑到了一半,之后就再無關(guān)于此中心的日志了。

 

記一次線程池故障,害阿里程序員差點被開除

 

這是什么原因?

我當(dāng)場就想到了三歪,肯定是三歪今天早上上班左腳先邁進(jìn)公司的,導(dǎo)致代碼水土不服,一定是這樣,我去找他去。

 

調(diào)試及原因

咳咳三歪是開玩笑的,我們還是需要找到真實原因。

可以想到的是,這條線程因為某些原因被阻塞了,并且沒有繼續(xù)進(jìn)行下去,但是日志又沒有任何異常信息…

可能有經(jīng)驗的工程師已經(jīng)知道了原因…

由于個人水平的線程,暫時沒有找到原因的我只能放棄使用線程池,乖乖用單線程跑…

幸運的是,單線程跑的任務(wù)竟然拋錯了(為什么要說幸運?),于是馬上想到,之前那條 WAIT 狀態(tài)的線程可能是因為同樣的拋錯所以被中斷了,導(dǎo)致任務(wù)沒有繼續(xù)進(jìn)行下去。

為什么說幸運?因為如果單線程的任務(wù)沒有拋錯的話,我可能很久都想不到是這個原因。

 

深入探究線程池的異常處理

工作上的問題到這里就找到原因了,之后的解決過程也十分簡單,這里就不提了。

但是疑問又來了,為什么使用線程池的時候,線程因異常被中斷卻沒有拋出任何信息呢?還有平時如果是在 main 函數(shù)里面的異常也會被拋出來,而不是像線程池這樣被吞掉。

如果子線程拋出了異常,線程池會如何進(jìn)行處理呢?

我提交任務(wù)到線程池的方式是: threadPoolExecutor.submit(Runnbale task); ,后面了解到使用 execute() 方式提交任務(wù)會把異常日志給打出來,這里研究一下為什么使用 submit 提交任務(wù),在任務(wù)中的異常會被“吞掉”。

對于 submit() 形式提交的任務(wù),我們直接看源碼:

  1. public Future<?> submit(Runnable task) { 
  2.     if (task == null) throw new NullPointerException(); 
  3.     // 被包裝成 RunnableFuture 對象,然后準(zhǔn)備添加到工作隊列 
  4.     RunnableFuture<Void> ftask = newTaskFor(task, null); 
  5.     execute(ftask); 
  6.     return ftask; 

它會被線程池包裝成 RunnableFuture 對象,而最終它其實是一個 FutureTask 對象,在被添加到線程池的工作隊列,然后調(diào)用 start() 方法后, FutureTask 對象的 run() 方法開始運行,即本任務(wù)開始執(zhí)行。

  1. public void run() { 
  2.     if (state != NEW || !UNSAFE.compareAndSwapObject(this,runnerOffset,null, Thread.currentThread())) 
  3.         return
  4.     try { 
  5.         Callable<V> c = callable; 
  6.         if (c != null && state == NEW) { 
  7.             V result; 
  8.             boolean ran; 
  9.             try { 
  10.                 result = c.call(); 
  11.                 ran = true
  12.             } catch (Throwable ex) { 
  13.                 // 捕獲子任務(wù)中的異常 
  14.                 result = null
  15.                 ran = false
  16.                 setException(ex); 
  17.             } 
  18.             if (ran) 
  19.                 set(result); 
  20.         } 
  21.     } finally { 
  22.         runner = null
  23.         int s = state; 
  24.         if (s >= INTERRUPTING) 
  25.             handlePossibleCancellationInterrupt(s); 
  26.     } 

在 FutureTask 對象的 run() 方法中,該任務(wù)拋出的異常被捕獲,然后在setException(ex); 方法中,拋出的異常會被放到 outcome 對象中,這個對象就是 submit() 方法會返回的 FutureTask 對象執(zhí)行 get() 方法得到的結(jié)果。

但是在線程池中,并沒有獲取執(zhí)行子線程的結(jié)果,所以異常也就沒有被拋出來,即被“吞掉”了。

這就是線程池的 submit() 方法提交任務(wù)沒有異常拋出的原因。

線程池自定義異常處理方法

在定義 ThreadFactory 的時候調(diào)用

setUncaughtExceptionHandler方法,自定義異常處理方法。例如:

  1. ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() 
  2.                 .setNameFormat("judge-pool-%d"
  3.                 .setUncaughtExceptionHandler((thread, throwable)-> logger.error("ThreadPool {} got exception", thread,throwable)) 
  4.                 .build(); 

這樣,對于線程池中每條線程拋出的異常都會打下 error 日志,就不會看不到了。

后續(xù)

在修復(fù)了單個線程任務(wù)的異常之后,我繼續(xù)使用線程池進(jìn)行重新統(tǒng)計業(yè)務(wù),終于跑完了,也終于完成了這個任務(wù)。

事后我也叫三歪以后進(jìn)公司一定要先邁出右腳進(jìn)來,不然對寫代碼的風(fēng)水影響很大。

 

小結(jié):丙這個事故也給大家一個警示,使用線程池時需要注意,子線程的異常,如果沒有被捕獲就會丟失,可能會導(dǎo)致后期根據(jù)日志調(diào)試時無法找到原因。

 

責(zé)任編輯:武曉燕 來源: 三太子敖丙
相關(guān)推薦

2021-04-13 08:54:28

dubbo線程池事故排查

2022-12-17 19:49:37

GCJVM故障

2019-03-15 16:20:45

MySQL死鎖排查命令

2021-05-13 08:51:20

GC問題排查

2022-11-29 21:26:26

跨域配置

2021-08-20 11:35:04

服務(wù)運維 故障

2023-01-04 18:32:31

線上服務(wù)代碼

2023-04-06 07:53:56

Redis連接問題K8s

2018-08-07 10:54:02

HTTPS郵箱瀏覽器

2021-12-02 07:50:30

NFS故障內(nèi)存

2024-04-10 08:48:31

MySQLSQL語句

2021-11-23 21:21:07

線上排查服務(wù)

2017-12-19 14:00:16

數(shù)據(jù)庫MySQL死鎖排查

2024-06-28 10:01:04

2021-11-01 17:29:02

Windows系統(tǒng)Fork

2021-01-08 13:52:15

Consul微服務(wù)服務(wù)注冊中心

2022-11-16 08:00:00

雪花算法原理

2017-09-01 09:17:51

DNS緩存慘案

2021-03-29 12:35:04

Kubernetes環(huán)境TCP

2018-07-03 10:49:22

性能故障排查
點贊
收藏

51CTO技術(shù)棧公眾號