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