100行代码实现加权负载均衡算法(WRR)

3 篇文章 0 订阅
2 篇文章 0 订阅

轮询算法round-robin是很基础的负载均衡算法,实际应用中wrr更为常见,但一般不需要自己实现,因为一般需要rr的场景,都已经在基础设施层面进行了支持,比如lvs或nginx通过配置即可实现,但业务上偶尔也需要自己实现负载均衡,所以有必要了解一下其技术原理。

谈到wrr的应用场景,一般是服务器配置存在差异时,比如集群里有一个2C4G和一个4C8G,那么我们希望4C8G能承担更多的业务请求。或者是,我们希望通过无条件的流量分发,在集群的一台机器上小范围的导入一些流量进行一些实验。我碰到的场景是,在网关层面对业务方的集群迁移进行支持,也属于第二种场景。

原理

权重可以看做期望目标出现的概率,比如两个节点A、B,权重分别是100和50,那么等于在说,我们期望66.7%的请求由A处理,剩余33.3%的请求由B处理。

一种比较挫的实现方式,是将所有节点按照权重比例分布在一个离散的区间内,比如1-100,然后使用随机数生成100以内的自然数,如果随机数0<x<=66就由A处理请求,如果66<x<=100就由B处理请求。这种方法看似实现容易,但致命缺陷是依赖随机数的有效性,而大部分场景我们使用的都是伪随机数。此外集群数量伸缩时,会出现精度问题。

wiki中提供了两种wrr的实现方式,分别是classic wrr和interleaved wrr,简称为WRR和IWRR,他们的区别可以通过下图来了解一下。




  • WRR
    初始权重水位c设置为0,外循环遍历所有的节点,内循环遍历当前节点的水位值,如果权重水位c在该值内,则使用当前节点处理请求,同时c值递增。直到c大于当前节点的权重值,重置c为0,再对下一个节点执行内循环。

  • IWRR
    外循环递增水位值,内循环遍历所有节点,如果当前水位值低于节点权重值,则由当前节点处理请求。遍历完所有节点后,递增水位值后再对所有节点进行遍历。


通过对比可以发现,如果集群中的节点权重相近,且值都比较高,则使用IWRR会明显的避免节点负载的尖刺情况,节点越多越明显。但这种情况会随着节点权重差异的增加而消退。极端假设集群中有一个节点权重是100,其他节点权重都是1,那么IWRR和WRR的效果是一样的。

此外,在IWRR的实践中,一般会用集群内节点权重值的最大公约数(gcd),来作为外循环水位值的递增步长,这样可以减轻少量权重较低节点的摆动。比如集群内有4个权重为100的节点ABCD,1个权重为20的节点E,当步长为1时,节点E会在开始的20个周期内执行20次,然后在剩下的80个周期内无事可做。如果使用gcd来作为步长,最多隔5个周期就会处理一次请求。


实践

这里用go实现了一个类IWRR的负载均衡器,区别在于横向扫描的方向是自顶向下的

package balancer

import (
	"errors"
)

var (
	ErrSuccess  = error(nil)
	ErrNoTarget = errors.New("no target to run")
)

type ITarget interface {
	Call() error // 实际工作负载方法
	Weight() int // 当前target权重
}

type Balancer struct {
	targets   []ITarget // 目标集
	maxweight int       // 当前集群中的最大权重
	current   int       // 当前权重水位
	stride    int       // 权重步长(最大公约数)
	index     int       // 当前目标索引
}

func (b *Balancer) AddTarget(t ITarget) {
	// 更新目标集
	b.targets = append(b.targets, t)

	// 更新步长
	b.stride = t.Weight()
	b.maxweight = t.Weight()
	for _, v := range b.targets {
		b.stride = b.gcd(b.stride, v.Weight())

		// 保存目标集中最大权重值
		if v.Weight() > b.maxweight {
			b.maxweight = v.Weight()
		}
	}

	// 更新当前水位
	b.current = b.maxweight

	// 更新当前索引位置
	b.index = 0
}

func (b *Balancer) Call() error {
	if len(b.targets) == 0 {
		return ErrNoTarget
	}

	// 查找满足当前权重水位的target
	i := b.index
	for {
		// 取得target对象
		target := b.targets[i]
		i++
		if target.Weight() >= b.current {
			target.Call()
			break
		}

		if i == len(b.targets) {
			// 重置索引、降低水位
			i, b.current = 0, b.current-b.stride
			if b.current <= 0 {
				// 重置当前水位
				b.current = b.maxweight
			}
		}
	}

	if i == len(b.targets) {
		// 重置索引、降低水位
		i, b.current = 0, b.current-b.stride
		if b.current <= 0 {
			// 重置当前水位
			b.current = b.maxweight
		}
	}
	b.index = i

	return ErrSuccess
}

// 辗转相除求最大公约数
func (b *Balancer) gcd(m, n int) int {
	if n == 0 {
		return m
	}
	return b.gcd(n, m%n)
}

使用方法如下:这里创建了三个节点,权重分别是100、50、20,期望的分布是10:5:2,所以在后面的循环里打印了17次。

package main

import (
	"bournex/weightbalance/balancer"
	"fmt"
)

// 虚构的集群目标节点
type Target struct {
	name   string
	weight int
}

