Azure Service Fabric: перші кроки


Чарлі Чаплін у фільмі «Нові часи»

Про Azure Service Fabric вже написано чимало статей і навіть книг, благо близько року продукт знаходився спроможні preview. Однак 1 квітня 2016 року без всяких жартів Azure Service Fabric нарешті досяг стану General availability, і є підстави вважати, що він затримається тут всерйоз і надовго. А раз так — чому б не пройтися по ньому якщо не з прикладного, то хоча б з академічного інтересу? Тим більше що інформації по Azure Service Fabric російською мовою явно замало.

Навіщо ж взагалі знадобився Azure Service Fabric? В світі існує досить багато серверів, прив'язаних до екосистемам певної мови або платформи. Історично так склалося, що в екосистемі Java таких серверів чи не найбільше — Tomcat, JBoss, WebSphere та ін. на Жаль, платформа .NET таким багатством вибору похвалитися поки що не може. На розум приходять хіба що IIS, «хмарні» сервіси Azure і їх «локальний» близнюк Windows Azure Pack (не рахуючи хелперів-обгорток типу Topshelf). Online Service Fabric покликаний розширити цей недовгий список в бік популярної нині концепції SOA і гостромодному подконцепции микросервисов, спрощуючи розгортання сервісів і забезпечуючи їх масштабованість та відмовостійкість. І після цього ліричного відступу перейдемо, нарешті, в наступ.

Галопом по Європах

Основне джерело знань про Azure Service Fabric (далі ASF) — документація. Настійно рекомендую і заодно зазначу, що російський переклад відстає від вихідного тексту як мінімум на скріншотах, так краще орієнтуватися на оригінал. Отже, поїхали — фізично ASF являє собою кластер з декількох серверів, на яких виконуються примірники сервісів. Щось на зразок цього:


Намальовано за допомогою creately.com

Що є сервіс? Згідно Вікіпедії сервіс визначається як «A service is a self-contained unit of functionality», інакше кажучи, це "є бойова одиниця сама в собі", окремо і незалежно развертываемая і використовується. В ASF кожна така «бойова одиниця» має свій маніфест, в якому визначені ім'я сервісу, конфігурація, кінцеві точки і пр. Незважаючи на атомарність одиницею розгортання є все ж не сервіс, а сукупність сервісів, звана додатком — в маніфесті програми можна, крім іншого, визначити партиционирование сервісу для підтримки масштабованості, а для відмовостійкості у разі виходу з ладу серверів ASF кожна партіцій може мати кілька примірників сервісів. І сервіси і додатки версионируются незалежно, що дозволяє оновлювати програму, не оновлюючи в ньому усі сервіси разом. Для розгорнутого додатки ASF створює потрібну кількість примірників сервісів, розміщує на ноди кластера, запускає, синхронізує стан між екземплярами, пересотворює примірники для балансування навантаження — для прикладу на картинці вище Service 1 має кінцеву точку для спілкування по TCP у двох примірниках розміщений на двох вузлах, Service 2 має виходи на HTTP і TCP і по одному екземпляру розміщений на всіх трьох ноди, а Service 3 зовнішніх комунікацій не надає і в одному примірнику крутиться на одній ноде. Буде одна з нод передчасно помре, ASF перебалансирует навантаження на останніх. В цілому ж забезпечення безпроблемного функціонування сервісів і є робота ASF.

Сервіси в ASF діляться на два типи — stateful і stateless, інакше кажучи, зберігають свій стан і не мають такого. Можна використовувати ці типи, просто породжуючи від них свої сервіси, а можна на їх основі будувати більш складні конструкції — скажімо, технологія ASF Actors сама побудовані на stateful сервісі. Для першого знайомства з ASF я використовую stateless сервіс як більш простий.

Тут буде місто-сад

… але далеко не відразу. Для початку облаштуємо середовище розробки. Отсюда викачуємо і встановлюємо бажаний варіант SDK можна автономний, а можна з елементами інтеграції в Visual Studio. Для інтегрованого варіанту необхідні Visual Studio 2015 або «15» Preview, ймовірно, підійде і варіант Community, а при написанні статті використовувалася Visual Studio Enterprise 2015 Update 2, і в ній усе гарантовано працює. Після установки SDK в списку шаблонів проектів (а саме в розділі Visual C#/Cloud) з'являється Service Fabric Application і можна, нарешті, перейти до справи — створюємо нове ASF-додаток, називаємо його MyFabric, натискаємо OK, вибираємо тип сервісу Stateless, називаємо MyStateless "і тупо дивимося, що до чого" в новоствореному рішенні. Зауважимо, що рішення безальтернативно створюється для x64, і в його складі:

  • проект MyStateless
    • Program.cs — вхідна точка сервісу. Все, що вона робить — реєструє сервіс в ASF, записує це знаменна подія в лог, після чого назавжди засинає.

    • MyStateless.cs — власне код сервісу. Містить простий нескінченний цикл, щомиті пише в лог і переривчастий зовнішнім CancellationToken'ом.
    • ServiceEventSource.cs — ASF використовує ETW через EventSource. Цей клас описує деякі загальновживані події, надаючи зручні методи для їх логування.
    • ServiceManifest.xml — маніфест сервісу MyStateless для ASF.
    • Settings.xml — конфігурація сервісу, згадана в маніфесті як ConfigPackage.
  • проект MyFabric
    • ApplicationManifest.xml — маніфест всього додатки MyFabric для ASF.

    • ApplicationParameters/(Local|Cloud).xml — перевизначення параметрів маніфесту програми.
    • PublishProfiles/(Local|Cloud).xml — профілі розгортання програми в різних середовищах. Фактично являють собою вказівку адреси ASF-кластера та переопределений з ApplicationParameters.
Запустимо рішення на виконання і через складання і розгортання бачимо ось таку симпатичну картинку SF Explorer (до речі, в налаштуваннях можна темну схему поміняти на світлу):



Що таке перед нами? Перед нами керування локальним 5-нодовым ASF кластером (як би безглуздо це не звучало) з розгорнутою на ньому додатком fabric:/MyFabric/MyStateless. Не варто бентежитися написи «2 applications» — на кластері за замовчуванням стоїть додаток System з 4 stateful сервісів, що використовується самим кластером. Згідно з маніфестом MyFabric сервіс MyStateless потрібно тільки в одному примірнику, і в нашому випадку цей примірник потрапив на 3-ю ноду кластера. Роботу програми можна бачити у вікні Diagnostic Events:



Можна побавитися, ганяючи на єдиний екземпляр нодам — для цього з випадаючого меню праворуч Actions у потрібній ноди виберіть будь-який варіант Deactivate. При цьому у вікні VS Diagnostic Events відображаються всі деталі того, що відбувається всередині ASF, зокрема те, що «старий» примірник MyStateless відмінився і зупинився, а «новий» почав відлік заново.



Що й не дивно — стану у сервісу немає.

Зазначу тут, що і сам SF Explorer та інше управління кластером доступно з «ромашки» в треї, а вікно Diagnostic Events — з меню View/Other Windows. Якщо ж це вікно раптом нічого не відображає треба додати рядок «MyCompany-MyFabric-MyStateless» в список ETW провайдерів під шестірнею.

Цикл життєдіяльності

Поразвлекавшись ламанням кластера, перейдемо до розбору деталей функціонування нашого сервісу. В цілому життєвий цикл сервісу визначається набором віртуальних методів класу StatelssService, від якого породжений наш MyStateless, а саме:

protected virtual IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
protected virtual Task RunAsync(CancellationToken cancellationToken)
protected virtual Task OnOpenAsync(CancellationToken cancellationToken)
protected virtual Task OnCloseAsync(CancellationToken cancellationToken)
protected virtual void OnAbort()

Цікавих нам методів всього п'ять, а для розуміння послідовності їх викликів модифікуємо код сервісу, якщо перевизначити методи і додавши логування (так-так, стара добра налагоджувальна друк):

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
ServiceEventSource.Current.ServiceMessage(this, "CreateServiceInstanceListeners");
return new ServiceInstanceListener[0];
}

protected override Task OnOpenAsync(CancellationToken cancellationToken)
{
ServiceEventSource.Current.ServiceMessage(this, "OnOpenAsync");
return Task.FromResult(0);
}

protected override Task OnCloseAsync(CancellationToken cancellationToken)
{
ServiceEventSource.Current.ServiceMessage(this, "OnCloseAsync");
return Task.FromResult(0);
}

protected override void OnAbort()
{
ServiceEventSource.Current.ServiceMessage(this, "OnAbort");
}

protected override async Task RunAsync(CancellationToken cancellationToken)
{
long iterations = 0;
while (!cancellationToken.IsCancellationRequested)
{
ServiceEventSource.Current.ServiceMessage(this, "Working-{0}", ++iterations);
await Task
.Delay(TimeSpan.FromSeconds(1), cancellationToken)
.ContinueWith(_ => { }, TaskContinuationOptions.NotOnFaulted);
}
}

RunAsync додатково перероблений для коректного завершення замість викидання винятку. Знову дивимося Diagnostic Events:



Першим очікувано викликається CreateServiceInstanceListeners — в ньому сервіс створює всі необхідні кінцеві точки для комунікацій із зовнішнім світом і всередині кластера. За ним очікується OnOpenAsync — щоб так? Так ось ні — далі йде RunAsync. Логіка такої поведінки неясна, але згідно документации OnOpenAsync є в деякому роді додатковим подією, необхідним (і цілком достатнім) служить виклик RunAsync. Після нього дійсно викликається OnOpenAsync і сервіс працює вже до зупинки OnCloseAsync/OnAbort (викликаної, наприклад, переразвертыванием сервісу):



Налаштування

У шаблонному коді конфігурувати особливо нічого, але дещо все ж знайдеться — щосекундне логування добре, але можна б і рідше. Для цього в конфігурацію Settings.xml внесемо параметр:

<Section Name="MyConfigSection">
<Parameter Name="MyFrequency" Value="10" />
</Section>

Код додамо отримання цього значення:

var configurationPackage = Context.CodePackageActivationContext.GetConfigurationPackageObject("Config");
var frequencyInSeconds = int.Parse(configurationPackage.Settings.Sections["MyConfigSection"].Parameters["MyFrequency"].Value);

І використовуємо отримане значення:

await Task
.Delay(TimeSpan.FromSeconds(frequencyInSeconds), cancellationToken)
.ContinueWith(_ => { }, TaskContinuationOptions.NotOnFaulted);

Тепер сервіс пише в лог раз в 10 секунд. Нагадаю, що параметри сервісу можна змінити як у маніфесті програми, так і в профілях розгортання для різних середовищ — як правило, вони різні для середовищ розробки і BETA/PROD.

Просто додай коди

Сервіс сервіс не, коли без комунікацій — нехай MyStateless буде сервісом часу a-la NTP, а для спрощення взаємодій з ним використовуємо ASF Remoting. Ця технологія дозволяє спілкуватися з сервісом, як із звичайним .NET об'єктом через інтерфейс сервісу. Створимо інтерфейс IMyStateless c єдиним методом Now():

public interface IMyStateless : IService
{
Task<DateTimeOffset> Now();
}

Для створення слухача модифікуємо CreateServiceInstanceListeners:

return new[] { new ServiceInstanceListener(this.CreateServiceRemotingListener) };

А в якості клієнта виступить звичайне консольний додаток:

public static void Main()
{
while (!Console.KeyAvailable)
{
var myStateless = ServiceProxy.Create<IMyStateless>(new Uri("fabric:/MyFabric/MyStateless"));
Console.WriteLine(myStateless.Now().Result);
Thread.Sleep(TimeSpan.FromSeconds(10));
}
}

Додаємо тільки код, конфігурацію правити не треба. Для того, щоб використовувати клієнта, ASF-додаток треба спочатку опублікувати — з контекстного меню сайту MyFabric вибираємо Publish, як Target Profile вибираємо Local.xml натискаємо Publish і чекаємо повідомлення про успішне завершення публікації. Тепер можна запустити клієнта і переконатися, що час локального кластера в точності збігається з системним (яка несподіванка).

Ростемо над собою

Строго кажучи, під stateless сервісами ASF розуміє сервіси, які не зберігають стан з допомогою самої ASF. Як цілком справедливо сказано в документации, партиционировать stateless сервіси має сенс тоді, коли вони все ж зберігають свій стан будь-яким іншим зручним для них способом (у БД, кешах, текстових файлах тощо). Втім, фактично партиционируются всі сервіси — просто stateless використовують окремий випадок SingletonPartition (і це відображено в маніфесті додатки). Ось і почнемо з масштабування цією єдиною партіціі — збільшимо кількість примірників MyStateless, поправивши ApplicationParameters/Local.xml:

<Parameter Name="MyStateless_InstanceCount" Value="5" />

Дерево вузлів в SF Explorer вже не вміщається в скріншот, так що повірте і перевірте самостійно, що п'ять примірників сервісу рівномірно (тобто по одному) розподілено по всіх п'яти нодам кластера. При такому масштабуванні можуть виникати проблеми, пов'язані з неразделяемыми ресурсами (наприклад, портами), однак для Remoting в нашому випадку ASF самостійно балансує виклики між запущеними екземплярами. Для простоти в метод Now() додамо логування InstanceId і у вікні Diagnostic Events побачимо, що виклики потрапляють в різні екземпляри MyStateless.

Гіпотетично припустимо, що MyStateless все-таки зберігає якийсь стан поза ASF і партиционируем його, використовуючи схему партиционирования по іменах:

<StatelessService ServiceTypeName="MyStatelessType" InstanceCount="[MyStateless_InstanceCount]">
<NamedPartition>
<Partition Name="even" />
<Partition Name="odd" />
</NamedPartition>
</StatelessService>

Переразвертываем і в SF Explorer бачимо, що дві партіціі по п'ять примірників кожна знову рівномірно (тобто по одному примірнику від партіціі) розподілено по всіх п'яти нодам кластера.

Зовсім була наша перемога, так тепер клієнт не працює, падаючи з виключенням FabricException і змістовним сполученням «Invalid partition key/ID '{0}' for selector {1}» — поправимо клієнта:

var isEven = true;
while (!Console.KeyAvailable)
{
var myStateless = ServiceProxy.Create<IMyStateless>(
new Uri("fabric:/MyFabric/MyStateless"),
new ServicePartitionKey(isEven ? "even" : "odd"));
Console.WriteLine(myStateless.Now().Result);
isEven = !isEven;
Thread.Sleep(TimeSpan.FromSeconds(10));
}

І заодно логування сервісу:

ServiceEventSource.Current.ServiceMessage(this, "Now - " + Context.PartitionId + " - " + Context.InstanceId);

Ось тепер все добре — клієнт направляє виклик в потрібну партицию, а ASF балансує виклики між рівноправними примірниками сервісу всередині партіціі. MyStateless отмасшабирован і став резервний.

Web API

І щоб два рази не вставати, заодно коротко торкнемося тему ASF Web API сервісів, оскільки з шаблонів вони створюються так же без стану. З контекстного меню сайту MyFabric/Services вибираємо Add, тип сервісу Stateless Web API, називаємо MyWebApi і отримуємо новий проект з файлами:

  • Program.cs — вже пізнавано містить реєстрацію сервісу в ASF.
  • MyWebApi.cs — код сервісу, який замість реалізації RunAsync реєструє слухача OwinCommunicationListener.
  • ServiceEventSource.cs/ServiceManifest.xml/Settings.xml — грають ті ж ролі з поправкою на утримання Web API.
  • OwinCommunicationListener.cs — шаблони Web API в ASF засновані на Owin, так що цей слухач реалізує методи ICommunicationListener, запускаючи і зупиняючи Owin Web-сервер.
  • Startup.cs — конфігурує роутинги в Owin
  • ValuesController.cs — простий тестовий контролер, шлях до якого налаштований в Startup.cs
Після публікації в SF Explorer бачимо другий сервіс fabric:/MyFabric/MyWebApi. Для перевірки його працездатності можна відкрити сторінку у браузері за адресою
http://localhost:8201/api/values
— в ній очікувано з'явиться XML-відповідь контролера від Get(), а за адресою
http://localhost:8201/api/values/123
повернеться відповідь від Get(int id).

«Як висловився Джордж, тут було що пожувати!»

І на цьому наші перші кроки закінчуються. Очевидно, що ця коротка стаття не вичерпала і десятої частки того, що можна розповісти про ASF. За бортом окрім тим дрібніші залишилася така масштабна тема, як stateful сервіси — для неї потрібна окрема стаття, сили і час, але якщо вийде, то там вже й до моделі Actor'ів в ASF буде недалеко. Можливо, що дійде і до цього — як кажуть, пишіть коментарі. Код статті доступний GitHub, а от тут можна подивитися «рідні» приклади від Microsoft.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.