昨天下午公司的短信發送服務掛掉,查日志發現有些短信服務提供商的服務器time out。馬上聯系對方,確認服務已經恢復正常,我們立馬重啟服務,恢復正常。
我們的短信服務是起一個線程T1從redis list去拿消息,然后創建一個發送短信的任務線程扔到線程池里執行,每一個發送短信的任務都會連接服務商的服務,time out就是從這里拋出來的,可是連接time out怎么能導致我們的服務掛掉呢? 還好當時做了thread dump,分析了下dump 文件,發現線程T1掛掉了,看代碼發現該線程的run方法塊里面只catch住了Exception,極有可能是有Throwable的錯誤拋了出來,導致該線程意外終止。我當時能夠想到的有OutOfMemmory 跟 StackOverflow,經過分析定位到以下我們封裝的線程池。
private ThreadPoolExecutor threadPoolTaskExecutor = new ThreadPoolExecutor(30, 100, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4), new ThreadFactoryBuilder("SmsServiceManager-threadPoolTaskExecutor"), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { if (!executor.isShutdown()) { executor.execute(r); } } });
注意該類指定了RejectedExecutionHandler,如果任務被reject那么將任務繼續添加到線程池。WTF!!!!真暴力啊,線程池拒絕了你的任務,你不做任何處理又把該任務扔進線程池,這就是stack over flow的原因!!!線程池一共有30個worker線程,當時正好某些服務商的短信服務出問題,所以這30個worker線程一直卡在連接對方服務上,當線程池中任務的數量超過100個,那么后來的任務將會被線程池reject,而你被reject后將該任務再一次扔進線程池,產生死循環直到線程T1棧溢出。
最后我們的解決方案是:
1. 在線程T1的run方法塊里面catch住Throwable。
2. 任務被線程池reject后將消息重新放到redis隊列里。
這兩天剛剛接手短信這塊,坑很多,且行且小心。
