Управление мастер-данными в микросервисной архитектуре

Дмитрий Литичевский
Дмитрий Литичевский · 26 октября 2020
Backend · Teamlead

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

Рассмотрим ситуацию:

  1. Микросервис A поддерживает актуальный справочник городов и предоставляет к нему доступ для потребителей. Только через этот микросервис редактируются данные о городах, а значит А отвечает за эти данные и является мастер-системой по данным о городах.
  2. Микросервис B предоставляет информацию о маршрутах общественного транспорта, существующих в пределах выбранного города. Микросервису B нужна информация о городах.

Вопрос в том, надо ли микросервису В каждый раз ходить в сервис А за данными о городах? Или иметь свою копию данных? Или самому завести справочник с городами и управлять им? А если иметь копию данных, то как синхронизировать их с микросервисом А?

Есть четыре основных архитектурных подхода к решению этой задачи.

№1 Дублирование мастер-систем

В первом варианте (рис. 1) каждый из микросервисов хранит и поддерживает в актуальном состоянии свой справочник городов.

Рис. 1. Первый вариант организации

Плюсы подхода:

  1. каждый микросервис полностью контролирует свой Roadmap работы с данными без оглядки на требования остальных.

Недостатки:

  1. каждому из сервисов приходится заново реализовывать логику поддержания актуальности данных;
  2. данные в разных сервисах могут быть рассинхронизированы, например в сервисе A новый город уже добавлен, а в сервисе B еще нет;
  3. сложности межсервисной интеграции, поскольку структура записей и их идентификаторы не совпадают.

№2 Единственная копия мастер-данных с доступом через API

Во втором варианте (рис. 2) справочник городов есть только в микросервисе A, а микросервис B получает данные через API, имея таким образом всегда актуальные данные.

Рис. 2. Второй вариант организации

Плюсы подхода:

  1. все микросервисы работают с одной версией данных;
  2. микросервисам, читающим данные, больше не нужно заботиться об их актуальности, в нашем примере это задача сервиса А.

Это решение, увы, также имеет и ряд недостатков:

  1. API микросервиса, в котором находятся данные, должно учитывать требования сразу всех потребителей;
  2. API микросервиса, в котором находятся данные, может быть перегружено запросами на их получение, что сделает микросервис “бутылочным горлышком”;
  3. другие микросервисы не знают, когда мастер-данные обновляются, поэтому DDoS-ят микросервис А, чтобы узнать, нет ли новых данных;
  4. сеть нагружена перегонкой больших объемов одних и тех же данных;
  5. проблемы с Fault Tolerance: если в нашем примере сервис A “ляжет”, то сервис B останется без данных и, возможно, не сможет продолжить работу.

№3 Организация Data warehouse (DWH)

В третьем варианте (рис. 3) справочник городов редактируется только через микросервис A, который в свою очередь сливает его изменения в специальное хранилище данных (DWH). К этому хранилищу, в свою очередь, ходят все остальные микросервисы системы, в нашем примере это микросервис B.

Рис. 3. Третий вариант организации

Плюсы подхода:

  1. все микросервисы работают с одной версией данных;
  2. микросервисам, читающим данные, не нужно заботиться об их актуальности, в нашем примере это задача сервиса А;
  3. поскольку все системы сливают данные в DWH, по ним можно строить сложные аналитические отчеты и выгрузки.

Хотя плюсов у данного варианта достаточно много, за них нужно платить:

  1. данные обновляются в DWH с некоторой задержкой, что может стать проблемой, если для бизнеса важно, чтобы эта задержка была нулевой или близкой к нулевой;
  2. DWH является высоконагруженной частью системы, как на чтение так и на запись, поскольку все владельцы данных в него пишут, и все желающие из него читают;
  3. опять же проблемы с Fault Tolerance, поскольку для многих DWH — единственный источник данных;
  4. сложно добавлять новые источники данных в DWH, поскольку либо DWH под всех подстраивается, либо все подстраиваются под требования DWH, а создать универсальное во всех смыслах решение с учетом нынешнего “зоопарка” технологий весьма сложно.

№4 Мастер-система отдает данные через очереди

В четвертом варианте актуальный справочник городов редактируется только через микросервис A. Микросервис B хранит у себя копию справочника, который постоянно актуализируется из микросервиса А через очередь сообщений с обновлениями справочника.

Рис. 4. Четвертый вариант организации

Такой подход решает несколько проблем:

4.1. Концепция

Поделим микросервисы, работающие с данными на две группы:

  1. микросервис, который имеет возможность записывать данные — мастер-система или владелец данных. В нашем примере для справочника городов сервис A— мастер-система;
  2. микросервисы, которые читают данные — потребители.

Как поддерживать актуальность копии данных в потребителях? Возможны варианты, которые сводятся к pull- или push-подходу:

  1. при pull-подход уже рассматривали в разделе “№2 Единственная копия мастер-данных с доступом через API”;
  2. при push-подходе (рис. 4) master-система сама уведомляет нас об обновлении данных в справочнике через сообщение в очереди.

