golang實現負載均衡算法

一、真實服務器

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "strconv"
    "syscall"
    "time"
)

type realServer struct {
    Addr string
}

func (rs *realServer) HelloHandler(w http.ResponseWriter,r *http.Request){
    data := fmt.Sprintf("[%s] http://%s%s \n\n",rs.Addr,rs.Addr,r.RequestURI)
    w.Write([]byte(data))
}

func (rs *realServer) Run(){
    fmt.Println("Http server tart to serve at :",rs.Addr)
    mux := http.NewServeMux()
    mux.HandleFunc("/",rs.HelloHandler)
    server := &http.Server{
        Addr: rs.Addr,
        Handler: mux,
        WriteTimeout: time.Second * 3,
    }
    go func(){
        if err := server.ListenAndServe();err != nil{
            log.Fatal("Start http server failed,err:",err)
        }
    }()
}

func main() {
    doneCh := make(chan os.Signal)

    for i:=0;i<5;i++{
        port := "808" + strconv.Itoa(i)
        addr := "127.0.0.1:" + port
        rs := &realServer{Addr: addr}
        go rs.Run()

    }

    signal.Notify(doneCh,syscall.SIGINT,syscall.SIGTERM)
    <- doneCh
}

二、反向代理代碼框架

// 
package httpServer

import (
    "math/rand"
    "time"
)

type HttpServer struct {
    Host string
}

type LoadBalance struct {
    Servers []*HttpServer
}

func NewLoadBalance()*LoadBalance{
    return &LoadBalance{Servers:make([]*HttpServer,0)}
}

func NewHttpServer(host string)*HttpServer{
    return &HttpServer{
        Host:host,
    }
}

func (lb *LoadBalance)Add(server *HttpServer){
    lb.Servers = append(lb.Servers,server)
}

啓動服務算法

// server.go
package main

import (
    "log"
    "net/http"
    "net/http/httputil"
    "net/url"
    . "gostudy/reverseProxyDemo/httpServer"
)

type ReveseProxyHandler struct {

}

func (rph *ReveseProxyHandler)ServeHTTP(w http.ResponseWriter,r *http.Request){
    lb := NewLoadBalance()
    lb.Add(NewHttpServer("http://127.0.0.1:8080"))
    lb.Add(NewHttpServer("http://127.0.0.1:8081"))
    lb.Add(NewHttpServer("http://127.0.0.1:8082"))
    lb.Add(NewHttpServer("http://127.0.0.1:8083"))
    lb.Add(NewHttpServer("http://127.0.0.1:8084"))

    url,err := url.Parse(lb.GetHttpServerByRandom().Host)
    if err != nil {
        log.Println("[ERR] url.Parse failed,err:",err)
        return
    }
    proxy := httputil.NewSingleHostReverseProxy(url)
    proxy.ServeHTTP(w,r)

}

func main() {
    proxy := &ReveseProxyHandler{}

    log.Println("Start to serve at 127.0.0.1:8888")
    if err := http.ListenAndServe(":8888",proxy);err !=nil{
        log.Fatal("Failed to start reverse proxy server ,err:",err)
    }
}

三、隨機負載均衡算法

// httpServer/reverseProxy.go
// 隨機負載均衡
func (lb *LoadBalance)GetHttpServerByRandom()*HttpServer{
    rand.Seed(time.Now().UnixNano())
    index := rand.Intn(len(lb.Servers))
    return lb.Servers[index]
}

測試結果後端

