Golang之 go实现基于离散概率分布算法负载均衡策略 - weighted random with alias-method algorithm

Posted by 董江 on Wednesday, February 28, 2024

Golang之 go实现基于离散概率分布算法负载均衡策略

背景

实现O(1) 加权随机算法, 基于离散概率分布中进行采样的有效算法,根据某个任意概率分布p i返回整数值1 ≤ i ≤ n。该算法通常使用O(nlogn)O(n)预处理时间,之后可以在O(1)时间内从分布中抽取随机值。

算法说明

  1. 根据实例权重weight 除以 总的权重之和weightSum, 让其概率分布计算累积分布,映射到[0,1]的线段上;
  2. 通过一个[0,1]之间均匀分布的随机生成器,生成一个[0,1]之间的随机数;
  3. 判断这个数落在[0,1]线段上的哪个分段,平输出最接近这个值的实例

实现算法

// Package aliasmethod implements alias-method algorithm load balance strategy.
package aliasmethod // weighted random with alias-method algorithm

import (
	"math/rand"

	"dubbo.apache.org/dubbo-go/v3/cluster/loadbalance"
	"dubbo.apache.org/dubbo-go/v3/protocol"
)

type aliasMethodPicker struct {
	invokers []protocol.Invoker // Instance

	weightSum int64
	alias     []int
	prob      []float64
}

func NewAliasMethodPicker(invokers []protocol.Invoker, invocation protocol.Invocation) *aliasMethodPicker {
	am := &aliasMethodPicker{
		invokers: invokers,
	}
	am.init(invocation)
	return am
}

// Alias Method: https://en.wikipedia.org/wiki/Alias_method
func (am *aliasMethodPicker) init(invocation protocol.Invocation) {
	n := len(am.invokers)
	weights := make([]int64, n)
	am.alias = make([]int, n)
	am.prob = make([]float64, n)

	totalWeight := int64(0)

	scaledProb := make([]float64, n)
	small := make([]int, 0, n)
	large := make([]int, 0, n)

	for i, invoker := range am.invokers {
		weight := loadbalance.GetWeight(invoker, invocation)
		weights[i] = weight
		totalWeight += weight
	}
	am.weightSum = totalWeight

	for i, weight := range weights {
		scaledProb[i] = float64(weight) * float64(n) / float64(totalWeight)
		if scaledProb[i] < 1.0 {
			small = append(small, i)
		} else {
			large = append(large, i)
		}
	}

	for len(small) > 0 && len(large) > 0 {
		l := small[len(small)-1]
		small = small[:len(small)-1]
		g := large[len(large)-1]
		large = large[:len(large)-1]

		am.prob[l] = scaledProb[l]
		am.alias[l] = g

		scaledProb[g] -= 1.0 - scaledProb[l]
		if scaledProb[g] < 1.0 {
			small = append(small, g)
		} else {
			large = append(large, g)
		}
	}

	for len(large) > 0 {
		g := large[len(large)-1]
		large = large[:len(large)-1]
		am.prob[g] = 1.0
	}

	for len(small) > 0 {
		l := small[len(small)-1]
		small = small[:len(small)-1]
		am.prob[l] = 1.0
	}
}

func (am *aliasMethodPicker) Pick() protocol.Invoker {
	i := rand.Intn(len(am.invokers))
	if rand.Float64() < am.prob[i] {
		return am.invokers[i]
	}
	return am.invokers[am.alias[i]]
}

性能

goos: darwin
goarch: amd64
pkg: dubbo.apache.org/dubbo-go/v3/cluster/loadbalance
cpu: VirtualApple @ 2.50GHz
BenchmarkRoudrobinLoadbalance-8                       	    3814	    284544 ns/op	   88099 B/op	    2555 allocs/op
BenchmarkLeastativeLoadbalance-8                      	    4214	    274662 ns/op	   81884 B/op	    2043 allocs/op
BenchmarkConsistenthashingLoadbalance-8               	   30619	     39091 ns/op	    3440 B/op	     267 allocs/op
BenchmarkP2CLoadbalance-8                             	 1508785	       792.5 ns/op	     472 B/op	      10 allocs/op
BenchmarkInterleavedWeightedRoundRobinLoadbalance-8   	   10000	    102737 ns/op	   45312 B/op	    1027 allocs/op
BenchmarkRandomLoadbalance-8                          	   13738	     87314 ns/op	   38992 B/op	     767 allocs/op
BenchmarkAliasMethodLoadbalance-8                     	   13190	     90566 ns/op	   49232 B/op	     772 allocs/op
PASS
ok  	dubbo.apache.org/dubbo-go/v3/cluster/loadbalance	12.402s

其他

「如果这篇文章对你有用,请随意打赏」

Kubeservice博客

如果这篇文章对你有用,请随意打赏

使用微信扫描二维码完成支付