背景:
最近的一個項目需要用到招標,臨時加了給我們的系統增加了一個性能需求,多少呢?
一秒鍾300次NTP(不知道ntp的同學可以百度一下),平均3ms一次啊,沒測試過,心里沒有底。(⊙o⊙)…
情境介紹:
系統是一個時間服務器系統,客戶端就是window系統,或者其他的一些服務器,來向時間服務器同步時間。
默認的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了。
在服務器執行了top一看,不得了:
這個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%左右,這個可以接受
測試結果:
每秒鍾1千2,這個也遠遠超過了性能指標,而且日志也全都記錄到數據中,不過這個不是及時性的,它會在測試程序結束1分鍾后,才會完成數據如插入,這個和隊列任務有關。
總結:
這個線程池我是沒有關閉的,因為每次任務提交后隊列中還有很多任務,如果關閉的話,每次在開啟一個線程池會降低速度,所以這個就不關閉了吧
如果有大神看出什么端倪的話,歡迎批評斧正,繼續優化,個人感覺還有提升空間。