Вопрос по построению архитектуры с 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 запросов, если взять вместе (условно).

Решений, которые я вижу, два:

  1. В принципе есть мысль создать еще одни инстансы B C и D, но нужно как-то сделать согласованность базы данных на каждом инстансе. То есть как сделать так, чтобы при разворачивании нескольких инстансов одного и того же сервиса - база была согласованная? Отдельно нужно вынести базу данных, чтобы инстансы сервисов общались с ней, при этом использовать транзакции и блокировки базы? Вот этот нюанс пока что не понятен.

  2. В целом была мысль сделать для отказоустойчивости по крохотному микросервису на каждый сервис (B, C, и D), который принимает запросы с gunicorn/uvicorn и кладет данные только в rabbitmq и это единственная его работа. Router key установлен в direct на каждый такой инстанс. Затем на каждом сервисе (B, C и D) вместо поднятого веб-сервера сделать потребителя, который забирает данные с очереди и уже делают какую-то работу, при этом на каждом из сервисов установлен auto_ack=False, чтобы подтвердить сообщение только тогда, если сервис реально рабочий, и тогда удалить его из rabbitmq. Насколько правильно понимаю - будет решена проблема сохранения запросов, то есть если "умрет" какой-то сервис, то микросервис будет сохранять данные в очередь до тех пор, пока не будет восстановлен какой-то из сервисов.

Также много начитался о том, что можно реализовать быстрый апскейл rabbitmq для повышения производительности, но пока не разобрался как. По-хорошему тут, наверное, нужно вынести все сервисы в контейнеры и закинуть в k8s, но знаний в оркестрации контейнеров пока что нет.

Правильный ли мой ход мыслей, разумно ли сюда вообще добавлять rabbitmq, как добиться хорошей отказоустойчивости?

Вернуться на верх