Twitter:如何使用Netty 4來減少JVM的GC開銷(譯文)

Twitter:如何使用Netty 4來減少JVM的GC開銷(譯文)

前言

在twitter,需要網絡功能的核心模塊使用的都是Netty。 比方說:

  • Finagle是我們的協議無關的RPC系統,它的傳輸層是在Netty之上構建的,許多內部的服務都是通過它來實現的,比如說搜索服務。
  • TFE(Twitter Front End,Twitter前端)是我們專門的填鴨式反向代理,它使用Netty支撐了大部分面向公眾的HTTP及SPDY的流量。
  • Cloudhopper每個月都通過Netty向世界各地的數百個移動運營商發送數十億的短消息。

可能有的人還沒聽說過Netty,它是一款開源的Java NIO框架,能讓你更容易編寫出高性能的網絡服務器。前一個版本的Netty 3使用Java對象來表示IO事件。這樣做比較簡單,但會產生大量的垃圾,尤其是在我們這種規模下。最新的版本Netty 4中做了一些改進,短生命週期的事件對象已經不復存在了,而是通過生命週期較長的管道對象來處理IO事件。 同時還有一個專門的緩衝區分配器,它使用緩衝區池來進行實現。

我們非常關注Netty項目的性能,可用性以及可持續性,也和Netty社區緊密合作以便全方位的對它進行完善。這裡我們特別會提到的是,我們是如何使用Netty 3的,以及項目移植到Netty 4後帶來的性能提升。

嘗試減少JVM的GC壓力以及內存帶寬的開銷

Netty 3存在一個問題是,它依賴於JVM的內存管理來進行緩衝區的分配。只要有接收到新的消息或者是用戶要發送遠程消息,Netty 3就會在堆裡分配一個緩衝區。也就是說每分配一個緩衝區就有一次new byte[capacity]操作。這些緩衝區給GC帶來了壓力,並且吞噬著內存的帶寬:分配一個新的緩衝區,為了安全性需要給數組填0,而這會消耗內存帶寬。然而填0的字節數組一般都會被真實的數據所填充,這同樣也會消耗內存帶寬。如果JVM可以提供一種方式來創建無需填充0的字節數組的話,就可以省掉50%的帶寬了,不過現在還沒有這樣的辦法。

為了解決這個問題,我們在Netty 4中做了如下的改進。

簡化事件處理

Netty 4使用不同的方法來處理不同類型的事件,而不是直接創建事件對象。在Netty 3中,ChannelHandler使用一個單獨的方法來處理所有的事件對象。

Twitter:如何使用Netty 4來減少JVM的GC開銷(譯文)

而Netty 4會為每種事件類型分配一個處理方法:

Twitter:如何使用Netty 4來減少JVM的GC開銷(譯文)

注意,這個handler還有一個‘userEventTriggered’方法,也就是說還可以通過它來創建一個自定義的事件對象。

新增了緩衝池

Netty 4引入了一個新的接口,’ByteBufAllocator。它通過這個接口提供了一個緩衝池的實現,這是jemalloc的一個純Java的實現版本,它提供了夥伴分配器以及SLAB分配器的實現。

既然Netty已經有了自己的緩衝區內存分配器,就不會再因為給緩衝區填0而造成內存帶寬的浪費了。然而,這種方式又帶來了另一件麻煩事——引用計數。因為我們不再依賴GC來將不用的緩衝區放回池裡,我們得自己去注意內存洩露的問題。哪怕只有一個handler忘了釋放緩衝區都會導致整個服務器的內存使用率無限制的增長。

如此大的改動是否值得?

由於以上的這些改動,因此Netty 4不再向下兼容Netty 3。這也意味著我們用Netty 3構建的項目,必須花上相當一段時間來進行遷移。這樣做值得嗎?

我們比較了兩個echo協議的服務器,分別用Netty 3和Netty 4進行開發(echo協議非 簡單,因此如果產生了任何垃圾,那肯定是Netty造成的,而和協議無關)。我用分佈式的echo協議客戶端對它們進行訪問,有16384個併發的連接,同時在不停地發送256字節的隨機負荷包,幾乎能讓千兆以太網滿負載地工作。

從我們的測試結果來看,Netty 4:

  • GC暫停的頻率低了5倍:45.5 vs. 9.2 times/min
  • 產生垃圾的速度也降低了5倍:207.11 vs 41.81 MiB/s

我還想確認下緩衝區池的性能是不是也足夠快。下圖的X軸和Y軸分別代表的是每次分配的緩衝區的大小以及分配緩衝所需要的時間。

Twitter:如何使用Netty 4來減少JVM的GC開銷(譯文)

可以看到,隨著緩衝區大小的增加,緩衝池的性能要比JVM更好。DirectBuffer的效果則更明顯。然而,對於小的緩衝區而言,性能和JVM相比沒有太大優勢。這塊我們還有不少工作要做。

儘管我們有些服務已經成功地遷移到Netty 4上了,但遷移是逐步來進行的,在這個過程中我們發現了一些問題,這阻礙了我們的遷移進度,希望在不久的將來這些問題能夠得到解決:

  • 緩衝區洩露: Netty有一個簡單的緩衝洩露報告機制,不過它提供的信息不夠詳細,難以迅速定位問題。
  • 核心功能足夠簡單:Netty是一個社區驅動的開源項目,因此核心功能越簡單對使用者來說更好。而那些非核心的特性往往導致核心功能發生改動,這導致了Netty的核心功能的不穩定性。我們希望只有真正核心的特性才能留在核心庫中,別的東西都通通閃開。

同時我們還希望能夠加入以下的新特性:

  • HTTP/2的實現
  • 客戶端HTTP和socks代理的支持
  • 異步DNS解析
  • Linux的本地擴展,這樣可以直接通過JNI使用epoll
  • 對連接劃分優先級,確保嚴格的響應時間

特別感謝

Netty項目的創始人Trustin Lee,也在2011年加入我們,一起來進行Netty 4的完善工作。我們還要感謝來自TFE團隊的Jeff Pinner,文中很多不錯的想法都是他提出來的,並且他毫不猶豫的充當了我們的小白鼠。同樣要感謝的還有,Norman Maurer,他是Netty代碼的核心提交者之一,為了幫助我們把這些想法付諸實踐,他付出了很多的努力 。還有許多人,他們積極體驗了各種不穩定的版本,幫助我們試驗了每一個改動的功能,這裡沒辦法一一列舉,裡面特別要感謝的有:Berk Demir (@bd), Charles Yang (@cmyang), Evan Meagher (@evanm), Larry Hosken (@lahosken), Sonja Keserovic (@thesonjake), 以及Stu Hood (@stuhood)。


分享到:


相關文章: