67

记一次线程池调优经历 - 风的轻语

 6 years ago
source link: https://www.cnblogs.com/superfj/p/8313469.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近的一个项目需要用到招标,临时加了给我们的系统增加了一个性能需求,多少呢?

一秒钟300次NTP(不知道ntp的同学可以百度一下),平均3ms一次啊,没测试过,心里没有底。(⊙o⊙)…

                                                           

情境介绍:

系统是一个时间服务器系统,客户端就是window系统,或者其他的一些服务器,来向时间服务器同步时间。

1167080-20180118223629787-2077612282.png

默认的window会向这个time.winodows.com进行时间同步,当然你也可以换成其他时间同步服务器。

划重点了:服务端NTP接口采用的是netty框架写的一个接口,netty想必大家都了解的吧,nio通信,性能超好的。

测试代码是使用Executors.newFixedThreadPool写的客户端,10个线程数发送ntp包

第一次测试

数据库连连接池最大设置为40个,测试结果俩一秒钟28次,是的你没有看错,连十分之一都没有,怎么这么差劲啊

达不到预期啊,不行啊,这不就达不到要求的了吗,得改啊,哪里改啊,怎么改啊?

回到代码中去,顺藤摸瓜找到具体业务类,就是继承SimpleChannelInboundHandler的类,从头到尾打量了一下业务代码,发现业务主要是构造返回消息,记录日志。

构造返回就是Java里的构造对象什么的,根本不耗时的。

                  

那就想不是有记录日志吗,不往数据库里面写东西了,把它注释掉,跑一遍试试看。

第二遍测试

哎呦妈呀,起飞了老铁,直接飙到了每秒钟2万次。

看到这个令人惊讶的数据,这远远超过要求啊,哈哈哈,妥妥的。

突然一想,不对啊,这好像和产品设计不符合啊,设计里是要求记录日志的啊,这个是有点滥竽充数啊,不行不行,这个得改。

仔细分析一下,日志得写到数据库,读了《java高并发程序设计》,心想是不是可以用异步的方式记录日志呢,弄一个线程池吧。

阿里巴巴JAVA开发手册里是不推荐使用Executors中的现成的线程池的(具体原因我就不说了,可以看一下),那就自己写一个吧。

第三次测试

考虑到任务提交速度快的原因,第一次构造线程池采用了直接提交的队列,这样任务处理的快一些

private static ExecutorService saveThreadPool = new ThreadPoolExecutor(2, 100, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());

测试代码再跑一遍,哎呦妈呀,又起飞了老铁!

心想我这么牛逼的吗,这随便写个线程池就OK了。

1167080-20180119083546318-1768504386.png

在服务器执行了top一看,不得了:

1167080-20180119084927474-1208577762.png

这个cpu直接占满了,系统里还有其他服务的,不能把资源全都给它啊。

这次留了个心眼看了下数据库日志,不对啊,没有全部记录啊,尼玛发了一百多万结果只记录了几万条,这个差太多了啊。

为什么漏掉了呢,回过头来继续看线程池的构造。

终于发现了纰漏,拒绝策略是用了new ThreadPoolExecutor.DiscardPolicy(),这个可出事了啊,不行不行,这直接丢弃了,记录日志的任务不能直接丢弃。

第四次测试

这次又把书拿出来看了看,看了一些大牛写的线程池构造,最终敲定了这样的构造方式。

private static ExecutorService saveThreadPool = new ThreadPoolExecutor(2, 40, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(50000), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy());

采用LinkedBlockingQueue,最多可有50000个任务在阻塞队列中,线程池最大值设置40个,核心池大小设置2个,多出来的38个线程最多活跃60秒就会被回收。

拒绝采用CallerRunsPolicy,不会丢弃任务,只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。

在代码测试中,执行了top,发现cpu的利用率稳定在24%左右,这个可以接受

 

1167080-20180119085503131-1595066396.png

测试结果:

每秒钟1千2,这个也远远超过了性能指标,而且日志也全都记录到数据中,不过这个不是及时性的,它会在测试程序结束1分钟后,才会完成数据如插入,这个和队列任务有关。

这个线程池我是没有关闭的,因为每次任务提交后队列中还有很多任务,如果关闭的话,每次在开启一个线程池会降低速度,所以这个就不关闭了吧

如果有大神看出什么端倪的话,欢迎批评斧正,继续优化,个人感觉还有提升空间。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK