Linux下Netty實現(xiàn)高性能UDP服務(wù)(SO_REUSEPORT)

當(dāng)UDP丟包的時候劫笙,我們正常情況下是增加各種緩沖區(qū)的大小溉躲,有調(diào)整內(nèi)核緩沖區(qū)的,也有調(diào)整應(yīng)用緩沖區(qū)的牍白。但是還有另外一種方式脊凰,就是加速UDP數(shù)據(jù)包的處理速度。

1.當(dāng)前Linux網(wǎng)絡(luò)應(yīng)用程序問題

運行在Linux系統(tǒng)上網(wǎng)絡(luò)應(yīng)用程序淹朋,為了利用多核的優(yōu)勢笙各,一般使用以下比較典型的多進程/多線程服務(wù)器模型:


多進程/多線程服務(wù)器模型

首先需要單線程listen一個端口上,然后由多個工作進程/線程去accept()在同一個服務(wù)器套接字上础芍。 但有以下兩個瓶頸:

  • 單線程listener杈抢,在處理高速率海量連接時,一樣會成為瓶頸
  • 多線程訪問server socket鎖競爭嚴重仑性。

那么怎么解決惶楼? 這里先別扯什么分布式調(diào)度,集群xxx的 , 就拿單機來說問題。在Linux kernel 3.9帶來了SO_REUSEPORT特性诊杆,她可以解決上面(單進程listen歼捐,多工作進程accept() )的問題.

Socket 分片

如上,SO_REUSEPORT是支持多個進程或者線程綁定到同一端口晨汹,提高服務(wù)器程序的吞吐性能豹储,具體來說解決了下面的幾個問題:

  • 允許多個套接字 bind()/listen() 同一個TCP/UDP端口
  • 每一個線程擁有自己的服務(wù)器套接字
  • 在服務(wù)器套接字上沒有了鎖的競爭,因為每個進程一個服務(wù)器套接字
  • 內(nèi)核層面實現(xiàn)負載均衡
  • 安全層面淘这,監(jiān)聽同一個端口的套接字只能位于同一個用戶下面

關(guān)于SO_REUSEPORT可以參考這篇文章SO_REUSEPORT學(xué)習(xí)筆記

2.Netty使用SO_REUSEPORT

要想在Netty中使用SO_REUSEPORT特性剥扣,需要滿足以下兩個前提條件

  • linux內(nèi)核版本 >= 3.9
  • Netty版本 >= 4.0.16

然后只需要兩步就可以使用SO_REUSEPORT特性了。第一步:添加Netty本地庫依賴铝穷。第二步:替換Netty中的Nio組件為原生組件钠怯。第三步:多線程綁定同一個端口

2.1.添加Netty本地庫依賴

Netty官方提供了使用本地庫的說明 Native transports
Netty是通過JNI本地庫的方式來提供的。而且這種本地庫的方式不是Netty核心的一部分曙聂,所以需要有額外依賴

  <build>
    <extensions>
      <extension>
        <groupId>kr.motd.maven</groupId>
        <artifactId>os-maven-plugin</artifactId>
        <version>1.5.0.Final</version>
      </extension>
    </extensions>
    ...
  </build>

  <dependencies>
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-transport-native-epoll</artifactId>
      <version>${project.version}</version>
      <classifier>${os.detected.name}-${os.detected.arch}</classifier>
    </dependency>
    ...
  </dependencies>

其中# os-maven-plugin 插件是為了自檢檢測當(dāng)前系統(tǒng)的名稱以及架構(gòu)晦炊。然后自動填充到classifier中的兩個變量 ${os.detected.name} 以及 ${os.detected.arch}。如果是在Linux 64系統(tǒng)宁脊,那么可能的結(jié)果就是os.detected.name=linux,os.detected.arch=x86_64 断国。

由于官網(wǎng)中沒有提供gradle的配置,所以這邊總結(jié)一下gradle的配置

// gradle構(gòu)建配置
buildscript {
     // buildscript 加上osdetector的依賴
    dependencies {
        classpath 'com.google.gradle:osdetector-gradle-plugin:1.6.0'
    }
}
// 添加原生依賴
dependencies{
    compile group: 'io.netty', name: 'netty-transport-native-epoll', version: '4.1.22.Final', classifier: osdetector.classifier
}

