NIO系列6:流行 NIO Framework netty 和 mina 性能測評與分析
算法
測試方法數據庫
採用 mina 和 netty 各實現一個 基於 nio 的EchoServer,測試在不一樣大小網絡報文下的性能表現apache
測試環境centos
客戶端-服務端:api
model name: Intel(R) Core(TM) i5-2320 CPU @ 3.00GHz 緩存
cache size: 6144 KB 網絡
cpu cores: 4 框架
jdk: 1.6.0_30-b12socket
network: 1000Mbtcp
memory: -Xms256m -Xmx256m
Linux: centos 5.7, kernel 2.6.18-274.el5
測試工具:
jmeter v2.4
版本:
mina 2.0.7
netty 3.6.2.Final
配置:
mina
io-processor cpu 核數
executor cpu 核數
buffer 初始 buffer 大小,設置爲 2048(2k)
netty
boss netty 默認配置 1
worker cpu 核數
executor cpu 核數
其實,從理論上來講, echo 型的應用不配置 executor 業務執行線程池會得到更好的性能和更低的消耗,但考慮在真實業務應用中,真實的業務場景處理一般涉及各類複雜邏輯計算,緩存、數據庫、外部接口訪問,爲避 免業務執行延時阻塞 io 線程執行致使吞吐下降,一般都會分離 io 處理線程 和 業務處理線程,所以咱們的測試案例中也配置了業務執行線程池考查它們線程池的調度效能。
mina 線程池設置
io processor:
IoAcceptor acceptor = new NioSocketAcceptor(Integer.parseInt(ioPool));
executor:
acceptor.getFilterChain().addLast("threadPool", new ExecutorFilter(Integer.parseInt(executorPool)));
netty 線程池設置
io worker:
new NioWorkerPool(Executors.newCachedThreadPool(), Integer.parseInt(ioPool))
executor:
new OrderedMemoryAwareThreadPoolExecutor(Integer.parseInt(executorPool), 0, 0)
測試結果
mina | tps | cpu | network io | art(average response time) |
90%rt(90% response time) |
1k | 45024/sec |
150% |
50MB/sec |
< 1ms |
1ms |
2k | 35548/sec |
170% | 81MB/sec |
< 1ms |
1ms |
5k | 10155/sec |
90% |
55MB/sec |
3 ms |
1ms |
10k | 8740/sec |
137% | 98MB/sec | 3ms | 4ms |
50k | 1873/sec | 128% | 100MB/sec | 16ms | 19ms |
100k | 949/sec | 128% | 100MB/sec | 33ms | 43ms |
netty | tps | cpu | network io | art(average response time) |
90%rt(90% response time) |
1k | 44653/sec |
155% |
50MB/sec |
< 1ms |
1ms |
2k | 35580/sec |
175% | 81MB/sec |
< 1ms |
1ms |
5k | 17971/sec |
195% |
98MB/sec |
3 ms |
1ms |
10k | 8806/sec |
195% | 98MB/sec | 3ms | 4ms |
50k | 1909/sec | 197% | 100MB/sec | 16ms | 18ms |
100k | 964/sec | 197% | 100MB/sec | 32ms | 45ms |
測試點評
mina 和 netty 在 1k、2k、10k、50k、100k 報文大小時 tps 接近
mina 在 5k 報文時有個明顯的異常(紅色標註),tps 較低,網絡 io 吞吐較低,比較 netty 在 5k 報文的網絡 io 吞吐 98MB/sec(基本接近前兆網卡極限)
5k 報文以上基本都能壓滿網絡 io,瓶頸在 io,因此 tps 和 響應時間基本相差不大。
疑問,爲何 mina 會在 5k 報文時 io 吞吐出現明顯下降?
測試分析
經過分析 mina 和 netty 的源碼,發現處理 io 讀事件時 buffer 分配策略上,兩個框架有一些區別。
在網絡 io 處理上,程序每次調用 socket api 從 tcp buffer 讀取的字節數是隨時變化的,它會受到報文大小,操做系統 tcp buffer 大小、tcp 協議算法實現、網絡鏈路帶寬各類因素影響。
所以 NIO 框架在處理每一個讀事件時,也須要每次動態分配一個 buffer 來臨時存放讀到的字節,buffer 分配的效能是影響網絡 io 框架程序性能表現的關鍵因素。
下面分別分析下 mina 和 netty buffer 的動態分配實現
mina
buffer 分配方式:
默認實現採用了 HeapByteBuffer,每次都是直接調用 ByteBuffer.allocate(capacity) 直接分配
buffer 分配大小預測:
根據每次讀事件實際讀到的字節數計算分配 buffer 的大小,若實際讀到字節將 ByteBuffer 裝滿,說明來自網絡的數據量可能較大而分配 buffer 容量不足,則擴大 buffer 一倍。
若連續 2 次讀到的實際字節數小於 buffer 容量的一半,則縮小 buffer 爲原來的一半
netty
buffer 分配方式
默認實現採用了 DirectByteBuffer,而且實現了 buffer cache,只要 buffer 大小不改變會重複利用已經分配的 buffer
buffer 分配大小預測:
初始化了一張 buffer size 靜態分配表以下(截取部分),假如當前默認 buffer 爲 2048
[1024, 1152, 1280, 1408, 1536, 1664, 1792, 1920, 2048, 2304, 2560, 2816, 3072, 3328, 3584, 3840, 4096]
| | | |
A D B C
根據每次讀事件實際讀到的字節數,進行預測下一次應該分配的 buffer 大小。
若實際讀到的字節數大於等於當前 buffer(上圖 B 位置) 則將當前 buffer 增大到上圖示 C 位置,每次增大步進爲 4
若連續 2 次實際讀到的字節數小於等於 A 位置指示的 buffer 大小,則縮小 buffer 到 D 位置
從上面的對比分析能夠看出,mina 採用了相對簡單的 buffer 分配和預測方式,buffer 的增加和縮小比例相同。
而 netty 採用了一種相對複雜點的 buffer 分配方式,buffer increment faster decrement slower。
事實證實 netty 的分配方式更有效的避免的 buffer 分配中的抖動問題(忽大忽小),而 buffer 分配抖動正是影響 io 吞吐的罪魁禍首。
mina 測試中 5k 報文正好產生了 buffer 分配抖動致使 io 吞吐大受影響,經過修改 mina 源碼採用固定 buffer 大小來測試則有效避免了 buffer 抖動,io 吞吐也恢復正常。
但實際狀況下,固定 buffer 確定不是有效的方式,不能很好的適應各類網絡環境的複雜性,但採用動態 buffer 分配時算法需首要考慮避免抖動。
另外能夠看出 netty 的 cpu 消耗明顯高出 mina 很多,懷疑 netty 採用的 executor 實現(OrderedMemoryAwareThreadPoolExecutor)存在比較多的鎖競爭和線程上下文切換。
下面是一組不使用 executor 時 netyy 的測試數據,其餘指標都差很少但 cpu 明顯降低很多
netty | cpu |
1k | 75% |
2k | 125% |
5k | 126% |
10k | 126% |
50k | 118% |
100k | 116% |
測試總結
mina: 需進一步優化其 buffer 分配,避免分配抖動致使的 io 吞吐波動
netty: 需進一步優化默認 executor 的實現,下降 cpu 消耗
其實 netty2 和 mina 都出自同一人 Trustin Lee,後來他轉投 apache 項目組將 netty 交給了社區繼續維護,以後從新設計 mina 這個框架。
從使用者感覺上來講 mina 的 api 接口設計明顯比 netty 更優雅。