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 取模獲得有一個固定的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,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] }