以上的gradle配置雖然沒什么問題朦佩,但是實際上大多數(shù)開發(fā)者實在Windows上開發(fā)的并思,所以osdetector.classifier=windows.x86_64,而實際上Netty并沒有這樣的組件语稠,所以會編譯報錯宋彼。

所以我的建議是直接寫死osdetector.classifier=linux-x86_64

2.2.替換Netty中的Nio組件為原生組件

直接在Netty啟動類中替換為在Linux系統(tǒng)下的epoll組件

  • NioEventLoopGroup → EpollEventLoopGroup
  • NioEventLoop → EpollEventLoop
  • NioServerSocketChannel → EpollServerSocketChannel
  • NioSocketChannel → EpollSocketChannel
    如下所示
        group = new EpollEventLoopGroup();//NioEventLoopGroup ->EpollEventLoopGroup
        bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(EpollDatagramChannel.class) // NioServerSocketChannel -> EpollDatagramChannel
                .option(ChannelOption.SO_BROADCAST, true)
                .option(EpollChannelOption.SO_REUSEPORT, true) // 配置EpollChannelOption.SO_REUSEPORT
                .option(ChannelOption.SO_RCVBUF, 1024 * 1024 * bufferSize)
                .handler( new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel channel)
                            throws Exception {
                        ChannelPipeline pipeline = channel.pipeline();
                        // ....
                    }
                });

不過要注意這些代碼只能在Linux上運行,如果實在windows或者mac上開發(fā)仙畦,那最好還是要換成普通Nio方式的输涕,Netty提供了方法Epoll.isAvailable()來判斷是否可用epoll

所以實際上優(yōu)化的時候需要加上是否支持epoll特性的判斷

        group = Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup();
        bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(Epoll.isAvailable() ? EpollDatagramChannel.class : NioDatagramChannel.class)
                .option(ChannelOption.SO_BROADCAST, true)
                .option(ChannelOption.SO_RCVBUF, 1024 * 1024)
                .handler( new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel channel)
                            throws Exception {
                        ChannelPipeline pipeline = channel.pipeline();
                    }
                });
        // linux平臺下支持SO_REUSEPORT特性以提高性能
        if (Epoll.isAvailable()) {
            bootstrap.option(EpollChannelOption.SO_REUSEPORT, true);
        }

2.3. 多線程綁定同一個端口

使用原生epoll組件替換nio原來的組件后,需要多次綁定同一個端口慨畸。

        if (Epoll.isAvailable()) {
            // linux系統(tǒng)下使用SO_REUSEPORT特性莱坎,使得多個線程綁定同一個端口
            int cpuNum = Runtime.getRuntime().availableProcessors();
            log.info("using epoll reuseport and cpu:" + cpuNum);
            for (int i = 0; i < cpuNum; i++) {
                ChannelFuture future = bootstrap.bind(UDP_PORT).await();
                if (!future.isSuccess()) {
                    throw new Exception("bootstrap bind fail port is " + UDP_PORT);
                }
            }
        }

3.測試

3.1優(yōu)化前

我們使用大概17萬的QPS來壓測我們的UDP服務(wù)

Jmeter
指標(biāo)數(shù)據(jù)

可以發(fā)現(xiàn)最終丟棄了一部分UDP。

下面再來看一下運行期間的CPU分布寸士¢苁玻可以看到其中一個線程占用99%的CPU碴卧。

線程所占CPU

我們來看一下是哪一個線程。

[root@localhost ~]# printf "%x\n" 1983
7bf

然后使用jstack命令dump出線程乃正∽〔幔可以看到是處理UDP的連接的線程比較繁忙,導(dǎo)致在高QPS的情況下處理不過來瓮具,從而丟包荧飞。


線程棧

3.2優(yōu)化后

使用epoll優(yōu)化后,在啟動的時候有一些錯誤信息值得關(guān)注名党。

03:23:06.155 [main] DEBUG io.netty.util.internal.NativeLibraryLoader - Unable to load the library 'netty_transport_native_epoll_x86_64', trying other loading mechanism.
java.lang.UnsatisfiedLinkError: no netty_transport_native_epoll_x86_64 in java.library.path
    ...
03:23:06.155 [main] DEBUG io.netty.util.internal.NativeLibraryLoader - netty_transport_native_epoll_x86_64 cannot be loaded from java.libary.path, now trying export to -Dio.netty.native.workdir: /tmp
java.lang.UnsatisfiedLinkError: no netty_transport_native_epoll_x86_64 in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
        ... 18 common frames omitted
