0 637 ru

Обзор Microsoft Orleans

📘 Введение в Microsoft Orleans

Давайте для начала разберемся с терминологией и основными концептами:

🤔 Что такое Microsoft Orleans?

Microsoft Orleans — это фреймворк от Microsoft для создания масштабируемых и отказоустойчивых облачных приложений. Он упрощает разработку распределённых систем, позволяя разработчикам сосредоточиться на бизнес-логике, а не на сложностях параллельных вычислений.

Orleans предоставляет разработчикам возможность работать с virtual actors (grains), которые представляют собой абстракции, облегчающие управление состоянием и взаимодействием между компонентами приложения. Это позволяет легко масштабировать приложения и автоматически обрабатывать ошибки.

👤 Что такое Actor Model?

Actor model

Actor Model — это модель параллельных вычислений, в которой основными единицами есть акторы. Акторы — это независимые объекты, которые:

  1. Обрабатывают сообщения: каждый актор имеет свою очередь сообщений и обрабатывает их одно за другим.
  2. Могут создавать новых акторов: это позволяет легко масштабировать систему.
  3. Могут отправлять сообщения другим акторам: взаимодействие между компонентами происходит через обмен сообщениями.
  4. Могут изменять своё состояние: актор может изменять своё внутреннее состояние в ответ на обработку сообщений.

Модель акторов упрощает разработку параллельных и распределённых систем, так как она изолирует состояние каждого актора и использует обмен сообщениями для взаимодействия. Это помогает избежать множества проблем, связанных с параллельным доступом к общему состоянию (например, гонки за ресурсами).

В Microsoft Orleans роли виртуальных акторов выполняют grains.

🔧 Orleans Runtime

Orleans runtime

Orleans Runtime — среда выполнения, которая поддерживает и управляет акторами в системе Orleans. Runtime предоставляет:

  1. Автоматическое масштабирование: Orleans Runtime автоматически распределяет актора по различным нодам (серверным машинам) в кластере, чтобы оптимально использовать ресурсы.
  2. Управление состоянием: состояния акторов автоматически сохраняются и восстанавливаются в случае сбоев, обеспечивая отказоустойчивость.
  3. Обработку ошибок: Orleans Runtime включает механизмы для автоматического восстановления после сбоев, перезапуская актора на других нодах.
  4. Гибкость в развертывании: поддерживает различные конфигурации развертывания, включая локальные и облачные среды.
  5. Гарантирует, что выполнение будет однопоточным и будет запущен всего один инстанс для stateful grains
  6. Позволяет управлять несколькими инстансами stateless grains если это требуется для улучшения перформанса

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

❓ Почему Microsoft Orleans, а не кастомная разработка?

❓ Почему Microsoft Orleans, а не кастомная разработка?

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

Почему Microsoft Orleans, а не кастомная разработка?

Т.е как видите, если нужно разработать систему в которой, к примеру есть 3 микросервиса, у каждого с них должна быть бд, и наверное кэш для перформанса. Далее чтобы сервисам общаться друг с другом и быть high available с хорошой пропускной способностью, нужен message broker чтобы хэндлить коммуникацию между ними. Ну и теперь в реальных проектах это может быть десатки сервисов, следовательно, все cross-cutting консерны нужно будет менеджить самим дев тимам. Тут и вступает Orleans, так как он предлагает инфраструктуру через свой рантайм и менеджит это сам. Причем поддерживает даже мультисерверный env. Т.е если вы хотите развернуть 1 или несколько silo на нескольких серверах - это супортиться Orleans runtime и работает с коробки без дополнительного эффорта. Отмечу, что общая база изображена для иллюстрации, конечно вы можете выбирать сторедж per Grain. 

🏛️ Архитектура Microsoft Orleans

Архитектура Microsoft Orleans

🛠️ Обзор компонентов

Orleans состоит из:

High-Level View ms orleans
  • Virtual Actors (Grains): логические единицы выполнения, которые инкапсулируют состояние и поведение, они живут в Silos и взаимодействуют друг с другом через обмен сообщениями.
  • Silos: контейнеры, которые выполняют и хранят Grains, распределяя нагрузку и обеспечивая отказоустойчивость.". Каждый Silo может содержать множество Grains.
  • Clients: внешние приложения, которые взаимодействуют с Grains через Silos.
  • Cluster: содержит набор взаимосвязанных Silos, который образует распределенную систему, чтобы обеспечить отказоустойчивость, распределение нагрузки и масштабируемость.
  • Orleans Runtime: среда выполнения, которая управляет взаимодействием между компонентами, масштабированием и обработкой ошибок и восстановлением после сбоев.

