分享
  1. 首页
  2. 文章

golang实现负载均衡算法

筑梦攻城狮 · · 1285 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

1、真实服务器

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、反向代理代码框架

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

3、随机负载均衡算法

// 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 这样的顺顺序将请求反向代理到后端服务器

原理是记录当前的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]
}

有疑问加站长微信联系(非本文作者)

本文来自:51CTO博客

感谢作者:筑梦攻城狮

查看原文:golang实现负载均衡算法

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1285 次点击 ∙ 1 赞
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