Goang Implements Load Balancing Algorithm

1. Real Server

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
}

2. Reverse Proxy Code Framework

// 
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)
}

Start Services

// 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)
    }
}

3. Random Load Balancing Algorithms

// httpServer/reverseProxy.go
// Random Load Balancing
func (lb *LoadBalance)GetHttpServerByRandom()*HttpServer{
    rand.Seed(time.Now().UnixNano())
    index := rand.Intn(len(lb.Servers))
    return lb.Servers[index]
}

test result

$ 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

Weighted Random

Principle: Get weight values for all nodes, add weight current node Index to a []int, and randomly get an index from it, for example:
A:B:C = 5:2:1 and the Index es of the three ABC nodes are 0,1,2, respectively. Then create a new one as follows:
[]int{0,0,0,0,0,1,1,2} and then get a random index by rand(len([]int))

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

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

// Weighted Random
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
// Weighted Random
    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)

Optimized version of weighted random algorithm

The weighted random algorithm above is relatively simple to implement, but has one obvious drawback. If the weight value is large, it will directly affect the size of the slice, for example, 5:2 is essentially the same as 50000:20000, but the latter will take up more memory space.So we need to optimize the algorithm, calculate the weight of N nodes into N intervals, and then take the random number rand(weightSum) to see which interval the number falls in and return the corresponding index value for that interval, for example:
Assume A:B:C = 5:2:1
So let's first calculate three intervals: 5,7 (5+2), 8 (5+2+1)
[0,5) [5,7) [7,8)
Then take rand(5+2+1) and assume the value is 6, it falls within [5,7] and returns index=1
It can be seen that rand(7) random numbers fall in the following distribution in each interval:
[0,5) : 0,1,2,3,4
[5,7) : 5,6
[7,8) : 7
Exactly 5:2:1

Here's the implementation:

// Weighted Random Optimized Version
func (lb *LoadBalance)GetHttpServerByRandomWithWeight2()*HttpServer{
    rand.Seed(time.Now().UnixNano())
    // Calculate the sum of all node weight values
    weightSum := 0
    for i:=0;i<len(lb.Servers);i++{
        weightSum += lb.Servers[i].Weight
    }
    // Random number acquisition
    randNum := rand.Intn(weightSum)

    sum := 0
    for i := 0;i<len(lb.Servers);i++{
        sum += lb.Servers[i].Weight
        // Because the interval is [), left closed and right open, random numbers less than the current weight sum represent falling within the interval, returning the current index
        if randNum < sum {
            return lb.Servers[i]
        }
    }
    return lb.Servers[0]
}

polling algorithm

Assuming there are three ABC machines, the request will be proxied back to the back-end server in a sequence like ABCABC

The principle is to record the current index value, and to request a + 1 modulus each time (this demonstrates the algorithm only, does not consider thread security issues, does not lock)

// loadbalance.go
// Since each request requires the current index value to be saved, use the global variable lb and initialize the lb instance in the initialization function
var lb *LoadBalance

func init(){
    lb = NewLoadBalance()
}
// httpserver.go
// Add the current index value to the structure
type LoadBalance struct {
    Index int
    Servers []*HttpServer
}

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

Weighted polling algorithm-slicing algorithm

/ Weighted Polling
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))

    // Weighted Polling
    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){

 // Browser access requests/Favicon.icoIgnore the URL here
    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)
    }
}

Weighted polling algorithm-interval algorithm

// Weighted polling interval algorithm
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 algorithm

// ip_hash
// Hatching client IP yields a fixed index, returning a fixed 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
// Incoming Client IP
    url,err := url.Parse(lb.GetHttpServerByIpHash(r.RemoteAddr).Host)

url_hash algorithm

// 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]
}

Tags: Go curl less

Posted on Fri, 26 Jun 2020 13:12:54 -0400 by Richzilla