Посевные базы данных микросервисов


10

Для данной службы A (CMS), которая контролирует модель (Product, давайте предположим, что у нее есть только поля, это id, title, price) и службы B (Shipping) и C (Emails), которые должны отображать данную модель, каким должен быть подход синхронизировать данные модели между этими службами в рамках подхода к источнику событий? Давайте предположим, что каталог продуктов редко изменяется (но меняется) и что есть администраторы, которые могут очень часто получать доступ к данным отправлений и электронной почты (пример функциональности: B: display titles of products the order containedи C:) display content of email about shipping that is going to be sent. Каждый из сервисов имеет свою БД.

Решение 1

Отправить всю необходимую информацию о продукте в рамках мероприятия - это означает следующую структуру для order_placed:

{
    order_id: [guid],
    product: {
        id: [guid],
        title: 'Foo',
        price: 1000
    }
}

На сервисе B и C информация о продукте хранится в productатрибуте JSON ordersтаблицы

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

Проблемы : в зависимости от того, какая другая информация должна быть представлена ​​в B и C, объем данных в событии может возрасти. B и C могут не требовать одинаковую информацию о продукте, но событие должно содержать оба (если мы не разделим события на две части). Если данные отсутствуют в данном событии, код не может их использовать - если мы добавим цветовую опцию к данному Продукту, для существующих заказов в B и C, данный продукт будет бесцветным, если мы не обновим события, а затем не запустим их повторно. ,

Решение 2

Отправлять только руководство продукта в рамках мероприятия - это означает следующую структуру для order_placed:

{
    order_id: [guid],
    product_id: [guid]
}

На услуги B и C информация о продукте хранится в product_idатрибуте ordersтаблицы

Информация о продукте извлекается службами B и C, когда это требуется, путем выполнения вызова API для A/product/[guid]конечной точки.

Проблемы : это делает В и С зависимыми от А (всегда). Если схема Продукта изменяется на A, изменения должны быть сделаны для всех сервисов, которые зависят от них (внезапно)

Решение 3

Отправлять только идентификатор продукта в рамках события - это означает следующую структуру для order_placed:

{
    order_id: [guid],
    product_id: [guid]
}

По услугам B и C информация о товаре хранится в productsтаблице; все еще product_idна ordersстоле, но есть репликация productsданных между A, B и C; B и C могут содержать информацию о продукте, отличную от A

Информация о продукте просвечивается при создании служб B и C и обновляется всякий раз, когда меняется информация о продуктах, путем вызова A/productконечной точки (которая отображает требуемую информацию обо всех продуктах) или путем прямого доступа к базе данных А и копирования необходимой информации о продукте, необходимой для данного служба.

Проблемы : это делает В и С зависимыми от А (при посеве). Если схема Продукта изменяется на A, изменения должны быть сделаны для всех сервисов, которые зависят от них (при заполнении)


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


Что касается updating event history- В событии источников событий история событий является вашим источником правды и никогда не должен быть изменен, а только идти вперед. Если события изменяются, вы можете использовать управление версиями событий или аналогичные решения, но при воспроизведении ваших событий до определенного момента времени состояние данных должно быть таким, каким оно было в тот момент.
Нет

Что касается хранения данных (схемы и т. Д.) Для запросов и полей, добавляемых / удаляемых и т. Д., Мы использовали cosmosDB, хранящий данные в JSON, как это было в то время. Единственное, что требует контроля версий, это события и / или команды. Вам также необходимо обновить контракты конечных точек и объекты значений, содержащие данные, отвечающие на запросы от клиента (веб, мобильный и т. Д.). Более старые данные, не имеющие поля, будут иметь значение по умолчанию или пустое значение, которое всегда подходит для бизнеса, но история событий остается в такте и только движется вперед.
Нет

@ Но updating event historyя имею в виду: пройти через все события, копируя их из одного потока (v1) в другой поток (v2), чтобы поддерживать согласованную схему событий.
eithed

