Sobes.tech
Senior

/* Existuje aplikácia s architektúrou mikroslužieb. Mikroslužba môže byť abstrahovaná pomocou rozhrania Backend. Na prístup k inštancii mikroslužby môžete použiť typ BackendImpl, ktorý je už implementovaný. Pre každú mikroslužbu je niekoľko desiatok spustených inštancií, každá dostupná na svojej adrese addr. Avšak jednotlivé inštancie mikroslužby nie sú spoľahlivé: môžu zlyhať, byť nedostupné alebo preťažené. Preto musíte implementovať typ Balancer, ktorý tiež implementuje rozhranie Backend a vykonáva vyvažovanie záťaže medzi inštanciami mikroslužby, vyberajúc vždy **najmenej zaťaženú** inštanciu. */ type Request interface{} type Response interface{} type Backend interface { Invoke(ctx context.Context, req Request) (Response, error) } var _ Backend = &BackendImpl{} // addr obsahuje ip:port konkrétnej inštancie func NewBackend(addr string) *BackendImpl type Balancer struct { //TODO } var _ Backend = &Balancer{} // addrs obsahujú adresy všetkých vyvažovaných inštancií func NewBalancer(addrs []string) *Balancer { //TODO }

sobes.tech AI

Odpoveď od AI

Для реализации client-side балансировщика нагрузки, который выбирает наименее нагруженный экземпляр Backend, можно сделать следующее:

  1. В структуре Balancer хранить срез BackendImpl и счётчики текущей нагрузки (например, количество активных запросов) для каждого экземпляра.
  2. При вызове Invoke выбирать Backend с минимальной нагрузкой.
  3. Перед вызовом Invoke увеличивать счётчик нагрузки, после завершения — уменьшать.

Пример реализации на Go:

import (
    "context"
    "sync"
)

type Balancer struct {
    backends []*BackendImpl
    mu       sync.Mutex
    loads    []int // количество активных запросов на каждый backend
}

func NewBalancer(addrs []string) *Balancer {
    b := &Balancer{
        backends: make([]*BackendImpl, len(addrs)),
        loads:    make([]int, len(addrs)),
    }
    for i, addr := range addrs {
        b.backends[i] = NewBackend(addr)
    }
    return b
}

func (b *Balancer) Invoke(ctx context.Context, req Request) (Response, error) {
    b.mu.Lock()
    // Найти индекс с минимальной нагрузкой
    minIdx := 0
    minLoad := b.loads[0]
    for i, load := range b.loads {
        if load < minLoad {
            minLoad = load
            minIdx = i
        }
    }
    b.loads[minIdx]++
    b.mu.Unlock()

    // Вызов backend
    resp, err := b.backends[minIdx].Invoke(ctx, req)

    b.mu.Lock()
    b.loads[minIdx]--
    b.mu.Unlock()

    return resp, err
}

Такой подход обеспечивает выбор наименее загруженного экземпляра в момент вызова, учитывая текущую активную нагрузку. Для более точного учёта можно использовать атомарные операции или более сложные метрики нагрузки.