<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    聶永的博客

    記錄工作/學習的點點滴滴。

    SO_REUSEPORT學習筆記

    前言

    本篇用于記錄學習SO_REUSEPORT的筆記和心得,末尾還會提供一個bindp小工具也能為已有的程序享受這個新的特性。

    當前Linux網絡應用程序問題

    運行在Linux系統上網絡應用程序,為了利用多核的優勢,一般使用以下比較典型的多進程/多線程服務器模型:

    1. 單線程listen/accept,多個工作線程接收任務分發,雖CPU的工作負載不再是問題,但會存在:
      • 單線程listener,在處理高速率海量連接時,一樣會成為瓶頸
      • CPU緩存行丟失套接字結構(socket structure)現象嚴重
    2. 所有工作線程都accept()在同一個服務器套接字上呢,一樣存在問題:
      • 多線程訪問server socket鎖競爭嚴重
      • 高負載下,線程之間處理不均衡,有時高達3:1不均衡比例
      • 導致CPU緩存行跳躍(cache line bouncing)
      • 在繁忙CPU上存在較大延遲

    上面模型雖然可以做到線程和CPU核綁定,但都會存在:

    • 單一listener工作線程在高速的連接接入處理時會成為瓶頸
    • 緩存行跳躍
    • 很難做到CPU之間的負載均衡
    • 隨著核數的擴展,性能并沒有隨著提升

    比如HTTP CPS(Connection Per Second)吞吐量并沒有隨著CPU核數增加呈現線性增長: 
    Image(2)

    Linux kernel 3.9帶來了SO_REUSEPORT特性,可以解決以上大部分問題。

    SO_REUSEPORT解決了什么問題

    linux man文檔中一段文字描述其作用:

    The new socket option allows multiple sockets on the same host to bind to the same port, and is intended to improve the performance of multithreaded network server applications running on top of multicore systems.

    SO_REUSEPORT支持多個進程或者線程綁定到同一端口,提高服務器程序的性能,解決的問題:

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

    其核心的實現主要有三點:

    • 擴展 socket option,增加 SO_REUSEPORT 選項,用來設置 reuseport。
    • 修改 bind 系統調用實現,以便支持可以綁定到相同的 IP 和端口
    • 修改處理新建連接的實現,查找 listener 的時候,能夠支持在監聽相同 IP 和端口的多個 sock 之間均衡選擇。

    代碼分析,可以參考引用資料 [多個進程綁定相同端口的實現分析[Google Patch]]。

    CPU之間平衡處理,水平擴展

    以前通過fork形式創建多個子進程,現在有了SO_REUSEPORT,可以不用通過fork的形式,讓多進程監聽同一個端口,各個進程中accept socket fd不一樣,有新連接建立時,內核只會喚醒一個進程來accept,并且保證喚醒的均衡性。

    模型簡單,維護方便了,進程的管理和應用邏輯解耦,進程的管理水平擴展權限下放給程序員/管理員,可以根據實際進行控制進程啟動/關閉,增加了靈活性。

    這帶來了一個較為微觀的水平擴展思路,線程多少是否合適,狀態是否存在共享,降低單個進程的資源依賴,針對無狀態的服務器架構最為適合了。

    新特性測試或多個版本共存

    可以很方便的測試新特性,同一個程序,不同版本同時運行中,根據運行結果決定新老版本更迭與否。

    針對對客戶端而言,表面上感受不到其變動,因為這些工作完全在服務器端進行。

    服務器無縫重啟/切換

    想法是,我們迭代了一版本,需要部署到線上,為之啟動一個新的進程后,稍后關閉舊版本進程程序,服務一直在運行中不間斷,需要平衡過度。這就像Erlang語言層面所提供的熱更新一樣。

    想法不錯,但是實際操作起來,就不是那么平滑了,還好有一個hubtime開源工具,原理為SIGHUP信號處理器+SO_REUSEPORT+LD_RELOAD,可以幫助我們輕松做到,有需要的同學可以檢出試用一下。

    SO_REUSEPORT已知問題

    SO_REUSEPORT根據數據包的四元組{src ip, src port, dst ip, dst port}和當前綁定同一個端口的服務器套接字數量進行數據包分發。若服務器套接字數量產生變化,內核會把本該上一個服務器套接字所處理的客戶端連接所發送的數據包(比如三次握手期間的半連接,以及已經完成握手但在隊列中排隊的連接)分發到其它的服務器套接字上面,可能會導致客戶端請求失敗,一般可以使用:

    • 使用固定的服務器套接字數量,不要在負載繁忙期間輕易變化
    • 允許多個服務器套接字共享TCP請求表(Tcp request table)
    • 不使用四元組作為Hash值進行選擇本地套接字處理,挑選隸屬于同一個CPU的套接字

    與RFS/RPS/XPS-mq協作,可以獲得進一步的性能:

    • 服務器線程綁定到CPUs
    • RPS分發TCP SYN包到對應CPU核上
    • TCP連接被已綁定到CPU上的線程accept()
    • XPS-mq(Transmit Packet Steering for multiqueue),傳輸隊列和CPU綁定,發送數據
    • RFS/RPS保證同一個連接后續數據包都會被分發到同一個CPU上
    • 網卡接收隊列已經綁定到CPU,則RFS/RPS則無須設置
    • 需要注意硬件支持與否

    目的嘛,數據包的軟硬中斷、接收、處理等在一個CPU核上,并行化處理,盡可能做到資源利用最大化。

    SO_REUSEPORT不是一貼萬能膏藥

    雖然SO_REUSEPORT解決了多個進程共同綁定/監聽同一端口的問題,但根據新浪林曉峰同學測試結果來看,在多核擴展層面也未能夠做到理想的線性擴展:

    可以參考Fastsocket在其基礎之上的改進,鏈接地址

    支持SO_REUSEPORT的Tengine

    淘寶的Tengine已經支持了SO_REUSEPORT特性,在其測試報告中,有一個簡單測試,可以看出來相對比SO_REUSEPORT所帶來的性能提升:

    使用SO_REUSEPORT以后,最明顯的效果是在壓力下不容易出現丟請求的情況,CPU均衡性平穩。

    Java支持否?

    JDK 1.6語言層面不支持,至于以后的版本,由于暫時沒有使用到,不多說。

    Netty 3/4版本默認都不支持SO_REUSEPORT特性,但Netty 4.0.19以及之后版本才真正提供了JNI方式單獨包裝的epoll native transport版本(在Linux系統下運行),可以配置類似于SO_REUSEPORT等(JAVA NIIO沒有提供)選項,這部分是在io.netty.channel.epoll.EpollChannelOption中定義(在線代碼部分)。

    在linux環境下使用epoll native transport,可以獲得內核層面網絡堆棧增強的紅利,如何使用可參考Native transports文檔。

    使用epoll native transport倒也簡單,類名稍作替換:

    NioEventLoopGroup → EpollEventLoopGroup
    NioEventLoop → EpollEventLoop
    NioServerSocketChannel → EpollServerSocketChannel
    NioSocketChannel → EpollSocketChannel
    

    比如寫一個PING-PONG應用服務器程序,類似代碼:

    public void run() throws Exception {
        EventLoopGroup bossGroup = new EpollEventLoopGroup();
        EventLoopGroup workerGroup = new EpollEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            ChannelFuture f = b
                    .group(bossGroup, workerGroup)
                    .channel(EpollServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch)
                                throws Exception {
                            ch.pipeline().addLast(
                                    new StringDecoder(CharsetUtil.UTF_8),
                                    new StringEncoder(CharsetUtil.UTF_8),
                                    new PingPongServerHandler());
                        }
                    }).option(ChannelOption.SO_REUSEADDR, true)
                    .option(EpollChannelOption.SO_REUSEPORT, true)
                    .childOption(ChannelOption.SO_KEEPALIVE, true).bind(port)
                    .sync();
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
    

    若不要這么折騰,還想讓以往Java/Netty應用程序在不做任何改動的前提下順利在Linux kernel >= 3.9下同樣享受到SO_REUSEPORT帶來的好處,不妨嘗試一下bindp,更為經濟,這一部分下面會講到。

    bindp,為已有應用添加SO_REUSEPORT特性

    以前所寫bindp小程序,可以為已有程序綁定指定的IP地址和端口,一方面可以省去硬編碼,另一方面也為測試提供了一些方便。

    另外,為了讓以前沒有硬編碼SO_REUSEPORT的應用程序可以在Linux內核3.9以及之后Linux系統上也能夠得到內核增強支持,稍做修改,添加支持。

    但要求如下:

    1. Linux內核(>= 3.9)支持SO_REUSEPORT特性
    2. 需要配置REUSE_PORT=1

    不滿足以上條件,此特性將無法生效。

    使用示范:

    REUSE_PORT=1 BIND_PORT=9999 LD_PRELOAD=./libbindp.so java -server -jar pingpongserver.jar &
    

    當然,你可以根據需要運行命令多次,多個進程監聽同一個端口,單機進程水平擴展。

    使用示范

    使用python腳本快速構建一個小的示范原型,兩個進程,都監聽同一個端口10000,客戶端請求返回不同內容,僅供娛樂。

    server_v1.py,簡單PING-PONG:

    # -*- coding:UTF-8 -*-
    
    import socket
    import os
    
    PORT = 10000
    BUFSIZE = 1024
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('', PORT))
    s.listen(1)
    
    while True:
        conn, addr = s.accept()
        data = conn.recv(PORT)
        conn.send('Connected to server[%s] from client[%s]\n' % (os.getpid(), addr))
        conn.close()
    
    s.close()
    

    server_v2.py,輸出當前時間:

    # -*- coding:UTF-8 -*-
    
    import socket
    import time
    import os
    
    PORT = 10000
    BUFSIZE = 1024
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('', PORT))
    s.listen(1)
    
    while True:
        conn, addr = s.accept()
        data = conn.recv(PORT)
        conn.send('server[%s] time %s\n' % (os.getpid(), time.ctime()))
        conn.close()
    
    s.close()
    

    借助于bindp運行兩個版本的程序:

    REUSE_PORT=1 LD_PRELOAD=/opt/bindp/libindp.so python server_v1.py &
    REUSE_PORT=1 LD_PRELOAD=/opt/bindp/libindp.so python server_v2.py &
    

    模擬客戶端請求10次:

    for i in {1..10};do echo "hello" | nc 127.0.0.1 10000;done
    

    看看結果吧:

    Connected to server[3139] from client[('127.0.0.1', 48858)]
    server[3140] time Thu Feb 12 16:39:12 2015
    server[3140] time Thu Feb 12 16:39:12 2015
    server[3140] time Thu Feb 12 16:39:12 2015
    Connected to server[3139] from client[('127.0.0.1', 48862)]
    server[3140] time Thu Feb 12 16:39:12 2015
    Connected to server[3139] from client[('127.0.0.1', 48864)]
    server[3140] time Thu Feb 12 16:39:12 2015
    Connected to server[3139] from client[('127.0.0.1', 48866)]
    Connected to server[3139] from client[('127.0.0.1', 48867)]
    

    可以看出來,CPU分配很均衡,各自分配50%的請求量。

    嗯,雖是小玩具,有些意思 :))

    bindp的使用方法

    更多使用說明,請參考README

    參考資料

    posted on 2015-02-12 16:50 nieyong 閱讀(28315) 評論(1)  編輯  收藏 所屬分類: Socket

    評論

    # re: SO_REUSEPORT學習筆記 2015-02-28 14:47 男性油性皮膚怎么祛痘

    非常詳細的一篇文章,學習了  回復  更多評論   

    公告

    所有文章皆為原創,若轉載請標明出處,謝謝~

    新浪微博,歡迎關注:

    導航

    <2015年2月>
    25262728293031
    1234567
    891011121314
    15161718192021
    22232425262728
    1234567

    統計

    常用鏈接

    留言簿(58)

    隨筆分類(130)

    隨筆檔案(151)

    個人收藏

    最新隨筆

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 亚洲成AV人综合在线观看| 中文亚洲AV片在线观看不卡| 亚洲高清视频在线播放| 国产成人无码区免费网站| 中文字幕无码精品亚洲资源网| 免费福利资源站在线视频| 国产成人无码免费视频97| 亚洲Av无码国产一区二区| 日本免费人成黄页网观看视频| 亚洲国产成a人v在线观看 | 国产免费高清69式视频在线观看| 亚洲国产精品自在拍在线播放 | 久久久久亚洲精品无码系列| 爽爽爽爽爽爽爽成人免费观看| 亚洲人成在线播放网站| 无码囯产精品一区二区免费| 亚洲精品国产成人| 日韩免费观看的一级毛片| 另类专区另类专区亚洲| 亚洲美女又黄又爽在线观看| 久久久久国产免费| 亚洲一级视频在线观看| 日韩特黄特色大片免费视频| 色妞www精品视频免费看| 一本久久a久久精品亚洲| 久久国产乱子伦免费精品| 亚洲AV无码专区在线亚| 国产禁女女网站免费看| 国产免费MV大全视频网站| 337p欧洲亚洲大胆艺术| 女人被免费视频网站| 国产精品无码永久免费888 | 亚洲精品无码久久久影院相关影片| 欧洲人成在线免费| 一本天堂ⅴ无码亚洲道久久| 亚洲国产精品一区二区九九| 中文字幕在线免费观看| 亚洲av成人一区二区三区观看在线 | 免费在线观看中文字幕| 免费观看在线禁片| 亚洲国产成人手机在线观看|