Sobes.tech
Middle

/* Er bestaat een toepassing met een microservices-architectuur. Een microservice kan worden geabstraheerd met behulp van een Backend-interface. Om toegang te krijgen tot een instantie van de microservice, kan het type BackendImpl worden gebruikt, dat al is geïmplementeerd. Elke microservice heeft tientallen exemplaren die draaien, elk bereikbaar via zijn eigen adres addr. Echter, individuele exemplaren van de microservice zijn niet betrouwbaar: ze kunnen crashen, niet bereikbaar zijn of overbelast zijn. Daarom moet je het type Balancer implementeren, dat ook de Backend-interface implementeert en client-side load balancing uitvoert tussen de microservice-exemplaren, waarbij telkens het **minimaal belaste** exemplaar wordt gekozen. */ type Request interface{} type Response interface{} type Backend interface { Invoke(ctx context.Context, req Request) (Response, error) } var _ Backend = &BackendImpl{} // addr bevat ip:poort van een specifiek exemplaar func NewBackend(addr string) *BackendImpl type Balancer struct { // TODO } var _ Backend = &Balancer{} // addrs bevatten de adressen van alle gebalanceerde exemplaren func NewBalancer(addrs []string) *Balancer { // TODO }

sobes.tech AI

Antwoord van AI

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

Примерный подход:

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

Пример кода:

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
}

Таким образом, каждый запрос направляется на наименее загруженный экземпляр, что обеспечивает client-side балансировку нагрузки.