Кроме того, в сфере коммерции / электронной коммерции вы можете захотеть зафиксировать цену, как указано, учитывая, что цены часто меняются. Цена, отображаемая для пользователя, может отличаться в момент сбора фактического заказа. Существует множество способов решения проблемы, но следует рассмотреть один из них.
CPerson

@CPerson yup - цена может быть одним из атрибутов, передаваемых внутри самого события. С другой стороны, URL - адрес для изображения может существовать в случае (представляющее намерение display image at the point when purchase was made) или не может (представляющее намерения display current image as it within catalog)
eithed

Ответы:


3

Решение № 3 действительно близко к правильной идее.

Можно подумать об этом: каждый из B и C кэширует «локальные» копии данных, которые им нужны. Сообщения, обработанные в B (и аналогично в C), используют локально кэшированную информацию. Аналогичным образом, отчеты создаются с использованием локально кэшированной информации.

Данные копируются из источника в кеши с помощью стабильного API. B и C даже не должны использовать один и тот же API - они используют любой протокол выборки, соответствующий их потребностям. По сути, мы определяем контракт - протокол и схему сообщения - которые ограничивают поставщика и потребителя. Тогда любой потребитель по этому контракту может быть подключен к любому поставщику. Обратно несовместимые изменения требуют нового контракта.

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

Это дает вам «автономию», в том смысле, что B и C могут продолжать приносить коммерческую выгоду, когда A временно недоступен.

Рекомендуемое чтение: Данные снаружи, Данные внутри , Pat Helland 2005.


Да, я полностью согласен с тем, что вы написали здесь, и решение 3 - это решение goto, которое я применил, однако это не подход к получению событий, так как, если мы будем воспроизводить события, мы не обязательно хотим использовать текущее состояние Продукта; мы хотим использовать состояние, как оно было в точке события. Конечно, это может быть хорошо (зависит от требований бизнеса). Однако если мы хотим , чтобы отслеживать изменения в каталог, который требует событие Sourcing тех , а также, и зависит от того , сколько данных , то есть, мы могли бы лучше откатиться к решению 1.
eithed

1
Я думаю, что у вас есть решение № 3. Если вам нужно согласовать воспроизведение с каталогом, то это также и источник событий. Вам нужно воспроизводить только при повторном заполнении, что, вероятно, происходит при запуске - после того, как вы подниметесь, вам нужно только смотреть на новые события, поэтому объем данных, вероятно, не является реальной проблемой. Однако даже тогда у вас есть возможность (если необходимо) использовать контрольные точки, то есть «здесь состояние с событием 1000», так что вы принимаете это, и теперь вам нужно только воспроизвести событие 1,001 до текущего вместо всей истории ,
Майк Б.

2

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

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

  1. Вызов API в службу A вызывает проблемы с производительностью.
  2. Стоимость обслуживания А, когда он не работает и не может получить данные, имеет большое значение для бизнеса.

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

Кэширование значительно усложняет систему и может создавать крайние случаи, о которых трудно рассуждать, и ошибки, которые очень сложно воспроизвести. Вы также должны снизить риск предоставления устаревших данных при наличии новых данных, что может быть намного хуже с точки зрения бизнеса, чем (например) отображение сообщения «Служба А не работает - повторите попытку позже».

Из этой прекрасной статьи Уди Дахана:

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

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


1
@SavvasKleanthous «Сеть надежна» - одна из ошибок распределенных вычислений. Но ответом на эту ошибку не должно быть «кэширование каждого бита данных из каждого сервиса в любом другом сервисе» (я понимаю, что это немного гиперболично). Ожидайте, что сервис может быть недоступен, и рассматривайте это как условие ошибки. Если у вас возникла редкая ситуация, когда выход службы А оказывает серьезное влияние на бизнес, то (внимательно!) Рассмотрите другие варианты.
Фил Сэндлер

1
@SavvasKleanthous также считают (как я уже упоминал в своем ответе), что возвращение устаревших данных во многих случаях может быть намного хуже, чем выдача ошибки.
Фил Сэндлер

