English translation is not available yet. Showing Russian content.

Настроить load balancing между агентами

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Настроить load balancing между агентами

1. Цель задачи

Научиться проектировать и реализовывать механизмы балансировки нагрузки между несколькими экземплярами агентов (AI-агентов или микросервисов) для обеспечения равномерного распределения запросов и минимизации времени отклика. В результате вы развернёте собственный балансировщик с двумя стратегиями (round-robin и consistent hashing), протестируете его под нагрузкой и убедитесь, что дисперсия загрузки агентов не превышает 10 %.

Ключевой результат Работающий программный балансировщик, который выбирает целевого агента по round-robin или consistent hashing; метрики распределения нагрузки (среднее, дисперсия) и визуализация; отчёт о сравнении стратегий.


2. Исходные данные

Что нужноОткуда взять
Код агентов-заглушек (отвечают на запросы)Написать самостоятельно (FastAPI / aiohttp)
Инструмент для генерации нагрузкиlocust или wrk2, либо собственный скрипт на asyncio
Python 3.10+Установить на локальную машину
Docker (опционально)Для изоляции агентов
Библиотека hashlibВстроена в Python
Визуализацияmatplotlib / pandas

Если нет реального инструмента — симулируем:

  1. Напишите три простых async-заглушки агента на aiohttp (или FastAPI), которые принимают GET-запрос, ждут 0.05–0.15 секунд (случайно) и возвращают {"agent_id": "A1", "request_id": ...}. Запустите их на портах 8081, 8082, 8083.
  2. Для генерации нагрузки напишите скрипт на asyncio, который отправляет 1000 запросов с фиксированной задержкой (например, 0.1 с между запросами). В каждом запросе передавайте параметр user_id (целое число от 1 до 100), чтобы имитировать привязку сессии.
  3. Если locust не установлен — не устанавливайте; достаточно асинхронного скрипта.

3. Технологический стек

КомпонентИнструментыНазначение
Язык программированияPython 3.10+Реализация балансировщика и агентов
Асинхронный фреймворкaiohttp (или FastAPI)Запуск HTTP-серверов (агенты, балансировщик)
Генератор нагрузкиasyncio + aiohttp (скрипт)Имитация клиентских запросов
Хэшированиеhashlib (md5 или sha256)Consistent hashing
Логированиеlogging (Python) + файлСбор статистики по агентам
Визуализацияmatplotlib + pandasПостроение гистограмм распределения
Контейнеризация (опционально)Docker + docker-composeИзоляция серверов

4. Этапы выполнения

Этап 1: Подготовка окружения и написание агентов-заглушек (оценка: 30 мин)

Действия

  1. Создайте корневую папку проекта agent_load_balancer/.
  2. Создайте файл agent.py — асинхронный сервер на aiohttp:
# agent.py (запускать по три экземпляра с разными аргументами)
import asyncio
from aiohttp import web
import time
import random
import os

AGENT_ID = os.getenv('AGENT_ID', 'A1')

async def handle(request):
    # Симуляция processing time
    await asyncio.sleep(random.uniform(0.05, 0.15))
    return web.json_response({
        "agent_id": AGENT_ID,
        "request_id": request.match_info.get('req_id', None),
        "user_id": request.rel_url.query.get('user_id', None),
        "timestamp": time.time()
    })

app = web.Application()
app.router.add_get('/process/{req_id}', handle)

if __name__ == '__main__':
    web.run_app(app, port=int(os.getenv('PORT', 8081)))
  1. Запустите три экземпляра в разных терминалах (или в docker-compose):

    • AGENT_ID=A1 PORT=8081 python agent.py
    • AGENT_ID=A2 PORT=8082 python agent.py
    • AGENT_ID=A3 PORT=8083 python agent.py
  2. Проверьте, что каждый отвечает: curl http://localhost:8081/process/test1

Ожидаемый результат этапа Три работающих агента на локальных портах.


Этап 2: Реализация балансировщика с round-robin (оценка: 1 час)

Действия

  1. Создайте файл balancer.py.
  2. Реализуйте класс RoundRobinBalancer:
