日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Go 如何利用 Linux 内核的负载均衡能力?

發布時間:2024/4/11 linux 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Go 如何利用 Linux 内核的负载均衡能力? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在測試 HTTP 服務時,如果該進程我們忘記關閉,而重新嘗試啟動一個新的服務進程,那么將會遇到類似以下的錯誤信息:

$?go?run?main.go listen?tcp?:8000:?bind:?address?already?in?use

這是由于默認情況下,操作系統不允許我們打開具有相同源地址和端口的套接字 socket。但如果我們想開啟多個服務進程去監聽同一個端口,這可以嗎?如果可以,這又能給我們帶來什么?

socket 五元組

socket 編程是每位程序員都應該掌握的基礎知識。因此,大家應該知道,socket 連接通過五元組唯一標識。任意兩條連接,它的五元組不能完全相同。

{<protocol>,?<src?addr>,?<src?port>,?<dest?addr>,?<dest?port>}

protocol 指的是傳輸層 TCP/UDP 協議,它在 socket 被創建時就已經確定。src addr/port 與 dest addr/port ?分別標識著請求方與服務方的地址信息。

因此,只要請求方的 dest addr/port 信息不相同,那么服務方即使是同樣的 src addr/port ,它仍然可以標識唯一的 socket 連接。

基于這個理論基礎,那實際上,我們可以在同一個網絡主機復用相同的 IP 地址和端口號。

Linux SO_REUSEPORT

為了滿足復用端口的需求,Linux 3.9 內核引入了 SO_REUSEPORT選項(實際在此之前有一個類似的選項 SO_REUSEADDR,但它沒有做到真正的端口復用,詳細可見參考鏈接1)。

SO_REUSEPORT 支持多個進程或者線程綁定到同一端口,用于提高服務器程序的性能。它的特性包含以下幾點:

  • 允許多個套接字 bind 同一個TCP/UDP 端口

    • 每一個線程擁有自己的服務器套接字

    • 在服務器套接字上沒有了鎖的競爭

  • 內核層面實現負載均衡

  • 安全層面,監聽同一個端口的套接字只能位于同一個用戶下(same effective UID)

有了 SO_RESUEPORT 后,每個進程可以 bind 相同的地址和端口,各自是獨立平等的。

讓多進程監聽同一個端口,各個進程中 accept socket fd 不一樣,有新連接建立時,內核只會調度一個進程來 accept,并且保證調度的均衡性。

其工作示意圖如下

有了 SO_REUSEADDR 的支持,我們不僅可以創建多個具有相同 IP:PORT 的套接字能力,而且我們還得到了一種內核模式下的負載均衡能力。

Go 如何設置 SO_REUSEPORT

Linux 經典的設計哲學:一切皆文件。當然,socket 也不例外,它也是一種文件。

如果我們想在 Go 程序中,利用上 linux 的 SO_REUSEPORT 選項,那就需要有修改內核 socket 連接選項的接口,而這可以依賴于 golang.org/x/sys/unix 庫來實現,具體就在以下這個方法。

import?“"golang.org/x/sys/unix"” ... unix.SetsockoptInt(int(fd),?unix.SOL_SOCKET,?unix.SO_REUSEPORT,?1)

因此,一個持有 SO_REUSEPORT 特性的完整 Go 服務代碼如下

package?mainimport?("context""fmt""net""net/http""os""syscall""golang.org/x/sys/unix" )var?lc?=?net.ListenConfig{Control:?func(network,?address?string,?c?syscall.RawConn)?error?{var?opErr?errorif?err?:=?c.Control(func(fd?uintptr)?{opErr?=?unix.SetsockoptInt(int(fd),?unix.SOL_SOCKET,?unix.SO_REUSEPORT,?1)});?err?!=?nil?{return?err}return?opErr}, }func?main()?{pid?:=?os.Getpid()l,?err?:=?lc.Listen(context.Background(),?"tcp",?"127.0.0.1:8000")if?err?!=?nil?{panic(err)}server?:=?&http.Server{}http.HandleFunc("/",?func(w?http.ResponseWriter,?r?*http.Request)?{w.WriteHeader(http.StatusOK)fmt.Fprintf(w,?"Client?[%s]?Received?msg?from?Server?PID:?[%d]?\n",?r.RemoteAddr,?pid)})fmt.Printf("Server?with?PID:?[%d]?is?running?\n",?pid)_?=?server.Serve(l) }

我們將其編譯為 linux 可執行文件 main

$?CGO_ENABLED=0?GOOS=linux?GOARCH=amd64?go?build?main.go

在 linux 主機上開啟三個同時監聽 8000 端口的進程,我們可以看到三個服務進程的 PID 分別是 32687 、32691 和 32697。

~?$?./main Server?with?PID:?[32687]?is?running ~?$?./main Server?with?PID:?[32691]?is?running ~?$?./main Server?with?PID:?[32697]?is?running

最后,通過 curl 命令,模擬多次 http 客戶端請求

~?$?for?i?in?{1..20};?do?curl?localhost:8000;?done Client?[127.0.0.1:56876]?Received?msg?from?Server?PID:?[32697] Client?[127.0.0.1:56880]?Received?msg?from?Server?PID:?[32687] Client?[127.0.0.1:56884]?Received?msg?from?Server?PID:?[32687] Client?[127.0.0.1:56888]?Received?msg?from?Server?PID:?[32687] Client?[127.0.0.1:56892]?Received?msg?from?Server?PID:?[32691] Client?[127.0.0.1:56896]?Received?msg?from?Server?PID:?[32697] Client?[127.0.0.1:56900]?Received?msg?from?Server?PID:?[32691] Client?[127.0.0.1:56904]?Received?msg?from?Server?PID:?[32691] Client?[127.0.0.1:56908]?Received?msg?from?Server?PID:?[32697] Client?[127.0.0.1:56912]?Received?msg?from?Server?PID:?[32697] Client?[127.0.0.1:56916]?Received?msg?from?Server?PID:?[32687] Client?[127.0.0.1:56920]?Received?msg?from?Server?PID:?[32691] Client?[127.0.0.1:56924]?Received?msg?from?Server?PID:?[32697] Client?[127.0.0.1:56928]?Received?msg?from?Server?PID:?[32697] Client?[127.0.0.1:56932]?Received?msg?from?Server?PID:?[32691] Client?[127.0.0.1:56936]?Received?msg?from?Server?PID:?[32697] Client?[127.0.0.1:56940]?Received?msg?from?Server?PID:?[32687] Client?[127.0.0.1:56944]?Received?msg?from?Server?PID:?[32691] Client?[127.0.0.1:56948]?Received?msg?from?Server?PID:?[32687] Client?[127.0.0.1:56952]?Received?msg?from?Server?PID:?[32697]

可以看到,20 個客戶端請求被均衡地打到了三個服務進程上。

總結

linux 內核自 3.9 提供的 SO_REUSEPORT 選項,可以讓多進程監聽同一個端口。

這種機制帶來了什么:

  • 提高服務器程序的吞吐性能:我們可以運行多個應用程序實例,充分利用多核 CPU 資源,避免出現單核在處理數據包,其他核卻閑著的問題。

  • 內核級負載均衡:我們不需要在多個實例前面添加一層服務代理,因為內核已經提供了簡單的負載均衡。

  • 不停服更新:當我們需要更新服務時,可以啟動新的服務實例來接受請求,再優雅地關閉掉舊服務實例。

如果你們的 Go 項目,一到高峰期就有請求堆積問題,這個時候就可以考慮采用 SO_REUSEPORT 選項。

參考鏈接:

【1. How do SO_REUSEADDR and SO_REUSEPORT differ?】https://stackoverflow.com/questions/14388706/how-do-so-reuseaddr-and-so-reuseport-differ

【2. SO_REUSEPORT 性能測試】 http://www.blogjava.net/yongboy/archive/2015/02/12/422893.html

【3. linux socket man-page】https://man7.org/linux/man-pages/man7/socket.7.html

感謝你的點贊在看哦~

總結

以上是生活随笔為你收集整理的Go 如何利用 Linux 内核的负载均衡能力?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。