微服务网关(九)负载均衡底层详细

news2024/11/13 14:53:28

微服务网关(九)负载均衡

四大负载均衡策略

  • 随机负载

    随机挑选目标服务器IP

  • 轮询负载

    ABC三台服务器,以ABCABC的顺序依次轮询

  • 加权轮询

    给目标服务器设置访问权重值,按照权重轮询负载

  • 一致性哈希轮询

    固定的一个客户端IP请求访问下游服务器都是访问一个指定IP

随机负载均衡

主要逻辑

使用rand.Intn方法获取随机索引

代码主体

type RandomBalance struct {
   //索引值
   curIndex int
   //包含IP地址的数组
   rss []string
   //观察主体(暂时不看,等到服务发现的时候再看)
   conf LoadBalanceConf
}

//添加服务器IP
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
}
//获取随机索引,用于实现随机负载均衡,由之后的get方法封装
func (r *RandomBalance) Next() string {
   if len(r.rss) == 0 {
      return ""
   }
   r.curIndex = rand.Intn(len(r.rss))
   return r.rss[r.curIndex]
}

轮询负载均衡

主要逻辑

索引值加一取余

代码主体

type RoundRobinBalance struct {
	//索引值
	curIndex int
	//包含IP地址的数组
	rss []string
	//观察主体
	conf LoadBalanceConf
}

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) //5
	if r.curIndex >= lens {
		r.curIndex = 0
	}
	curAddr := r.rss[r.curIndex]
    //简单的加一取余逻辑
	r.curIndex = (r.curIndex + 1) % lens
	return curAddr
}

加权负载均衡

  • Weight:初始化时对节点约定的权重

  • currentWeight:节点临时权重,每轮都会变化

    currentWeight = currentWeight + effectiveWeight

  • effectiveWeight:节点有效权重,默认与Weight相同

    如果一个节点发生故障,那么这个节点的effectiveWeight就会是Weight-1

    例如A:B:C = 2:1:1,A节点故障一次,那么就将A节点的effectiveWeight - 1 = 1;直到为零,可以有效控制故障节点的移除

  • totalWeight:所有节点有效权重之和:sum(effectiveWeight)

主要逻辑

  1. currentWeight = currentWeight + effectiveWeight
  2. 选中最大的currentWeight节点为选中节点
  3. 选中节点的currentWeight = currentWeight - totalWeight

image-20221012163343051

代码主体

type WeightRoundRobinBalance struct {
   curIndex int
   rss      []*WeightNode
   rsw      []int
   //观察主体
   conf LoadBalanceConf
}

type WeightNode struct {
   addr            string
   weight          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.weight
   r.rss = append(r.rss, node)
   return nil
}

func (r *WeightRoundRobinBalance) Next() string {
   total := 0
   var best *WeightNode
   for 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 -= total
   return best.addr
}

一致性hash

  • 单调性

    通过hash函数运算出来的值都是唯一固定的值

  • 平衡性

    一致性hash中有两种元素,一个是数据元素,一个是服务器元素。数据元素是否均匀落地到目标元素便是k考虑的平衡性

  • 分散性

    考虑hash函数设计上是否有哈希散列性

主要逻辑

  1. 将对象映射到环空间上

    假如有4个用户对象,通过hash函数,产生对应的key,通过单调hash函数,将值存储到环的对应位置上面

image-20221012170849524
  1. 把server映射到环空间上

    假如有3个服务器,通过hash函数,产生对应的key,通过单调hash函数,将值存储到环的对应位置上面

image-20221012171031490
  1. 将对象映射到服务器上

    上图映射在环上后,会以顺时针的顺序做映射关系,映射关系如图所示

    image-20221012171325338
  2. 添加和移除服务器

    删除服务器B,添加服务器D,如图所示。那么对象c将会指向服务器A,对象a将会指向服务器D

优点:因为我们是一致性hash的算法,因此就保持了单调性,所以就会以最小的形式将对象映射到服务器上面,这么做对分布式集群来说是非常合适的,它避免了大量的数据迁移,减轻了服务器的压力。

image-20221012171544306

一致性hash的平衡性

哈希算法并不能保证百分百的平衡性,当服务器比较少的时候,对象就不能均匀地映射到服务器上。因此引入虚拟节点的概念

虚拟节点

服务器节点具有几个拷贝的值,它们会按照一定的规则,均匀地分布到环上

image-20221012172040426

代码主体

结构体
type ConsistentHashBanlance struct {
	mux      sync.RWMutex
	hash     Hash              //环结构
	replicas int               //复制因子(虚拟节点)
	keys     UInt32Slice       //已排序的节点hash切片
	hashMap  map[uint32]string //节点哈希和Key的map,键是hash值,值是节点key
	conf     LoadBalanceConf   //观察主体
}
构造函数
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
}
Add方法

添加节点

  1. 根据所有虚拟节点来逐一计算hash值
  2. 将所得的hash值存入已排序的节点hash切片(m.keys)中,同时将map中通过hash值对应上服务器节点IP
// 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方法

