Как работать с общим состоянием в архитектуре микросервиса?
В нашей компании мы переходим от огромного монолитного приложения к архитектуре микросервиса. Основными техническими драйверами для этого решения были необходимость иметь возможность масштабировать услуги независимо и масштабируемость разработки - у нас есть десять команд схватки, работающих в разных проектах (или "микро-службах" ).
Процесс перехода является плавным, и мы уже начали извлекать выгоду из преимуществ этих новых технических и организационных структур. Теперь, с другой стороны, есть основная проблема боли, с которой мы боремся: как управлять "состоянием" зависимостей между этими микросервисами.
Приведем пример: один из микро-сервисов касается пользователей и регистрации. Эта услуга (позвольте ей позвонить Х) несет ответственность за сохранение идентификационной информации и, таким образом, является основным поставщиком идентификаторов пользователей. Остальные микросервисы сильно зависят от этого. Например, существуют некоторые службы, ответственные за информацию профиля пользователя (A), разрешения пользователя (B), группы пользователей (C) и т.д., Которые полагаются на эти идентификаторы пользователей и, следовательно, существует потребность в поддержании некоторой синхронизации данных между этими службами (т.е. служба A не должна иметь информацию для пользователя, не зарегистрированного в сервисе X). В настоящее время мы поддерживаем эту синхронизацию, уведомляя изменения состояния (например, новые регистрации) с помощью RabbitMQ.
Как вы можете себе представить, существует много Xs: множество "основных" сервисов и множество более сложных зависимостей между ними.
Основная проблема возникает при управлении различными средами dev/testing. Каждая команда (и, следовательно, каждая служба) должна пройти через несколько окружений, чтобы добавить код в живую: непрерывная интеграция, интеграция в группы, приемочные испытания и живая среда.
Очевидно, нам нужны все службы, работающие во всех этих средах, чтобы проверить, что система работает в целом. Теперь это означает, что для тестирования зависимых сервисов (A, B, C,...) мы должны не только полагаться на сервис X, но и на его состояние. Таким образом, нам нужно как-то поддерживать целостность системы и хранить глобальное и согласованное состояние.
Наш текущий подход к этому - получение снимков всех БД из живой среды, что делает некоторые преобразования для сокращения и защиты конфиденциальности данных и распространения их во всех средах перед тестированием в конкретной среде. Очевидно, это огромные накладные расходы, как организационно, так и в вычислительных ресурсах: у нас есть десять непрерывных интеграционных сред, десять интеграционных сред и одна среда приемочного тестирования, которые все нужно "обновить" с помощью этих общих данных из живой и последней версии кода часто.
Мы изо всех сил пытаемся найти лучший способ облегчить эту боль. В настоящее время мы оцениваем два варианта:
- использование докереподобных контейнеров для всех этих сервисов.
- с двумя версиями каждой службы (одна предназначена для разработки этой службы и одна другая как песочница, которая будет использоваться остальными командами при их тестировании на разработку и интеграцию).
Ни одно из этих решений не облегчает боль обмена данными между службами. Мы хотели бы знать, как некоторые другие компании/разработчики решают эту проблему, поскольку мы считаем, что это должно быть общим в архитектуре микросервисов.
Как вы, ребята, это делаете? У вас также есть эта проблема? Любые рекомендации?
Извините за длинное объяснение и большое спасибо!
Ответы
Ответ 1
На этот раз я прочитал ваш вопрос с разных точек зрения, так что вот "другое мнение". Я знаю, что может быть слишком поздно, но надеюсь, что это поможет в дальнейшем развитии.
Похоже, что shared state
является результатом неправильной развязки. В "правильной" архитектуре микросервиса все микросервисы должны быть изолированы функционально, а не логически. Я имею в виду, что все три user profile information (A), user permissions (B), user groups (C)
выглядят функционально одинаковыми и более или менее функционально когерентными. Они кажутся одиночными user microservice
с последовательным хранилищем. Я не вижу здесь причин развязывания их (или, по крайней мере, вы не сказали о них).
Таким образом, реальная проблема связана с изоляцией микросервиса. В идеале каждый микросервис может жить как полный автономный продукт и предоставлять четко определенную стоимость бизнеса. При разработке архитектуры системы мы разбиваем ее на маленькие логические единицы (A, B, C и т.д. В вашем случае или даже меньше), а затем определяем функционально-когерентные подгруппы. Я не могу сказать вам точных правил, как это сделать, возможно, некоторых примеров. Сложная связь/зависимости между единицами, многие общие термины на их вездесущих языках, поэтому похоже, что такие единицы относятся к одной и той же функциональной группе и, следовательно, к микросервису.
Итак, из вашего примера, поскольку есть одно хранилище, у вас есть только способ управления его консистенцией, как и вы.
Кстати, я задаюсь вопросом, каким образом вы решили свою проблему? Кроме того, если вам нравится моя идея, вы можете принять ее.
Ответ 2
Позвольте мне попытаться переформулировать проблему:
Актеры:
- X: UserIds (состояние учетной записи)
- предоставлять услугу для получения идентификатора (на основе учетных данных) и статуса учетной записи
- A: UserProfile
- Использование X для проверки состояния учетной записи пользователя. Сохраняет имя вместе со ссылкой на аккаунт
- предоставлять услугу для получения/редактирования имени на основе идентификатора
- B: UserBlogs
- Использование X таким же образом. Сохраняет сообщение в блоге вместе со ссылкой на аккаунт, когда пользователь пишет один
- Использование A для поиска сообщения в блоге на основе имени пользователя
- предоставлять сервис для получения/редактирования списка записей в блоге на основе ID
- предоставлять услуги для поиска записи в блоге на основе имени (полагается на A)
- C: MobileApp
- обертывает функции X, A, B в мобильное приложение.
- предоставлять все вышеперечисленные услуги, опираясь на четко определенный контракт на связь со всеми остальными (по запросу @neleus)
Требования:
- Работа команд X, A, B, C должна быть отсоединена.
- Интеграционные среды для X, A, B, C необходимо обновить с помощью функций latests (чтобы выполнить интеграционные тесты)
- Интеграционные среды для X, A, B, C должны иметь "достаточный" набор данных (для выполнения нагрузочных тестов и поиска краевых случаев).
Следуя идее @yugene: иметь mocks для каждой услуги, предоставляемой каждой командой, позволит 1) и 2)
- стоимость - это больше развития от команд.
- также обслуживание макетов, а также основная функция
- препятствие - это тот факт, что у вас есть монолитная система (у вас пока нет набора четко определенных/изолированных сервисов).
Рекомендуемое решение:
Как насчет того, чтобы разрешить общую среду с набором основных данных для решения 3)? Все "поставленные услуги" (то есть работающие на производстве) будут доступны. Каждая команда могла выбрать, какие услуги они будут использовать отсюда и какой из них они будут использовать из своей собственной среды.
Один непосредственный недостаток, который я вижу, - это общие состояния и согласованность данных.
Рассмотрим автоматические тесты, выполненные против основных данных, например:
- B изменяет имена (принадлежащие A), чтобы работать в своем блоге
- A изменяет статус учетной записи для работы над некоторыми сценариями разрешения
- C меняет все на одном аккаунте
Мастер-набор данных быстро станет непоследовательным и потеряет значение для требования 3) выше.
Таким образом, мы могли бы добавить "обычный" уровень на общие основные данные: любой может читать из полного набора, но может только изменять созданные им объекты?
Ответ 3
С моей точки зрения только объекты используют службы, которые должны иметь состояние. Рассмотрим ваш пример: служба X, ответственная за идентификатор пользователя, службу Ответственность за информацию профиля и т.д. Предположим, что пользователь Y, у которого есть определенный токен безопасности (который может быть создан, например, с использованием имени пользователя и пароля, должен быть уникальные) записи в систему. Затем клиент, содержащий информацию пользователя, отправляет токен безопасности службе X. Служба X содержит информацию об идентификаторе пользователя, связанном с таким токеном. В случае нового пользователя служба X создает новый идентификатор и сохраняет его токен. Затем служба X возвращает идентификатор объекту пользователя. Пользовательский объект запрашивает службу A о профиле пользователя, предоставляя идентификатор пользователя. Служба A берет идентификатор и запрашивает службу X, если этот идентификатор существует. Служба X отправляет положительный ответ, тогда служба A может осуществлять поиск информации профиля по идентификатору пользователя или попросить пользователя предоставить такую информацию для ее создания. Та же логика должна работать с службами B и C. Они должны говорить друг с другом, но им не нужно знать о состоянии пользователя.
Несколько слов о средах. Я бы предложил использовать куклы. Это способ автоматизации процесса развертывания службы. Мы используем куклы для развертывания служб в разных средах. Марионеточный script является досягаемым и обеспечивает гибкую конфигурацию.