天天看點

關于Go利用Linux核心實作負載均衡

作者:mj談雲技術

概述

一般情況下,對某個HTTP服務的程序忘記關閉了。而重新嘗試啟動一個新的服務程序,會遇到以下的錯誤資訊:

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

這是由于預設情況下,作業系統不允許我們打開具有相同源位址和端口的socket。但如果我們想開啟多個服務程序去監聽同一個端口,可以使用以下的操作步驟實作。

Linux SO_REUSEPORT

為了滿足複用端口的需求,Linux3.9核心引入了SO_REUSEPORT選項(實際在此之前有一個類似的選項SO_REUSEADDR,但它沒有做到真正的端口複用)。

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

1.允許多個套接字bind同一個TCP/UDP 端口;

①每一個線程擁有自己的伺服器套接字

②在伺服器套接字上沒有了鎖的競争

2.核心層面實作負載均衡;

3.安全層面,監聽同一個端口的套接字隻能位于同一個使用者下(same effective UID)。

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

讓多程序監聽同一個端口,各個程序中accept socket fd不一樣,有新連接配接建立時,核心隻會排程一個程序來accept,并且保證排程的均衡性。

示意圖如下:

關于Go利用Linux核心實作負載均衡

有了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 main
import (
"context"
"fmt"
"golang.org/x/sys/unix"
"net"
"net/http"
"os"
"syscall"
)
var lc = net.ListenConfig{
Control: func(network, address string, c syscall.RawConn) error {
var socketErr error
if err := c.Control(func(fd uintptr) {
socketErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
}); err != nil {
return err
}
return socketErr
},
}
func main() {
pid := os.Getpid()
listen, err := lc.Listen(context.Background(), "tcp", "127.0.0.1:8080")
if err != nil {
panic(err)
}
server := &http.Server{}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(w, "The Client [%s] Received Message from Server PID: [%d] \n", r.RemoteAddr, pid)
})
fmt.Printf("Server with PID: [%d] is running \n", pid)
_ = server.Serve(listen)
}           

我們将其編譯為Linux可執行檔案main

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

在Linux主機上開啟三個同時監聽8080端口的程序,我們可以看到三個服務程序的 PID 分别是1569 、2419 和 3124。

~ $ ./main
Server with PID: [1569] is running
~ $ ./main
Server with PID: [2419] is running
~ $ ./main
Server with PID: [3124] is running           

通過curl指令,模拟多次http用戶端請求

# for i in {1..20}; do curl localhost:8080; done
The Client [127.0.0.1:19958] Received Message from Server PID: [1569]
The Client [127.0.0.1:19962] Received Message from Server PID: [2419]
The Client [127.0.0.1:19966] Received Message from Server PID: [3124]
The Client [127.0.0.1:19970] Received Message from Server PID: [2419]
The Client [127.0.0.1:19974] Received Message from Server PID: [3124]
The Client [127.0.0.1:19978] Received Message from Server PID: [2419]
The Client [127.0.0.1:19982] Received Message from Server PID: [2419]
The Client [127.0.0.1:19986] Received Message from Server PID: [2419]
The Client [127.0.0.1:19990] Received Message from Server PID: [3124]
The Client [127.0.0.1:19994] Received Message from Server PID: [3124]
The Client [127.0.0.1:19998] Received Message from Server PID: [2419]
The Client [127.0.0.1:20002] Received Message from Server PID: [3124]
The Client [127.0.0.1:20006] Received Message from Server PID: [1569]
The Client [127.0.0.1:20010] Received Message from Server PID: [1569]
The Client [127.0.0.1:20014] Received Message from Server PID: [2419]
The Client [127.0.0.1:20018] Received Message from Server PID: [2419]
The Client [127.0.0.1:20022] Received Message from Server PID: [2419]
The Client [127.0.0.1:20026] Received Message from Server PID: [2419]
The Client [127.0.0.1:20030] Received Message from Server PID: [2419]
The Client [127.0.0.1:20034] Received Message from Server PID: [1569]           

可以看到,20 個用戶端請求都均衡地在這三個服務程序上。

總結

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

這種機制有以下優點:

1.提高伺服器程式的吞吐性能:我們可以運作N個應用程式執行個體,充分利用多核CPU資源,避免出現單核在處理資料,其他核卻閑着的問題。

2.核心級的負載均衡:我們不需要在多個執行個體前面添加一層服務代理,因為核心已經提供了簡單的負載均衡。

以上就是今天的内容,如果大家有疑問或者新的想法,歡迎聯系我溝通交流。

繼續閱讀