【微服务网关——负载均衡】

1. 四大负载均衡策略

  • 随机负载
    • 随机挑选目标服务器IP
  • 轮询负载
    • ABC三台服务器,ABCABC依次轮询
  • 加权负载
    • 给目标设置访问权重,按照权重轮询
  • 一致性hash负载
    • 请求固定URL访问指定IP

2.随机负载均衡

可以通过random函数来随机选择一个ip

2.1 代码实现

type RandomBalance struct {
	curIndex int
	rss      []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_balance

import (
	"fmt"
	"testing"
)

func TestRandomBalance(t *testing.T) {
	rb := &RandomBalance{}
	rb.Add("127.0.0.1:2003") //0
	rb.Add("127.0.0.1:2004") //1
	rb.Add("127.0.0.1:2005") //2
	rb.Add("127.0.0.1:2006") //3
	rb.Add("127.0.0.1:2007") //4

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

2.3 测试结果

在这里插入图片描述

3.轮询负载均衡

按一定顺序进行轮询

3.1 代码实现

type RoundRobinBalance struct {
	curIndex int
	rss      []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) //5
	if r.curIndex >= lens {
		r.curIndex = 0
	}
	curAddr := r.rss[r.curIndex]
	// 这里是轮询实现的重点
	r.curIndex = (r.curIndex + 1) % lens
	return curAddr
}

func (r *RoundRobinBalance) Get(key string) (string, error) {
	return r.Next(), nil
}

3.2 测试代码

package load_balance

import (
	"fmt"
	"testing"
)

func Test_main(t *testing.T) {
	rb := &RoundRobinBalance{}
	rb.Add("127.0.0.1:2003") //0
	rb.Add("127.0.0.1:2004") //1
	rb.Add("127.0.0.1:2005") //2
	rb.Add("127.0.0.1:2006") //3
	rb.Add("127.0.0.1:2007") //4

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

3.3 测试结果

在这里插入图片描述

4.加权负载均衡

  • Weight
    • 初始化时对节点约定的权重
  • currentWeight
    • 节点的临时权重,每轮都会变化
  • effectiveWeight
    • 节点有效权重,默认与Weight相同
    • 当发生故障时,effectiveWeight会减少
  • totalWeight
    • 所有节点有效权重之和:sum(effectiveWeight)

根据不同的权重给于不同的概率,权重往往与地域、服务器的性能相关。
在这里插入图片描述

4.1 代码实现

type WeightRoundRobinBalance struct {
	curIndex int
	rss      []*WeightNode
	rsw      []int
}

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
}

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") //0
	rb.Add("127.0.0.1:2004", "3") //1
	rb.Add("127.0.0.1:2005", "2") //2

	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())
	fmt.Println(rb.Next())
}

4.3 测试结果

在这里插入图片描述

5.一致性负载均衡

  • 一致性Hash指标
    • 单调性
    • 平衡性
    • 分散性

链接: 一致性负载均衡理论介绍

5.1 代码实现

package load_balance

import (
	"errors"
	"fmt"
	"hash/crc32"
	"sort"
	"strconv"
	"strings"
	"sync"
)

type Hash func(data []byte) uint32

type UInt32Slice []uint32

