Go实现随机、轮训、权重、哈希负载均衡

作者: 太阳上的雨天 分类: Go 发布时间: 2022-11-16 16:22

1 什么是负载均衡

负载均衡(Load Balance,简称 LB)是高并发、高可用系统必不可少的关键组件,目标是 尽力将网络流量平均分发到多个服务器上,以提高系统整体的响应速度和可用性

负载均衡的主要作用:

高并发:负载均衡通过算法调整负载,尽力均匀的分配应用集群中各节点的工作量,以此提高应用集群的并发处理能力(吞吐量)。
伸缩性:添加或减少服务器数量,然后由负载均衡进行分发控制。这使得应用集群具备伸缩性。
高可用:负载均衡器可以监控候选服务器,当服务器不可用时,自动跳过,将请求分发给可用的服务器。这使得应用集群具备高可用的特性。
安全防护:有些负载均衡软件或硬件提供了安全性功能,如:黑白名单处理、防火墙,防 DDos 攻击等。

2 软件负载均衡

软件负载均衡的 主流产品 有:Nginx、HAProxy、LVS

  • LVS 可以作为四层负载均衡器。其负载均衡的性能要优于 Nginx。
  • HAProxy 可以作为 HTTP 和 TCP 负载均衡器。
  • Nginx、HAProxy 可以作为四层或七层负载均衡器。

3 Go实现负载均衡

完整代码: https://github.com/jeffcail/load-balance 觉得有帮助可以点个小星星star

部分代码如下:

3.1 随机

添加多个服务节点

func (r *RandomBalancing) 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 *RandomBalancing) Next() string {
    if len(r.rss) == 0 {
        return ""
    }
    r.curlIndex = rand.Intn(len(r.rss))
    return r.rss[r.curlIndex]
}
3.2 轮训

添加多个服务节点

func (r *RoundRotationBalance) 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 *RoundRotationBalance) Next() string {
    if len(r.rss) == 0 {
        return ""
    }

    lens := len(r.rss)
    if r.curIndex >= lens {
        r.curIndex = 0
    }
    curAddr := r.rss[r.curIndex]
    r.curIndex = (r.curIndex + 1) % lens
    return curAddr
}
3.3 加权

添加多个服务节点

func (r *WeightBalance) 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 nil
    }
    node := &WeightedNode{addr: params[0], weight: int(parInt)}
    node.effectiveWeight = node.weight
    r.rss = append(r.rss, node)
    return nil
}

根据当前权重和有效权重计算出权重最大的服务节点进行请求的转发

func (r *WeightBalance) Next() string {
    total := 0
    var best *WeightedNode
    for i := 0; i < len(r.rss); i++ {
        w := r.rss[i]
        total += w.effectiveWeight
        w.currentWeight += w.effectiveWeight

        if w.effectiveWeight < w.weight {
            w.effectiveWeight++
        }
        if best == nil || w.currentWeight > best.currentWeight {
            best = w
        }
    }
    if best == nil {
        return ""
    }

    best.currentWeight -= total
    return best.addr
}

3.4 一致性Hash

创建一个hash轮训对象实例

func NewConsistentHashBalance(replicas int, fn Hash) *ConsistentHashBalance {
    m := &ConsistentHashBalance{
        hash:     fn,
        replicas: replicas,
        hashMap:  make(map[uint32]string),
    }
    if m.hash == nil {
        m.hash = crc32.ChecksumIEEE
    }
    return m
}

调用对象的Add方法添加多个服务节点

func (c *ConsistentHashBalance) 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()
    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
}

创建一个字节数组,然后引用sort.Search从字节数组中根据索引通过二分查找的方法拿出索引对应的值,从hashMap中返回节点服务

func (c *ConsistentHashBalance) Get(key string) (string, error) {
    if c.IsEmpty() {
        return "", errors.New("node is empty")
    }
    hash := c.hash([]byte(key))

    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
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注