@Heggi

Варианты синхронизации данных между БД разных микросервисов?

Пусть имеем абстрактный проект на микросервисах.
Сервис А хранит список юзеров (id, ФИО), сервис Б обрабатывает транзакции и хранит список транзакций с привязкой к user_id.
БД (PostgreSQL) микросервисов разные (разные схемы, сервер может быть как один, так и разные)

Еще есть вебинтерфейс, который показывает список транзакций в табличном виде (id, user_id, ФИО, ...) с пагинацией и поиском по ФИО. Список формирует сервис Б, которому для этого нужна информация о ФИО.
Глобально вариантов решения я смог придумать фактически 3:
1. При создании транзакции в таблице делаем поле ФИО и туда сохраняем текущее значение. Его и используем в дальнейшем отобрадении и поиске.
Плюсы: дернуть сервис А во время создания транзакции просто и быстро.
Минусы: пример абстрактный, это может быть не ФИО, а что-то другое, что в принципе может менять значение в системе (да и ФИО может измениться), и если оно изменится в БД сервиса А, то должно измениться и при отображении транзакций.

2. Каким-то образом таблицу user из А забираем в БД сервиса Б (только нужные нам поля).
Плюсы: Банальным JOIN двух таблиц получаем всю нужную информацию. Возможность поиска по ФИО.
Минусы: в зависимости от реализации разные и будут рассмотрены отдельно.

3. На каждый запрос напрягаем сервис А через API, запрашиваем ФИО по каждому пользователю, попавшему в выборку, результат, возможно, помещаем в кеш.
Плюсы: У нас всегда актуальная информация о ФИО юзера.
Минусы: Сильно напрягаем сервис А. Что делать с поиском? Делать отдельный эндпоинт в API, который по строке будет возвращать список подходящих user_id? Выглядит монструозно и долго.

При взвешивании этих всех вариантов, выбор пал на вариант 2. Синхронизация данных нужных таблиц в нужные сервисы.
Первый вариант хорош для систем, где исходные данные никогда не меняются или на момент создания записи в БД нам нужно сохранить текущие ФИО и другие данные и они не должны никогда меняться в дальнейшем. Но это редкое явление и не подходит для большинства операций в нашем абстрактном проекте.
Поэтому вариант 2.

Нам каким-то образом необходимо передать кучу данных (миллионы записей) из сервиса А в сервис Б и поддерживать данные в таблице сервиса Б актуальными. К тому же надо придумать механизм первоначальной синхронизации для сервиса В, который будет развернут когда-нибудь потом, но ему тоже нужен будет список юзеров.
Варианты решения:
1. Самое простое - это сделать view через dblink. Сработает как в пределах одного сервера, так и если серверов несколько.
Плюсы: отсутствует избыточное хранение данных, мы можем пользоваться JOIN, актуальность данных поддерживается механизмами СУБД. Нет никаких затрат на первоначальную синхронизацию.
Минусы: Завязаны жестко на структуру таблицы-донора (поле не переименовать, тип данных не поменять. Такое редко когда нужно, но все же). Не сработает для СУБД разных типов. При перемещении БД с таблицами-донорами на другой сервер вызовет необходимость перестраивать все view.
Тщательное гугление в интернетах не нашло даже упоминания такого подхода, не говоря уже о реализациях. Может плохо гуглил?
2. Синхронизация данных через шину данных, типа RabbitMQ, Kafka.
Плюсы: полная независимость от типа СУБД.
Минусы: Сама СУБД в шину данных данные писать не умеет, а значит это должен делать сам сервис. Аналогично и с приемом данных из шины - сервис должен слушать шину и нужные ему данные сохранять в таблицу в своей БД. Это достаточно много когда, часть которого можно вынести в библиотеки, но все-равно он достаточно специфичен из проекта в проект.

Сейчас в нашем реальном проекте используется именно такой подход.
В сервисах на сохранение сущностей в таблицу стоят обработчики и сохраненные данные отправляются в RabbitMQ. У каждого сервиса/сущности есть свой уникальный route_key, на который сервисы могут подписываться, и к ним в очередь будут поступать нужные им данные, которые уже сам сервис должен сохранять в таблицу.
Так же предусмотрен и способ первоначальной синхронизации. Сервис при старте (при необходимости), кидает запрос в RabbitMQ в определеном формате, где указывается имя сервиса, имя сущности и имя очереди. Соответствующий сервис отправляет всю таблицу в указанную очередь.

И в целом это все работает хорошо, стабильно и в процессе эксплуатации (уже порядка 2-х лет) никаких проблем не вызывало.
НО!
Таблицы подросли и первоначальная синхронизация занимает уже больше часа (что весьма долго). Причем она весьма значительно нагружает как кролика, так и БД (да и сам сервис жрет CPU как не в себя).

Поэтому главные вопросы:
А можно ли лучше?
Как такие вопросы решаются в других проектах?
Возможно существуют какие-то готовые опенсурс системы синхронизации данных между БД?
  • Вопрос задан
  • 218 просмотров
Пригласить эксперта
Ответы на вопрос 1
petermzg
@petermzg
Самый лучший программист
Использую Event Driven Architecture и при таком подходе будет.
А. Сервис "Пользователей," со всей необходимой информацией по пользователю и со всеми действиями касающимися пользователя. Тут информация по пользователю - выступает "Источником правды".
В. Сервис "Транзакций", который содержит всю информацию по транзакциям и минимальную иформацию по пользователям. Может просто хранить только user id (для проверки целостности данных)
С. Сервис "Поиска". Хранит всю информацию необходимую для поисковых запросов пользователя. Может содержать только Elastic или что-то подобное и не иметь реляционной или обьектной БД.
D. Сервис "Message broker"

Сервис "А" по любому событию над пользователем (CRUD) посылает всем подписанным сервисам, через "D" собщения об изменении информации по пользователю. Сервисы "В" из сообщений берут нужный минимум информации из сообщений и сохраняют у себя. Сервис "С" агрегирует всю нужную информацию для пользовательских запросов. Все подписанные на "D" сервисы поддерживают дублирование сообщений.
При возникновении проблем сервис являющийся "Источником правды" просто заново отсылает все сообщения с данными, а подписанные сервисы обновляют свое состояние.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы