Apache Tomcat 8 中的 NIO 2
Apache Tomcat 8 有一個新的基于 NIO 2 的連接器正接近正常的使用狀態,而現在被標記成測試版. NIO 2 不僅向 Servlet 3.1的異步IO看齊,它還不止這一個好處.
速度
首先,是一個快速的速度測試. 原生的速度使用一個Servlet寫1KB的數據來衡量, 使用 ab -k -c 100 (啟用超過100個并發連接并保持存活) 以讓其只去測量做一次快的寫入和在兩個請求之前保持連接. 明顯這是一個可怕的環境標準,但是這個主意只是要看看 NIO 2 是不是足夠快, 因為在你注意到它的高級API的時候,它看起來是有點慢的. 這可能會消除NIO 2作為一個很有用的解決方案在你心目中的存在感,因為Tomcat中已經存在一個穩定的NIO連接器了, 不過在可選范圍的另外一端,APR是接近原生速度的. 我很高興的像大家報告 NIO 2 顯著地比 NIO 在這種純塊/輪詢的壓力測試上要快, 要快上大約50%, 并且相當于APR做這種任務的速度.
在這個關鍵問題有了結果之后,我們就有了一個比目前的連接器更優雅的選擇, 因為NIO和APR的輪詢管理,NIO的阻塞IO和APR的本地代碼已經被證明存在看似無休止的復雜、思索、奔潰、平臺特定等諸多問題.
不過,盡管一些初步的薄弱環節已經確定可以使用JSSE和靜態文件服務(見下文)來解決,其在現實世界的好處和資源消耗怎么樣現在還是個未知數. 隨著線程和輪詢管理被完全的抽象出來,JVM最終將會把一切掌控起來,已提供優化的行為.
一個簡單的API
那么它是一個簡單的API嗎 ? 實際上,只有阻塞IO使用NIO 2來做才非常簡單. 像使用NIO一樣,一次讀或者寫會立即返回結果, 但是不同于 NIO 這種操作沒必要是完整的,它還可以繼續異步存在與進程中. 為了能有所顯示,最基本的讀/寫API都使用到了一個可以在一旁被輪詢(這是一個糟糕的點子)或者阻塞的Future對象.因此,簡單的帶有操作時效的阻塞,看起來不錯.
"非阻塞"由于在Servlet 3.1中被引入,需要使用更加復雜的使用任務完成處理程序來通知操作現在已經完成的API. 那同樣也是聽起來很簡單,但是有特殊的問題需要處理,因為NIO 2 API不會提供讓處理那種問題更簡單的所有東西. 一次調用可以完全是(很明顯也可以不是)完全內聯的, 同步是不直觀的 (當一個操作被掛起是,沒有代碼塊會同步上, 不過看樣子一些像buffer這樣的重要的對象的狀態沒有被定義;死鎖的風險也還存在), 不完整的操作是可能的,等等.
API 確實對一些更加重要的IO進行了優化,使用分散和集中. 我嘗試去利用Tomcat中后者的優勢,未來可以在其上做更多的工作.
為什么NIO 2 會更好
NIO 2看起來簡單,快速且直觀, 但它內部的一些東西仍有待改進.
發送文件的支持
NIO 的transferTo API并不被NIO 2 異步通道所支持, 并且我不認為這樣是明智的. 因此, 盡管NIO 2連接器的原始速度不慢, 并且在大多數情況下它也足夠快速, 但它仍不是最高效的文件服務器. 雖然無關緊要,但因為實現起來也不太費事, 所以這是個不幸.
JSSE集成
與使用SSL引擎的API類似, 通過NIO可以提供良好控制和非阻塞能力. 但所有人都要寫相似的異步通道封裝代碼. 而JSSE通道代碼已經被NIO 2包括了.
JSSE (無) 速度
與OpenSSL相比, JSSE仍跟以前一樣慢. 雖然到目前為止你已經對這傷心事有了免疫力, JSSE 現在仍看起來是在浪費服務器資源. 然而,這個 JVM組件是可撥插的, 所以我們看以后是否會有所改善.
更好的狀態控制
當使用completion handler時沒法做像查詢操作狀態這樣的基本的事, 除非使用Future. 待定標識可以在其他地方獲得, 并且實際上這個標識是一個與future(能夠等候待定操作完成)共享的int類型的信號燈. 最后, 雖然這看起來挺直觀也沒有什么困難的, 但它會導致比需要的更為復雜.
所以NIO 2仍有改進的空間. 下面, 期待它的發生!