昨天下午公司的短信发送服务挂掉,查日志发现有些短信服务提供商的服务器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队列里。
这两天刚刚接手短信这块,坑很多,且行且小心。