golang实践录:使用gin框架实现转发功能:一些负载均衡算法的实现(代码片段)

李迟 李迟     2023-03-09     580

关键词:

近段时间需要实现一个转发 post 请求到指定后端服务的小工具,由于一直想学习 gin 框架,所以就使用这个框架进行尝试,预计会产生几篇文章。本文研究一些负载均衡算法的实现。

概述

本文实现的负载均衡纯粹是为了笔者的一个念想,并不具有实际指导意义。

本文假定有一个后端服务 URL 的数组,因此,在实现上,仅是输出数组的索引值。权重数组每个索引值对应一个后端服务。比如"1 3 3",表示有3台服务器,第1台权重为1,第2、3台权重均为3。

算法实现及测试结果

负载均衡有很多类型,如:随机、加权随机;简单轮询、加权轮询、平滑加权轮询,等。本文仅实现几种轮询算法,并且按请求次序递增,不使用随机数。

简单轮询算法

算法描述:

按请求先后轮询服务器。接收到第一次请求,转发到第1台服务器,第二次请求,转发到第2台服务器,依次类推,如果轮询到最后一台服务器,再转发第一台。

代码:


var lb_index int = 0

// 简单轮询
func getOneServerUrlForLB_RR(exTime string) (url string) 

    url = ""
    count := conf.TotalPort //len(conf.BackInfo)

    if lb_index >= count 
        lb_index = 0
    
    fmt.Println("indx: ", lb_index, count)
    url = fmt.Sprintf("http://127.0.0.1:%d", conf.BackPorts[lb_index])
    fmt.Println("got url: ", url)

    lb_index += 1

    return

加权轮询算法

一个权重的示意:

3台机器(分别为A、B、C),权重分别为5,3,2。排列(注:按设置的权重先后列出):

   5       3    2
1       5     8   10
| ----- | --- | -- |
   A       B     C

某一区间大,表示该服务器的权重大。
设一数,其值在1~10之间,即范围在权重总和之内,依次与5、3、2对比,如小则在该区间,如大,则减去前一数,再比对。看落到哪个区域。
举例:
设该数为3,与5对比,小,则落到第1区间,即选择服务器 A。
设该数为7,与5对比,大,取下一区间,即减去5,得2,与3对比,小,则落到第2区间,即选择服务器 B。
设该数为9,与5对比,大,减去5得4,与3对比,大,减去3得1,与2对比,小,第3区间,即选择服务器 C。

注意,我们关注的是某个区间出现的次数,并不关注是哪一个索引。以轮询 10 次为例,只要保证A、B、C服务器分别访问了5、3、2次即可。最简单的算法,就是将这次 10 次按权重依次分配到A、B、C服务器。

代码:

/*
测试用,假定有3组IP,每组分配好权重,根据请求顺序,按权重分配
可以认为,IP总数为权重总值,只是部分重复了,所以请求循环的次数就是权重总值
*/
// 因为测试,所以直接赋值,测试的总数不超过 conf.TotalPort
// conf.Weights 示例:[3]int2, 5, 3, 数组索引0表示第1个后端,1表示第2个后端

func getIndex_WRR(offset int, totalWeight int) (index int) 

    // 如果不指定,使用随机
    if offset == -1 
        rand.Seed(time.Now().UnixNano())
        offset = rand.Int() % totalWeight
    
    // fmt.Println("total: ", totalWeight, offset)

    // 注:这里关注的是某个索引值的次数,与顺序无关
    for index, w := range conf.Weights 
        if offset < w 
            return index
        
        offset -= w
    

    return


func getOneServerUrlForLB_WRR(exTime string) (url string) 

    url = ""
    count := conf.TotalPort

    totalWeight := 0
    for _, w := range conf.Weights 
        totalWeight += w
    

    count = totalWeight // 注:按权重总数轮询
    if lb_index >= count 
        lb_index = 0
    
    new_lb_index := getIndex_WRR(lb_index, totalWeight)

    if lb_index == 100 
        fmt.Println("indx: ", lb_index, new_lb_index, count)
    
    fmt.Printf("%d ", new_lb_index)

    url = fmt.Sprintf("http://127.0.0.1:%d", conf.BackPorts[new_lb_index])
    fmt.Println("got url: ", url)

    lb_index += 1

    return

实验结果:

./httpforward.exe -w "5 3 2"
0 0 0 0 0 1 1 1 2 2 0 0 0 0 0 1 1 1 2 2

./httpforward.exe -w "1 3 3"
0 1 1 1 2 2 2 0 1 1 1 2 2 2

./httpforward.exe -w "5 3 1"
0 0 0 0 0 1 1 1 2 0 0 0 0 0 1 1 1 2

为了数据可靠,轮询了2遍,下同。

平滑加权轮询算法

上述算法并不考虑服务器处理的效率,比如前面5次均在A服务器,其它服务器均为空闲状态,由此引出平滑加权轮询算法。笔者暂未参透该算法的证明过程,因此本文不涉及。

算法:
设置如下变量:
总权重值:totalWeight,其值固定,即所有权重值之和。
固定权重数组:Weights,其值固定,一般由用户指定。
当前权重数组 CurrentWeights,其值可变。
当前权重数组最大值 maxWeight,CurrentWeights数组的最大值。

0、某次请求到来。
1、判断当前权重数组(对应下表当前权重1)值是否全为0,如是,则用固定权重初始化之。
2、在当前权重数组中查找最大值,其对应的索引,即为需要返回的服务器。
3、将第2步的索引对应的值,减去权重总和,其它索引的值保持不变。该值可以为负数。(对应下表当前权重2
4、 将第3步得到的当前权重数组的每个值,加上对应的固定权重值。
5、回到第1步,重复。

假定有3台服务器A、B、C,其权重依次为 5、1、1,权重总和为7。演算过程如下:

初始时,当前权重为[0,0,0],则用固定权重[5,1,1]初始化之。
此时,最大值为5,索引为0,返回服务器A。
将[5,1,1]的索引0值减去权重总和7,得到[-2,1,1]。
将[-2,1,1]加上[5,1,1],得到新的当前权重[3,2,2]。
此时,最大值为3,索引为0,返回服务器A。
将[3,2,2]的索引0值减去权重总和7,得到[-4,2,2]。
将[-4,2,2]加上[5,1,1],得到新的当前权重[1,3,3]。
(下略)

总体过程如下表:

请求次数当前权重1返回服务器当前权重2
0[0, 0,0]--初始化为[5, 1, 1]
1[5, 1, 1]A[-2, 1, 1]
2[3, 2, 2]A[-4, 2, 2]
3[1, 3, 3]B[1, -4, 3]
4[6, -3, 4]A[-1, -3, 4]
5[4, -2, 5]C[4, -2, -2]
6[9, -1, -1]A[2, -1, -1]
7[7, 0, 0]A[0, 0, 0]
8[5, 1, 1]A[-2, 1, 1]

代码:

// 平滑加权
func getIndex_SWRR(offset int, totalWeight int) (index int) 

    initflag := 0

    // 0.判断是否全为0
    for _, w := range conf.CurrentWeights 
        if w == 0 
            initflag++
        
    
    // 全为0,则需要初始化
    if initflag == len(conf.CurrentWeights) 
        for idx, w := range conf.Weights 
            conf.CurrentWeights[idx] = w
        
    

    // 1. 查询当前权重表最大者,并取该索引,即为所需结果
    maxWeight := 0
    for idx, w := range conf.CurrentWeights 
        if w > maxWeight 
            maxWeight = w
            index = idx
        
    
    // 2. 最大的那个权重,其值减去总权重
    conf.CurrentWeights[index] = maxWeight - totalWeight
    // fmt.Println("current222: ", conf.CurrentWeights)

    // 3. 新的当前权重,所对应的值加上初始权重
    // 为下次循环计算打下基础
    for idx, w := range conf.Weights 
        conf.CurrentWeights[idx] += w
    
    return


func getOneServerUrlForLB_SWRR(exTime string) (url string) 
    url = ""

    totalWeight := 0

    for _, w := range conf.Weights 
        totalWeight += w
    

    // 注:按权重总数轮询
    if lb_index >= totalWeight 
        lb_index = 0
    
    new_lb_index := getIndex_SWRR(lb_index, totalWeight)

    if lb_index == 100 
        fmt.Println("indx1: ", lb_index, new_lb_index, totalWeight)
    
    fmt.Printf("%d ", new_lb_index)
    url = fmt.Sprintf("http://127.0.0.1:%d", conf.BackPorts[new_lb_index])
    //fmt.Println("got url: ", url)

    lb_index += 1

    return

实验结果:

./httpforward.exe -w "5 1 1"
0 0 1 0 2 0 0 0 0 1 0 2 0 0 

./httpforward.exe -w "1 3 3"
1 2 0 1 2 1 2 1 2 0 1 2 1 2

./httpforward.exe -w "5 3 1"
0 1 0 2 0 1 0 1 0 0 1 0 2 0 1 0 1 0 

./httpforward.exe -w "2 5 3"
1 2 0 1 1 2 1 0 2 1 1 2 0 1 1 2 1 0 2 1

从结果上看,权重保持着比例,但响应的服务器分布较平衡。

小结

本文主要根据网络的相关资料整理并用 golang 代码实现负载均衡部分算法。

参考

https://www.cnblogs.com/wsw-seu/p/11336634.html
https://juejin.cn/post/6844903793012768781

李迟 2021.9.22 晚

golang实践录:使用gin框架实现转发功能:利用nginx转发(代码片段)

近段时间需要实现一个转发post请求到指定后端服务的小工具,由于一直想学习gin框架,所以就使用这个框架进行尝试,预计会产生几篇文章。本文研究如何利用nginx容器和后端服务进行转发工具的测试。概述转发的工... 查看详情

golang实践录:使用gin框架实现转发功能:管理后端服务(代码片段)

近段时间需要实现一个转发post请求到指定后端服务的小工具,由于一直想学习gin框架,所以就使用这个框架进行尝试,预计会产生几篇文章。本文研究如何管理后端服务。思路在启动gin服务前,先启动所有的后端... 查看详情

golang实践录:使用gin框架实现转发功能:一些负载均衡算法的实现(代码片段)

近段时间需要实现一个转发post请求到指定后端服务的小工具,由于一直想学习gin框架,所以就使用这个框架进行尝试,预计会产生几篇文章。本文研究一些负载均衡算法的实现。概述本文实现的负载均衡纯粹是为了... 查看详情

golang实践录:使用gin框架实现转发功能:管理后端服务(代码片段)

近段时间需要实现一个转发post请求到指定后端服务的小工具,由于一直想学习gin框架,所以就使用这个框架进行尝试,预计会产生几篇文章。本文研究如何管理后端服务。思路在启动gin服务前,先启动所有的后端... 查看详情

golang实践录:使用gin框架实现转发功能:利用nginx转发(代码片段)

近段时间需要实现一个转发post请求到指定后端服务的小工具,由于一直想学习gin框架,所以就使用这个框架进行尝试,预计会产生几篇文章。本文研究如何利用nginx容器和后端服务进行转发工具的测试。概述转发的工... 查看详情

golang实践录:使用gin框架实现转发功能:上传文件并转(代码片段)

近段时间需要实现一个转发post请求到指定后端服务的小工具,由于一直想学习gin框架,所以就使用这个框架进行尝试,预计会产生几篇文章。本文先研究如何在gin框架中实现上传和转发功能。问题提出一后台web服务&#... 查看详情

golang实践录:使用gin框架实现转发功能:一些负载均衡算法的实现(代码片段)

近段时间需要实现一个转发post请求到指定后端服务的小工具,由于一直想学习gin框架,所以就使用这个框架进行尝试,预计会产生几篇文章。本文研究一些负载均衡算法的实现。概述本文实现的负载均衡纯粹是为了... 查看详情

golang实践录:使用gin实现cas单点登录(代码片段)

本文介绍使用Golang语言实现cas单点登录。起因新年伊始,上班第一天收到消息。原来那台用于部署内部网页工具的服务器因安全问题被停止使用,需更新服务器部署,但从中带出一个问题,那个应用服务程序必须... 查看详情

golang实践录:使用gin实现httpbasic认证(代码片段)

本文介绍使用Golang语言实现httpbasic认证,并给出测试。起因接前文,按要求,接入内网环境的服务应用不能提供无任何验证方式的post接口,线上系统有相关的模块,但过于复杂。经讨论确定,使用httpbasic... 查看详情

golang实践录:ssh及scp实现的优化(代码片段)

本文对上文的实现的优化。问题提出上一文章中,基本上已经达到使用了,但为了适应更多场合,需要对上传、下载功能进行优化,本文实现对目录的传输。设计思路主要框架和上文相同,不再赘述。对于目... 查看详情

golang实践录:ssh及scp的实现(代码片段)

本文介绍golang的scp实现和使用。问题提出工作中经常要查询日志,一般情况下需使用堡垒机登陆到远程机器,确认日志位置、文件名称,再用winscp软件下载,这过程比较繁琐,为节省时间,考虑用golang实现... 查看详情

golang实践录:ssh及scp的实现(代码片段)

本文介绍golang的scp实现和使用。问题提出工作中经常要查询日志,一般情况下需使用堡垒机登陆到远程机器,确认日志位置、文件名称,再用winscp软件下载,这过程比较繁琐,为节省时间,考虑用golang实现... 查看详情

web框架gin

 Web框架GinHTTP协议简介Restful风格编程golanghttp标准库Golang标准库templateHttpRouterGin简介Gin实现用户登录Gin请求参数Gin表单处理Gin数据绑定Gin访问静态文件集成BootStrap框架Gin使用中间件使用GinBasicAuth中间件Gincookie的使用Gin使用SessionGin... 查看详情

cqrs简单入门(golang)(代码片段)

一、简单入门之入门  CQRS/ES和领域驱动设计更搭,故整体分层沿用经典的DDD四层。其实要实现的功能概要很简单,如下图。   基础框架选择了https://github.com/looplab/eventhorizon,该框架功能强大、示例都挺复杂的,囊括的... 查看详情

golang实践录:ssh及scp的实现(代码片段)

本文介绍golang的scp实现和使用。问题提出工作中经常要查询日志,一般情况下需使用堡垒机登陆到远程机器,确认日志位置、文件名称,再用winscp软件下载,这过程比较繁琐,为节省时间,考虑用golang实现... 查看详情

Golang/gin:如何将 db 传递给路由器功能

】Golang/gin:如何将db传递给路由器功能【英文标题】:Golang/gin:HowtoPassdbtorouterfunctions【发布时间】:2017-06-0409:33:37【问题描述】:我正在使用gin框架。我正在像这样在主函数中打开sqlite数据库funcmain()...db,err:=sql.Open("sqlite3","./libre... 查看详情

golang实践录:获取目录文件列表(代码片段)

获取目录下匹配某种规则的文件,返回文件列表,在开发中比较常用。本文实现此功能,并做了些扩展。起因笔者开发的内部工具,需要查找各式文件,比如:数据文件,以csv结尾;信息文件,... 查看详情

golang实践录:获取目录文件列表(代码片段)

获取目录下匹配某种规则的文件,返回文件列表,在开发中比较常用。本文实现此功能,并做了些扩展。起因笔者开发的内部工具,需要查找各式文件,比如:数据文件,以csv结尾;信息文件,... 查看详情