03:23:06.174 [main] DEBUG io.netty.util.internal.NativeLibraryLoader - Successfully loaded the library /tmp/libnetty_transport_native_epoll_x86_647320427488873314678.so

初看上去好像是啟動出錯了叹阔,但是再細看實際上沒什么問題。因為其實上面的日志只是在說netty在加載本地庫的時候有優(yōu)先級传睹。前兩次加載失敗了耳幢,最后一次加載成功了。所以這段時間可以忽略蒋歌。關(guān)于這個問題github上也有人提出了issue帅掘。可以關(guān)注一下When netty_transport_native_epoll_x86_64 cannot be found, stacktrace is logged

我們同樣適用大概17萬的QPS來壓測我們的UDP服務(wù)


Jmeter
指標(biāo)

可以看到?jīng)]有丟包

我們再來看一下接受連接的線程所占的CPU


線程所在CPU
線程棧

可以看到同時有4個線程負責(zé)處理UDP連接堂油。其中3個線程比較繁忙修档。

可能是因為QPS還不夠高,所以4個線程中只有3個比較繁忙府框,剩余一個幾乎不占用CPU吱窝。但是由于單機Jmeter能轟出的UDP QPS有限(我本機大概在17萬左右),所以暫時無法測試迫靖。后續(xù)我們可以使用分布式j(luò)meter來測試院峡,敬請期待。

3.3測試結(jié)論

使用SO_REUSEPORT優(yōu)化后系宜,不但性能提升了照激,而且CPU占用更加均衡,在一定程度上性能和CPU個數(shù)成正相關(guān)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盹牧,一起剝皮案震驚了整個濱河市俩垃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌汰寓,老刑警劉巖口柳,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異有滑,居然都是意外死亡跃闹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來望艺,“玉大人苛秕,你說我怎么就攤上這事≌夷” “怎么了想帅?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長啡莉。 經(jīng)常有香客問我,道長旨剥,這世上最難降的妖魔是什么咧欣? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮轨帜,結(jié)果婚禮上魄咕,老公的妹妹穿的比我還像新娘。我一直安慰自己蚌父,他們只是感情好哮兰,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著苟弛,像睡著了一般喝滞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上膏秫,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天右遭,我揣著相機與錄音,去河邊找鬼缤削。 笑死窘哈,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亭敢。 我是一名探鬼主播滚婉,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼帅刀!你這毒婦竟也來了让腹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤劝篷,失蹤者是張志新(化名)和其女友劉穎哨鸭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娇妓,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡像鸡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片只估。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡志群,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蛔钙,到底是詐尸還是另有隱情锌云,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布吁脱,位于F島的核電站桑涎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏兼贡。R本人自食惡果不足惜攻冷,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望遍希。 院中可真熱鬧等曼,春花似錦、人聲如沸凿蒜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽废封。三九已至州泊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間漂洋,已是汗流浹背拥诡。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留氮发,地道東北人渴肉。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像爽冕,于是被迫代替她去往敵國和親仇祭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Netty的簡單介紹 Netty 是一個 NIO client-server(客戶端服務(wù)器)框架颈畸,使用 Netty...
    AI喬治閱讀 8,391評論 1 101
  • 1乌奇、Netty基礎(chǔ)入門 Netty是由JBOSS提供的一個java開源框架。Netty提供異步的眯娱、事件驅(qū)動的網(wǎng)絡(luò)應(yīng)...
    我是嘻哈大哥閱讀 4,687評論 0 31
  • 前奏 https://tech.meituan.com/2016/11/04/nio.html 綜述 netty通...
    jiangmo閱讀 5,842評論 0 13
  • 不知道什么時候身邊的姑娘會變成這樣礁苗,在不合適的飯點,在老板不知道哪里去了的街邊餐車旁徙缴,穿著拖鞋试伙,裹得厚厚的,沒...
    白新生閱讀 97評論 0 0
  • 7點起床,3小時高速回冷疏叨,隨即帶隊加班到下午四點多潘靖,走出單位,整個人有點恍惚蚤蔓,洗了頭卦溢,做了肩頸,直接...
    T伊恩閱讀 131評論 0 0