背景
在我们日常业务开发过程中,或多或少都会用到并发的功能,那么在用到并发功能的过程中,就肯定会碰到下面这个问题
并发线程池到底设置多大呢?
引用>通常有点年纪的程序员或许都听说这样一个说法(其中N代表CPU的个数)
<李> CPU密集型应用,线程池大小设置为N + 1 李> <李> IO密集型应用,线程池大小设置为2 n 李>
这个说法到底是不是正确的呢?
其实这是极不正确的。那为什么呢?
<李>首先我们从反面来看,假设这个说法是成立的,那我们在一台服务器上部署多少个服务都无所谓了。因为线程池的大小只能服务器的核数有关,所以这个说法是不正确的。那具体应该怎么设置大小呢?李> <李>假设这个应用是两者混合型的,其中任务即有CPU密集,也有IO密集型的,那么我们改怎么设置呢?是不是只能抛硬盘来决定呢?李>
那么我们到底该怎么设置线程池大小呢?有没有一些具体实践方法来指导大家落地呢?让我们来深入地了解一下。
小定律(利特尔法则)
<代码>一个系统请求数等于请求的到达率与平均每个单独请求花费的时间之乘积代码>假设服务器单核的,对应业务需要保证请求量(每秒):10,真正处理一个请求需要1秒,那么服务器每个时刻都有10个请求在处理,即需要10个线程
同样,我们可以使用利特尔法则(小定律)来判定线程池大小。我们只需计算请求到达率和请求处理的平均时间,然后,将上述值放到利特尔法则(小定律)就可以算出系统平均请求数。估算公式如下
* 线程池大?((线程IO时间+线程CPU时间)/线程CPU时间) CPU数目* *
引用>具体实践
通过公式,我们了解到需要3个具体数值
<李>一个请求所消耗的时间(线程IO时间+线程CPU时间)李> <李>该请求计算时间(线程CPU时间)李> <李> CPU数目李>
请求消耗时间
Web服务容器中,可以通过过滤器来拦截获取该请求前后消耗的时间
<代码>公共类MoniterFilter实现滤波器{ 私有静态最终日志记录器=LoggerFactory.getLogger (MoniterFilter.class); @Override 公共空间的doFilter (ServletRequest请求,ServletResponse响应FilterChain链)抛出IOException, ServletException { 长开始=System.currentTimeMillis (); HttpServletRequest httpRequest=(HttpServletRequest)请求; HttpServletResponse httpResponse=(HttpServletResponse)反应; 字符串uri=httpRequest.getRequestURI (); 字符串params=getQueryString (httpRequest); 尝试{ 链。doFilter (httpRequest httpResponse); 最后}{ 长期成本=System.currentTimeMillis()——开始; logger.info(“访问url({}{}),时间成本({})ms)”, uri, params,成本); } 私人字符串getQueryString (HttpServletRequest点播){ StringBuilder缓冲=new StringBuilder (“?”); EnumerationemParams=req.getParameterNames (); 尝试{ 而(emParams.hasMoreElements ()) { 字符串sParam=emParams.nextElement (); 字符串sValues=req.getParameter (sParam); buffer.append (sParam) .append ("=") .append (sValues) .append (", "); } 返回缓冲区。substring (0, buffer.length () - 1); }捕捉(异常e) { 记录器。错误(“参数错误”,buffer.toString ()); } 返回"; } }代码> CPU计算时间
CPU计算时间=请求总耗时,CPU IO时间
引用>假设该请求有一个查询DB的操作,只要知道这个查询DB的耗时(CPU IO时间),计算的时间不就出来了嘛,我们看一下怎么才能简洁,明了的记录DB查询的耗时。通过(JDK动态代理/CGLIB)的方式添加AOP切面,来获取线程IO耗时。代码如下,请参考
<代码>公共类DaoInterceptor实现MethodInterceptor { 私有静态最终日志记录器=LoggerFactory.getLogger (DaoInterceptor.class); @Override 公共对象调用(MethodInvocation调用)抛出Throwable { 秒表看=new秒表(); watch.start (); 对象的结果=零; Throwable t=零; 尝试{ 结果=invocation.proceed (); }捕捉(Throwable e) { t===null吗?空:e.getCause (); 把e; 最后}{ watch.stop (); logger.info(“女士({})”,watch.getTotalTimeMillis ()); } 返回结果; } }如何确定线程池大小