Вопрос по построению архитектуры с 4 сервисами и rabbitmq
Стоит задача мне как backend
-разработчику по работе улучшить архитектуру работы 4 сервисов, добавить своего рода отказоустойчивость и сохранение результатов при сбое приложений с восстановлением ранее отправленных запросов, поэтому хочу разобраться с правильным построением новой и правильной архитектуры, которую не пришлось бы позже переделывать и иметь возможность быстрый апскейлинг при нагрузке.
Предыстория:
Есть один большой монолит на django
, который содержит какую-то информацию в базе данных.
В целом это дашборд компании с различными сервисами, и там около 20 django
-приложений с локальной postgres
.
Назовем его сервис A.
Допустим в 1 таблице сервиса А лежит ~ 20 записей, они редко изменяются, но в какой-то момент там данные могут быть изменены админом в админской части.
Есть сервис B, С, и D, это обычные веб-серверы либо на flask
(gunicorn
) либо fastapi
(uvicorn
) которые каждый раз делают десятки-сотни запросов на сервис A и получают эти ~ 20 записей, то есть условный GET
данных, когда приходит какой-то запрос на сервис B, C или D.
Сервисы B, С и D не взаимосвязаны друг с другом, но в целом логика у них схожая. Каждый сервис содержит в себе локально свою postres
-базу данных.
Когда сервисы B, C, или D обработали пришедшую на них ссылку с данными (в секунду может на каждый сервис приходить условно 50-100 запросов, иногда 5-10, в зависимости от ситуации, раньше нагрузки всегда были небольшими, около 5-10 запросов в с.) и каждый сервис обрабатывает условно каждый запрос за 0.2-0.3 секунды, но может занимать и 1-2 секунды, в общем делают какую-то работу, и отправляют в конце своей работы на сервис A запись в другую таблицу, что данный запрос успешно обработан и статус его либо 200 или, допустим, 400. В целом вопрос не в том, что запросов накапливается больше чем время, за которое может быть обработан запрос сервисом.
Были проблемы на стороне сервиса А, что логично, так как он стал перегружаться из-за постоянного извлечения данных с бд при каждом запросе из сервисов B C и D.
Сначала было решено сделать на сервисе А кэш на n
-минут, так как данные особо не меняются. Это было сделано за счет django.cache
, который добавлял этих ~ 20 записей в кэш и если приходит запрос, тогда он отдается с кэша в течение n
-минут. Но если админ добавляет/меняет запись, тогда срабатывает django
-сигнал на модель и удаляет данные с кэша, поэтому при новом запросе данные просто снова добавляются в кэш, что решает часть проблемы с нагрузкой на базу. Но остается одна проблема на записи POST
, потому что каждый сервис B C и D в конце работы отправляет POST
запрос на A. Это в принципе не так критично.
Проблема следующая: сервисы B C и D испытывают большую нагрузку, от чего gunicorn
может падать с ошибкой:
[CRITICAL] WORKER TIMEOUT pid (id процесса)
[INFO] Worker exiting
[INFO] Booting worker with pid
Поэтому часть логики при обработке каким-то сервисом просто не срабатывает, и данные уже получаются утерянными.
Нюанс в том, что сервис B, C и D подняты на 2 ядерном, 2 ядерном и 4 ядерном хосте соответственно. То есть для этих сервисов подняты 2 или 4 процесса. Например:
gunicorn -b :5000 -w 2 app:app
либо
gunicorn -b :8000 -w 4 app:app
Данные проксируются через nginx
и попадают уже непосредственно на сервисы.
Критичен один из фактов, что уже есть ошибки, типа
[CRITICAL] WORKER TIMEOUT pid
.
Но, в целом, не только в этом проблема, хочется решить для себя и разобраться, как строить хорошую архитектуру для таких приложений.
Вопрос. Как лучше переделать архитектуру сервисов B C и D? Есть мысль создать как-то это все на rabbitmq
- ранее с ним не работал, но много посмотрел туториалов по настройке и вообще о его применении. В принципе kafka
не нужна, так как там всего будет на всех 3 сервисах до 500 запросов, если взять вместе (условно).
Решений, которые я вижу, два:
В принципе есть мысль создать еще одни инстансы B C и D, но нужно как-то сделать согласованность базы данных на каждом инстансе. То есть как сделать так, чтобы при разворачивании нескольких инстансов одного и того же сервиса - база была согласованная? Отдельно нужно вынести базу данных, чтобы инстансы сервисов общались с ней, при этом использовать транзакции и блокировки базы? Вот этот нюанс пока что не понятен.
В целом была мысль сделать для отказоустойчивости по крохотному микросервису на каждый сервис (B, C, и D), который принимает запросы с
gunicorn
/uvicorn
и кладет данные только вrabbitmq
и это единственная его работа.Router key
установлен вdirect
на каждый такой инстанс. Затем на каждом сервисе (B, C и D) вместо поднятого веб-сервера сделать потребителя, который забирает данные с очереди и уже делают какую-то работу, при этом на каждом из сервисов установленauto_ack=False
, чтобы подтвердить сообщение только тогда, если сервис реально рабочий, и тогда удалить его изrabbitmq
. Насколько правильно понимаю - будет решена проблема сохранения запросов, то есть если "умрет" какой-то сервис, то микросервис будет сохранять данные в очередь до тех пор, пока не будет восстановлен какой-то из сервисов.
Также много начитался о том, что можно реализовать быстрый апскейл rabbitmq
для повышения производительности, но пока не разобрался как. По-хорошему тут, наверное, нужно вынести все сервисы в контейнеры и закинуть в k8s
, но знаний в оркестрации контейнеров пока что нет.
Правильный ли мой ход мыслей, разумно ли сюда вообще добавлять rabbitmq
, как добиться хорошей отказоустойчивости?