Как денормализации данных работа с шаблоном микрослужб?


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

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

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

поэтому я спрашиваю:учитывая базу данных, которая полностью состоит из связанных таблиц, как денормализация на мелкие фрагменты (группы таблиц) так, что осколки могут контролироваться отдельными микросервисов?

например, учитывая следующее (довольно маленький, но примерный) база данных:

[users] table
=============
user_id
user_first_name
user_last_name
user_email

[products] table
================
product_id
product_name
product_description
product_unit_price

[orders] table
==============
order_id
order_datetime
user_id

[products_x_orders] table (for line items in the order)
=======================================================
products_x_orders_id
product_id
order_id
quantity_ordered

не тратьте слишком много времени на критику моего дизайна, я сделал это на лету. Дело в том, что для меня, это делает логический смысл разделить эту базу на 3 микрослужб:

  1. UserService - для грубых пользователей в системе; должен в конечном итоге управлять [users] стол; и
  2. ProductService - для грубых продуктов в системе; должен в конечном счете управлять [products] стол; и
  3. OrderService - для грубых заказов внутри система; должна в конечном счете управлять [orders] и [products_x_orders] таблицы

однако все эти таблицы имеют отношения внешнего ключа друг с другом. Если мы денормализовать их и относиться к ним как монолиты, они теряют все их смыслового значения:

[users] table
=============
user_id
user_first_name
user_last_name
user_email

[products] table
================
product_id
product_name
product_description
product_unit_price

[orders] table
==============
order_id
order_datetime

[products_x_orders] table (for line items in the order)
=======================================================
products_x_orders_id
quantity_ordered

теперь нет никакого способа узнать, кто что заказал, в каком количестве и когда.

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

4 63

4 ответа:

это субъективно, но следующее решение работало для меня, моей команды и нашей команды DB.

  • на прикладном уровне микросервисы разлагаются на семантические функции.
    • например a Contact сервис может контакты твар (метаданные о контактах: имена, телефонные номера, контактная информация и т. д.)
    • например a User сервис может пользователям CRUD с учетные данные для входа, ролей, полномочий и т. д.
    • например a Payment обслуживание может быть CRUD платежи и работа под капотом с 3rd party PCI совместимым сервисом, таким как Stripe и т. д.
  • на уровне БД таблицы могут быть организованы, однако люди devs/DBs / devops хотят, чтобы таблицы были организованы

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

interface PaymentService {
    PaymentInfo makePayment(User user, Payment payment);
}

модель это так:

interface PaymentService {
    PaymentInfo makePayment(Long userId, Payment payment);
}

этот кстати, организаций, которые принадлежат к другим микрослужб только ссылка внутри конкретной службы по идентификатору, а не по ссылке на объект. Это позволяет таблицам БД иметь внешние ключи повсюду, но на уровне приложения "внешние" сущности (то есть сущности, живущие в других службах) доступны через ID. Это останавливает каскадирование объектов от выхода из-под контроля и четко очерчивает границы обслуживания.

проблема заключается в том, что он требует больше сети звонки. Например, если я дал каждому Payment сущность a User ссылка, я мог бы получить пользователя для конкретного платежа с помощью одного звонка:

User user = paymentService.getUserForPayment(payment);

но используя то, что я предлагаю здесь, вам понадобятся два звонка:

Long userId = paymentService.getPayment(payment).getUserId();
User user = userService.getUserById(userId);

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

Это действительно одна из ключевых проблем в микрослужб что вполне удобно опускать в большинстве статей. К счастью, есть решения для этого. В качестве основы для обсуждения приведем таблицы, которые вы предоставили в вопросе. enter image description here На рисунке выше показано, как таблицы будут выглядеть в монолите. Всего несколько таблиц с объединениями.


для рефакторинга этого на микросервисы мы можем использовать несколько стратегий:

Api Join

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

enter image description here У него есть очевидный недостаток. Это медленнее.

только для чтения просмотров

во втором решении вы можете создать копию таблицы во второй базе данных. Копия доступна только для чтения. Каждый микросервис может используйте изменяемые операции над его таблицами чтения/записи. Когда речь заходит о таблицах только для чтения, которые копируются из других баз данных, они могут (очевидно) использовать только чтение enter image description here

высокая производительность чтения

