1. 四大负载均衡策略
- 随机负载
- 随机挑选目标服务器IP
- 轮询负载
- ABC三台服务器,ABCABC依次轮询
- 加权负载
- 给目标设置访问权重,按照权重轮询
- 一致性hash负载
- 请求固定URL访问指定IP
2.随机负载均衡
可以通过random函数来随机选择一个ip
2.1 代码实现
type RandomBalance struct {curIndex intrss []string
}func (r *RandomBalance) Add(params ...string) error {if len(params) == 0 {return errors.New("param len 1 at least")}addr := params[0]r.rss = append(r.rss, addr)return nil
}func (r *RandomBalance) Next() string {if len(r.rss) == 0 {return ""}r.curIndex = rand.Intn(len(r.rss))return r.rss[r.curIndex]
}func (r *RandomBalance) Get(key string) (string, error) {return r.Next(), nil
}
2.2 测试代码
package load_balanceimport ("fmt""testing"
)func TestRandomBalance(t *testing.T) {rb := &RandomBalance{}rb.Add("127.0.0.1:2003") //0rb.Add("127.0.0.1:2004") //1rb.Add("127.0.0.1:2005") //2rb.Add("127.0.0.1:2006") //3rb.Add("127.0.0.1:2007") //4fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())
}
2.3 测试结果
3.轮询负载均衡
按一定顺序进行轮询
3.1 代码实现
type RoundRobinBalance struct {curIndex intrss []string
}func (r *RoundRobinBalance) Add(params ...string) error {if len(params) == 0 {return errors.New("param len 1 at least")}addr := params[0]r.rss = append(r.rss, addr)return nil
}func (r *RoundRobinBalance) Next() string {if len(r.rss) == 0 {return ""}lens := len(r.rss) //5if r.curIndex >= lens {r.curIndex = 0}curAddr := r.rss[r.curIndex]// 这里是轮询实现的重点r.curIndex = (r.curIndex + 1) % lensreturn curAddr
}func (r *RoundRobinBalance) Get(key string) (string, error) {return r.Next(), nil
}
3.2 测试代码
package load_balanceimport ("fmt""testing"
)func Test_main(t *testing.T) {rb := &RoundRobinBalance{}rb.Add("127.0.0.1:2003") //0rb.Add("127.0.0.1:2004") //1rb.Add("127.0.0.1:2005") //2rb.Add("127.0.0.1:2006") //3rb.Add("127.0.0.1:2007") //4fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())
}
3.3 测试结果
4.加权负载均衡
- Weight
- 初始化时对节点约定的权重
- currentWeight
- 节点的临时权重,每轮都会变化
- effectiveWeight
- 节点有效权重,默认与Weight相同
- 当发生故障时,effectiveWeight会减少
- totalWeight
- 所有节点有效权重之和:sum(effectiveWeight)
根据不同的权重给于不同的概率,权重往往与地域、服务器的性能相关。
4.1 代码实现
type WeightRoundRobinBalance struct {curIndex intrss []*WeightNodersw []int
}type WeightNode struct {addr stringweight int //权重值currentWeight int //节点当前权重effectiveWeight int //有效权重
}func (r *WeightRoundRobinBalance) Add(params ...string) error {if len(params) != 2 {return errors.New("param len need 2")}parInt, err := strconv.ParseInt(params[1], 10, 64)if err != nil {return err}node := &WeightNode{addr: params[0], weight: int(parInt)}node.effectiveWeight = node.weightr.rss = append(r.rss, node)return nil
}func (r *WeightRoundRobinBalance) Next() string {total := 0var best *WeightNodefor i := 0; i < len(r.rss); i++ {w := r.rss[i]//step 1 统计所有有效权重之和total += w.effectiveWeight//step 2 变更节点临时权重为的节点临时权重+节点有效权重w.currentWeight += w.effectiveWeight//step 3 有效权重默认与权重相同,通讯异常时-1, 通讯成功+1,直到恢复到weight大小if w.effectiveWeight < w.weight {w.effectiveWeight++}//step 4 选择最大临时权重点节点if best == nil || w.currentWeight > best.currentWeight {best = w}}if best == nil {return ""}//step 5 变更临时权重为 临时权重-有效权重之和best.currentWeight -= totalreturn best.addr
}func (r *WeightRoundRobinBalance) Get(key string) (string, error) {return r.Next(), nil
}
4.2 测试代码
func TestLB(t *testing.T) {rb := &WeightRoundRobinBalance{}rb.Add("127.0.0.1:2003", "4") //0rb.Add("127.0.0.1:2004", "3") //1rb.Add("127.0.0.1:2005", "2") //2fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())fmt.Println(rb.Next())
}
4.3 测试结果
5.一致性负载均衡
- 一致性Hash指标
- 单调性
- 平衡性
- 分散性
链接: 一致性负载均衡理论介绍
5.1 代码实现
package load_balanceimport ("errors""fmt""hash/crc32""sort""strconv""strings""sync"
)type Hash func(data []byte) uint32type UInt32Slice []uint32func (s UInt32Slice) Len() int {return len(s)
}func (s UInt32Slice) Less(i, j int) bool {return s[i] < s[j]
}func (s UInt32Slice) Swap(i, j int) {s[i], s[j] = s[j], s[i]
}type ConsistentHashBanlance struct {mux sync.RWMutexhash Hashreplicas int //复制因子keys UInt32Slice //已排序的节点hash切片hashMap map[uint32]string //节点哈希和Key的map,键是hash值,值是节点key
}func NewConsistentHashBanlance(replicas int, fn Hash) *ConsistentHashBanlance {m := &ConsistentHashBanlance{replicas: replicas,hash: fn,hashMap: make(map[uint32]string),}if m.hash == nil {//最多32位,保证是一个2^32-1环m.hash = crc32.ChecksumIEEE}return m
}// 验证是否为空
func (c *ConsistentHashBanlance) IsEmpty() bool {return len(c.keys) == 0
}// Add 方法用来添加缓存节点,参数为节点key,比如使用IP
func (c *ConsistentHashBanlance) Add(params ...string) error {if len(params) == 0 {return errors.New("param len 1 at least")}addr := params[0]c.mux.Lock()defer c.mux.Unlock()// 结合复制因子计算所有虚拟节点的hash值,并存入m.keys中,同时在m.hashMap中保存哈希值和key的映射for i := 0; i < c.replicas; i++ {hash := c.hash([]byte(strconv.Itoa(i) + addr))c.keys = append(c.keys, hash)c.hashMap[hash] = addr}// 对所有虚拟节点的哈希值进行排序,方便之后进行二分查找sort.Sort(c.keys)return nil
}// Get 方法根据给定的对象获取最靠近它的那个节点
func (c *ConsistentHashBanlance) Get(key string) (string, error) {if c.IsEmpty() {return "", errors.New("node is empty")}hash := c.hash([]byte(key))// 通过二分查找获取最优节点,第一个"服务器hash"值大于"数据hash"值的就是最优"服务器节点"idx := sort.Search(len(c.keys), func(i int) bool { return c.keys[i] >= hash })// 如果查找结果 大于 服务器节点哈希数组的最大索引,表示此时该对象哈希值位于最后一个节点之后,那么放入第一个节点中if idx == len(c.keys) {idx = 0}c.mux.RLock()defer c.mux.RUnlock()return c.hashMap[c.keys[idx]], nil
}
5.2 测试代码
package load_balanceimport ("fmt""testing"
)func TestNewConsistentHashBanlance(t *testing.T) {rb := NewConsistentHashBanlance(10, nil)rb.Add("127.0.0.1:2003") //0rb.Add("127.0.0.1:2004") //1rb.Add("127.0.0.1:2005") //2rb.Add("127.0.0.1:2006") //3rb.Add("127.0.0.1:2007") //4//url hashfmt.Println(rb.Get("http://127.0.0.1:2002/base/getinfo"))fmt.Println(rb.Get("http://127.0.0.1:2002/base/error"))fmt.Println(rb.Get("http://127.0.0.1:2002/base/getinfo"))fmt.Println(rb.Get("http://127.0.0.1:2002/base/changepwd"))//ip hashfmt.Println(rb.Get("127.0.0.1"))fmt.Println(rb.Get("192.168.0.1"))fmt.Println(rb.Get("127.0.0.1"))
}
5.3 测试结果
6.为代理添加负载均衡
package mainimport ("bytes""github.com/e421083458/gateway_demo/proxy/load_balance""io/ioutil""log""net""net/http""net/http/httputil""net/url""strconv""strings""time"
)var (addr = "127.0.0.1:2002"transport = &http.Transport{DialContext: (&net.Dialer{Timeout: 30 * time.Second, //连接超时KeepAlive: 30 * time.Second, //长连接超时时间}).DialContext,MaxIdleConns: 100, //最大空闲连接IdleConnTimeout: 90 * time.Second, //空闲超时时间TLSHandshakeTimeout: 10 * time.Second, //tls握手超时时间ExpectContinueTimeout: 1 * time.Second, //100-continue状态码超时时间}
)func NewMultipleHostsReverseProxy(lb load_balance.LoadBalance) *httputil.ReverseProxy {// 请求协调者director := func(req *http.Request) {// 获取请求者的req.RemoteAddr,根据这个获得下游ipnextAddr, err := lb.Get(req.RemoteAddr)if err != nil {log.Fatal("get next addr fail")}target, err := url.Parse(nextAddr)if err != nil {log.Fatal(err)}targetQuery := target.RawQueryreq.URL.Scheme = target.Schemereq.URL.Host = target.Hostreq.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)if targetQuery == "" || req.URL.RawQuery == "" {req.URL.RawQuery = targetQuery + req.URL.RawQuery} else {req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery}if _, ok := req.Header["User-Agent"]; !ok {req.Header.Set("User-Agent", "user-agent")}}//更改内容modifyFunc := func(resp *http.Response) error {//请求以下命令:curl 'http://127.0.0.1:2002/error'if resp.StatusCode != 200 {//获取内容oldPayload, err := ioutil.ReadAll(resp.Body)if err != nil {return err}//追加内容newPayload := []byte("StatusCode error:" + string(oldPayload))resp.Body = ioutil.NopCloser(bytes.NewBuffer(newPayload))resp.ContentLength = int64(len(newPayload))resp.Header.Set("Content-Length", strconv.FormatInt(int64(len(newPayload)), 10))}return nil}//错误回调 :关闭real_server时测试,错误回调//范围:transport.RoundTrip发生的错误、以及ModifyResponse发生的错误errFunc := func(w http.ResponseWriter, r *http.Request, err error) {//todo 如果是权重的负载则调整临时权重http.Error(w, "ErrorHandler error:"+err.Error(), 500)}return &httputil.ReverseProxy{Director: director, Transport: transport, ModifyResponse: modifyFunc, ErrorHandler: errFunc}
}func singleJoiningSlash(a, b string) string {aslash := strings.HasSuffix(a, "/")bslash := strings.HasPrefix(b, "/")switch {case aslash && bslash:return a + b[1:]case !aslash && !bslash:return a + "/" + b}return a + b
}func main() {rb := load_balance.LoadBanlanceFactory(load_balance.LbWeightRoundRobin)if err := rb.Add("http://127.0.0.1:2003/base", "10"); err != nil {log.Println(err)}if err := rb.Add("http://127.0.0.1:2004/base", "20"); err != nil {log.Println(err)}proxy := NewMultipleHostsReverseProxy(rb)log.Println("Starting httpserver at " + addr)log.Fatal(http.ListenAndServe(addr, proxy))
}