1
@eithed Я имел в виду этот комментарий: «Если, однако, мы хотим отслеживать изменения в каталоге, то это также требует источников событий». В любом случае у вас есть правильная идея - служба Продукта должна отвечать за отслеживание изменений во времени, а не последующие службы.
Фил Сэндлер

1
Кроме того, хранение данных, которые вы наблюдаете, хотя и имеет некоторые сходства с кэшированием, не вызывает тех же проблем. В частности, недействительность не требуется; вы получите новую версию данных, когда это произойдет. То, что вы испытываете, - это задержанная последовательность. Однако даже при использовании веб-запроса есть окно несоответствия (хотя и крошечное).
Савва Клинтус

1
@SavvasKleanthous В любом случае, моя главная цель - не пытаться решать проблемы, которые еще не существуют, особенно с помощью решений, которые приносят свои проблемы и риски. Вариант 2 является самым простым решением, и он должен быть выбран по умолчанию до тех пор, пока он не будет соответствовать бизнес-требованиям . Если вы думаете, что выбрать простейшее решение, которое может сработать, - «как вы говорите» - «очень плохо», то я думаю, что мы просто не согласны.
Фил Сэндлер

2

Очень сложно просто сказать, что одно решение лучше другого. Выбор одного из решений № 2 и № 3 зависит от других факторов (длительность кэша, допуск согласованности, ...)

Мои 2 цента:

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

Решение № 1 (NOK)

  • Данные дублируются в нескольких системах

Решение № 2 (ОК)

  • Обеспечивает сильную согласованность
  • Работает только в том случае, если услуга продукта доступна и обеспечивает высокую производительность
  • Если служба электронной почты готовит резюме (с большим количеством продуктов), то общее время ответа может быть больше

Решение № 3 (сложное, но предпочтительное)

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

1

Вообще говоря, я настоятельно рекомендую использовать вариант 2 из-за временной связи между этими двумя службами (если связь между этими службами не является сверхстабильной и не очень частой). Временная связь - это то, что вы описываете this makes B and C dependant upon A (at all times), и означает, что если A отключен или недоступен из B или C, B и C не могут выполнять свою функцию.

Я лично считаю, что оба варианта 1 и 3 имеют ситуации, когда они являются допустимыми вариантами.

Если связь между A и B & C настолько высока, или объем данных, необходимых для участия в событии, достаточно велик, чтобы вызвать беспокойство, тогда вариант 3 является наилучшим вариантом, поскольку нагрузка на сеть намного ниже и задержка операций будет уменьшаться по мере уменьшения размера сообщения. Другие проблемы для рассмотрения здесь:

  1. Стабильность контракта: если контракт сообщения, покидающего А, часто меняется, то добавление большого количества свойств в сообщение приведет к большим изменениям у потребителей. Однако в этом случае я считаю, что это не будет большой проблемой, потому что:
    1. Вы упомянули, что система A является CMS. Это означает, что вы работаете над стабильным доменом, и поэтому я не думаю, что вы будете часто видеть изменения
    2. Поскольку B и C являются отправкой и электронной почтой, а вы получаете данные от A, я полагаю, что вы будете испытывать аддитивные изменения, а не ломать их, которые можно безопасно добавлять всякий раз, когда вы обнаружите их без каких-либо переделок.
  2. Сцепление: здесь очень мало сцепления. Во-первых, поскольку связь осуществляется посредством сообщений, во время заполнения данных не существует никакой связи между службами, кроме коротких временных, и договором об этой операции (который не является связью, которую вы можете или должны попытаться избежать)

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

Другой вариант, который я бы предложил, это небольшое изменение до 3, которое заключается не в том, чтобы запускать процесс во время запуска, а в том, чтобы вместо этого наблюдать событие «ProductAdded и« ProductDetailsChanged »на B и C, когда есть изменение в каталоге продукта. в A. Это ускорит развертывание (и, следовательно, упростит исправление проблемы / ошибки, если вы ее обнаружите).


Редактировать 2020-03-03

У меня есть определенный порядок приоритетов при определении интеграционного подхода:

  1. Какова стоимость последовательности? Можем ли мы принять несколько миллисекунд несоответствия между вещами, измененными в А, и их отражением в B & C?
  2. Вам нужны запросы на определенный момент времени (также называемые временными запросами)?
  3. Есть ли источник правды для данных? Сервис, который владеет ими и считается восходящим?
  4. Если есть владелец / единственный источник правды, это стабильно? Или мы ожидаем увидеть частые переломные изменения?

Если цена несоответствия высока (в основном данные продукта в A должны быть как можно быстрее согласованы с продуктом, кэшированным в B и C), тогда вам не избежать необходимости принять недоступность и сделать синхронный запрос (например, веб-сайт). / запрос покоя) из B & C в A для получения данных. Будь в курсе! Это по-прежнему не означает транзакционную согласованность, но сводит к минимуму окна для несогласованности. Если вы абсолютно, безусловно, должны быть немедленно последовательны, вам нужно изменить границы своих услуг. Тем не менее, я очень сильно верю, что это не должно быть проблемой. Исходя из опыта, на самом деле крайне редко, когда компания не может принять несколько секунд несогласованности, поэтому вам даже не нужно делать синхронные запросы.

Если вам нужны запросы на определенный момент времени (которые я не заметил в вашем вопросе и, следовательно, не включил выше, возможно, неправильно), затраты на поддержание этого в последующих сервисах очень высоки (вам придется дублировать внутренняя логика проецирования событий во всех нисходящих сервисах), которая делает решение ясным: вы должны оставить владение A и запрашивать Ad-hoc через веб-запрос (или аналогичный), а A должен использовать источник событий для получения всех событий, о которых вы знали в то время, чтобы проецировать в состояние и вернуть его. Я предполагаю, что это может быть вариант 2 (если я правильно понял?), Но затраты таковы, что временная связь лучше, чем стоимость обслуживания дублированных событий и логики проекции.

Если вам не нужен момент времени, и нет ясного, единственного владельца данных (который в моем первоначальном ответе я предполагал, основываясь на вашем вопросе), тогда очень разумным было бы хранить представления продукта в каждой услуге отдельно. Когда вы обновляете данные для продуктов, вы обновляете A, B и C параллельно, выполняя параллельные веб-запросы к каждому, или у вас есть командный API, который отправляет несколько команд каждому из A, B и C. B & C используют их локальная версия данных для выполнения своей работы, которая может быть или не быть устаревшей. Это не какой-либо из указанных выше вариантов (хотя его можно сделать близким к варианту 3), поскольку данные в A, B и C могут различаться, а «весь» продукта может представлять собой совокупность всех трех данных. источники.

Знание, имеет ли источник истины стабильный контракт, полезно, потому что вы можете использовать его для использования домена / внутренних событий (или событий, которые вы храните в своем источнике событий в качестве шаблона хранения в A) для интеграции между A и сервисами B и C. Если договор стабилен, вы можете интегрироваться через доменные события. Тем не менее, у вас возникает дополнительная проблема в случае частых изменений или того, что контракт сообщений достаточно велик, что делает транспортировку проблемой.

Если у вас есть явный владелец с ожидаемым стабильным контрактом, лучшим вариантом будет вариант 1; заказ будет содержать всю необходимую информацию, а затем B и C выполнят свою функцию, используя данные в событии.

Если контракт может изменяться или часто нарушаться, следуя вашему варианту 3, то есть возврат к веб-запросам на получение данных о продукте - на самом деле лучший вариант, поскольку поддерживать несколько версий гораздо проще. Таким образом, B сделает запрос на v3 продукта.


Да, я согласен. Хотя ProductAddedили ProductDetailsChangedусложняя отслеживание изменений каталога продуктов, нам необходимо каким-то образом синхронизировать эти данные между базами данных, в случае, если события воспроизводятся, и нам необходим доступ к данным каталога из прошлого.
eithed

@eithed Я обновил ответ, чтобы расширить некоторые предположения, которые я сделал.
Савва Клинтус
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.