func (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.RWMutex
	hash     Hash
	replicas 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_balance

import (
	"fmt"
	"testing"
)

func TestNewConsistentHashBanlance(t *testing.T) {
	rb := NewConsistentHashBanlance(10, nil)
	rb.Add("127.0.0.1:2003") //0
	rb.Add("127.0.0.1:2004") //1
	rb.Add("127.0.0.1:2005") //2
	rb.Add("127.0.0.1:2006") //3
	rb.Add("127.0.0.1:2007") //4

	//url hash
	fmt.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 hash
	fmt.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 main

import (
	"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,根据这个获得下游ip
		nextAddr, 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.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}
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

反激开关电源输入电解电容选型及计算

输入电解电容波形如下 计算 1、输入电解电容经验选取法 当输入电压为220V20%&#xff08;AC176-264V&#xff09;时 按照输出功率选取&#xff1a;W/μF 耐压&#xff08;400V&#xff0c;输入176-264V最高DC峰值373V&#xff09; 当输入电压为11020%时&#xff08;AC88-132V…

Python --- 如何修改Jupyter Notebook在本地保存文件的默认路径?

如何修改Jupyter Notebook在本地保存文件的默认路径&#xff1f; 一直以来都比较喜欢jupter notebook&#xff0c;自从用了以后就爱上了。平时用的时候&#xff0c;因为大多都是临时调用&#xff0c;每次在界面里直接new一个新的file就开干。 曾经也想过我创建的这些python文件…

支持的 Google Pixel 设备

Android 15 OTA 和下载内容适用于以下 Pixel 设备&#xff1a; Pixel 6 和 Pixel 6 ProPixel 6aPixel 7 和 Pixel 7 ProPixel 7aPixel FoldPixel TabletPixel 8 和 Pixel 8 ProPixel 8a 获取 Android 15 Beta 版 1 、HONOR Android 15 Beta 2 Magic6 Pro Android 15 Beta …

48-1 内网渗透 - 内核漏洞提权

权限提升 提权是指将普通用户的权限进行提升,也叫特权提升。在渗透测试中,通过利用各种漏洞提升webshell权限以夺得服务器权限。 系统内核漏洞提权 当目标系统存在已知的内核漏洞且没有更新安全补丁时,攻击者可以利用这些漏洞进行提权,从而获得系统级别的访问权限。 ###…

element v-loading 加载组件增加按钮

2024.06.19今天我学习了如何对v-loading组件增加按钮&#xff0c;效果如下&#xff1a; 代码如下&#xff1a; <template><el-table v-loading"loading"><template v-slot:append><el-button v-if"loading" type"warning"…

文献解读-流行病学-第十期|《SARS-CoV-2 德尔塔和奥密克戎合并感染和重组的证据》

关键词&#xff1a;基因组变异检测&#xff1b;全基因组测序&#xff1b;流行病学&#xff1b; 文献简介 标题&#xff08;英文&#xff09;&#xff1a;Evidence for SARS-CoV-2 Delta and Omicron co-infections and recombination标题&#xff08;中文&#xff09;&#xf…

【网络安全学习】漏洞扫描:-01- 漏洞数据库searchsploit的使用

漏洞数据库是收集和存储各种软件漏洞信息的资源库。 漏洞数据库通常包含漏洞的名称、编号、描述、影响范围、危害等级、解决方案等信息&#xff0c;有些还提供漏洞的分析报告、演示视频、利用代码等内容。 1.常用的在线漏洞库&#xff1a; 国家信息安全漏洞共享平台 https:/…

FREERTOS QUENE

队列&#xff1a;先进先出 将3 2 1 0 按顺序写入队列 所以读取队列信息时&#xff0c;也是 3 2 1 0

转让北京劳务派遣许可证公司需要多少钱办理要求有哪些

北京各区办理要求也不尽相同&#xff0c;有的区的劳务派遣公司相对饱和&#xff0c;审批难度也会加大&#xff0c;比如朝阳&#xff0c;朝阳的劳务公司饱和&#xff0c;办理时间周期上也会需要3-4个月&#xff0c;如果是现有的公司批劳务资质&#xff0c;公司经营范围就必须有企…

PyQT5 键盘模拟/鼠标连点器的实现

近来在玩一个游戏&#xff0c;找不到合适的鼠标连点器&#xff0c;不是有广告就是功能太复杂&#xff0c;自己写了一个&#xff0c;分享出来&#xff0c;如果有需要的可以自行运行研究。 准备工作 Python版本&#xff1a;Python 3.12.3&#xff1b;运行前确保pyQT5已经安装&a…

再次优化Notes启动速度!

大家好&#xff0c;才是真的好。 作为Notes客户机的重度爱好和使用者&#xff0c;很多人对Notes客户机的启动和运行速度表示了一定的质疑。 以前我们提到过,影响Notes运行性能是多方面的&#xff0c;比如操作系统上的杀毒软件或者windows自带的defender会扫描Notes文件&#…

python代码

# 请在______处使用一行代码或表达式替换# 注意&#xff1a;请不要修改其他已给出代码s input("请输入一个字符串:") print("{:*^30}".format(s))# 请在______处使用一行代码或表达式替换 # # 注意&#xff1a;请不要修改其他已给出代码a, b 0, 1 while …

C#的Switch语句3(如何为一段代码应用多个case标签)

文章目录 上一篇文章中断函数执行堆叠caseswitch中实用的字符串函数将字符串转换为小写 switch例子 上一篇文章 C#的Switch语句2 中断函数执行 switch语句内部可以使用return语句&#xff0c;这为控制程序流程和函数返回值提供了一种直接的方式。 当在switch语句块中遇到re…

前端高级架构师课程(总共111门课程,1150GB)

由阿里P8前端架构师亲自精心筛选整理的全网最新最具价值的前端进阶学习课程&#xff01; 培训机构原版教程&#xff01; 课程知识点和一线大厂完美匹配&#xff01; 所有课程资源完整成套&#xff0c;不残缺&#xff0c;不拼凑&#xff0c;不拆开乱发&#xff01; 这系列课程包…

【Linux】进程_9

文章目录 五、进程12. 自定义shell完整程序 未完待续 五、进程 12. 自定义shell 根据我们之前学的知识&#xff0c;这里来编写一个 shell 程序。 #include<stdio.h> #include<s…

【机器学习】 第1章 概述

一、概念 1.机器学习是一种通过先验信息来提升模型能力的方式。 即从数据中产生“模型”( model )的算法&#xff0c;然后对新的数据集进行预测。 2.数据集&#xff08;Dataset&#xff09;&#xff1a;所有数据的集合称为数据集。 训练集&#xff1a;用来训练出一个适合模…

深度判别特征学习在口音识别中的应用

论文&#xff1a;https://arxiv.org/pdf/2011.12461 代码&#xff1a;https://github.com/coolEphemeroptera/AESRC2020 摘要 使用深度学习框架进行口音识别是一项与深度说话人识别相似的工作&#xff0c;它们都期望为输入语音提供可识别的表示。相比于说话人识别网络学习的个…

【数据结构】线性表之《无头单链表》超详细实现

单链表 一.链表的概念及结构二.顺序表与链表的区别与联系三.单链表的实现1.创建单链表2.初始化单链表3.购买节点4.打印单链表5.插入操作1.头插2.尾插3.给定位置之前插入 6.删除操作1.头删2.尾删3.删除给定位置的结点 7.查找数据8.修改数据9.求单链表长度10.清空单链表11.销毁单…

机器学习课程复习——聚类算法

Q:什么是硬聚类,什么是软聚类? 如果一个样本只能属于一个类,则称为硬聚类(hard clustering); 如果一个样本可以属于多个类,则称为软聚类(soft clustering)。 Q:聚类和分类的区别? 聚类分类学习类型无监督学习方法 不需要事先标记的数据 通过发现数据中的模式或结构来组…

【毕业设计】Django 校园二手交易平台(有源码+mysql数据)

此项目有完整实现源码&#xff0c;有需要请联系博主 Django 校园二手交易平台开发项目 项目选择动机 本项目旨在开发一个基于Django的校园二手交易平台&#xff0c;为大学生提供一个安全便捷的二手物品买卖平台。该平台将提供用户注册和认证、物品发布和搜索、交易信息管理等…