class RoundRobinBalancer:
    def __init__(self, agent_urls):
        self.agent_urls = agent_urls
        self.index = 0

    def get_agent(self, request_context=None):
        # request_context игнорируется
        agent = self.agent_urls[self.index]
        self.index = (self.index + 1) % len(self.agent_urls)
        return agent
  1. Реализуйте HTTP-сервер балансировщика на порту 8000:

    • Принимает GET /process/{req_id}?user_id={id}
    • Получает URL агента от балансировщика
    • Проксирует запрос к агенту (через aiohttp.ClientSession)
    • Возвращает ответ агента клиенту
  2. Добавьте логирование: в stdout пишите, какой агент выбран для каждого request_id.

  3. Запустите балансировщик и отправьте 10 тестовых запросов, убедитесь, что агенты выбираются по кругу.

Ожидаемый результат этапа Балансировщик корректно распределяет запросы round-robin, лог показывает чередование агентов.


Этап 3: Реализация consistent hashing (оценка: 1-1.5 часа)

Действия

  1. Реализуйте класс ConsistentHashBalancer с использованием виртуальных нод:
import hashlib

class ConsistentHashBalancer:
    def __init__(self, agent_urls, virtual_nodes=150):
        self.virtual_nodes = virtual_nodes
        self.ring = {}      # хэш -> agent_url
        self.sorted_keys = []
        for url in agent_urls:
            for i in range(virtual_nodes):
                key = self._hash(f"{url}:{i}")
                self.ring[key] = url
                self.sorted_keys.append(key)
        self.sorted_keys.sort()

    def _hash(self, key_str):
        return int(hashlib.md5(key_str.encode()).hexdigest(), 16)

    def get_agent(self, request_context):
        user_id = request_context.get('user_id', 0)
        key = self._hash(str(user_id))
        # binary search to find first key >= user hash
        import bisect
        idx = bisect.bisect_left(self.sorted_keys, key)
        if idx == len(self.sorted_keys):
            idx = 0
        return self.ring[self.sorted_keys[idx]]
  1. Модифицируйте балансировщик так, чтобы стратегия выбиралась через переменную окружения STRATEGY (rr или ch). По умолчанию rr.

  2. Проверьте привязку: для одного и того же user_id consistent hashing должен возвращать одного и того же агента (даже если список агентов меняется). Проверьте, удалив на секунду один агент (заменив url на несуществующий) — запросы с тем же user_id должны уйти на тот же agent, если он ещё жив, либо на следующий по кольцу.

  3. Добавьте в лог также user_id и вычисленный хэш.

Ожидаемый результат этапа Реализованы обе стратегии, consistent hashing привязывает user_id к агенту.


Этап 4: Gенерация нагрузки и сбор метрик (оценка: 1-2 часа)

Действия

  1. Напишите скрипт load_test.py, который отправляет 2000 запросов с user_id от 1 до 200 ( каждый user_id повторяется 10 раз). Используйте asyncio + aiohttp для параллельной отправки (например, 10 concurrent workers).
import asyncio
import aiohttp
import random

BALANCER_URL = "http://localhost:8000/process"
USER_IDS = list(range(1, 201))

async def send_request(session, req_id, user_id):
    async with session.get(f"{BALANCER_URL}/{req_id}?user_id={user_id}") as resp:
        return await resp.json()

async def run_load(strategy):
    async with aiohttp.ClientSession() as session:
        tasks = []
        for i in range(2000):
            user_id = random.choice(USER_IDS)
            tasks.append(send_request(session, i, user_id))
        results = await asyncio.gather(*tasks)
    return results
  1. После завершения сбора результатов (для каждой стратегии отдельно) подсчитайте:

    • Количество запросов, обработанных каждым агентом.
    • Для consistent hashing: сколько раз один пользователь попал на одного и того же агента (степень привязки).
    • Среднее время ответа на агента.
  2. Постройте гистограммы распределения нагрузки (файл distribution_rr.png и distribution_ch.png). Используйте matplotlib.

  3. Выведите в консль: разброс (max – min) по числу запросов, дисперсию.

Ожидаемый результат этапа Получены численные метрики и графики распределения. Для round-robin ожидается почти равномерное распределение (отклонение < 5%). Для consistent hashing — неравномерность может быть до 10-15% из-за случайности хэшей; вы можете уменьшить её, увеличив количество виртуальных нод.


Этап 5: Документирование и финальные проверки (оценка: 30 мин)

