實現負載均衡器,打造億級流量入口
負載均衡概述
什麼是負載均衡
負載均衡是指通過某種手段, 將任務合理地分配到多個操作單元上進行執行, 從而使得系統的負載分散在不同操作單元上, 避免因爲單個操作單元負載過高而影響請求服務能力的技術手段。
比如將大量的客戶端請求分擔給多個服務器處理, 從而避免單個服務器超負荷工作對服務的影響。
負載均衡的作用
負載均衡主要有以下幾個作用:
降低單個服務器的壓力, 防止服務器過載;
增加系統容量, 實現擴展;
提高系統可用性和可靠性, 一臺服務器宕機不會導致整體服務不可用;
實現流量分發, 調節流量分佈;
軟件負載均衡和硬件負載均衡
軟件負載均衡 是通過負載均衡軟件和算法在一臺或多臺標準服務器上實現的負載均衡服務。常用開源軟件 Nginx,HAProxy 等。
優點是成本低, 擴展性好。缺點是性能瓶頸取決於軟件和標準服務器的性能。
硬件負載均衡 是通過專門的硬件設備來實現負載均衡。如 F5,Array 等硬件負載均衡器。
優點是性能好, 能力強。缺點是價格昂貴, 擴展性差。
軟硬件負載均衡各有優劣。一般軟負載均衡經濟實惠, 硬負載均衡性能更強。關鍵看應用場景需求。
如何實現負載均衡
Go 語言是一個高性能、高併發的編程語言, 非常適合用於編寫高性能服務端程序。藉助 Go 語言強大的併發編程支持, 可以很方便地實現自己的負載均衡器。
併發編程支持
Go 語言內置了強大的併發支持, 利用 goroutine 和 channel 就可以很容易實現高併發。理論上單個 Go 程序可以同時處理成千上萬的併發。這爲實現高性能負載均衡提供了很好的語言層面支持。
net/http 包
net/http 包提供了 HTTP 客戶端和服務端的實現。可基於 http 包快速搭建 HTTP 服務。配合 goroutine 可以輕鬆實現高併發的 HTTP 負載均衡。
goroutines
goroutine 是 Go 語言內置的輕量級線程, 啓動成本極低, 一個 Go 程序可以同時併發成千上萬個 goroutine。goroutine 與 channels 一起可以很容易實現高併發服務。
channels
channel 是 Go 內置的 goroutine 通信機制。可利用 channel 在多個 goroutine 間傳遞消息和數據。
負載均衡算法
常見的負載均衡算法有: 輪詢算法、最少連接算法、權重輪詢算法、一致性哈希算法等。
輪詢算法
輪詢算法是最簡單的負載均衡算法。它根據後端服務器列表順序, 將請求依次分發到各個後端服務器。
有新請求時, always 選擇列表中的下一個服務器。
優點: 實現簡單, 無狀態, 層次擴展簡單
缺點: 不能考慮服務器的性能差異, 負載可能不均衡
最少連接算法
最少連接算法會統計每個後端服務器當前的活躍連接數, always 選擇活躍連接數最少的服務器處理請求。
優點: 考慮了後端服務器處理負載能力, 請求負載較爲均衡
缺點: 需要記錄每個服務器的連接數, 實現相對複雜
權重輪詢算法
權重輪詢算法爲每個後端服務器設置權重, 根據權重大小將請求分發到不同後端。權重越大的服務器處理的請求越多。
優點: 可以靈活配置不同後端服務器的處理能力
缺點: 權重配置合理性影響效果, 實現也相對複雜
哈希算法
哈希算法使用哈希函數根據請求內容選擇後端服務器。相同請求 always 路由到同一臺後端。
優點: 處理請求性能高, 分發穩定
缺點: 後端服務器擴展性差, 哈希函數合理性影響效果
負載均衡的實現
主要涉及到 2 大部分:
1、負載均衡器, 負責接收客戶端請求, 實現請求負載分發
2、後端服務器, 處理實際的客戶端請求
輪詢算法
一、定義後端 Server 結構體
// 後端服務器結構體
type Server struct {
URL string
}
// 服務器列表
var Servers = []Server{
{
URL: "http://127.0.0.1:8001",
},
{
URL: "http://127.0.0.1:8002",
},
{
URL:"http://127.0.0.1:8003",
}
}
二、實現代理服務 Hanlder
// 代理服務Handler
func Forward(w http.ResponseWriter, r *http.Request) {
// 輪詢選擇後端服務器
server := Servers[index]
index = (index + 1) % len(Servers)
// 重寫請求路徑
r.URL.Scheme = "http"
r.URL.Host = server.URL
// 生成請求
req, _ := http.NewRequest(r.Method, server.URL, r.Body)
// 轉發請求
resp, _ := http.DefaultClient.Do(req)
// 返回響應
// ......
}
三、啓動 HTTP 服務
http.HandleFunc("/", Forward)
log.Println("Start proxy at :8888")
err := http.ListenAndServe(":8888", nil)
if err != nil {
log.Fatal(err)
}
最少連接算法
// 記錄服務器連接數
var connCount [3]int
// 負載均衡器處理
func Forward(w http.ResponseWriter, r *http.Request) {
// 選擇活躍連接最少的服務器
idx := 0
for i := 0; i < len(connCount); i++ {
if connCount[i] < connCount[idx] {
idx = i
}
}
server := Servers[idx]
// 請求處理
req, _ := http.NewRequest(r.Method, server.URL, r.Body)
resp, _ := http.DefaultClient.Do(req)
// 更新連接數
connCount[idx]++
// 返回響應
defer func() {
connCount[idx]--
}()
}
權重輪詢算法
// 定義服務器結構體 加入權重字段
type Server struct {
URL string
Weight int // 權重
}
// 負載均衡器處理
func Forward(w http.ResponseWriter, r *http.Request){
// 根據權重選擇服務器
cw := rand.Intn(sumWeights)
var idx int
for _, server := range Servers {
cw -= server.Weight
if cw <= 0 {
break
}
idx++
}
// 請求處理
s := Servers[idx]
req,_ := http.NewRequest(r.Method, s.URL, r.Body)
resp, _ := http.DefaultClient.Do(req)
// 返回響應
}
哈希算法
// 哈希算法
func getServer(r *http.Request) Server {
// 定義hash函數
h := fnv.New32a()
h.Write([]byte(r.RemoteAddr))
idx := h.Sum32() % uint32(len(Servers))
return Servers[idx]
}
func Forward(w http.ResponseWriter, r *http.Request) {
// 1. 哈希選擇服務器
server := getServer(r)
// 2. 轉發請求
req, _ := http.NewRequest(r.Method, server.URL, r.Body)
resp, _ := http.DefaultClient.Do(req)
// 3. 返回響應
// ......
}
實際應用案例
Web 應用
可以部署多個 Go Web 服務器, 並置一個 Go 語言編寫的負載均衡器於其前。
負載均衡器收到請求後通過負載算法選擇後端服務器, 實現用戶請求的負載分發。
API 服務器
類似地,可以搭建多個 API 應用服務器, 負載均衡器收到 API 請求後分發到後端 API 服務器。這樣能提高 API 服務器負載能力, 實現高可用性。
總結
Go 語言實現負載均衡的優勢
併發能力強, 易實現高併發負載均衡
net/http 包足夠靈活, 可實現層層代理轉發
自帶 channel 和網絡庫, 簡化開發高併發服務
跨平臺支持, 編譯後可在多平臺運行
優化的方向
可添加服務發現機制, 支持後端服務器動態擴容縮容
添加健康檢查機制, 避免轉發請求到異常服務器
支持更多負載均衡算法, 比如一致性哈希等
可添加可配置化特性, 通過配置文件控制規則
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/1DsjVotStErbfWjaHDFxdg