В 2019 году я писал о том, как создать хранилище событий, основанное на Redis. Я рассказывал о том, что потоки Redis хорошо подходят для организации хранения событий, так как они позволяют хранить события с использованием иммутабельного механизма, напоминающего журнал транзакций, поддерживающего только присоединение новых данных к уже имеющимся, но не изменение существующих данных. Теперь же, используя обновлённое приложение OrderShop, речь о котором шла в вышеупомянутом материале, я хочу продемонстрировать пример использования Redis для организации работы очереди событий, продолжая рассказывать о возможностях применения Redis Enterprise, выходящих за пределы кеширования.
Redis — это отлично решение, применимое для создания инфраструктурных сервисов наподобие очередей сообщений и хранилищ событий. Но при использовании микросервисных архитектур при разработке распределённых систем нужно принимать во внимание несколько важных моментов. Реляционные базы данных часто хорошо показывали себя в монолитных приложениях, но лишь NoSQL-базы данных, вроде Redis, способны удовлетворить требованиям по масштабируемости и доступности, выдвигаемым микросервисными архитектурами.
В распределённых системах используется и распределённое состояние приложений. В соответствии с теоремой CAP в любой реализации распределённых вычислений возможно обеспечить не более двух из трёх следующих свойств: согласованность данных (consistency), доступность информации (availability) и устойчивость к разделению (partition tolerance) (первые буквы английских наименований этих свойств и сформировали акроним CAP). Получается, что для того чтобы сделать свою реализацию распределённой системы устойчивой к отказам, необходимо выбирать между доступностью информации и согласованностью данных. Если мы выберем доступность информации — то, в итоге, в нашем распоряжении окажется то, что называют «согласованностью в конечном счёте» (eventual consistency). То есть — данные будут согласованными, но лишь через некоторое время после последнего обновления. А если выбрать согласованность данных — это окажет воздействие на производительность, так как возникнет необходимость в синхронизации и изоляции операций записи данных во всей распределённой системе.
Порождение событий (event sourcing) — это концепция, в соответствии с которой состояние бизнес-сущностей, таких, как заказ или клиент, хранится в виде последовательности событий, изменяющих состояние. Это обеспечивает доступность информации вместо согласованности данных. В результате можно применять очень просто устроенные операции записи, но выполнение операций чтения при этом усложняется, так как в том случае, если такие операции распространяются на несколько сервисов, для их выполнения может потребоваться использование особой модели чтения данных.
Для организации коммуникаций в распределённой системе может использоваться брокер, но можно обойтись и без него. Системы, в которых нет брокера, широко известны. Самая известная из них — это, пожалуй, система обмена данными, реализованная в рамках протокола HTTP. Системы, в которых используется брокер, как можно судить из их названия, обладают особой сущностью-брокером, находящейся между отправителем и получателем сообщения. Брокер разделяет отправителя и получателя, позволяя организовывать синхронный и асинхронный обмен данными. Это улучшает отказоустойчивость системы, так как потребитель сообщения не обязательно должен быть доступен в момент отправки сообщения. Коммуникации, основанные на использовании брокера, кроме того, позволяют независимо масштабировать системы, занимающиеся отправкой и получением сообщений. Вот материал, содержащий некоторые сведения по вопросу выбора технологии для организации синхронного и асинхронного обмена данными в распределённых системах.
OrderShop — это своего рода «Hello World» из сферы микросервисных архитектур, простая реализация системы электронной коммерции, в которой используется подход, основанный на событиях. Этот демонстрационный проект использует простую модель предметной области, но данная модель удовлетворяет нуждам приложения. Здесь мы будем рассматривать вторую версию OrderShop.
Оркестровка OrderShop выполняется с использованием Docker Compose. Все сетевые коммуникации основаны на gRPC. Центральными компонентами системы являются Event store (хранилище событий) и Message queue (очередь событий). Абсолютно все сервисы подключены только к ним по gRPC. Код OrderShop написан на Python. Вот GitHub-репозиторий проекта. Обратите внимание на то, что этот проект не рассчитан на продакшн-использование. Это — лишь учебный пример распределённой системы.
Сначала клонируйте GitHub-репозиторий проекта. После этого вы сможете выполнять различные действия. А именно:
В нашем случае серверная архитектура проекта состоит из нескольких сервисов. Состояние приложения распределено по нескольким доменным сервисам, но хранится в едином хранилище событий. В компоненте Read model (модель чтения) сосредоточена логика чтения и кеширования данных.
Архитектура и потоки данных приложения OrderShop v2
Команды и запросы передаются через компонент Message queue. А передача событий осуществляется через компонент Event store, который, кроме прочего, играет роль шины событий.
В OrderShop v2 все одноадресные передачи данных выполняются через компонент Message queue. Для реализации этого механизма используются списки Redis (Redis List), в частности — два списка, объединённые в так называемую «надёжную очередь» (reliable queue). Система обрабатывает простые команды (например — операции, затрагивающие единственную сущность) синхронно, а команды, на выполнение которых нужно длительное время (например — операции пакетной обработки данных, операции, выполняемые при работе с почтой) асинхронно и, в исходном виде, умеет реагировать на синхронные сообщения.
Компонент Event store основан на потоках Redis (Redis stream). Сервисы предметной области (в нашем случае это — просто макеты реальных сервисов, используемые для демонстрации функционала OrderShop) подписаны на потоки событий, имена которых соответствуют именам тем событий (то есть — именам сущностей) и отправляют события в эти потоки. Каждое событие — это элемент потока, которому назначена временная метка события, играющая роль ID. Совокупность всех событий, отправленных в потоки, представляет собой состояние всей системы.
Компонент Read model кеширует полученные из Event store сущности в Redis, используя модель предметной области. Несмотря на использование кеша это — система, не хранящая состояние.
Компонент API gateway (шлюз API) тоже не хранит состояние и обслуживает REST-API на порте 5000. Он перехватывает HTTP-соединения и перенаправляет их либо к компоненту Read model для чтения данных из состояния приложения (для выполнения запросов), либо к выделенным доменным сервисам для записи данных в состояние (для выполнения команд). Это концептуальное разделение между операциями чтения и записи данных представляет собой паттерн, называемый Command Query Responsibility Segregation (CQRS). При применении этого паттерна код, изменяющий состояние приложения, отделяется от кода, читающего это состояние.
Сервисы предметной области получают команды на запись данных от компонента API gateway через компонент Message queue. После успешного выполнения команды они отправляют соответствующее событие в компонент Event store. А операции чтения, в свою очередь, обрабатываются компонентом Read model, который получает сведения о состоянии от компонента Event store.
CRM service (Customer Relation Management service, сервис системы управления взаимоотношениями с клиентами) — это компонент, не хранящий состояние. Он подписан на доменные события из хранилища событий и отправляет клиентам электронные письма, используя Mail service (почтовый сервис).
Центральная доменная сущность называется Order (заказ). У неё есть поле, называемое
Состояния, в котором может пребывать заказ
Эти переходы выполняются в нескольких обработчиках событий, которые подписаны на доменные события (тут используется паттерн SAGA).
Работа клиентов имитируется с использованием фреймворка для проведения модульных тестов из Python. В настоящий момент реализовано 10 модульных тестов. Узнать подробности об этом можно, заглянув в файл
Если обратиться к порту 5000 — можно увидеть простой интерфейс, используемый для наблюдения за событиями и для просмотра состояния приложения (с использованием WebSockets).
Для взаимодействия с экземпляром Redis можно использовать контейнер RedisInsight, обратившись к нему по адресу
RedisInsight — удобное средство для работы с базами данных Redis
Redis — это не только мощный инструмент для построения доменного слоя (например — для хранения каталога товаров и для организации поиска по нему) и слоя приложения проектов (например — для хранения HTTP-сессий). Redis может применяться и в инфраструктурном слое (например — в хранилище событий и в очереди сообщений). Использование Redis в этих слоях проектов способствует снижению накладных расходов на разработку и поддержку приложений и позволяет программистам пользоваться технологиями, с которыми они уже знакомы.
Если вас заинтересовало то, о чём вы узнали из этого материала — взгляните на код демонстрационного приложения и попробуйте реализовать что-то подобное сами. Надеюсь, это поможет вам ощутить универсальность и гибкость Redis при создании доменных и инфраструктурных сервисов, а так же — покажет то, что Redis может оказаться полезным не только в деле кеширования данных.
Как вы используете Redis?
Обзор микросервисов, инфраструктурных сервисов и распределённых систем
Redis — это отлично решение, применимое для создания инфраструктурных сервисов наподобие очередей сообщений и хранилищ событий. Но при использовании микросервисных архитектур при разработке распределённых систем нужно принимать во внимание несколько важных моментов. Реляционные базы данных часто хорошо показывали себя в монолитных приложениях, но лишь NoSQL-базы данных, вроде Redis, способны удовлетворить требованиям по масштабируемости и доступности, выдвигаемым микросервисными архитектурами.
В распределённых системах используется и распределённое состояние приложений. В соответствии с теоремой CAP в любой реализации распределённых вычислений возможно обеспечить не более двух из трёх следующих свойств: согласованность данных (consistency), доступность информации (availability) и устойчивость к разделению (partition tolerance) (первые буквы английских наименований этих свойств и сформировали акроним CAP). Получается, что для того чтобы сделать свою реализацию распределённой системы устойчивой к отказам, необходимо выбирать между доступностью информации и согласованностью данных. Если мы выберем доступность информации — то, в итоге, в нашем распоряжении окажется то, что называют «согласованностью в конечном счёте» (eventual consistency). То есть — данные будут согласованными, но лишь через некоторое время после последнего обновления. А если выбрать согласованность данных — это окажет воздействие на производительность, так как возникнет необходимость в синхронизации и изоляции операций записи данных во всей распределённой системе.
Порождение событий (event sourcing) — это концепция, в соответствии с которой состояние бизнес-сущностей, таких, как заказ или клиент, хранится в виде последовательности событий, изменяющих состояние. Это обеспечивает доступность информации вместо согласованности данных. В результате можно применять очень просто устроенные операции записи, но выполнение операций чтения при этом усложняется, так как в том случае, если такие операции распространяются на несколько сервисов, для их выполнения может потребоваться использование особой модели чтения данных.
Для организации коммуникаций в распределённой системе может использоваться брокер, но можно обойтись и без него. Системы, в которых нет брокера, широко известны. Самая известная из них — это, пожалуй, система обмена данными, реализованная в рамках протокола HTTP. Системы, в которых используется брокер, как можно судить из их названия, обладают особой сущностью-брокером, находящейся между отправителем и получателем сообщения. Брокер разделяет отправителя и получателя, позволяя организовывать синхронный и асинхронный обмен данными. Это улучшает отказоустойчивость системы, так как потребитель сообщения не обязательно должен быть доступен в момент отправки сообщения. Коммуникации, основанные на использовании брокера, кроме того, позволяют независимо масштабировать системы, занимающиеся отправкой и получением сообщений. Вот материал, содержащий некоторые сведения по вопросу выбора технологии для организации синхронного и асинхронного обмена данными в распределённых системах.
Проект OrderShop: пример реализации распределённой системы электронной коммерции
OrderShop — это своего рода «Hello World» из сферы микросервисных архитектур, простая реализация системы электронной коммерции, в которой используется подход, основанный на событиях. Этот демонстрационный проект использует простую модель предметной области, но данная модель удовлетворяет нуждам приложения. Здесь мы будем рассматривать вторую версию OrderShop.
Оркестровка OrderShop выполняется с использованием Docker Compose. Все сетевые коммуникации основаны на gRPC. Центральными компонентами системы являются Event store (хранилище событий) и Message queue (очередь событий). Абсолютно все сервисы подключены только к ним по gRPC. Код OrderShop написан на Python. Вот GitHub-репозиторий проекта. Обратите внимание на то, что этот проект не рассчитан на продакшн-использование. Это — лишь учебный пример распределённой системы.
Работа с OrderShop v2
Сначала клонируйте GitHub-репозиторий проекта. После этого вы сможете выполнять различные действия. А именно:
- Для запуска приложения используйте команду
docker-compose up
. - Если после запуска приложения открыть браузер и перейти по адресу
http://localhost:5000/
, можно будет наблюдать за событиями и анализировать состояние проекта. - Запустить клиент можно командой
python -m unittest tests/unit.py
. - Если открыть другую вкладку браузера и перейти по адресу
http://localhost:8001/
— можно будет, использовавredis:6379
, подключиться к тестовой базе данных. - Для остановки приложения воспользуйтесь командой
docker-compose down
.
Архитектура OrderShop v2
В нашем случае серверная архитектура проекта состоит из нескольких сервисов. Состояние приложения распределено по нескольким доменным сервисам, но хранится в едином хранилище событий. В компоненте Read model (модель чтения) сосредоточена логика чтения и кеширования данных.
Архитектура и потоки данных приложения OrderShop v2
Команды и запросы передаются через компонент Message queue. А передача событий осуществляется через компонент Event store, который, кроме прочего, играет роль шины событий.
Инфраструктурные сервисы
В OrderShop v2 все одноадресные передачи данных выполняются через компонент Message queue. Для реализации этого механизма используются списки Redis (Redis List), в частности — два списка, объединённые в так называемую «надёжную очередь» (reliable queue). Система обрабатывает простые команды (например — операции, затрагивающие единственную сущность) синхронно, а команды, на выполнение которых нужно длительное время (например — операции пакетной обработки данных, операции, выполняемые при работе с почтой) асинхронно и, в исходном виде, умеет реагировать на синхронные сообщения.
Компонент Event store основан на потоках Redis (Redis stream). Сервисы предметной области (в нашем случае это — просто макеты реальных сервисов, используемые для демонстрации функционала OrderShop) подписаны на потоки событий, имена которых соответствуют именам тем событий (то есть — именам сущностей) и отправляют события в эти потоки. Каждое событие — это элемент потока, которому назначена временная метка события, играющая роль ID. Совокупность всех событий, отправленных в потоки, представляет собой состояние всей системы.
Сервисы приложения
Компонент Read model кеширует полученные из Event store сущности в Redis, используя модель предметной области. Несмотря на использование кеша это — система, не хранящая состояние.
Компонент API gateway (шлюз API) тоже не хранит состояние и обслуживает REST-API на порте 5000. Он перехватывает HTTP-соединения и перенаправляет их либо к компоненту Read model для чтения данных из состояния приложения (для выполнения запросов), либо к выделенным доменным сервисам для записи данных в состояние (для выполнения команд). Это концептуальное разделение между операциями чтения и записи данных представляет собой паттерн, называемый Command Query Responsibility Segregation (CQRS). При применении этого паттерна код, изменяющий состояние приложения, отделяется от кода, читающего это состояние.
Сервисы предметной области
Сервисы предметной области получают команды на запись данных от компонента API gateway через компонент Message queue. После успешного выполнения команды они отправляют соответствующее событие в компонент Event store. А операции чтения, в свою очередь, обрабатываются компонентом Read model, который получает сведения о состоянии от компонента Event store.
CRM service (Customer Relation Management service, сервис системы управления взаимоотношениями с клиентами) — это компонент, не хранящий состояние. Он подписан на доменные события из хранилища событий и отправляет клиентам электронные письма, используя Mail service (почтовый сервис).
Центральная доменная сущность называется Order (заказ). У неё есть поле, называемое
status
, указывающее на состояние заказа. Переходы между состояниями осуществляются с использованием конечного автомата. Это показано на следующем рисунке.Состояния, в котором может пребывать заказ
Эти переходы выполняются в нескольких обработчиках событий, которые подписаны на доменные события (тут используется паттерн SAGA).
Клиенты
Работа клиентов имитируется с использованием фреймворка для проведения модульных тестов из Python. В настоящий момент реализовано 10 модульных тестов. Узнать подробности об этом можно, заглянув в файл
tests/units.py
.Если обратиться к порту 5000 — можно увидеть простой интерфейс, используемый для наблюдения за событиями и для просмотра состояния приложения (с использованием WebSockets).
Для взаимодействия с экземпляром Redis можно использовать контейнер RedisInsight, обратившись к нему по адресу
http://localhost:8001/
и воспользовавшись redis:6379
для подключения к тестовой базе данных.RedisInsight — удобное средство для работы с базами данных Redis
Итоги
Redis — это не только мощный инструмент для построения доменного слоя (например — для хранения каталога товаров и для организации поиска по нему) и слоя приложения проектов (например — для хранения HTTP-сессий). Redis может применяться и в инфраструктурном слое (например — в хранилище событий и в очереди сообщений). Использование Redis в этих слоях проектов способствует снижению накладных расходов на разработку и поддержку приложений и позволяет программистам пользоваться технологиями, с которыми они уже знакомы.
Если вас заинтересовало то, о чём вы узнали из этого материала — взгляните на код демонстрационного приложения и попробуйте реализовать что-то подобное сами. Надеюсь, это поможет вам ощутить универсальность и гибкость Redis при создании доменных и инфраструктурных сервисов, а так же — покажет то, что Redis может оказаться полезным не только в деле кеширования данных.
Как вы используете Redis?