// 虚构的执行过程
func (t Target) Call() error {
	fmt.Printf("%s", t.name)
	return nil
}

// 获取当前目标的权重值
func (t Target) Weight() int {
	return t.weight
}

func main() {
	var (
		x = Target{name: "A", weight: 100}
		y = Target{name: "B", weight: 50}
		z = Target{name: "C", weight: 20}
		b balancer.Balancer
	)

	if err := b.Call(); err != nil {
		fmt.Println(err.Error())
	}

	b.AddTarget(x)
	b.AddTarget(y)

	for i := 0; i < 17; i++ {
		b.Call()
	}

	fmt.Println()

	b.AddTarget(z)
	for i := 0; i < 17; i++ {
		b.Call()
	}
	fmt.Println()
}

执行输出如下

# ./app
no target to run
AABAABAABAABAABAA
AAAAAABABABABCABC

第一行打印由于还没有添加Target,所以当前集群没有可以执行任务的节点,输出error内容"no target to run"。
第二行是节点AB处理负载的情况,以AAB为模式循环处理请求。满足100:50的负载均衡需求。
第三行是添加了节点Z的结果,总共输出17次,A10次,B5次,C2次

作为对比,如果采用1替换gcd作为步长实现IWRR,则它的模式是这样的:

ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC
ABABABABABABABABABABABABABABABABABABABABABABABABABABABABABAB
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

可以看到,C要隔很久才会再次出现

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Server2019是微软推出的最新版本的服务器操作系统。其网络负载均衡功能可以帮助组织和企业更好地管理网络流量和确保网络高可用性。 网络负载均衡是一种分配网络服务请求的技术,以确保多个服务器能够处理来自客户端的请求,并通过合理分配负载以避免单个服务器的超负荷处理。 Server2019中的网络负载均衡实现了多个节点(服务器)之间的水平扩展。它通过组合相同的硬件来提供高性能和可扩展性,并通过一个统一的入口点来解决L4负载均衡请求。 Server2019网络负载均衡功能可以为多个IP地址提供服务,这些IP地址可以代表单个节点或服务器池。它使用一些负载均衡算法,如RR(轮询算法)、WRR(平权轮询算法)、IP Hash(根据源IP地址进分配)和比率(根据给定的权重分配流量),方便管理员选择最适合其环境的负载均衡算法。 此外,Server2019中的网络负载均衡功能还提供了网络地址转换(NAT)功能,可以将内部IP地址转换为外部IP地址,并提供端口映射等功能,以改善安全性,避免网络攻击和滥用等问题。 总之,Server2019网络负载均衡功能可以帮助组织和企业更好地管理其网络流量,提高高可用性和可扩展性,并提供更高效的网络服务。 ### 回答2: 服务器的负载均衡是一种网络技术,旨在将来自一组网络请求的负载均衡器平均分配到多个服务器,以避免任何一个服务器承受过多的负载。这使得可以更好地分配服务器资源,从而提高系统的可靠性和性能。微软公司推出了一种新的网络负载均衡技术,即Windows Server 2019网络负载均衡。 Windows Server 2019网络负载均衡技术强大并且容易使用。它提供了多种负载均衡算法,这些算法可根据实际情况进选择。此外,网络负载均衡在服务器故障时可以自动识别,自动分配服务到其他服务器上,以确保服务的连续性。 此外,Windows Server 2019网络负载均衡技术还支持自动服务器健康检查,以确保负载均衡器只将请求分配给可用的服务器。如果任何服务器变得不可用,则负载均衡器会自动跳过该服务器并将请求分配给其他可用的服务器。 总的来说,Windows Server 2019网络负载均衡技术是一种强大而灵活的技术,可以提高系统的性能和可靠性。它易于使用,支持多种负载均衡算法,并自动检测和处理故障。如果您需要在您的服务器上实现负载均衡,Windows Server 2019网络负载均衡技术是一个优秀的选择。 ### 回答3: 服务器负载平衡是指通过多台服务器来共享负载和流量来均衡网络负载,从而提高网络系统的性能和可靠性。Server 2019是微软推出的最新服务器操作系统,其中有许多内置的网络负载平衡功能,使它成为一种十分有用的工具。 首先,Server 2019支持基于硬件和基于软件的网络负载平衡。它使用基于硬件的方案来提供预定的可扩展性,通过硬件平衡流量,从而提高了可靠性和安全性。同时,Server 2019还支持基于软件的网络负载平衡,这意味着使用者可以利用操作系统内置的负载平衡服务来分配流量。 其次,Server 2019提供了多种负载均衡算法,包括基于轮询、IP散列、源地址散列等。这些算法使得用户能够根据具体的负载需求来选择适合自己的最佳算法。此外,Server 2019还支持基于会话的负载均衡,收到请求后可以把该请求的所有会话都发送到同一台服务器上进处理,从而确保请求的连续性。 最后,除了自己内置的网络负载平衡功能,Server 2019还能够与第三方负载平衡产品进兼容。这意味着,如果你已经使用了现有的网络负载平衡软件,你不需要从头开始,只需要将它们集成到Server 2019中,它便可以与你的系统无缝兼容,大大简化了系统管理。 综上所述,Server 2019的网络负载平衡功能是非常强大和灵活的,提供了多种负载均衡算法和第三方工具的兼容性,它适合任何规模的企业和任何特定的负载需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值