Есть приложение с микросервисной архитектурой.

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

Короткий ответ

Что ответить на собеседовании

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

Подробный разбор

Ответ с пояснениями

Условие

/ * Есть приложение с микросервисной архитектурой. Микросервис можно абстрагировать с помощью интерфейса Backend. Для доступа к одному экземпляру микросервиса можно использовать тип BackendImpl, который уже реализован.

Для каждого микросервиса есть несколько десятков запущенных экземпляров, каждый из которых доступен по своему адресу addr. Однако отдельные экземпляры микросервиса ненадежны: они могут падать, быть недоступными либо перегруженными. Поэтому вам нужно реализовать тип Balancer, который также реализует интерфейс Backend и осуществляет client-side балансировку нагрузки между экземплярами микросервиса, выбирая каждый раз наименее нагруженный экземпляр.

исключать постоянно сбоящие экземпляры (после N ошибок) из балансировки, через некоторое время (после K секунд) возвращать назад

  • /

Ответ

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

  1. Хранить список BackendImpl с их адресами и текущей нагрузкой.
  2. При каждом вызове метода Backend выбирать экземпляр с минимальной нагрузкой.
  3. Обновлять информацию о нагрузке после каждого запроса (например, увеличивать счетчик активных запросов и уменьшать после завершения).

Пример на Go:

package main

import (
    "errors"
    "sync"
)

// Backend интерфейс микросервиса
type Backend interface {
    DoRequest() error
}

// BackendImpl - конкретный экземпляр микросервиса
// В реальности здесь будет реализация обращения по адресу addr
// Для примера просто имитируем

type BackendImpl struct {
    addr string
}

func (b *BackendImpl) DoRequest() error {
    // Реализация запроса к микросервису
    return nil
}

// Balancer реализует Backend и балансирует нагрузку

type Balancer struct {
    backends []*backendWithLoad
    mu       sync.Mutex
}

type backendWithLoad struct {
    backend Backend
    load    int // количество активных запросов
}

func NewBalancer(addrs []string) *Balancer {
    b := &Balancer{}
    for _, addr := range addrs {
        b.backends = append(b.backends, &backendWithLoad{
            backend: &BackendImpl{addr: addr},
            load:    0,
        })
    }
    return b
}

func (b *Balancer) DoRequest() error {
    b.mu.Lock()
    // Найти backend с минимальной нагрузкой
    var selected *backendWithLoad
    minLoad := int(^uint(0) >> 1) // макс int
    for _, be := range b.backends {
        if be.load < minLoad {
            minLoad = be.load
            selected = be
        }
    }
    if selected == nil {
        b.mu.Unlock()
        return errors.New("no backends available")
    }
    selected.load++
    b.mu.Unlock()

    // Выполнить запрос
    err := selected.backend.DoRequest()

    // После завершения уменьшить нагрузку
    b.mu.Lock()
    selected.load--
    b.mu.Unlock()

    return err
}

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

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее