Огляд Akka.NET: проектувати IoT-системи з допомогою цієї бібліотеки

Всім привіт! Мене звуть Влад Медведовський, і я вже 10-й рік працюю в сфері IT. Починав як розробник .NET, втім вдалося попрацювати і з З++, і з JVM-мовами — Scala і Java. Зараз я тимлид в команді .NET. За свою кар'єру бачив чимало проектів в різних доменах. Наприклад, будівництво, медицина, SCADA-системи та IoT. Про IoT ми зараз і поговоримо: розглянемо традиційні підходи до проектування IoT-систем і альтернативи, можливості бібліотеки Akka.NET і реальний приклад її застосування.

Взагалі IoT і .NET — це слова, які рідко стояли рядом до недавніх пір. Але з розвитком Online-сервісів серед них з'явилися й такі, як, наприклад, IoT gateway. І тепер є можливість будувати рішення для IoT на платформі .NET. До Azure є ряд питань, а саме:

Якщо ви тільки збираєтеся проектувати IoT-рішення або зіткнулися з якоюсь з проблем, зазначених вище, а може, вас не влаштовують готові Azure-сервіси — ласкаво просимо.

Традиційні підходи для вирішення IoT-завдань

З яких основних частин складається рішення для IoT? Зазвичай, якщо опустити cross-cutting concerns начебто авторизації, моніторингу та логування, то це:

Ніяке IoT-рішення не існує без пристроїв, які взаємодіють із фізичним світом. Зазвичай IoT-пристрої проектують автономними, а це накладає обмеження на потужність передавача. Тому для групи пристроїв часто додають стаціонарний gateway, у якого таких обмежень немає.

Gateway — шлюз, який обробляє запити від кінцевих пристроїв. Він не тільки виконує функцію ретрансляції, але і розбирає протокол, зрозумілий API/бэкенду і з яким спілкується кінцевий пристрій, на команди. Бекенд, у свою чергу, містить код, що зазвичай реалізує всі бізнес-правила системи.

Для найбільш поширених сценаріїв в Azure є набір готових сервісів. З них можна зібрати архітектуру, яка представлена на рисунку 1.

Рис. 1. Еталонна архітектура IoT-рішення, на думку Microsoft Docs

Ряд основних недоліків такої архітектури:

Усунення хоча б деяких з перерахованих вище недоліків вимагає певного рівня навичок і дисципліни в команді. Мій досвід говорить, що це є не завжди і не скрізь. Іноді втручаються дедлайни, іноді потрібно інвестувати час і гроші в навчання розробників. Ці ресурси (час і гроші) теж доступні не завжди.

Актори

Які ж існують альтернативи підходам, побудованим навколо сервісів і традиційного листкового підходу до проектування IoT-систем? Спробуємо уявити кожне пристрій або взагалі кожну сутність системи як окремий об'єкт, який будемо називати актором.

Більше того, так як мова йде про пристрої, ми можемо додати ряд природніх обмежень на взаємодію між акторами.

Актори не можуть безпосередньо викликати методи один одного. В реальності пристрої використовують асинхронну мережу для різного роду комунікацій (P2P, Client-server). Замість цього комунікація здійснюється за допомогою обміну повідомленнями.

IoT-пристрою досить обмежені в обчислювальних потужностях. Тому ми можемо розглядати кожен актор як однопотоковий. Це означає, що він може обробляти тільки одне повідомлення в кожний момент часу.

У кожного актора повинен бути унікальний адреса для комунікації. Без нього неможливо доставити повідомлення адресату, якому воно призначалося.

Актори повинні мати можливість створювати інші актори. Наприклад, для сценарію виявлення нових пристроїв шляхом broadcast-повідомлень всередині мережі. Тоді пристрій, яке здійснює broadcasting, буде отримувати acknowledge-пакети на свою адресу, а значить, володіти інформацією про знайдені пристрої. В цьому випадку виглядає природним, якщо програмна сутність буде створювати дочірні самостійно.