👥 Virtual Actors (Grains)

Virtual Actors (Grains)

Grains — это основные единицы вычислений в Orleans. Grains представляют собой акторов, которые инкапсулируют состояние и поведение. Они являются виртуальными, потому что их активация, деактивация, распределение полностью управляются Orleans Runtime. Состояние Grain'a сохраняется в памяти, пока зерно активно, что приводит к снижению задержки и уменьшению нагрузки на хранилища данных.

Virtual Actors (Grains) state

Преимущества Grains:

Меньше операций чтения базы данных:

  • Hot/warm данные хранятся в памяти, cold data — в базе данных.
  • Нет необходимости в отдельном кэше

Меньше конфликтов при записи в базу данных: Каждый Grain владеет своим состоянием

Меньше проблем с согласованностью кэша:

  • Write операция – обновляет состояние grain, далее grain обновляет базу данных
  • Grain работают по схеме Write-Through кэша.
Write-Through кэш

Лучшая масштабируемость:Grains распределены по экземплярам вашего приложения.

Low-latency writes: Нет необходимости в асинхронной записи через очередь или воркера.

Простота: Меньше кода, меньше информации, меньше обработки ошибок, меньше retry логики.

Автоматическое управление жизненным циклом: Orleans Runtime автоматически создает и уничтожает Grains по мере необходимости, что упрощает управление ресурсами.

Масштабируемость: благодаря распределению Grains по различным Silos, Orleans позволяет легко масштабировать приложение.

Отказоустойчивость: состояние Grains автоматически сохраняется и восстанавливается в случае сбоев, обеспечивая непрерывность работы системы.

🏢 Silos

Silos

Silos — это контейнеры, которые выполняют и хранят Grains. Silos исполняют и хранят Grains. Они обеспечивают балансировку нагрузки и отказоустойчивость системы. Каждый Silo может взаимодействовать с другими Silos, формируя единую распределённую систему.

🔗 Clients

Клиенты — взаимодействуют с grains через Silo. Они инициируют запросы на обработку или поиск данных.

Тип клиентов:

Co-hosted
Co-hosted

Это дает ряд преимуществ, включая снижение нагрузки на сеть и процессор, а также уменьшение задержки и повышение пропускной способности и надежности. Клиент использует знания ноды о топологии и состоянии кластера, и ему не требуется использовать отдельный gateway. Это позволяет избежать сетевых прыжков и сериализации/десериализации туда и обратно. Таким образом, это также повышает надежность, поскольку количество необходимых nods между клиентом и grain'ом сведено к минимуму.

Внешние клиенты
Внешние клиенты orleans

 

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

📡 Cluster

Cluster — это набор взаимосвязанных Silos, работающих вместе. Кластер обеспечивает масштабируемость, отказоустойчивость и распределение нагрузки. В кластере Orleans, Silos автоматически координируют свои действия, чтобы обеспечить равномерное распределение Grains и управление их состоянием. Кластеризация позволяет системе оставаться устойчивой и доступной, даже если один или несколько Silos выходят из строя.

✨ Ключевые фичи Orleans

👥 Модель виртуальных акторов (Virtual Actor Model)

1. Модель виртуальных акторов (Virtual Actor Model)

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

💾 State Management

state management

Состояние Grains может сохраняться в оперативной памяти или в хранилище: SQL, NoSQL, и тд. Orleans поддерживает транзакции и обеспечивает консистентность данных.

⚖️ Load Balancing и Fault Tolerance

Load Balancing и Fault Tolerance

Orleans автоматически распределяет Grains по Silos и восстанавливает их работу в случае сбоев.

🛠️ Лёгкость интеграции и развертывания

Load Balancing и Fault Tolerance

Orleans поддерживает развертывание в облаке и легко интегрируется с существующими системами.

🔄 Асинхронное программирование

Асинхронное программирование

Коммуникация происходит через async методы, это влияет на следующее:

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

🌍 Богатая экосистема и поддержка сообщества

Богатая экосистема и поддержка сообщества

У Orleans активное сообщество и он поддерживается Microsoft:

  • Регулярные обновления и улучшения: Microsoft и сообщество разработчиков регулярно обновляют Orleans, добавляя новые фичи и улучшая производительность.
  • Документация и примеры: обширная документация и github (OrleansContrib, orleans) помогают быстро начать работу и разобраться в особенностях Orleans.
  • Интеграция с популярными инструментами: Orleans интегрируется с различными инструментами и библиотеками, что упрощает разработку и развертывание.

