Interrogation d'algorithme d'équilibrage de charge

Interrogation d'algorithme d'équilibrage de charge


table des matières

  1. Aperçu
  2. Sondage simple
  3. Sondage pondéré
  4. Interrogation pondérée fluide

1. Vue d'ensemble

  1. Dans un système distribué, afin de réaliser l'équilibrage de charge, des algorithmes de planification de charge, tels que la découverte de services Nginx et RPC, sont inévitablement impliqués. Les algorithmes courants d'équilibrage de charge incluent l'interrogation, le hachage d'adresse source et le plus petit nombre de connexions. L'interrogation est l'algorithme le plus simple et le plus largement utilisé.
  2. Trois algorithmes d'ordonnancement à tour de rôle courants sont le round-robin simple, le round-robin pondéré et le round-robin pondéré lisse. Les quatre services suivants seront utilisés pour illustrer en détail le processus de planification des sondages.
Instance de service Poids
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. Sondage simple

  1. L'interrogation simple est l'algorithme d'interrogation le plus simple, mais comme il ne prend pas en charge la charge de configuration, il a moins d'applications.

1. Description de l'algorithme

  1. En supposant qu'il existe N instances S = {S1, S2,…, Sn}, la variable indicatrice currentPos représente l'ID d'instance actuellement sélectionnée, qui est initialisé à -1. L'algorithme peut être décrit comme:
    1. Planifiez à l'instance suivante;
    2. Si toutes les instances ont été planifiées une fois, recommencez depuis le début;
    3. Répétez les étapes 1 et 2 pour chaque planification;
demande currentPos Instance sélectionnée
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. Mise en œuvre du code

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. Avantages et inconvénients

  1. Dans les applications réelles, le même service sera déployé dans différents environnements matériels et des performances différentes se produiront. Si vous utilisez directement l'algorithme de planification circulaire simple pour donner à chaque instance de service la même charge, il y aura inévitablement un gaspillage de ressources. Par conséquent, afin d'éviter cette situation, certaines personnes ont proposé l'algorithme de sondage pondéré suivant.

2. Sondage pondéré

  1. L'algorithme de round-robin pondéré introduit la valeur "weight" et améliore l'algorithme de round-robin simple. Le poids de la charge d'instance peut être configuré en fonction des performances matérielles, de manière à réaliser une utilisation rationnelle des ressources.

1. Description de l'algorithme

  1. Supposons qu'il y ait N instances S = {S1, S2,…, Sn}, poids W = {W1, W2,…, Wn}, la variable indicatrice currentPos représente l'ID d'instance actuellement sélectionnée, initialisé à -1; la variable currentWeight représente le poids actuel , La valeur initiale est max (S); max (S) représente la valeur de poids maximum de N instances et pgcd (S) représente le plus grand diviseur commun du poids de N instances.

  2. L'algorithme peut être décrit comme:

    1. À partir de la dernière instance de planification, parcourez chaque instance suivante;
    2. Si toutes les instances ont été parcourues une fois, réduisez currentWeight à currentWeight-gcd (S) et commencez à parcourir depuis le début; si currentWeight est inférieur ou égal à 0, réinitialisez à max (S);
    3. Il se termine lorsque le poids de l'instance traversée est égal ou supérieur à currentWeight et que l'instance est l'instance qui doit être planifiée;
    4. Répétez les étapes 1, 2 et 3 pour chaque planification;
  3. Par exemple, pour les 4 services ci-dessus, le poids maximum max (S) est 4, et le plus grand diviseur commun pgcd (S) est 1. Le processus de planification est le suivant:

demande currentPos poids actuel Instance sélectionnée
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
dix 3 4 192.168.10.4:2202

2. Mise en œuvre du code

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. Avantages et inconvénients

  1. Bien que l'algorithme d'interrogation pondérée résout le problème d'utilisation des ressources de l'interrogation simple en configurant les poids d'instance, il présente toujours un défaut relativement évident.
  2. Par exemple: instance de service S = {a, b, c}, poids W = {5, 1, 1}, la séquence d'instances générées par la planification à tour de rôle pondéré est {a, a, a, a, a, b , c}, Ensuite, il y aura 5 requêtes consécutives programmées pour l'instance a. En pratique, cette charge inégale n'est pas autorisée, car les requêtes continues vont soudainement augmenter la charge de l'instance a, ce qui peut provoquer de graves accidents.
  3. Afin de résoudre le défaut de planification circulaire pondérée inégale, un algorithme de planification circulaire pondérée lisse est proposé, qui génère une séquence de planification plus uniforme {a, a, b, a, c, a, a}.

4. Interrogation pondérée fluide

1. Description de l'algorithme

  1. Supposons qu'il y ait N instances S = {S1, S2,…, Sn}, le poids de configuration W = {W1, W2,…, Wn} et le poids effectif CW = {CW1, CW2,…, CWn}. En plus d'un poids de configuration Wi pour chaque instance i, il existe également un poids effectif actuel CWi, et CWi est initialisé à Wi; la variable indicatrice currentPos représente l'ID d'instance actuellement sélectionnée, qui est initialisé à -1; la somme des poids de configuration de toutes les instances est weightSum;

  2. Ensuite, l'algorithme de planification peut être décrit comme:

    1. Initialement, le poids effectif actuel CWi de chaque instance i est le poids de configuration Wi, et le poids de configuration et le poidsSum sont obtenus;
    2. Sélectionnez l'instance avec le poids effectif actuel le plus grand, soustrayez le poids et le poidsSomme de toutes les instances du poids effectif actuel CWi, et la variable currentPos pointe vers cette position;
    3. Ajouter le poids de configuration Wi au poids effectif actuel CWi de chaque instance i;
    4. Récupère l'instance pointée par la variable currentPos;
    5. Répétez les étapes 2, 3, 4 ci-dessus pour chaque planification;
  3. Pour les trois services ci-dessus, le poids de configuration et le poidsSum sont de 7, et le processus de planification est le suivant:

demande Poids actuel avant sélection currentPos Instance sélectionnée Poids actuel après sélection
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}
sept {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}
  1. L'idée de cet algorithme de planification à tour de rôle a été proposée pour la première fois par les développeurs de Nginx

2. Mise en œuvre du code

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. Résumé

  1. Bien que l'algorithme de tourniquet pondéré lisse améliore les lacunes de la planification de l'algorithme de tourniquet pondéré, c'est-à-dire la distribution inégale de la séquence de planification et évite la possibilité d'une augmentation soudaine de la charge de l'instance, il ne peut toujours pas percevoir dynamiquement la charge. de chaque instance.
  2. Si la configuration du poids de l'instance est déraisonnable ou si la charge du système est aggravée par d'autres raisons, le tourniquet à pondération fluide ne peut pas réaliser l'équilibrage de charge pour chaque instance, et un algorithme de planification avec état est nécessaire pour le terminer.

Je suppose que tu aimes

Origine blog.csdn.net/weixin_41910694/article/details/112966461
conseillé
Classement