获取对象顺时针所对应的最近服务器节点

  1. 通过二分法查找第一个大于数据对象的服务器节点索引值(顺时针的第一个节点)
  2. 如果查找结果大于服务器节点的最大索引,就返回第一个节点的索引值
  3. 根据索引返回节点
// 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
}

为反向代理插上负载均衡的翅膀

  • 使用工厂方法进行拓展

    通过设置工厂方法传入的参数,指定我们使用的对应负载均衡策略

  • 使用接口统一封装

    将方法统一,返回时使用统一的方法返回

代码主体

注:iota,这里会是按顺序0123

LbRandom->0

LbRoundRobin->1

LbWeightRoundRobin->2

LbConsistentHash->3

在使用iota时,需要注意以下几点:

  1. 每当定义一个const,iota的初始值为0
  2. 每当定义一个常量,就会自动累加1
  3. 直到下一个const出现,清零
  4. 如果中断iota自增,则必须显式恢复。且后续自增值按行序递增
  5. 自增默认是int类型,可以自行进行显示指定类型
  6. iota 可以参与运算
type LbType int

const (
   LbRandom LbType = iota
   LbRoundRobin
   LbWeightRoundRobin
   LbConsistentHash
)

func LoadBanlanceFactory(lbType LbType) LoadBalance {
   switch lbType {
   case LbRandom:
      return &RandomBalance{}
   case LbConsistentHash:
      return NewConsistentHashBanlance(10, nil)
   case LbRoundRobin:
      return &RoundRobinBalance{}
   case LbWeightRoundRobin:
      return &WeightRoundRobinBalance{}
   default:
      return &RandomBalance{}
   }
}

由于我们的目的是LoadBanlanceFactory方法返回统一的LoadBanlance这个接口

所以定义LoadBanlance接口

type LoadBalance interface {
   Add(...string) error
   Get(string) (string, error)

   //后期服务发现补充
   Update()
}

main.go使用时,传入常量即可获得负载均衡策略。接着便是添加节点,将策略传入反向代理并开启服务(反向代理的创建参考微服务网关(六)网络代理详解)

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

将策略传入反向代理NewMultipleHostsReverseProxy(反向代理的创建参考微服务网关(六)网络代理详解)

func NewMultipleHostsReverseProxy(lb load_balance.LoadBalance) *httputil.ReverseProxy {
   //请求协调者
   director := func(req *http.Request) {
      //获取负载均衡策略中取得的下游地址(根据实际需求决定)
      //基于客户端ip获得下游的服务(127.0.0.1)
      nextAddr, err := lb.Get(req.RemoteAddr)
      //基于客户端请求的URL获得下游的服务(/base/ba)
      //nextAddr, err := lb.Get(req.URL.String())
      if err != nil {
         log.Fatal("get next addr fail")
      }
      target, err := url.Parse(nextAddr)
      if err != nil {
         log.Fatal(err)
      }
      targetQuery := target.RawQuery
      req.URL.Scheme = target.Scheme
      req.URL.Host = target.Host
      req.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}
}
gth", 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}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/343498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图文解答之最短路径||

最短路径|| 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为“Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为“Finish”&#xff09;。 现在考虑网格中有障碍物。那么从左上角到…

rust过程宏 proc-macro-workshop解题-0-介绍

名字版本号rust1.69.0OSubuntu22.04proc-macro-workshop是一个学习rust过程宏非常好的工程。里边包含五大类题目。并且每种题目都有实际价值,可以应用在企业级项目中。我们在这里先介绍一下这个项目如何运行如何测试,如何验证过程宏的正确性以及如何调试。 本文只围绕以下几个…

2月编程语言排行榜出炉,第一名势头强劲

近日&#xff0c;TIOBE公布了2023年2月编程语言排行榜&#xff0c;本月各个语言表现如何&#xff1f;谁又摘得桂冠&#xff1f; TIOBE 2月Top15编程语言&#xff1a; 详细榜单查看TIOBE官网 https://www.tiobe.com/tiobe-index/ 关注IT行业的小伙伴们都知道&#xff0c;编程…

Linux入门篇(一)

Linux前言Linux初探Linux内核GNU实用工具shellLinux发行版bash shell 基础Linux文件系统Linux文件操作命令前言 在阅读诸如docker之类的书的时候&#xff0c;经常碰到Linux的知识。同时&#xff0c;大部分的盲区也是在Linux方面。因此就想稍微了解一下这个广为人使用的操作系统…

docker-入门到精通

docker知识总结 参考文档 https://jiajially.gitbooks.io/dockerguide/content/chapter_fastlearn/docker_run/–volumes-from.html 1、什么是docker ​ 容器技术、虚拟化技术已经成为一种被大家广泛认可的服务器资源共享方式&#xff0c;容器技术可以在按需构建操作系统实例…

vue3 Proxy响应式原理分析(面试题)

在开始正文前&#xff0c;先理一下vue2 Object.defineProperty 和 vue 3 Proxy 区别&#xff1a; Object.defineProperty&#xff1a;数据劫持 Proxy&#xff1a;数据代理 注意&#xff1a; 响应式原理和双向数据绑定原理是两回事&#xff0c;一般面试官会先问响应式原理再问双…