🔍 Инструменты мониторинга и диагностики

Инструменты мониторинга и диагностики

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

  • Поддержка телеметрии: Orleans может собирать и отправлять данные о производительности и состоянии системы в реальном времени.
  • Диагностика ошибок: встроенные механизмы помогают быстро обнаруживать и устранять проблемы в работе системы.

К примеру можно подключить Nuget package OrleansDashboard созданный коммюнити и у вас будет веб интерфейс для отслеживания состояния вашего Orleans кластера:

Microsoft Orleans dashboards

💼 Use Cases для Orleans

Use Cases для Orleans

Microsoft Orleans предоставляет гибкие возможности для создания масштабируемых и надежных распределённых систем. Однако, как и любой инструмент, Orleans подходит не для всех сценариев. Ниже представлены примеры подходящих и неподходящих случаев использования Orleans, основанные на лучших практиках и рекомендациях.

✔️Подходящие сценарии использования

Подходящие сценарии использования

Orleans предоставляет возможности для построения масштабируемых и надежных систем, что делает его хорошим решением для следующих сценариев.

1. 🌐 Масштабируемые облачные сервисы (Scalable Cloud Services)

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

Пример: Azure и другие облачные сервисы Microsoft. Orleans активно используется в Microsoft Azure и других облачных платформах, обеспечивая масштабируемость и надежность сервисов, таких как Xbox, Skype и PlayFab.

2. 🎮 Игры

Orleans используется для управления состоянием игроков и объектов в реальном времени 

Пример: Halo и Gears of War. Эти игры используют Orleans для управления состоянием игроков и игровых объектов, обеспечивая плавный игровой процесс и поддержку огромного числа пользователей.

3. 💳 Финансовые и банковские системы

Подходит для управления состоянием и обработкой транзакций (например, системы онлайн-банкинга).

4. 💬 Приложения для обмена сообщениями

Обеспечивает быструю и надежную доставку сообщений

Пример: Skype. Orleans используется в Skype для обработки сообщений в реальном времени и управления состоянием сессий пользователей.

5. 📡 IoT

 Управляет состоянием и взаимодействием множества устройств.

6. 🛒 E-commerce

Подходит для управления состоянием корзин покупок и заказов.

7. 🗳️ Системы голосования

Поддерживает высокую надежность и точность данных.

8. 📉 Торговля акциями

Обрабатывает большой объем данных и транзакций в реальном времени.

🚫 Неподходящие сценарии использования

Неподходящие сценарии использования

1. 📋 Простые CRUD приложения

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

Пример: Простые корпоративные веб-приложения. Если ваше приложение в основном выполняет базовые операции CRUD без сложной бизнес-логики, традиционные инструменты могут быть более подходящими.

2. 🧮 Высоконагруженные вычислительные задачи

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

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

3. 📉 Приложения с низкими требованиями к масштабируемости

Если ваше приложение не требует значительной масштабируемости или роста числа пользователей и объектов, Orleans может быть излишним.

Пример: Локальные бизнес-приложения. Для приложений, работающих в небольшой среде или с ограниченным числом пользователей, использование Orleans может не оправдать себя.

4. 🔗 Тightly Coupled Systems

Orleans лучше всего подходит для систем с изоляцией компонентов и минимальным взаимозависимым состоянием.

Пример: Монолитные приложения с тесной связью между компонентами. Приложения, где компоненты имеют много взаимозависимого состояния, могут не получить всех преимуществ Orleans.

🔍 Как работает Microsoft Orleans

Как работает Microsoft Orleans

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

🏗️ Reference Architecture: Orleans в k8s

Reference Architecture: Orleans в k8s

Для того чтобы упростить понимание, давайте рассмотрим пример архитектуры Orleans развернутого в Azure Kubernetes Service. Давайте разберемся по элементам, которые мы видим.

  • Load balancer нужен, для того, чтобы разбрасывать нагрузку по Orleans API.
  • orleans-api – выполняет роль клиента Ms Orleans
  • Silo-0, silo-1 –экземпляры Silo в k8s.
  • Azure storage tables – юзается как сторедж и membership table, выше, вы видите пример такой таблице, где описаны 2 активных Silo.

По сути самое важное это Silo и persistance storage, куда мы складываем инфо про наши активные Silo и их стейт.

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

🔗 Cluster Membership в Orleans

Cluster Membership в Orleans
Выше пример как новый кластер A джоинится для обработки сообщений к кластеру B.

Orleans управляет членством в кластере, добавляя и удаляя Silos автоматически. Это обеспечивает масштабируемость и устойчивость к сбоям. Каждый Silo отслеживает состояние других Silos и координирует свои действия для поддержания актуальности информации о членстве.

Основные аспекты Cluster Membership:

  • Динамическое добавление и удаление Silos: Orleans поддерживает автоматическое добавление новых Silos в кластер для увеличения мощности или удаления их для уменьшения нагрузки. Это происходит без необходимости остановки или перезагрузки всего кластера.
  • Обнаружение сбоев: Orleans постоянно отслеживает состояние каждого Silo в кластере. Если одина из node выходит из строя, Orleans быстро обнаруживает это и перенаправляет Grains на другие, работающие Silos.
  • Координация и согласование: Каждый Silo в кластере ведёт реестр текущих членов кластера. Orleans использует распределённые алгоритмы для обеспечения согласованности и актуальности информации о членстве. Это включает координацию действий при добавлении новых node или при сбоях существующих.
  • Восстановление после сбоев: Когда Silo выходит из строя, Orleans автоматически перераспределяет Grains, которые работали на этой ноде, на другие ноды в кластере. Это минимизирует влияние сбоев на общее состояние системы.

 Gossip содержит некоторые или все следующие сведения:

  • Текущая конфигурация мультикластера с  timestamp.
  • Словарь, содержащий информацию о gateway в кластернах . Ключом является адрес шлюза, а значение содержит timestamp, идентификатор кластера и статус, который является активным или неактивным.

📊 Мониторинг в Orleans

 Мониторинг в Orleans

Orleans использует механизм "пинг-понг", чтобы проверить доступность Silos. Если Silo не отвечает, Orleans перенаправляет запросы на другие ноды, обеспечивая отказоустойчивость.

Ping-Pong:

Мониторинг
  • Регулярные проверки состояния: Каждый Silo периодически отправляет пинг-сообщения другим нодам в кластере. Этот механизм позволяет нодам проверять наличие друг друга и их доступность.
  • Обнаружение задержек и сбоев: Если Silo не отвечает на пинг-сообщения в течение заданного времени, он считается недоступным или вышедшим из строя. Этот процесс помогает быстро обнаружить и реагировать на сбои ноды.
  • Балансировка нагрузки: Мониторинг состояния ноды также помогает в распределении нагрузки. Если один из Silos перегружен или работает медленнее, Orleans может перенаправить часть нагрузки на другие, менее загруженные ноды.
  • Интеграция с внешними системами мониторинга: Orleans может интегрироваться с внешними системами мониторинга и телеметрии, такими как Azure Monitor, для предоставления более детальной информации о состоянии и производительности кластера.

💻 Имплементация Grains в Orleans

Имплементация Grains в Orleans

Grains реализуют бизнес-логику и управляют состоянием. Orleans автоматически обрабатывает создание и уничтожение Grains, а также сохраняет их состояние в памяти или базе данных.

Вот пример иплементации PlayerGrain с доки майкрософт:

public interface IPlayerGrain : IGrainWithGuidKey
{
    Task<IGameGrain> GetCurrentGame();

    Task JoinGame(IGameGrain game);

    Task LeaveGame(IGameGrain game);
}

public class PlayerGrain : Grain, IPlayerGrain
{
    private IGameGrain _currentGame;

    // Game the player is currently in. May be null.
    public Task<IGameGrain> GetCurrentGame()
    {
       return Task.FromResult(_currentGame);
    }

    // Game grain calls this method to notify that the player has joined the game.
    public Task JoinGame(IGameGrain game)
    {
       _currentGame = game;

       Console.WriteLine(
           $"Player {GetPrimaryKey()} joined game {game.GetPrimaryKey()}");

       return Task.CompletedTask;
    }

   // Game grain calls this method to notify that the player has left the game.
   public Task LeaveGame(IGameGrain game)
   {
       _currentGame = null;

       Console.WriteLine(
           $"Player {GetPrimaryKey()} left game {game.GetPrimaryKey()}");

       return Task.CompletedTask;
   }
}

Доступные типы ключей для Grain

Grain поддерживает несколько разных типов primary ключей. По ним вы будете иметь доступ к конкретному экземпляру Grain'a:

Stateless grain

