Огляд Akka.NET: проектувати IoT-системи з допомогою цієї бібліотеки
Всім привіт! Мене звуть Влад Медведовський, і я вже 10-й рік працюю в сфері IT. Починав як розробник .NET, втім вдалося попрацювати і з З++, і з JVM-мовами — Scala і Java. Зараз я тимлид в команді .NET. За свою кар'єру бачив чимало проектів в різних доменах. Наприклад, будівництво, медицина, SCADA-системи та IoT. Про IoT ми зараз і поговоримо: розглянемо традиційні підходи до проектування IoT-систем і альтернативи, можливості бібліотеки Akka.NET і реальний приклад її застосування.
Взагалі IoT і .NET — це слова, які рідко стояли рядом до недавніх пір. Але з розвитком Online-сервісів серед них з'явилися й такі, як, наприклад, IoT gateway. І тепер є можливість будувати рішення для IoT на платформі .NET. До Azure є ряд питань, а саме:
- Підсумкова вартість обслуговування вирішення. На початкових етапах вона не висока, але з ростом навантаження і кількості пристроїв вартість буде зростати. Не завжди у замовника є на це бюджету.
- На підприємствах вже є мережева інфраструктура, і не завжди доцільно (і можливо) переносити її в хмару.
- Іноді дані з пристроїв не можна зберігати в публічному хмарі, якщо вони являють собою конфіденційну інформацію — привіт, GDPR :)
- Деякі пристрої не підтримують популярні протоколи типу MQTT, зате надають свій унікальний протокол. Ще краще, якщо пристрій не підтримує TCP-транспорт і push-модель, а, наприклад, пише якісь файли на FTP-сервер. У такому випадку доводиться періодично опитувати FTP на предмет наявності нових даних.
Якщо ви тільки збираєтеся проектувати IoT-рішення або зіткнулися з якоюсь з проблем, зазначених вище, а може, вас не влаштовують готові Azure-сервіси — ласкаво просимо.
Традиційні підходи для вирішення IoT-завдань
З яких основних частин складається рішення для IoT? Зазвичай, якщо опустити cross-cutting concerns начебто авторизації, моніторингу та логування, то це:
- IoT-пристрій;
- gateway;
- business logic API.
Ніяке IoT-рішення не існує без пристроїв, які взаємодіють із фізичним світом. Зазвичай IoT-пристрої проектують автономними, а це накладає обмеження на потужність передавача. Тому для групи пристроїв часто додають стаціонарний gateway, у якого таких обмежень немає.
Gateway — шлюз, який обробляє запити від кінцевих пристроїв. Він не тільки виконує функцію ретрансляції, але і розбирає протокол, зрозумілий API/бэкенду і з яким спілкується кінцевий пристрій, на команди. Бекенд, у свою чергу, містить код, що зазвичай реалізує всі бізнес-правила системи.
Для найбільш поширених сценаріїв в Azure є набір готових сервісів. З них можна зібрати архітектуру, яка представлена на рисунку 1.
Рис. 1. Еталонна архітектура IoT-рішення, на думку Microsoft Docs
Ряд основних недоліків такої архітектури:
- складнощі, пов'язані з інтеграцією та розгортанням кожної підсистеми;
- необхідність контролю версій різних підсистем та їх сумісності між собою;
- необхідність мати raytracing для налагодження;
- необхідність мати розвинену систему моніторингу для спостереження за окремими компонентами системи і системою цілком;
- «зоопарк» використовуваних сервісів і API в проекті:
- підвищує поріг входу для новачків;
- вимагає ведення документації, щоб знання про архітектуру не загубилися.
Усунення хоча б деяких з перерахованих вище недоліків вимагає певного рівня навичок і дисципліни в команді. Мій досвід говорить, що це є не завжди і не скрізь. Іноді втручаються дедлайни, іноді потрібно інвестувати час і гроші в навчання розробників. Ці ресурси (час і гроші) теж доступні не завжди.
Актори
Які ж існують альтернативи підходам, побудованим навколо сервісів і традиційного листкового підходу до проектування 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#.
Короткий список можливостей бібліотеки:
- базові типи для створення акторів;
- велика кількість готових типів для звичайних завдань на зразок маршрутизації повідомлень, балансування навантаження між акторами і так далі;
- управління mailbox і різні варіанти реалізації для нього;
- «середовище виконання» для акторів — Actor System;
- управління іменами акторів (втім, завжди можна написати щось своє);
- віддалений обмін повідомленнями з іншими акторами по мережі;
- кластеризація системи акторів — можливість запустити додаток з коробки на декількох машинах;
- вбудована чергу для dead letter;
- підтримка DI;
- підтримка персистентних акторів.
Відразу перерахую деякі недоліки:
- Складна і неочевидна конфігурація бібліотеки:
- не всі задокументовані налаштування працюють;
- деякі не працюють так, як задумано;
- деякі взагалі не працюють;
- Незвичні підходи до імен (dead letters, наприклад) і концепції деяких сутностей в бібліотеці. Не все відразу зрозуміло, документація теж не завжди допомагає. Благо, Akka.NET — це бібліотека з відкритим вихідним кодом і завжди можна взяти исходники, зібрати з них бібліотеку і подивитися, що там і як всередині відбувається.
- Крута крива навчання. Гайд по бібліотеці рівно один, плюс є кілька прикладів (які не завжди робітники). Документація не завжди вичерпна.
Routers and routing
В Akka.NET є ряд вбудованих типів, які можна використовувати для почергової відправки повідомлень групами акторів. Ці типи називають Routers. В бібліотеці є кілька реалізацій для найчастіших сценаріїв:
- Round-robin — послідовна відправлення повідомлень акторам.
- Hash key — адресація по ключу в повідомленні.
- Random — адресат вибирається випадковим чином.
- Weighted round robin — те ж, що і Round-robin, але з допомогою вагових коефіцієнтів можна налаштовувати частоту відправлення повідомлень конкретним адресатам.
- Broadcast — повідомлення надсилається всім акторам з групи.
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:
- відправляти повідомлення на віддалені актори;
- розгортати актори віддалено (на приймаючій стороні);
- є можливість розширення для підтримки інших мережних протоколів, не тільки TCP і UDP.
Важливі зауваження при використанні 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-рішення і спростити його підтримку. До того ж базові концепції бібліотеки досить прості, і при правильному підході до розподілу відповідальності між акторами можна побудувати легко розширювану систему.
Корисні ресурси:
- документація Akka.NET з прикладами використання ;
- простий Service Discovery для Akka.Cluster ;
- сериализатор повідомлень за замовчуванням Аkka.NET ;
- варто звернути увагу на набір підтримуваних адаптерів для actor persistence (Redis, Cassandra etc.) .
Опубліковано: 24/04/20 @ 10:00
Розділ Різне
Рекомендуємо:
Введення в GraphQL: що це за мова і як використовувати його під Android
Варіанти кроссплатформної розробки мобільних додатків
По той бік огорожі: бізнес-аналітик про роботу в ролі продакт-оунера
«Потрібно давати людям грати. Ставити складні завдання. Платити за їх помилки». Олександр Конотопський — про завдання Ajax Systems, найм інженерів і українському продукті
Набір на 6 потік мого курсу SEO Шаолінь