4.2. Реализация

С точки зрения мастер-системы, она может выгружать в очередь сообщений:

  1. полный слепок обновленной сущности;
  2. только изменившееся поле.

С точки зрения потребителей, хранение мастер-данных может быть реализовано тремя способами:

  1. хранение только актуальных данных;
  2. хранение слепков данных на моменты времени их изменения, если вам нужна история. В случае использования Kafka механизм хранения перекладывается на шину;
  3. хранение транзакций изменений, если вы используете Event Sourcing.

Выбор конкретного подхода для мастер-системы и потребителя зависит от ваших бизнес-требований.

4.2.1. Конвенции именования очередей

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

Важно, чтобы возможность создавать очереди и настраивать правила проверки именований, была у ограниченного круга лиц (администраторы), которые будут следить за соблюдением конвенций. В обязанности администраторов очередей обычно входит и создание биндингов до нужных обменников.

Каждая очередь создается в трех экземплярах (рис. 5):

  1. api.{мастер система}.{ключ данных}.{система потребитель} — основная рабочая очередь, через которую приходят все изменения в реальном времени. У этой очереди всегда должен быть потребитель;
  2. flood.{мастер система}.{ключ данных}.{система потребитель} — очередь для разовой проливки исторических данных;
  3. error.{мастер система}.{ключ данных}.{система потребитель} — очередь ошибок системы потребителя, содержит исходное сообщение и ошибку, которая возникла при обработке сообщения. После исправления ошибки, сообщение переотправляется в основную очередь (api.*) для обработки специальным механизмом.

Очередь ошибок нужна для:

Рис. 5. Пример именования очередей

Мастер-система знает ключ маршрутизации, по которому пушит сообщения api.{мастер система}.{ключ данных}.*. Потребители читают уже конкретно из своей очереди api.{мастер система}.{ключ данных}.{система потребитель}.

Примеры конвенций ориентированы на RabbitMQ, однако могут быть адаптированы и для других шин.

4.2.2. Первоначальная проливка данных

При появления нового потребителя, встаёт вопрос о разовой проливке мастер-данных. Для этого чаще всего используют 2 подхода:

  1. разовая постраничная выгрузка всех данных через API;
  2. разовая постраничная проливка всех данных через шину сообщений.

Оба варианта примерно равны по трудозатратам. Выбор зависит от конкретных бизнес-требований.

4.2.3. Версионирование (историчность) данных

В некоторых случаях потребителю важно знать состояние данных на определенный момент времени. Например, город Санкт-Петербург когда-то назывался Ленинградом, а гипотетическому потребителю важно точно знать как какой город назывался в конкретный момент времени.

Для реализации такого требования потребителю потребуется уметь хранить версии записей с указанием периода действия каждой из них, а мастер-системе выгружать на шину дополнительную meta-информацию, по которой читатели могут определить, создается ли новая версия записи или правится последняя действующая.

4.3. Плюсы и минусы подхода

Плюсы:

  1. за актуальность данных отвечает мастер-система, она является единственным источником правды;
  2. мастер-система и микросервисы-потребители независимы и ничего не знают друг о друге;
  3. при работе с данными в системе нет высоконагруженных микросервисов, потому что нагрузка уходит на уровень инфраструктуры;
  4. возможность горизонтально масштабировать потребителей очередей;
  5. часть работы по обеспечению Fault Tolerance перекладывается на шину сообщений;
  6. шина предоставляет возможности управления рассылкой сообщений.

Минусы:

  1. eventual consistency может стать проблемой, если скорость обновления мастер-данные должна быть нулевой или близкой к нулевой и это критично для бизнеса;
  2. более высокий порог входа, т.к. решение требует дополнительных организационных и технических решений;
  3. высокая стоимость первоначальной настройки.

Вывод

Каждый вариант имеет свои сильные и слабые стороны. Общие рекомендации при выборе следующие:

  1. выделять мастер-системы для данных и добиваться того, чтобы данные редактировались только в них;
  2. при работе с данными избегать создания “бутылочных горлышек”, так как каждый новый потребитель может обрушить систему.

И частные:

  1. если впереди маячит перспектива построения сложных отчетов и аналитических выгрузок, то без DWH не обойтись;
  2. если eventual consistency становится проблемой, то стоит обратить внимание на второй вариант;
  3. во всех прочих ситуациях стоит реализовывать вариант с раздачей данных через очереди.

В нашей практике мы обычно реализуем четвертый вариант с той лишь оговоркой, что иногда в связи со сложностью интеграции с очередями и ограничениями временного бюджета, сначала реализуется pull-подход и уже потом push-подход.

Ссылки

  1. Architectural considerations for event-driven microservices-based systems
  2. Enterprise Integration Using REST
  3. TolerantReader