内网渗透(二十三)之Windows协议认证和密码抓取-Mimikatz介绍和各种模块使用方法

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

ShardingSphere-Proxy 数据库协议交互解读

数据库协议对于大部分开发者来说算是比较冷门的知识&#xff0c;一般的用户、开发者都是通过现成的数据库客户端、驱动使用数据库&#xff0c;不会直接操作数据库协议。不过&#xff0c;对数据库协议的特点与流程有一些基本的了解&#xff0c;有助于开发者在排查数据库功能、性…

Fabric磁盘扩容后数据迁移

线上环境原来的磁盘比较小&#xff0c;随着业务数据的增多&#xff0c;磁盘需要扩容&#xff0c;因此需要把原来docker数据转移至新的数据盘。 数据迁移 操作系统&#xff1a; centOS 7   docker默认的数据目录为/var/lib/docker   创建一个新的目录/opt/dockerdata&…

Halcon转OpenCV实例--OCR字符识别(附源码)

导 读 本文主要介绍Halcon转OpenCV实例--OCR字符识别(附源码)。 实例来源 实例来源于51Halcon论坛的帖子,原贴地址: https://www.51halcon.com/forum.php?mod=viewthread&tid=889 Halcon实现 测试图: 实现代码与效果: read_image (Image, ET.png)decompose3…

智慧园区解决方案

智慧园区解决方案 智慧园区是以互联网为载体&#xff0c;“互联网产业”融合产业模式为手段&#xff0c;面向园区提供全产业链支撑服务的解决方案。能够帮助园区在信息化方面建立统一的组织管理协调架构&#xff0c;业务管理平台和对内对外服务运营平台。将相关资源形成紧密联…

Java最新学习路线

Java语言是目前流行的互联网等企业的开发语言&#xff0c;是市面上很多程序员喜欢并且在用的程序设计语言。关于学习java&#xff0c;有一部分人是为了就业或自己创业&#xff0c;而大多数人是希望使用java这个开发语言用来工作&#xff0c;开发出计算机后端系统&#xff0c;利…

python带你采集回车桌面高清写真壁纸

前言 大家早好、午好、晚好吖 ❤ ~ 壁纸嘛~大家都在用&#xff0c;每个人喜欢的壁纸类型也不同 那今天来教大家怎么批量保存一批高质量壁纸~ 开发环境: Python 3.8 Pycharm 模块使用: requests >>> pip install requests 数据请求 parsel >>> pip instal…

soapui + groovy 接口自动化测试

1.操作excel的groovy脚本 package pubimport jxl.* import jxl.write.Label import jxl.write.WritableWorkbookclass ExcelOperation {def xlsFiledef workbookdef writableWorkbookdef ExcelOperation(){}//设置xlsFile文件路径def ExcelOperation(xlsFile){this.xlsFile x…

景联文科技:您的模型性能问题需要标注数据来解决

为什么需要重新考虑模型开发当人们想到人工智能时&#xff0c;他们的脑海中常常充满对未来世界幻想的画面&#xff0c;在这个世界中&#xff0c;算法为机器人提供动力&#xff0c;这些机器人负责处理他们的日常职责。他们的虚拟助手为他们提供建议并管理他们的日程安排&#xf…

数组的复制与二维数组的用法

今天学习的主要内容有 数组的复制 数组的复制 利用循环进行数组的复制 import java.util.Arrays; public class Main3 {public static void main(String[] args) {int []arr new int[]{1,2,3,4,5,6};int []arr1 new int[arr.length];for (int i 0; i < arr.length; i…

SpringBoot 整合 Redis 缓存

文章目录前言1、缓存 概念知识1.1、什么是缓存1.2、缓存的优缺点1.3、为什么使用缓存2、Redis 概念知识2.1、Redis 简介2.2、为什么用 Redis 作为缓存2.3、Redis 支持的数据类型2.3、Redis是如何判断数据是否过期2.4、过期的数据的删除策略2.5、Redis 事务2.6、Redis 持久化机制…

Windows软件界面字体和图标太小的解决办法

有时候我们装好软件之后&#xff0c;打开软件会发现部分字体变得非常小&#xff0c;难以看清屏幕中的文字&#xff0c;如图所示&#xff1a; 下面小编在这里以Windows 11系统&#xff08;其余版本Windows系统的设置步骤没有改变&#xff0c;只是部分选项的位置有所改变&#xf…

开发不停机的服务程序

使用守护进程、心跳机制、调度程序实现服务程序永不死机。 调度程序:启动服务程序&#xff0c;服务程序死掉后调度程序休眠n秒再次调度。 进程心跳:使用共享内存维护自己的心跳信息&#xff0c;当前时间减去最新时间如果大于超时时间就认为故障了&#xff0c;守护进程就会遍历…

共享模型之不可变

1.日期转换的问题 1>.代码示例 Slf4j public class TestDateFormatDemo1 {public static void main(String[] args) {SimpleDateFormat sdf new SimpleDateFormat("yyyy-MM-dd");for (int i 0; i < 10; i) {//多个线程调用日期格式化对象的方法new Thread(…