負荷分散アルゴリズム-ポーリング
目次
- 概要概要
- 簡単なポーリング
- 加重ポーリング
- スムーズな加重ポーリング
1。概要
- 分散システムでは、負荷分散を実現するために、NginxやRPCサービス検出などの負荷スケジューリングアルゴリズムが必然的に含まれます。一般的な負荷分散アルゴリズムには、ポーリング、送信元アドレスハッシュ、および最小接続数が含まれます。ポーリングは、最も単純で最も広く使用されているアルゴリズムです。
- 3つの一般的なラウンドロビンスケジューリングアルゴリズムは、単純なラウンドロビン、加重ラウンドロビン、および滑らかな加重ラウンドロビンです。次の4つのサービスを使用して、ポーリングのスケジューリングプロセスを詳細に説明します。
サービスインスタンス | 重み |
---|---|
192.168.10.1:2202 | 1 |
192.168.10.2:2202 | 2 |
192.168.10.3:2202 | 3 |
192.168.10.4:2202 | 4 |
2.単純なポーリング
- 単純なポーリングは最も単純なポーリングアルゴリズムですが、構成のロードをサポートしていないため、アプリケーションが少なくなります。
1.アルゴリズムの説明
- N個のインスタンスS = {S1、S2、…、Sn}があるとすると、インジケーター変数currentPosは、現在選択されているインスタンスIDを表し、-1に初期化されます。アルゴリズムは次のように説明できます。
- 次のインスタンスにスケジュールします。
- すべてのインスタンスが一度スケジュールされている場合は、最初から開始します。
- スケジュールごとに手順1と2を繰り返します。
リクエスト | currentPos | 選択したインスタンス |
---|---|---|
1 | 0 | 192.168.10.1:2202 |
2 | 1 | 192.168.10.2:2202 |
3 | 2 | 192.168.10.3:2202 |
4 | 3 | 192.168.10.4:2202 |
5 | 0 | 192.168.10.1:2202 |
2.コードの実装
type Round struct {
curIndex int
rss []string
}
func (r *Round) Add(params ...string) error {
if len(params) == 0 {
return errors.New("至少需要1个参数")
}
r.rss = append(r.rss, params...)
return nil
}
func (r *Round) Next() (string, error) {
if len(r.rss) == 0 {
return "", errors.New("不存在参数")
}
curElement := r.rss[r.curIndex]
r.curIndex = (r.curIndex + 1) % len(r.rss)
return curElement, nil
}
3.長所と短所
- 実際のアプリケーションでは、同じサービスが異なるハードウェア環境に展開され、異なるパフォーマンスが発生します。単純なラウンドロビンスケジューリングアルゴリズムを直接使用して各サービスインスタンスに同じ負荷を与えると、必然的にリソースの浪費が発生します。したがって、この状況を回避するために、次の加重ポーリングアルゴリズムを提案する人もいます。
2.加重ポーリング
- 加重ラウンドロビンアルゴリズムは、「重み」値を導入し、単純なラウンドロビンアルゴリズムを改善します。インスタンス負荷の重みは、リソースの合理的な使用を実現するために、ハードウェアパフォーマンスに応じて構成できます。
1.アルゴリズムの説明
-
N個のインスタンスS = {S1、S2、…、Sn}、重みW = {W1、W2、…、Wn}があり、インジケーター変数currentPosは現在選択されているインスタンスIDを表し、-1に初期化されます。変数currentWeightは現在の重みを表します。 、初期値はmax(S)です。max(S)はN個のインスタンスの最大重み値を表し、gcd(S)はN個のインスタンスの重みの最大公約数を表します。
-
アルゴリズムは次のように説明できます。
- スケジューリングの最後のインスタンスから、後続のすべてのインスタンスをトラバースします。
- すべてのインスタンスが一度トラバースされた場合は、currentWeightをcurrentWeight-gcd(S)に減らし、最初からトラバースを開始します。currentWeightが0以下の場合は、max(S)にリセットします。
- トラバースされたインスタンスの重みがcurrentWeight以上になると終了し、インスタンスはスケジュールする必要のあるインスタンスです。
- スケジュールごとに手順1、2、および3を繰り返します。
-
たとえば、上記の4つのサービスの場合、最大重みmax(S)は4であり、最大公約数gcd(S)は1です。スケジューリングプロセスは次のとおりです。
リクエスト | currentPos | 現在の体重 | 選択したインスタンス |
---|---|---|---|
1 | 3 | 4 | 192.168.10.4:2202 |
2 | 2 | 3 | 192.168.10.3:2202 |
3 | 3 | 3 | 192.168.10.4:2202 |
4 | 1 | 2 | 192.168.10.2:2202 |
… | … | … | …。 |
9 | 2 | 1 | 192.168.10.3:2202 |
10 | 3 | 4 | 192.168.10.4:2202 |
2.コードの実装
var slaveDns = map[int]map[string]interface{
}{
0: {
"connectstring": "root@tcp(172.16.0.164:3306)/shiqu_tools?charset=utf8", "weight": 2},
1: {
"connectstring": "root@tcp(172.16.0.165:3306)/shiqu_tools?charset=utf8", "weight": 4},
2: {
"connectstring": "root@tcp(172.16.0.166:3306)/shiqu_tools?charset=utf8", "weight": 8},
}
var last int = -1 //表示上一次选择的服务器
var cw int = 0 //表示当前调度的权值
var gcd int = 2 //当前所有权重的最大公约数 比如 2,4,8 的最大公约数为:2
var devCount int = 2 //当前机器数
func getDns() string {
for {
last = (last + 1) % len(slaveDns)
if last == 0 {
cw = cw - gcd
if cw <= 0 {
cw = getMaxWeight()
if cw == 0 {
return ""
}
}
}
if weight, _ := slaveDns[last]["weight"].(int); weight >= cw {
return slaveDns[last]["connectstring"].(string)
}
}
}
func getMaxWeight() int {
max := 0
for _, v := range slaveDns {
if weight, _ := v["weight"].(int); weight >= max {
max = weight
}
}
return max
}
func Add(addr string, weight int) {
tmap := make(map[string]interface{
})
tmap["connectstring"] = addr
tmap["weight"] = weight
slaveDns[devCount] = tmap
devCount = devCount + 1
if devCount == 0 {
gcd = weight
} else {
gcd = Gcd(gcd, weight)
}
}
func Gcd(gcd int, weight int) int {
for weight != 0 {
gcd, weight = weight, gcd%weight
}
return gcd
}
3.長所と短所
- 加重ポーリングアルゴリズムは、インスタンスの重みを設定することで単純なポーリングのリソース使用率の問題を解決しますが、それでも比較的明らかな欠陥があります。
- 例:サービスインスタンスS = {a、b、c}、重みW = {5、1、1}、重み付きラウンドロビンスケジューリングによって生成されるインスタンスのシーケンスは{a、a、a、a、a、b 、c}、次に、インスタンスaにスケジュールされた5つの連続したリクエストがあります。実際には、この不均一な負荷は許可されていません。これは、継続的な要求によってインスタンスaの負荷が突然増加し、重大な事故を引き起こす可能性があるためです。
- 不均一な加重ラウンドロビンスケジューリングの欠陥を解決するために、より均一なスケジューリングシーケンス{a、a、b、a、c、a、a}を生成する滑らかな加重ラウンドロビンスケジューリングアルゴリズムが提案されています。
4.スムーズな加重ポーリング
1.アルゴリズムの説明
-
N個のインスタンスS = {S1、S2、…、Sn}、構成の重みW = {W1、W2、…、Wn}、および有効な重みCW = {CW1、CW2、…、CWn}があるとします。各インスタンスiの構成重みWiに加えて、現在の実効重みCWiもあり、CWiはWiに初期化されます。インジケーター変数currentPosは、現在選択されているインスタンスIDを表し、-1に初期化されます。すべてのインスタンスの構成の重みはweightSumです。
-
次に、スケジューリングアルゴリズムは次のように記述できます。
- 最初に、各インスタンスiの現在の実効重みCWiは構成重みWiであり、構成重みとweightSumが取得されます。
- 現在の実効重みが最大のインスタンスを選択し、現在の実効重みCWiからすべてのインスタンスの重みとweightSumを引くと、変数currentPosがこの位置を指します。
- 各インスタンスiの現在の実効重みCWiに構成重みWiを追加します。
- 変数currentPosが指すインスタンスを取得します。
- スケジューリングごとに上記の手順2、3、4を繰り返します。
-
上記の3つのサービスの場合、構成の重みとweightSumは7であり、スケジューリングプロセスは次のとおりです。
リクエスト | 選択前の現在の重量 | currentPos | 選択したインスタンス | 選択後の現在の重量 |
---|---|---|---|---|
1 | {5、1、1} | 0 | 192.168.10.1:2202 | {-2、1、1} |
2 | {3、2、2} | 0 | 192.168.10.1:2202 | {-4、2、2} |
3 | {1、3、3} | 1 | 192.168.10.2:2202 | {1、-4、3} |
4 | {6、-3、4} | 0 | 192.168.10.1:2202 | {-1、-3、4} |
5 | {4、-2、5} | 2 | 192.168.10.3:2202 | {4、-2、-2} |
6 | {9、-1、-1} | 0 | 192.168.10.1:2202 | {2、-1、-1} |
7 | {7、0、0} | 0 | 192.168.10.1:2202 | {0、0、0} |
8 | {5、1、1} | 0 | 192.168.10.1:2202 | {-2、1、1} |
- このラウンドロビンスケジューリングアルゴリズムのアイデアは、Nginx開発者によって最初に提案されました
2.コードの実装
type LoadBalance interface {
//选择一个后端Server
//参数remove是需要排除选择的后端Server
Select(remove []string) *Server
//更新可用Server列表
UpdateServers(servers []*Server)
}
type Server struct {
//主机地址
Host string
//主机名
Name string
Weight int
//主机是否在线
Online bool
}
type Weighted struct {
Server *Server
Weight int
CurrentWeight int //当前机器权重
EffectiveWeight int //机器权重
}
func (this *Weighted) String() string {
return fmt.Sprintf("[%s][%d]", this.Server.Host, this.Weight)
}
type LoadBalanceWeightedRoundRobin struct {
servers []*Server
weighted []*Weighted
}
func NewLoadBalanceWeightedRoundRobin(servers []*Server) *LoadBalanceWeightedRoundRobin {
new := &LoadBalanceWeightedRoundRobin{
}
new.UpdateServers(servers)
return new
}
func (this *LoadBalanceWeightedRoundRobin) UpdateServers(servers []*Server) {
if len(this.servers) == len(servers) {
for _, new := range servers {
isEqual := false
for _, old := range this.servers {
if new.Host == old.Host && new.Weight == old.Weight && new.Online == old.Online {
isEqual = true
break
}
}
if isEqual == false {
goto build
}
}
return
}
build:
log.Println("clients change")
log.Println(this.servers)
log.Println(servers)
weighted := make([]*Weighted, 0)
for _, v := range servers {
if v.Online == true {
w := &Weighted{
Server: v,
Weight: v.Weight,
CurrentWeight: 0,
EffectiveWeight: v.Weight,
}
weighted = append(weighted, w)
}
}
this.weighted = weighted
this.servers = servers
log.Printf("weighted[%v]", this.weighted)
}
func (this *LoadBalanceWeightedRoundRobin) Select(remove []string) *Server {
if len(this.weighted) == 0 {
return nil
}
w := this.nextWeighted(this.weighted, remove)
if w == nil {
return nil
}
return w.Server
}
func (this *LoadBalanceWeightedRoundRobin) nextWeighted(servers []*Weighted, remove []string) (best *Weighted) {
total := 0
for i := 0; i < len(servers); i++ {
w := servers[i]
if w == nil {
continue
}
isFind := false
for _, v := range remove {
if v == w.Server.Host {
isFind = true
}
}
if isFind == true {
continue
}
w.CurrentWeight += w.EffectiveWeight
total += w.EffectiveWeight
if w.EffectiveWeight < w.Weight {
w.EffectiveWeight++
}
if best == nil || w.CurrentWeight > best.CurrentWeight {
best = w
}
}
if best == nil {
return nil
}
best.CurrentWeight -= total
return best
}
func (this *LoadBalanceWeightedRoundRobin) String() string {
return "WeightedRoundRobin"
}
3.まとめ
- スムーズ加重ラウンドロビンアルゴリズムは、加重ラウンドロビンアルゴリズムスケジューリングの欠点、つまりスケジューリングシーケンスの不均一な分散を改善し、インスタンスの負荷が突然増加する可能性を回避しますが、それでも動的に負荷を認識できません。各インスタンスの。
- インスタンスの重み構成が不合理であるか、他の理由でシステム負荷が悪化している場合、スムーズな重み付きラウンドロビンは各インスタンスの負荷分散を実現できず、それを完了するにはステートフルスケジューリングアルゴリズムが必要です。