1. 程式人生 > >socket so_reuseport提高服務端效能

socket so_reuseport提高服務端效能

以前就在國外的論壇接觸過 SO_REUSEPORT,這兩天朋友群又在傳播nginx 1.9  reuseport多程序監聽引數。 那咱們簡單說下 SO_REUSEPORT的應用場景, 為什麼會用他? 然而在講解SO_REUSEPORT之前,需要先說下我們常用的網路模型。

當前Linux網路應用程式問題

執行在Linux系統上網路應用程式,為了利用多核的優勢,一般使用以下比較典型的多程序/多執行緒伺服器模型:

    單執行緒listen/accept,多個工作執行緒接收任務分發,雖CPU的工作負載不再是問題,但會存在:

        單執行緒listener,在處理高速率海量連線時,一樣會成為瓶頸

        CPU快取行丟失套接字結構(socket structure)現象嚴重

    所有工作執行緒都accept()在同一個伺服器套接字上呢,一樣存在問題:

        多執行緒訪問server socket鎖競爭嚴重

        高負載下,執行緒之間處理不均衡,有時高達3:1不均衡比例

        導致CPU快取行跳躍(cache line bouncing)

        在繁忙CPU上存在較大延遲

上面模型雖然可以做到執行緒和CPU核繫結,但都會存在:

    單一listener工作執行緒在高速的連線接入處理時會成為瓶頸

    快取行跳躍

    很難做到CPU之間的負載均衡

    隨著核數的擴充套件,效能並沒有隨著提升

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

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

在多核時代,一般使用以下比較典型的多程序/多執行緒伺服器模型。


首先需要單執行緒listen一個埠上,然後由多個工作程序/執行緒去accept()在同一個伺服器套接字上。 

第一個效能瓶頸,單執行緒listener,在處理高速率海量連線時,一樣會成為瓶頸

第二個效能瓶頸,多執行緒訪問server socket鎖競爭嚴重。

那麼怎麼解決? 這裡先別扯什麼分散式排程,叢集xxx的 , 就拿單機來說問題。在 Linux kernel 3.9帶來了SO_REUSEPORT特性,她可以解決上面(單程序listen,多工作程序accept() )的問題.


看圖說話,對比SO_REUSADDR的模型,我想你應該看懂SO_REUSEPORT是個什麼東西了。   SO_REUSEPORT是

支援多個程序或者執行緒繫結到同一埠,提高伺服器程式的吞吐效能,具體來說解決了下面的幾個問題:

允許多個套接字 bind()/listen() 同一個TCP/UDP埠

每一個執行緒擁有自己的伺服器套接字

在伺服器套接字上沒有了鎖的競爭,因為每個程序一個伺服器套接字

核心層面實現負載均衡

安全層面,監聽同一個埠的套接字只能位於同一個使用者下面

我這邊用python做了一個關於python  SO_REUSEPORT服務端測試.   測試之前,已經要確定你的linux核心版本是3.9, 在mac下進行so_reuseport測試,貌似不會提示埠被繫結,但是後啟動的程序會阻塞.

file: reuseport.py

Python

import socket
import os
#xiaorui.cc
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
s.bind(('0.0.0.0', 1234))
s.listen(1)

while True:
    conn, addr = s.accept()
    print('Connected to {}'.format(os.getpid()))
    data = conn.recv(1024)
    conn.send(data)
    conn.close()
 
importsocket
importos
#xiaorui.cc
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEPORT,1)
s.bind(('0.0.0.0',1234))
s.listen(1)
 
whileTrue:
    conn,addr=s.accept()
    print('Connected to {}'.format(os.getpid()))
    data=conn.recv(1024)
    conn.send(data)
    conn.close()
 

開始測試 reuseport.py 

Python

nohup python reuseport.py &
nohup python reuseport.py &
nohup python reuseport.py &
nohup python reuseport.py &
nohup python reuseport.py &
 
nohup pythonreuseport.py&
nohup pythonreuseport.py&
nohup pythonreuseport.py&
nohup pythonreuseport.py&
nohup pythonreuseport.py&
 

使用nc測試

Python

echo 'xiaorui.cc' | nc localhost 1234
 
echo'xiaorui.cc'|nclocalhost1234
 

有些文章說,在python下多程序繫結同一個埠,也就是有人常說的prefork,他其實也是單個程序去listen監聽埠,剩餘的worker去accept獲取使用者請求而已.  如果想用python實現真正的多程序繫結在多一個埠,那隻能是用so_reuseport模式 。 

其實用python開發支援SO_REUSEPORT的服務端有個大好處,不用寫多程序,多執行緒了..   算是一個偷懶的方法。 我自己覺得python離 SO_REUSEPORT真實提高socket效能的應用場景比較的遠,就python這效能…. 倒是可以迅速的提高socket開發效率..      

另外標註下,SO_REUSEADDR和SO_REUSEPORT的區別

SO_REUSEADDR提供如下四個功能:

SO_REUSEADDR允許啟動一個監聽伺服器並捆綁其眾所周知埠,即使以前建立的將此埠用做他們的本地埠的連線仍存在。這通常是重啟監聽伺服器時出現,若不設定此選項,則bind時將出錯。

SO_REUSEADDR允許在同一埠上啟動同一伺服器的多個例項,只要每個例項捆綁一個不同的本地IP地址即可。對於TCP,我們根本不可能啟動捆綁相同IP地址和相同埠號的多個伺服器。

SO_REUSEADDR允許單個程序捆綁同一埠到多個套介面上,只要每個捆綁指定不同的本地IP地址即可。這一般不用於TCP伺服器。

SO_REUSEADDR允許完全重複的捆綁:當一個IP地址和埠繫結到某個套介面上時,還允許此IP地址和埠捆綁到另一個套介面上。一般來說,這個特性僅在支援多播的系統上才有,而且只對UDP套介面而言(TCP不支援多播)。

SO_REUSEPORT選項有如下語義:

此選項允許完全重複捆綁,但僅在想捆綁相同IP地址和埠的套介面都指定了此套介面選項才行。

如果被捆綁的IP地址是一個多播地址,則SO_REUSEADDR和SO_REUSEPORT等效。

學習SO_REUSEPORT時,參考的文章:

http://www.blogjava.net/yongboy/archive/2015/02/12/422893.html

http://www.cnblogs.com/mydomain/archive/2011/08/23/2150567.html