Go 構建一個簡單的負載均衡器

Go

負載均衡器在現代軟件開發中至關重要。作爲開發者你肯定好奇過請求是如何在多個服務器之間分配的,或者爲什麼某些網站即使在高流量時也感覺更快,這就是負載均衡器的作用。

沒有負載均衡器

在這篇文章中,我們將使用 Go 語言構建一個簡單的應用程序負載均衡器,使用輪詢算法(Round Robin)。這篇文章的目的是一步步理解負載均衡器的內部工作原理。

什麼是負載均衡器?

負載均衡器是一個系統,它將傳入的網絡流量分配到多個服務器上。它確保沒有單個服務器承受過多的負載,防止瓶頸,提高整體用戶體驗。負載均衡方法還確保如果一個服務器失敗,流量可以自動重定向到另一個可用的服務器,從而減少故障的影響並增加可用性。

爲什麼使用負載均衡器?

負載均衡算法

有多種算法和策略用於分配流量,常見的有:

在這篇文章中,我們將專注於實現一個輪詢負載均衡器。

什麼是輪詢算法?

輪詢算法按循環方式將每個傳入的請求發送到下一個可用的服務器。如果服務器 A 處理了第一個請求,服務器 B 將處理第二個,服務器 C 將處理第三個。一旦所有服務器都收到了請求,它就從服務器 A 重新開始。

現在,讓我們跳到代碼中,構建我們的負載均衡器!

步驟 1:定義負載均衡器和服務器

type LoadBalancer struct {
    Current int
    Mutex   sync.Mutex
}

type Server struct {
    URL       *url.URL
    IsHealthy bool
    Mutex     sync.Mutex
}

我們首先定義一個簡單的 LoadBalancer 結構,其中包含一個 Current 字段,用於跟蹤下一個應該處理請求的服務器。Mutex 確保我們的代碼可以併發使用。

我們負載均衡的每個服務器由 Server 結構定義:

type Server struct {
    URL       *url.URL
    IsHealthy bool
    Mutex     sync.Mutex
}

這裏,每個服務器都有一個 URL 和一個 IsHealthy 標誌,指示服務器是否可用於處理請求。

步驟 2:輪詢算法

我們負載均衡器的核心是輪詢算法。它的工作原理如下:

func (lb *LoadBalancer) getNextServer(servers []*Server) *Server {
    lb.Mutex.Lock()
    defer lb.Mutex.Unlock()

    for i := 0; i < len(servers); i++ {
        idx := lb.Current % len(servers)
        nextServer := servers[idx]
        lb.Current++

        nextServer.Mutex.Lock()
        isHealthy := nextServer.IsHealthy
        nextServer.Mutex.Unlock()

        if isHealthy {
            return nextServer
        }
    }

    return nil
}

這個方法以輪詢方式循環遍歷服務器列表。如果選定的服務器是健康的,它就返回該服務器來處理傳入的請求。

我們使用 Mutex 確保一次只有一個 goroutine 可以訪問和修改負載均衡器的 Current 字段。這確保了當多個請求被併發處理時,輪詢算法能夠正確運行。

每個 Server 也有自己的 Mutex。當我們檢查 IsHealthy 字段時,我們鎖定服務器的 Mutex,以防止多個 goroutine 同時訪問。

如果沒有 Mutex 鎖定,另一個 goroutine 可能正在更改值,這可能導致讀取到不正確或不一致的數據。

我們一旦更新了 Current 字段或讀取了 IsHealthy 字段值,就立即解鎖 Mutex,以儘可能保持關鍵部分儘可能小。通過這種方式,我們使用 Mutex 來避免任何競態條件。

步驟 3:配置負載均衡器

我們的配置存儲在一個 config.json 文件中,其中包含服務器 URL 和健康檢查間隔(下面部分會詳細介紹)。

type Config struct {
    Port                string   `json:"port"`
    HealthCheckInterval string   `json:"healthCheckInterval"`
    Servers             []string `json:"servers"`
}

配置文件可能如下所示:

{
  "port"":8080",
  "healthCheckInterval""2s",
  "servers"[
    "http://localhost:5001",
    "http://localhost:5002",
    "http://localhost:5003",
    "http://localhost:5004",
    "http://localhost:5005"
  ]
}

步驟 4:健康檢查

我們希望在路由任何傳入流量之前確保服務器是健康的。這是通過向每個服務器發送定期健康檢查來完成的:

func healthCheck(s *Server, healthCheckInterval time.Duration) {
    for range time.Tick(healthCheckInterval) {
        res, err := http.Head(s.URL.String())
        s.Mutex.Lock()
        if err != nil || res.StatusCode != http.StatusOK {
            fmt.Printf("%s is down\n", s.URL)
            s.IsHealthy = false
        } else {
            s.IsHealthy = true
        }
        s.Mutex.Unlock()
    }
}

每隔幾秒鐘(如配置中指定),負載均衡器向每個服務器發送一個 HEAD 請求以檢查它是否健康。如果服務器宕機,IsHealthy 標誌被設置爲 false,阻止將來的流量被路由到它。

步驟 5:反向代理

當負載均衡器接收到請求時,它使用反向代理將請求轉發到下一個可用的服務器。在 Golang 中,httputil 包提供了一種內置的方式來處理反向代理,我們將通過 ReverseProxy 函數在我們的代碼中使用它:

func (s *Server) ReverseProxy() *httputil.ReverseProxy {
    return httputil.NewSingleHostReverseProxy(s.URL)
}

什麼是反向代理?

反向代理是位於客戶端和一個或多個後端服務器之間的服務器。它接收客戶端的請求,將其轉發給其中一個後端服務器,然後將服務器的響應返回給客戶端。客戶端與代理交互,不知道哪個特定的後端服務器正在處理請求。

在我們的例子中,負載均衡器充當反向代理,位於多個服務器前面,將傳入的 HTTP 請求分配給它們。

步驟 6:處理請求

當客戶端向負載均衡器發出請求時,它使用 getNextServer 函數中實現的輪詢算法選擇下一個可用的健康服務器,並將客戶端請求代理到該服務器。如果沒有健康的服務器可用,則我們向客戶端發送服務不可用錯誤。

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        server := lb.getNextServer(servers)
        if server == nil {
            http.Error(w, "No healthy server available", http.StatusServiceUnavailable)
            return
        }
        w.Header().Add("X-Forwarded-Server", server.URL.String())
        server.ReverseProxy().ServeHTTP(w, r)
    })

ReverseProxy 方法將請求代理到實際服務器,我們還添加了一個自定義頭部 X-Forwarded-Server 用於調試目的(儘管在生產環境中,我們應該避免暴露像這樣的內部服務器細節)。

步驟 7:啓動負載均衡器

最後,我們在指定的端口上啓動負載均衡器:

log.Println("Starting load balancer on port", config.Port)
err = http.ListenAndServe(config.Port, nil)
if err != nil {
        log.Fatalf("Error starting load balancer: %s\n", err.Error())
}

總結

在這篇文章中,我們使用輪詢算法從零開始在 Golang 中構建了一個基本的負載均衡器。這是一種簡單但有效的方式來在多個服務器之間分配流量,並確保你的系統能夠高效地處理更高的負載。

還有更多可以支持的能力,比如添加複雜的健康檢查,實現不同的負載均衡算法,或提高容錯性。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Hlmwl_reGzO6T08tcM33jg