<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)

    個人收藏

    最新隨筆

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 久久一区二区免费播放| 精品视频免费在线| 成全高清在线观看免费| 国内精品99亚洲免费高清| 无码免费又爽又高潮喷水的视频 | 中文字幕第一页亚洲| 国产精品免费一区二区三区| 亚洲精品国产高清不卡在线| 一级做a爰片久久毛片免费陪| 亚洲av手机在线观看| 色多多A级毛片免费看| 亚洲女久久久噜噜噜熟女| 男人进去女人爽免费视频国产 | 亚洲天堂中文字幕在线| www一区二区www免费| 亚洲成年轻人电影网站www| 中文字幕免费在线看线人| 亚洲精品国产首次亮相| 亚洲国产成人精品女人久久久| AAAAA级少妇高潮大片免费看| 麻豆亚洲AV永久无码精品久久| www视频免费看| 亚洲AV无码AV吞精久久| 国产aⅴ无码专区亚洲av麻豆| 亚洲欧洲免费视频| 亚洲人成www在线播放| 全部免费毛片在线| 免费一级毛片无毒不卡| 亚洲天堂2016| 亚洲人成无码网WWW| 99爱在线精品视频免费观看9| 亚洲欧洲专线一区| 亚洲精品乱码久久久久久中文字幕 | 国产免费久久精品99久久| 亚洲系列国产精品制服丝袜第| 成年女人毛片免费播放视频m| 久久精品免费大片国产大片| 67194在线午夜亚洲| 国产成人精品久久亚洲| 67194熟妇在线永久免费观看 | 久久亚洲AV无码精品色午夜麻豆|