$ for i in {0..9};do curl -s http://127.0.0.1:8888/reverseproxydemo?id=123;done
[127.0.0.1:8083] http://127.0.0.1:8083/reverseproxydemo?id=123
[127.0.0.1:8084] http://127.0.0.1:8084/reverseproxydemo?id=123
[127.0.0.1:8082] http://127.0.0.1:8082/reverseproxydemo?id=123
[127.0.0.1:8080] http://127.0.0.1:8080/reverseproxydemo?id=123
[127.0.0.1:8081] http://127.0.0.1:8081/reverseproxydemo?id=123
[127.0.0.1:8082] http://127.0.0.1:8082/reverseproxydemo?id=123
[127.0.0.1:8080] http://127.0.0.1:8080/reverseproxydemo?id=123
[127.0.0.1:8081] http://127.0.0.1:8081/reverseproxydemo?id=123
[127.0.0.1:8080] http://127.0.0.1:8080/reverseproxydemo?id=123
[127.0.0.1:8084] http://127.0.0.1:8084/reverseproxydemo?id=123

加權隨機

原理:獲取到全部節點的權重值,將weight個當前節點Index加到一個[]int,並隨機從中獲取一個index,例如:
A : B : C = 5:2:1 且ABC三個節點的Index分別爲0,1,2,那麼新建一個以下是切片:
[]int{0,0,0,0,0,1,1,2} ,而後經過rand(len([]int)) 隨機拿到一個index瀏覽器

// httpserver.go
type HttpServer struct {
    Host string
    Weight int
}

func NewHttpServer(host string,weight int)*HttpServer{
    return &HttpServer{
        Host:host,
        Weight:weight,
    }
}

// 加權隨機
func (lb *LoadBalance)GetHttpServerByRandomWithWeight(httpServerArr []int)*HttpServer{
    rand.Seed(time.Now().UnixNano())
    index := rand.Intn(len(httpServerArr))
    return lb.Servers[httpServerArr[index]]
}
// loadBalanceDemo/loadbalance.go
// 加權隨機
    var httpServerArr []int
    for index,server := range lb.Servers{
        if server.Weight > 0 {
            for i:=0;i<server.Weight;i++{
                httpServerArr = append(httpServerArr,index)
            }
        }
    }

    url,err := url.Parse(lb.GetHttpServerByRandomWithWeight(httpServerArr).Host)

加權隨機算法優化版

上面的加權隨機算法實現起來比較簡單,但存在一個明顯弊端,若是weight值的大小將直接影響切片大小,例如5:2 跟 50000:20000 本質上是同樣的,但後者將佔用更多的內存空間。所以咱們須要對該算法作下優化,將N個節點權重計算出N個區間,而後取隨機數rand(weightSum),看該數落在哪一個區間就返回該區間對應的index值,舉個例子:
假設A:B:C = 5:2:1
那麼咱們先計算出3個區間:5,7(5+2),8(5+2+1)
[0,5) [5,7) [7,8)
而後取rand(5+2+1),假設獲取到的值爲6,則落在[5,7) 這個區間,返回index=1
能夠看出rand(7)隨機數落在各個區間分佈以下:
[0,5) : 0,1,2,3,4
[5,7) :5,6
[7,8) :7
正好是5:2:1安全

下面是具體實現:服務器

// 加權隨機優化版
func (lb *LoadBalance)GetHttpServerByRandomWithWeight2()*HttpServer{
    rand.Seed(time.Now().UnixNano())
    // 計算全部節點權重值之和
    weightSum := 0
    for i:=0;i<len(lb.Servers);i++{
        weightSum += lb.Servers[i].Weight
    }
    // 隨機數獲取
    randNum := rand.Intn(weightSum)

    sum := 0
    for i := 0;i<len(lb.Servers);i++{
        sum += lb.Servers[i].Weight
        // 由於區間是[ ) ,左閉右開,故隨機數小於當前權重sum值,則表明落在該區間,返回當前的index
        if randNum < sum {
            return lb.Servers[i]
        }
    }
    return lb.Servers[0]
}

輪詢算法

假設有ABC 3臺機器,那麼請求過來將按照ABCABC 這樣的順順序將請求反向代理到後端服務器app

原理是記錄當前的index值,每次請求+1 取模(這裏僅演示算法,未考慮線程安全問題,沒有加鎖)負載均衡

// loadbalance.go
// 因爲每次請求須要保存當前的index值,因此使用全局變量lb,並在初始化函數中初始化lb實例
var lb *LoadBalance

