本文转载自微信公众号《小姐姐的狗》,作者:小姐姐品味。转载本文请联系姐姐养的狗狗公众号。很久很久以前,我有一段痛苦的回忆。被故障驱动的感觉在脑海里久久萦绕,无法驱散。原因无他,有些小伙伴已经开始了线程池的暴力使用模式。没错,就是下面这篇文章。致命故障!炸毁投资人!我需要简要回顾一下。主要原因是开发人员为每个方法调用创建了一个单独的线程池。这样一来,如果请求数量增加,整个操作系统的压力就会耗尽,最终所有业务都无法响应。我一直认为这是一个非常偶然的低级错误,出现频率非常低。但是随着这样的故障越来越多,xjjdog意识到这是一个普遍的现象。以异步性能优化为目的,导致整体业务不可用,这是一个非常尴尬的优化。一、Spring的异步代码Spring作为Java世界的杠杆子框架,以其过度封装的API而深受开发者的喜爱。按照语义编程的逻辑,只要某些关键字在语言层面是可以接受的,我们就可以添加。比如@Async注解。一直想不通是什么让开发者有勇气加上这个@Async注解,因为这种涉及到多线程的东西,就算是自己创建一个线程,也是心存敬畏,唯恐扰乱了运行的安宁系统。像@Async这样的黑盒真的可以这么流畅的使用吗?让我们调试代码,让子弹飞一会儿。首先生成一个小工程,然后在主类中添加必要的注解。嗯,不要忘了这个链接,不然你后面加的评论就没用了。@SpringBootApplication@EnableAsyncpublicclassDemoApplication{创建一个用@Async注解的方法。@ComponentpublicclassAsyncService{@Asyncpublicvoidasync(){try{Thread.sleep(1000);System.out.println(Thread.currentThread());}catch(Exceptionex){ex.printStackTrace();然后做一个对应的测试接口,访问的时候会调用这个async方法。@ResponseBody@GetMapping("test")publicvoidtest(){service.async();}访问时直接打断点获取执行异步线程的线程池。可以看出,异步任务使用了corePoolSize=8的线程池,阻塞队列使用了无界队列LinkedBlockingQueue。一旦采用这样的组合,最大线程数就没有用了,因为超过8个线程的任务都会被放到无界队列中。让下面的代码成为一个装饰品。thrownewTaskRejectedException("Executor["+executor+"]没有接受任务:"+task,var4);如果你的访问量很大,这些任务都会堆积在LinkedBlockingQueue中。如果情况好一些,这些任务的执行就会变得很延迟;如果情况更糟,任务过多会直接导致内存溢出OOM!你可能会说,我可以自己指定另一个ThreadPoolExceute,然后用@Async注解来声明。说这话的同学肯定是比较有本事的,或者Review的代码比较少,没受过猪队友的洗礼。2、SpringBoot救了你SpringBoot是个好东西。在TaskExecutionAutoConfiguration中,默认的Executor是通过生成ThreadPoolTask??Executor的Bean来提供的。@ConditionalOnMissingBean({Executor.class})publicThreadPoolTask??ExecutorapplicationTaskExecutor(TaskExecutorBuilderbuilder){returnbuilder.build();}就是我们上面说的。没有SpringBoot的帮助,Spring会默认使用SimpleAsyncTaskExecutor。请参见org.springframework.aop.interceptor.AsyncExecutionInterceptor。@Override@Nullableprotected执行器getDefaultExecutor(@NullableBeanFactorybeanFactory){执行器defaultExecutor=super.getDefaultExecutor(beanFactory);return(defaultExecutor!=null?defaultExecutor:newSimpleAsyncTaskExecutor());}这就是Spring所做的。SimpleAsyncTaskExecutor类设计得很糟糕,因为它每次执行都会创建一个单独的线程,根本没有共享线程池。比如你的TPS是1000,你异步执行任务,那么你每秒就会产生1000个线程!这明显是要耗尽操作系统的节奏。protectedvoiddoExecute(Runnabletask){Threadthread=(this.threadFactory!=null?this.threadFactory.newThread(task):createThread(task));thread.start();}3.End使用新线程的处理方式会很糟糕。但是就Spring本身来说,引用SimpleAsyncTaskExecutor类的地方也不少,包括比较流行的AsyncRestTemplate。这暴露了很多风险,尤其是在这些列表中看到了redis。这个类的设计使得任务的执行非常不可控。看着这个API,感觉Spring进入了一种设计痴迷的状态。这东西隐藏的bug可能更深!比如org.springframework.context.event.EventListener注解就是用来实现DDD所谓的事件驱动模式的。很多框架直接设置了AsyncRestTemplate,那就等死吧。赶快把SimpleAsyncTaskExecutor添加到你的API黑名单,或者埋坑名单!创建线程有那么难吗?您需要使用Spring创建的线程吗?有时候真的想不通暴露这样一个接口的目的是什么。我们甚至还没有弄清楚本机线程池。你还包起来,方便我们怪罪!作者简介:品味小姐姐(xjjdog),一个不允许程序员走弯路的公众号。专注于基础架构和Linux。十年架构,每天百亿流量,与你探讨高并发世界,给你不一样的滋味。