В Microsoft Orleans существует два типа Grains: stateful (состояние сохраняется) и stateless (без  сохранения состояния). Понимание различий между ними важно для эффективного использования фреймворка. Рассмотрим более подробно, что такое stateless grain и когда его стоит использовать.

Stateless Grain — это тип Grain в Microsoft Orleans, который не сохраняет и не управляет своим состоянием между вызовами. В отличие от stateful grain, который может сохранять данные в памяти или базе данных и восстанавливать их при необходимости, stateless grain считается "бесстатичным". Это означает, что каждый вызов метода stateless grain является независимым и не полагается на данные, сохраненные от предыдущих вызовов.

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

  • Обработка запросов в реальном времени: Stateless grain могут быть использованы для обработки запросов, которые не зависят от предыдущих данных. Например, обработка HTTP-запросов в веб-приложениях или API.
  • Математические или логические вычисления: Если Grain выполняет вычисления, не требующие сохранения промежуточных результатов, stateless grain будет идеальным выбором. Это может включать в себя генерацию отчетов или расчет метрик.
  • Работа с внешними ресурсами: Stateless grain могут быть полезны для взаимодействия с внешними системами, такими как внешние API или базы данных, где состояние управляется этими системами.
  • Разделение нагрузки: В сценариях, где необходимо распределить входящие запросы на несколько нод для обработки параллельно, stateless grain обеспечивают идеальное решение, так как они могут быть активированы на нескольких нодах одновременно.

Так же, стоит упомянуть, что stateful grain всегда имеет только 1 экземпляр в рамках Silo, а для stateless же позволяет иметь более чем 1, это конфигурируется с помощью кода: 

[StatelessWorker(2)] // max 2 activation per silo
public class MyLonelyWorkerGrain : ILonelyWorkerGrain
{
    //...
}

Grains placement strategy

Так как наши Grain'ы могут быть размещены в разных Silo, в тч в даже в разных кластерах/серверах. В Orleans существует способ определения куда поместить ваш активированный Grain.  Это может быть в тч кастомная реализация стратегии. 

Вот список существующих стратегий:

  • Random placement используется по умолчанию
  • Local Placement если локальный сервер совместим, выбирается локальный сервер, в противном случае выбирается случайный сервер.
  • Hash-based placement Хеширует идентификатор grain в целое неотрицательное число и по модулю и определяет количество совместимых серверов. Выбирает соответствующий сервер из списка совместимых серверов, упорядоченного по адресу сервера.
     
Hash-based placement
  • Activation-count-based placement Эта стратегия размещения предназначена для размещения новых активаций grain на наименее busy сервере, исходя из количества недавно занятых grains. Она включает механизм, в котором все серверы периодически публикуют общее количество активаций для всех остальных серверов. Затем 'placement director' выбирает сервер, на котором, по прогнозам, будет меньше всего активаций grain'a, изучая последнее сообщение о количестве активаций, и прогнозирует текущее количество активаций на основе последнего подсчета активаций, сделанного placement director'ом на текущем сервере. 
     
Activation-count-based placement
  • Stateless worker placement Stateless worker placement - это специальная стратегия размещения, используемая stateless grain'ами. Это работает почти так же, как PreferLocalPlacement, за исключением того, что на каждом сервере может быть несколько активаций одного и того же grain'a, и grain не регистрируется в grain каталоге, поскольку в этом нет необходимости.
  • Silo-role based placement Детерминированная стратегия размещения, которая помещает grain в Silo с определенной ролью.Эта стратегия размещения настраивается путем добавления атрибута SiloRoleBasedPlacementAttribute к Grain'y.
  • Resource-optimized placement Стратегия размещения с оптимизацией ресурсов пытается оптимизировать ресурсы кластера, балансируя активации grain между Silo на основе доступной памяти и использования процессора. Она присваивает 'вес' статистике времени выполнения для определения приоритетности различных ресурсов и вычисляет нормализованный балл для каждого Silo. Для размещения предстоящей активации выбирается Silo с наименьшим показателем. Нормализация гарантирует, что каждое свойство вносит пропорциональный вклад в общую оценку. Весовые коэффициенты могут быть скорректированы с помощью ResourceOptimizedPlacementOptions на основе специфических требований пользователя и приоритетов для различных ресурсов. Подробнее алгоритм описан тут.

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

📦 Persistence структура в Orleans

Persistence структура в Orleans

Orleans storage provider

В Orleans с "коробки" доступны следующие storage провайдеры:

Основа persistance это методы интерфейса IPersistentState