func init(){
    lb = NewLoadBalance()
}
// httpserver.go
// 結構體中加上當前index值
type LoadBalance struct {
    Index int
    Servers []*HttpServer
}

// 輪詢
func (lb *LoadBalance)GetHttpServerByRoundRobin() *HttpServer{
    server := lb.Servers[lb.Index]
    lb.Index = (lb.Index + 1)% len(lb.Servers)
    return server
}

加權輪詢算法-切片算法

/ 加權輪詢
func (lb *LoadBalance)GetHttpServerByRoundRobinWithWeight(indexArr []int) *HttpServer{
    lb.Index = (lb.Index + 1)% len(indexArr)
    fmt.Println(indexArr)
    return lb.Servers[indexArr[lb.Index]]
}
package main

import (
    "log"
    "net/http"
    "net/http/httputil"
    "net/url"
    . "loadBalanceDemo/httpServer"
)

type ReveseProxyHandler struct {

}

var lb *LoadBalance
var indexArr []int

func init(){
    lb = NewLoadBalance()

    lb.Add(NewHttpServer("http://127.0.0.1:8082",5))
    lb.Add(NewHttpServer("http://127.0.0.1:8083",2))
    lb.Add(NewHttpServer("http://127.0.0.1:8084",1))

    // 加權輪詢
    indexArr = make([]int,0)
    for index,server := range lb.Servers{
        if server.Weight > 0{
            for i:=0;i<server.Weight;i++{
                indexArr = append(indexArr,index)
            }
        }
    }
}

func (rph *ReveseProxyHandler)ServeHTTP(w http.ResponseWriter,r *http.Request){

 // 瀏覽器訪問時默認會請求/favicon.ico,這裏忽略該URL
    if r.URL.Path == "/favicon.ico"{
        return
    }
    url,err := url.Parse(lb.GetHttpServerByRoundRobinWithWeight(indexArr).Host)

    if err != nil {
        log.Println("[ERR] url.Parse failed,err:",err)
        return
    }
    proxy := httputil.NewSingleHostReverseProxy(url)
    proxy.ServeHTTP(w,r)

}

func main() {
    proxy := &ReveseProxyHandler{}

    log.Println("Start to serve at 127.0.0.1:8888")
    if err := http.ListenAndServe(":8888",proxy);err !=nil{
        log.Fatal("Failed to start reverse proxy server ,err:",err)
    }
}

加權輪詢算法-區間算法

// 加權輪詢區間算法
func (lb *LoadBalance)GetHttpServerByRoundRobinWithWeight2()*HttpServer{
    server := lb.Servers[0]
    sum := 0

    for i:=0;i<len(lb.Servers);i++{
        sum += lb.Servers[i].Weight
        if lb.Index < sum{
            server = lb.Servers[i]
            if lb.Index == sum -1 && i != len(lb.Servers)-1{
                lb.Index++
            }else{
                lb.Index = (lb.Index+1) % sum
            }
            fmt.Println(lb.Index)
            break
        }
    }
    return server
}

ip_hash 算法

// ip_hash
// 對客戶端IP 作hash 取模獲得有一個固定的index,返回固定的httpserver
func (lb *LoadBalance)GetHttpServerByIpHash(ip string) *HttpServer{
    index := int(crc32.ChecksumIEEE([]byte(ip))) % len(lb.Servers)
    return lb.Servers[index]
}
// server.go
// ip_hash
// 傳入客戶端IP
    url,err := url.Parse(lb.GetHttpServerByIpHash(r.RemoteAddr).Host)

url_hash 算法

// url_hash
    url,err := url.Parse(lb.GetHttpServerByUrlHash(r.RequestURI).Host)
// url_hash
func (lb *LoadBalance) GetHttpServerByUrlHash(url string) *HttpServer{
    index := int(crc32.ChecksumIEEE([]byte(url))) % len(lb.Servers)
    return lb.Servers[index]
}
相關文章
相關標籤/搜索