Mailbox. Оскільки актори однопоточні за своєю природою, є сенс ввести для кожного з них якусь чергу, в яку будуть надходити всі вхідні повідомлення. Така чергу в термінології акторів називається mailbox. За замовчуванням повідомлення з mailbox обробляються за принципом FIFO — first in, first out. Однак на практиці необхідно деякі повідомлення обробляти з підвищеним або зниженим пріоритетом. Це повідомлення тривоги, повідомлення про помилки або про статус пристрої, які вимагають негайної реакції. Наприклад, є пристрої, які вміють сигналізувати про те, що їх намагаються зламати (можна пошукати по anti-tampering protection). Очевидно, отримання такого повідомлення вимагає швидкої реакції оператора.

Akka.NET

Все вищезазначене надає бібліотека під назвою Akka.NET. Це, по суті, безальтернативна реалізація моделі акторів в середовищі .NET. Є API як для C#, так і для F#.

Короткий список можливостей бібліотеки:

Відразу перерахую деякі недоліки:

Routers and routing

В Akka.NET є ряд вбудованих типів, які можна використовувати для почергової відправки повідомлень групами акторів. Ці типи називають Routers. В бібліотеці є кілька реалізацій для найчастіших сценаріїв:

Akka.NET use case

Перейдемо тепер до реального приклад використання Akka.NET. Є деяка система, яка обробляє інформацію з датчиків. Як вже було сказано, датчики підключені безпосередньо до сервера системи з досить скромних можливостей для автономної зв'язку на далекі відстані та особливостей топології їх розміщення.

Інформація з кінцевих пристроїв надходить в систему через стаціонарні шлюзи.

Уявімо кожен шлюз у вигляді актора. Тоді актор шлюзу може створювати актори кінцевих пристроїв, які будуть до нього підключені. Таким чином у нас вийшла невелика ієрархія пристроїв.

Додамо в цю ієрархію актор, який буде обробляти помилки кінцевих пристроїв — між шлюзом та датчиком. Він буде періодично опитувати датчик на предмет його стану і відправляти команду перезавантаження, якщо що-то з кінцевим пристроєм не так. Додамо також актор над шлюзом, який візьме на себе управління нестабільним TCP-з'єднанням і буде займатися буферизацією, повторної відправкою повідомлень від шлюзу на сервер при необхідності.

На хвилинку перестанемо додавати актори, зупинимося і розглянемо. Як видно, основний спосіб розширення системи — це додавання до неї нових акторів. Вони можуть брати на себе довільні функції — як розширення поведінки існуючих акторів, так і цілком нові сценарії в системі. Це дозволяє розширювати систему з мінімальними витратами на впровадження нового функціоналу.

Повернемося до нашого прикладу зі шлюзом і кінцевими пристроями, які до нього підключені. Ускладнюємо завдання: у нас є 6 пристроїв, які вимірюють наявність або відсутність групи хімічних сполук в рідини у двох паралельних трубопроводах.

Пристрої виробляють вимірювання по команді — занурюють вимірювальний стрижень, а потім якимось чином читають свідчення. Самі пристрої дублюють один одного (групами по 3) і розділені на дві групи. Немає сенсу постійно запитувати вимірювання з усіх шести датчиків — це недоцільно, тому що реактиви не дешеві. Замість цього ми можемо по черзі опитувати кожну групу і відправляти команду на вимір з допомогою Round-robin router.

Рис. 2. Отримана ієрархія акторів

Це був приклад базового сценарію використання Akka.NET. Перейдемо тепер до більш складного. Природне запитання, яке хочеться задати наступним — як мені масштабувати систему? Як зробити її більш відмовостійкої?

Akka Remoting

Перш ніж торкатися питання про масштабованість, потрібно зрозуміти, як актори на різних фізичних машинах обмінюються повідомленнями. За рахунок посилальної прозорості та асинхронної природи обміну повідомленнями в моделі акторів легко зробити перехід від in-process до мережевої комунікації. Це принципово відрізняє модель акторів від, наприклад, DCOM або RPC (у вигляді WCF наприклад), де за синхронними викликами методів стояли асинхронні виклики по мережі.

В Akka.NET вже є розширення для обміну повідомленнями — Akka.Remoting. Що вміє Akka.Remoting:

Важливі зауваження при використанні Akka.Remoting

При використанні Akka для мережевої комунікації важливо ДО того, як проект починає розростатися, продумати, як будуть сериализоваться повідомлення.