public interface IPersistentState<TState> where TState : new()
{
    TState State { get; set; }
    string Etag { get; }
    Task ClearStateAsync();
    Task WriteStateAsync();
    Task ReadStateAsync();
}

Далее в реализации Grain мы используем аттрибут, который указывает. что за стор мы будем юзать, к примеру:

[StorageProvider(ProviderName="store1")]
public class UserGrain : Grain, IUserGrain
    {
    private readonly IPersistentState<ProfileState> _profile;

    public UserGrain(
        [PersistentState("profile", "profileStore")]
        IPersistentState<ProfileState> profile)
    {
        _profile = profile;
    }

    public Task<string> GetNameAsync() => Task.FromResult(_profile.State.Name);

    public async Task SetNameAsync(string name)
    {
        _profile.State.Name = name;
        await _profile.WriteStateAsync();
    }
}

Следовательно, если мы хотим реализовать свой персистанс сторедж, к примеру Redis, или любой другой. Нам нужно переопределить реализацию методов интерфейса IGrainStorage

 /// <summary>
/// Interface to be implemented for a storage able to read and write Orleans grain state data.
/// </summary>
public interface IGrainStorage
{
    /// <summary>Read data function for this storage instance.</summary>
    /// <param name="stateName">Name of the state for this grain</param>
    /// <param name="grainId">Grain ID</param>
    /// <param name="grainState">State data object to be populated for this grain.</param>
    /// <typeparam name="T">The grain state type.</typeparam>
    /// <returns>Completion promise for the Read operation on the specified grain.</returns>
    Task ReadStateAsync<T>(
        string stateName, GrainId grainId, IGrainState<T> grainState);

    /// <summary>Write data function for this storage instance.</summary>
    /// <param name="stateName">Name of the state for this grain</param>
    /// <param name="grainId">Grain ID</param>
    /// <param name="grainState">State data object to be written for this grain.</param>
    /// <typeparam name="T">The grain state type.</typeparam>
    /// <returns>Completion promise for the Write operation on the specified grain.</returns>
    Task WriteStateAsync<T>(
        string stateName, GrainId grainId, IGrainState<T> grainState);

    /// <summary>Delete / Clear data function for this storage instance.</summary>
    /// <param name="stateName">Name of the state for this grain</param>
    /// <param name="grainId">Grain ID</param>
    /// <param name="grainState">Copy of last-known state data object for this grain.</param>
    /// <typeparam name="T">The grain state type.</typeparam>
    /// <returns>Completion promise for the Delete operation on the specified grain.</returns>
    Task ClearStateAsync<T>(
        string stateName, GrainId grainId, IGrainState<T> grainState);
}

Структура таблиц orleans хранилища

Если посмотреть на те таблички, которые юзает Orleans внутри стореджа, мы увидим следующие таблицы:

Persistence структура в Orleans

Все скрипты для создания таблиц можно найти тут.

OrleansQuery – используется для запросов, выполняемых кластером Microsoft Orleans.

OrleansMembershipTable – табличка, где храняться активные и не активные Silo и их ip адреса

OrleansMembershipTable

OrleansMembershipVersionTable – таблица, в которой будет хранится список кластеров Orleans. Важно знать, что эта таблица содержит столбец с именем «Версия», в котором будет указан номер версии развернутого кластера.

OrleansMembershipVersionTable

OrleansStorage – Эта таблица работает следующим образом: для каждого экземпляра grain, который идентифицируется значением столбцов GrainIdHash или GrainIdExtensionString, у нас будет одна строка, которая будет содержать значение состояния grain, сохраненное в определенном формате: двоичный, XML или JSON. Состояние сохраняется как значение столбца PayloadBinary, PayloadXml или PayloadJson.

Вот как это выглядит внутри таблички:

OrleansStorage

Кроме того, стоит отметить, что версия в Grain — это просто счетчик, увеличивающийся каждый раз, когда состояние grain изменяется и сохраняется в таблице.

Еще есть 2 других типа, которые мы не будем затрагивать подробно в статье, но просто упомяну о них:

Reminders  – Orleans предоставляет два механизма, называемые timers и reminders, которые позволяют разработчику задавать периодическое поведение grain'a. 

Вот пример их использования:

using Orleans.Runtime;
using Orleans.Timers;

namespace Timers;

public sealed class PingGrain : IGrainBase, IPingGrain, IDisposable
{
    private const string ReminderName = "ExampleReminder";

    private readonly IReminderRegistry _reminderRegistry;

    private IGrainReminder? _reminder;