можно достичь высокой производительности чтения, введя такие решения, как redis/memcached поверх read only view решение. Обе стороны соединения должны быть скопированы в плоскую структуру, оптимизированную для чтения. Вы можете ввести совершенно новое состояние микросервис, который можно использовать для чтения из этого хранилища. Хотя кажется, что много хлопот, стоит отметить, что он будет иметь более высокую производительность, чем монолитное решение поверх реляционной базы данных.


есть несколько возможных решений. Те, которые просты в реализации, имеют самую низкую производительность. Внедрение высокопроизводительных решений займет несколько недель.

Я понимаю, что это, возможно, не хороший ответ, но какого черта. Ваш вопрос был:

дали базу данных, которая полностью состоит из связанных таблиц, как один денормализует это на более мелкие фрагменты (группы таблиц)

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

то есть люди, толкающие микросервисы со строгим правилом no shared DB, запрашивают базу данных дизайнеры отказаться от внешних ключей (и они делают это неявно или явно). Когда они явно не заявляют о потере FK, это заставляет вас задаться вопросом, действительно ли они знают и распознают значение внешних ключей (потому что это часто вообще не упоминается).

Я видел большие системы, разбитые на группы таблиц. В этих случаях может быть либо A) между группами не допускается FK, либо B) одна специальная группа, которая содержит" основные " таблицы, на которые могут ссылаться FK таблицы в других группах.

... но в этих системах "группы таблиц" часто составляют 50+ таблиц, поэтому недостаточно малы для строгого соблюдения микросервисов.

для меня другой связанный с этим вопрос рассмотреть с приближением конструирование разделения БД воздействия этого представления, вопрос о том, как все данные собраны вместе для отчетности и/или загрузки в хранилище данных.

несколько связана также тенденция игнорировать встроенный БД функции репликации в пользу обмена сообщениями (и как репликация на основе БД основных таблиц / общего ядра DDD) влияет на дизайн.

EDIT: (стоимость соединения с помощью вызовов REST)

когда мы разделяем БД, как это предлагается микросервисами, и удаляем FK, мы не только теряем принудительное декларативное бизнес-правило (FK), но и теряем способность БД выполнять соединение(ы) через эти границы.

в OLTP FK значения являются вообще не "UX Friendly", и мы часто хотим присоединиться к ним.

в примере, если мы получим последние 100 заказов, мы, вероятно, не хотим показывать значения идентификатора клиента в UX. Вместо этого нам нужно сделать второй звонок клиенту, чтобы узнать его имя. Однако, если нам также нужны строки заказа, нам также нужно сделать еще один звонок в службу продуктов, чтобы показать название продукта, sku и т. д., а не идентификатор продукта.

В общем, мы можем найти, что когда мы разбиваем дизайн БД в таким образом, нам нужно сделать много звонков "присоединиться через REST". Так какова же относительная стоимость этого?

реальная история: примере затраты на участие через REST' против ДБ присоединяется

есть 4 микросервисов и они предполагают много "присоединиться через REST". Тестовая нагрузка для этих 4 сервисов доходит до ~15 минут. Эти 4 микросервиса, преобразованные в 1 сервис с 4 модулями против общей БД (что позволяет объединяться), выполняют ту же нагрузку в ~20 секунды.

к сожалению, это не прямое сравнение яблок с яблоками для DB joins vs "JOIN via REST", так как в этом случае мы также изменили NoSQL DB на Postgres.

удивительно ли, что "JOIN via REST" работает относительно плохо по сравнению с БД, которая имеет оптимизатор на основе затрат и т. д.

в некоторой степени, когда мы разбиваем БД, как это, мы также уходим от "оптимизатора на основе затрат" и всего, что происходит с выполнением запроса планирование для нас в пользу написания нашей собственной логики соединения (мы несколько пишем наш собственный относительно неискушенный план выполнения запроса).

Я бы рассматривал каждый микросервис как объект , и, как и любой ORM, вы используете эти объекты для извлечения данных, а затем создаете соединения в своих коллекциях кода и запросов, микросервисы должны обрабатываться аналогичным образом. Разница только здесь будет каждый микросервис должен представлять один объект за раз, чем полное дерево объектов. Уровень API должен использовать эти службы и моделировать данные таким образом, чтобы они были представлены или сохранены.

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

@Всеобъемлющей конвенции о международном терроризме-Спенс, мне понравился подход пересечения услуг, но как она может быть разработана и использоваться другими службами? Я считаю, что это создаст своего рода зависимость для других служб.

любые комментарии, пожалуйста?