Сериализатор практично неможливо поміняти після того, як ваш проект досягне певних розмірів. Вам потрібно буде підтримувати дві паралельні копії системи з різними сериализаторами для апгрейда — Akka.NET (втім, як і будь-яка інша розподілена система) не зможе десериализовать повідомлення, якщо вони були упаковані іншим способом. Ніякої метаінформації про тип транспортного протоколу з повідомленнями не передається. Втім, завжди можна написати свій сериализатор, який буде абстракцією над механізмом серіалізації.

Akka Cluster

Якщо ми хочемо об'єднати безліч акторів, розміщених на різних машинах, в один логічний кластер, то для цього існує спеціальне розширення — Akka.Cluster. Воно реалізує логіку роботи кластера акторів поверх Akka.Remoting. Поговоримо про те, як працює кластер в Akka.NET. Для роботи кластера акторів необхідно, щоб виконувалися наступні обмеження.

На кожній машині система акторів (ActorSystem) повинна мати однакову назву. Не обов'язково, щоб у кожній системі був один і той же набір акторів, важливо саме назву. Назва системи бере участь у побудові адреси до актору всередині кластера.

У кластері обязательныо повинні бути присутніми seed nodes. Akka.Cluster реалізований поверх протоколу Gossip (рис. 3). Призначення протоколу — per-to-peer обмін інформацією між вузлами мережі або, в нашому випадку — інформацією про машини в кластері. Сам по собі протокол ніяк не регламентує додавання нових учасників мережі у кластер. Щоб обійти це, Akka.Cluster є seed nodes — це точки входу в кластер. Вони вказуються як доменні імена/адреси вузлів у відповідній секції конфігураційного файлу програми. Якщо в кластері немає seed node або вона вийшла з ладу, то нові машини не зможуть приєднатися до кластера. Тому має сенс тримати 2 і більше seed nodes для забезпечення відмовостійкості кластера.

Рис. 3. Схема роботи Gossip протоколу (малюнок документації )

Akka.NET use case — cluster

Повернемося до нашого прикладу. В реальності датчики досить старі. Кожна група датчиків була підключена до х86-пристрою з Windows для зчитування показань через COM-порт. До впровадження рішення поверх акторів співробітниками підприємства вручну відбувався обхід і збиралися файли з інформацією від датчиків. Трохи розширюємо схему з малюнка 2 і отримуємо:

Рис. 4. Кластер з вимірювальних пристроїв

Єдина відмінність від попереднього прикладу — в схемі з'явилися at least-once delivery актори, які відповідають за гарантовану доставку повідомлень на центральний сервер. Для їх реалізації були взяті персистентные актори з Akka.NET трохи модифіковані для використання оперативної пам'яті в якості сховища відправлених/доставлених повідомлень.

У підсумку завдяки Akka.NET вийшло побудувати просте і зрозуміле IoT-рішення для Legacy-пристроїв з підтримкою масштабування, якщо раптом це знадобиться в майбутньому. Від базового варіанту, який працював на одному сервері, без особливих зусиль вийшло перейти до распределенному рішенням, яке вже можна розгортати в кластері. При цьому актори, які відповідають за бізнес-логіку, залишилися незмінні — додавання обміну повідомленнями між ними по мережі ніяк не вплинуло на початковий приклад.

Висновки

Akka.NET — це непогана альтернатива конвенціональним підходів для побудови розподілених систем. З коробки ви отримуєте велику кількість розширюваної і модифікується функціональності. Модулі в бібліотеці досить незалежні один від одного. Використовуючи надану функціональність як фундамент, можна прискорити розробку IoT-рішення і спростити його підтримку. До того ж базові концепції бібліотеки досить прості, і при правильному підході до розподілу відповідальності між акторами можна побудувати легко розширювану систему.

Корисні ресурси:

Опубліковано: 24/04/20 @ 10:00
Розділ Різне

Рекомендуємо:

Введення в GraphQL: що це за мова і як використовувати його під Android
Варіанти кроссплатформної розробки мобільних додатків
По той бік огорожі: бізнес-аналітик про роботу в ролі продакт-оунера
«Потрібно давати людям грати. Ставити складні завдання. Платити за їх помилки». Олександр Конотопський — про завдання Ajax Systems, найм інженерів і українському продукті
Набір на 6 потік мого курсу SEO Шаолінь