Действия

  1. Напишите README.md с описанием:

    • Как запустить агентов и балансировщик.
    • Как выбрать стратегию.
    • Как запустить нагрузочный тест.
    • Примеры команд.
    • Выводы о равномерности распределения.
  2. Убедитесь, что все файлы (agent.py, balancer.py, load_test.py, requirements.txt) лежат в репозитории.

  3. Зафиксируйте результаты тестов в виде таблицы в README:

СтратегияСреднее на агентаMinMaxDispersion
Round-robin666.76656681.0
Consistent hash666.76007331500
  1. Оформите репозиторий (добавьте .gitignore, лицензию MIT).

Ожидаемый результат этапа Готовый проект с документацией и измеримыми артефактами.


5. Критерии приемки (Definition of Done)

  • Балансировщик запускается с выбором стратегии (rr или ch) через переменную окружения.
  • Round-robin распределяет запросы строго по кругу; после 1000 запросов разница в количестве запросов на агентов не превышает 2.
  • Consistent hashing: для одного user_id в течение всей сессии возвращается один и тот же агент, если он не удалён.
  • При удалении одного агента consistent hashing перераспределяет только его запросы (влияние минимально).
  • Скрипт load_test.py собирает статистику и строит гистограмму.
  • В репозитории есть README.md с инструкцией запуска (не более 5 шагов) и таблицей результатов.
  • Код содержит комментарии и использует аннотации типов.

6. Ожидаемый результат

Основной артефакт Папка проекта agent_load_balancer/ с файлами:

  • agent.py — код агента-заглушки.
  • balancer.py — балансировщик с обеими стратегиями.
  • load_test.py — тест нагрузки с визуализацией.
  • requirements.txt — зависимости.
  • README.md — описание проекта, инструкция, таблица результатов.
  • distribution_rr.png, distribution_ch.png — гистограммы распределения нагрузки.

Дополнительно (опционально):

  • docker-compose.yml для запуска трёх агентов и балансировщика.
  • Makefile для удобства запуска.

7. Возможные сложности и их решение

СложностьРешение
Consistent hashing даёт сильную неравномерность при малом количестве агентов (3)Увеличить количество виртуальных нод (virtual_nodes = 300 или 500). Либо использовать библиотеку hash_ring.
Запросы с одного user_id попадают на разных агентов из-за race condition при переборе кольцаУбедиться, что метод get_agent — thread-safe (в асинхронном коде достаточно одного инстанса балансировщика).
При тестировании на локальной машине агенты могут не успевать отвечатьУменьшить количество concurrent запросов (worker=5) или увеличить среднее время обработки, чтобы не было перегрузки.
Ошибки при парсинге URL (query string)Использовать request.rel_url.query из aiohttp, а не парсить самостоятельно.

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Подготовка окружения и заглушки30 мин
Этап 2: Реализация round-robin1 час
Этап 3: Реализация consistent hashing1.5 часа
Этап 4: Нагрузка и сбор метрик2 часа
Этап 5: Документирование30 мин
Итого5.5 часов

Примечание Для первого раза рекомендуем заложить 6–7 часов, включая поиск информации о consistent hashing.


9. Связанные вопросы из базы знаний

ВопросТема
12Основы балансировки нагрузки (round-robin, least connections)
45Consistent hashing в распределённых кэшах
78Виртуальные ноды и равномерность распределения
112Асинхронное программирование на Python (asyncio)
203Метрики нагрузки: QPS, среднее время отклика, дисперсия
305Нагрузочное тестирование с locust и wrk
412Прокси-серверы и reverse proxy (nginx, haproxy)
567Мониторинг распределённых систем (Prometheus + Grafana)
689Использование Docker-compose для микросервисов
777Сравнение стратегий балансировки (random, power of two choices)

10. Чек-лист самопроверки

  • Я запустил трёх агентов и балансировщик без ошибок.
  • Round-robin: после отправки 30 запросов с user_id=1 (все подряд) агенты выбрались A1, A2, A3, A1, ... (циклично).
  • Consistent hashing: для user_id=42 в пяти последовательных запросах возвращается один и тот же агент (если список агентов не менялся).
  • После удаления одного агента (порт не слушает) consistent hashing перенаправляет запросы с его user_id на следующий, а остальные остаются на месте.
  • Скрипт load_test.py завершается без исключений и генерирует два PNG-файла.
  • В README описана команда STRATEGY=ch python balancer.py для выбора стратегии.
  • Все зависимости перечислены в requirements.txt (aiohttp, matplotlib, pandas).