    public  IGrainContext GrainContext { get; }

    public PingGrain(
        ITimerRegistry timerRegistry,
        IReminderRegistry reminderRegistry,
        IGrainContext grainContext)
    {
        // Register timer
        timerRegistry.RegisterTimer(
            grainContext,
            asyncCallback: static async state =>
            {
                // Omitted for brevity...
                // Use state

                await Task.CompletedTask;
            },
            state: this,
            dueTime: TimeSpan.FromSeconds(3),
            period: TimeSpan.FromSeconds(10));

        _reminderRegistry = reminderRegistry;

        GrainContext = grainContext;
    }

    public async Task Ping()
    {
        _reminder = await _reminderRegistry.RegisterOrUpdateReminder(
            callingGrainId: GrainContext.GrainId,
            reminderName: ReminderName,
            dueTime: TimeSpan.Zero,
            period: TimeSpan.FromHours(1));
    }

    void IDisposable.Dispose()
    {
        if (_reminder is not null)
        {
            _reminderRegistry.UnregisterReminder(
                GrainContext.GrainId, _reminder);
        }
    }
}

Streams – Orleans streaming предоставляет набор абстракций и API, которые упрощают и повышают надежность работы с Streams. Streams  в Orleans- это логическая сущность, которая всегда существует и никогда не может выйти из строя. Streams идентифицируются по их StreamId. Streams позволяют отделить создание данных от их обработки, как во времени, так и в пространстве. Streams одинаково работают в grains и клиентах Orleans, а также совместимы с широким спектром существующих технологий очередей, таких как Event Hubs, ServiceBus, Azure Queues и Apache Kafka. Подробнее можно почитать в документации.

🚀 Getting Started с Orleans

Getting Started с Orleans

Детальнее про создание проекта можно почитать в документации. Так же тут есть несколько примеров проектов, под разные usecases.

1. Нам понадобится создать 4 .NET проекта:

  1. Silo – Проект для Silo
  2. Client – Реализация клиента
  3. Grain Interfaces – Контракты для Grains
  4. Grains – Реализация Grains

2. Добавляем references

  1. В Grains референс на GrainInterfaces.
  2. В Silo референс на Grains.
  3. В Client референс на GrainInterfaces.

3. Добавляем nuget пакеты к проектам:

Silo

  • Microsoft.Orleans.Server
  • Microsoft.Extensions.Logging.Console
  • Microsoft.Extensions.Hosting

Client

  • Microsoft.Orleans.Client
  • Microsoft.Extensions.Logging.Console
  • Microsoft.Extensions.Hosting

Grain Interfaces

  • Microsoft.Orleans.Sdk

Grains

  • Microsoft.Orleans.Sdk
  • Microsoft.Extensions.Logging.Abstractions

4. Создаем grain interface

namespace GrainInterfaces;

public interface IHello : IGrainWithIntegerKey
{
    ValueTask<string> SayHello(string greeting);
}

5. Теперь пилим реализацию Grain класса

using GrainInterfaces;
using Microsoft.Extensions.Logging;

namespace Grains;

public class HelloGrain : Grain, IHello
{
    private readonly ILogger _logger;

    public HelloGrain(ILogger<HelloGrain> logger) => _logger = logger;

    ValueTask<string> IHello.SayHello(string greeting)
    {
        _logger.LogInformation("""
            SayHello message received: greeting = "{Greeting}"
            """,
            greeting);
        
        return ValueTask.FromResult($"""

            Client said: "{greeting}", so HelloGrain says: Hello!
            """);
    }
}

6. Реализация Silo

Регистрируем в startup.cs наш SQL clustering и SQL как Grain storage

builder.Host.UseOrleans((context, siloBuilder) =>
{
    var connectionString = context.Configuration["DBConnectionString"]!;

    siloBuilder.UseAdoNetClustering(options =>
     {
         options.ConnectionString = connectionString;
         options.Invariant = "Microsoft.Data.SqlClient";
     });

    siloBuilder.AddAdoNetGrainStorage("orleans", options =>
      {
          options.ConnectionString = connectionString;
          options.Invariant = "Microsoft.Data.SqlClient";
      });
});

7. Реализуем клиент

Наконец, вам нужно настроить клиент для связи с grains. Конфигурация кластера должна совпадать с той, которую вы использовали для Silo.

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using GrainInterfaces;


var connectionString = context.Configuration["DBConnectionString"]!;
IHostBuilder builder = Host.CreateDefaultBuilder(args)
    .UseOrleansClient(client =>
    {
        client.UseAdoNetClustering(options =>
     {
         options.ConnectionString = connectionString;
         options.Invariant = "Microsoft.Data.SqlClient";
     });
    })
    .ConfigureLogging(logging => logging.AddConsole())
    .UseConsoleLifetime();

using IHost host = builder.Build();
await host.StartAsync();

IClusterClient client = host.Services.GetRequiredService<IClusterClient>();

IHello friend = client.GetGrain<IHello>(0);
string response = await friend.SayHello("Hi friend!");

Console.WriteLine($"""
    {response}

    Press any key to exit...
    """);

Console.ReadKey();

await host.StopAsync();

🧪 Тестирование

Тестирование

Microsoft уже позаботился об этом предротавил Nuget Microsoft.Orleans.TestingHost.

Вот пример теста unit testа, где тестят функционал Grain'a:

using Orleans.TestingHost;

namespace Tests;

public class HelloGrainTests
{
    [Fact]
    public async Task SaysHelloCorrectly()
    {
        var builder = new TestClusterBuilder();
        var cluster = builder.Build();
        cluster.Deploy();

        var hello = cluster.GrainFactory.GetGrain<IHelloGrain>(Guid.NewGuid());
        var greeting = await hello.SayHello("World");

        cluster.StopAllSilos();

        Assert.Equal("Hello, World!", greeting);
    }
}

📊 Orleans Benchmarks

Benchmarks

Окей, все звучит довольно заманчиво, но вы скажете:

 гуд, а что по бенчмаркам?

Ведь нужно потратить довольно много времени команды на изучение того, как работать с этим зверем, что мы получим взамен?

Первый benchmark от команды Microsoft, где они сравнивают работу Orleans 3.0 с 7 версией:

orleans benchmarks

Как видим, Orleans с hosted клиентом может обработать до 4.5 млн сообщений в секунду.

Второй бенчмарк сравнивает actor model framework'и доступные в .NET такие как:

  1. Dapr 1.7.0
  2. Akka.Net 1.4.36
  3. Proto.Actor 0.32
  4. Orleans 3.6.0

Тест был выполен в 2022, что не совсем релеватно, так как с примера 1 мы видим что Orleans вырос довольно сильно в производительности с версии 7, но все же, можно взять к сведенью:

Детали энва на котором тестили:

The VMs are 4 core / 16GB each, with 10Gbps+ expected network bandwidth (D4s v4 and D4s v5 Azure VMs).

orleans test benchmark

В первом тесте используется простой обмен сообщениями ping - pong между исполнителем теста и актором. Test runner генерирует случайную строку X, упаковывает ее в сообщение Ping и ожидает получить "Hello " + X в ответном сообщении Pong.

req orleans
latency test
latency 95p
latency 99
cpu usage

В этом тесте автор тестирует активацию акторов, отправляя им пустое сообщение Ping.

Ms orleans benchmark
Ms orleans benchmark
Ms orleans benchmark
Ms orleans benchmark
Ms orleans benchmark

 

Ms orleans benchmark

🧽 Заключение

Заключение

Microsoft Orleans — это мощный и гибкий фреймворк, который упрощает разработку масштабируемых и надежных распределённых систем. Благодаря модели виртуальных акторов (Grains), Orleans позволяет автоматизировать управление состоянием и эффективное распределение нагрузки. Это делает его идеальным выбором для создания облачно-нативных приложений, таких как онлайн-игры, системы IoT, финансовые сервисы и платформы обмена сообщениями.

Основные преимущества Orleans включают простоту масштабирования, высокую производительность и встроенную отказоустойчивость. Фреймворк обеспечивает автоматическое управление жизненным циклом компонентов и позволяет сосредоточиться на бизнес-логике, а не на технических деталях инфраструктуры.

Если вы ищете способ улучшить производительность и надежность своих приложений, Orleans может быть хорошим решением.

Следующие шаги:

  • Изучите Orleans глубже: Ознакомьтесь с официальной документацией, где вы найдете подробные руководства и примеры использования.
  • Попробуйте Orleans в действии: Посетите репозиторий Orleans на GitHub и найдите примеры проектов для различных сценариев.
  • Orleans Deployment: в Azure, kubernetes, service fabric и другие
  • Внедрите Orleans в своем проекте: Попробуйте использовать Orleans в своем следующем проекте и оцените его возможности по созданию высокопроизводительных и устойчивых к сбоям систем.

Comments:

Please log in to be able add comments.