Сокращение потребления памяти в микросервисной архитектуре без изменения кода и перезапуска контейнеров
Всем привет! Меня зовут Владимир, я занимаюсь Java разработкой уже несколько лет и недавно меня начали беспокоить высокие цены на RAM. Я разработал некоторое решение эффективностью которого хотелось бы тут поделится.
Архитектура тестового стенда
Стараясь пойти по самому простому и быстрому пути я развернул микросервисный комплекс Online Boutique.
Комплекс представляет собой небольшой интернет магазин из 12 независимых микросервисов, написанных на разных языках программирования и работающих в единой сети Docker.
- demo-frontend (Golang) — шлюз API и веб-интерфейс
- demo-cartservice (C#) — управление корзиной покупателей
- demo-adservice (Java) — сервис контекстной рекламы
- demo-currencyservice (Node.js) — конвертация валют
- demo-paymentservice (Node.js) — процессинг платежей
- demo-emailservice (Python) — рассылка уведомлений
- demo-recommendationservice (Python) — сервис товарных рекомендаций
- demo-productcatalogservice (Golang) — каталог товаров
- demo-checkoutservice (Golang) — оформление заказов
- demo-shippingservice (Golang) — служба доставки
- demo-redis-cart (C / Redis) — in-memory хранилище данных
- demo-loadgenerator (Python / Locust) — эмулятор реального поведения пользователей
Я выбирал самый разнообразный стек чтобы наблюдать эффективность оптимизации и доказать что не нужно править сам код и нет зависимости от технологии, да и мне самому хотелось хорошенько протестировать технологию перед тем как её кому-то показывать.
Градуальный стресс-тест: от 10 до 500 пользователей
Чтобы понять базовый аппетит системы, я провёл ступенчатый прогрев и сбор метрик
10 пользователей --> RAM: ~340 MiB, CPU: ~14%
100 пользователей --> RAM: ~387 MiB, CPU: ~49%
500 пользователей --> RAM: 565.9 MiB, CPU: ~322% (91.3 RPS)
Динамика потребления ресурсов до поднятия контейнера оптимизатора
- На 10 пользователях: Система находится в покое. Суммарный объем RAM сервисов — ~340 MiB, нагрузка CPU — ~14%.
- На 100 пользователях: Нагрузка выросла до 18 запросов в секунду (RPS). Обработано 1 982 запроса (0.00% ошибок). Память выросла до ~387 MiB, CPU — ~49%.
- На 500 пользователях (Стабильное плато): Суммарный поток запросов достиг 91.3 RPS. Успешно обработано 30 416 сквозных сценариев без единого сбоя.Нагрузка на процессор суммарно превысила 322%.Память всех микросервисов достигла пикового значения 565.9 MiB.
Поднятие контейнера оптимизатора
Когда система находилась под нагрузкой в 500 пользователей, я запустил свою утилитку.
- Утилита автоматически обнаружила все 12 микросервисов
- Просканировал структуру рабочего пространства в оперативной памяти
- Применил свои магические механизмы оптимизации
- Ни один сервис не бул перезапущен, соединение с пользователями не прерывалось ни на миллисекунду
Результаты замеров
Освобождено чистой оперативной памяти: 80.4 Mb (14.2% по всему кластеру).
P.S. После включения утилитки потребление ресурсов CPU не изменилось.
Дополнительная информация
Я проводил ещё эксперимент и поднимал архитектуру в 40 микросервисов и повышал потребление оперативной памяти до 20 гб. Самое интересное, что процент оптимизации вырастал до 40-55%. К сожалению я не вёл записей по тому эксперименту, а повторять его было максимально лень. На основании того что получилось, могу сказать, что моя утилитка будет интересна разве что средним и более проектам. Возможно я проведу эксперимент более подробный и с учётом всех нюансов, но только если это будет кому-то интересно.