Реактивні програми на Java з Akka

Я працюю програмістом вже більше 13 років: займаюся високонавантаженими і розподіленими системами, розглядаю і оцінюю різні підходи і рішення.

В даний момент я виділяю два типи систем, які визначають підсумковий стек технологій, з яким варто мати справу:
— системи, які постачають дані кінцевим користувачам;
— системи, призначені для обчислень.

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

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

Багатопоточність і масштабування

З розвитком технологій розвивається і модель програмування. Її еволюція почалася з великих монолітних завдань, які насправді були набором інструкцій для послідовного виконання. Еволюціонуючи, ці завдання ставали все більше і більше. Модульний підхід дозволив поділити великі завдання на дрібні і виконувати їх послідовно, перемикаючи контекст з однієї задачі на іншу. За подальшої еволюції стало ясно, що деякі завдання можуть виконуватися незалежно від інших. Це стало основою для багатопоточного програмування:

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

Можливість виконувати завдання паралельно кинула виклик, пов'язаний з масштабуванням додатків. Масштабування буває вертикальне або горизонтальне.

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

При горизонтальному масштабуванніви додаєте ще один сервер замість оновлення вже існуючого. Це дозволяє купувати недороге обладнання в більшій кількості — в сумі виходить дешевше, ніж покупка і оновлення великих серверів. До того ж ви отримуєте відмовостійкий кластер. Навіть якщо один сервер з десяти відмовить, буде доступно достатньо ресурсів для повноцінної роботи системи. Оновлення такого кластера означає додавання ще одного недорогого сервера (вузла).

Резюмувати обидві моделі масштабування можна так:

Вертикальне масштабування Горизонтальне масштабування
Опції дорожче з кожним оновленням У порівнянні з вертикальним масштабом, опції обходяться дешевше
Великий ризик відмови системи при відмові обладнання Відмова декількох вузлів не призводить до відмови всієї системи (якщо система спроектована правильно)
Обмежені опції оновлень в майбутньому Оновлювати систему легко
Один вузол для всього Паралельні вузли дозволяють паралельні обчислення

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

Реактивні додатки

Якщо чесно, почувши вперше термін «реактивні програми», я був здивований. Я міг уявити реактивні двигуни, літаки і навіть машини, а не програми. Почитавши трохи, я зрозумів, що мається на увазі: мова йде про розподілених системах, які є великими за своєю природою. Великі системи потрібно підтримувати деякими домовленостями і правилами, щоб вони залишалися актуальними і расширяемыми через роки.

Реактивні програми підтримуються Реактивним маніфестом. Реактивний маніфест — це звід правил і домовленостей, які можуть утримати систему на плаву, якщо імплементовані правильно. Насправді в ньому немає нічого нового, багато хто з нас вже знайомі з цими підходами. На жаль, іноді порушуються правила бажанням швидко імплементувати якусь «фічу» або якимись іншими вимогами. Я вірю в те, що Реактивний маніфест був зібраний не для того, щоб нас навчити, а для того, щоб нагадати «чому?», «як?» і «що?» ми робимо. Прочитати повний текст Реактивного маніфесту можна на офіційному сайті . В момент написання статті актуальною була версія 2.0. Також доступний переклад на російську (правда, старої версії).

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

Щоб називатися реактивними, додатки повинні бути чуйними, пружними, еластичними і спілкуватися асинхронним обміном повідомлень:

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

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

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

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

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

Модель акторів

Якщо придивитися, всі перераховані вище властивості вже дотримуються в Java EE. Хоча, іноді ми витрачаємо дуже багато часу для досягнення цих властивостей і ще більше для підтримки підсумкового програми. Звичайно, якщо додаток рівня Enterprise буде розгорнуто на парі Enterprise серверів додатків (EAS) в кластері за лоад-балансером, я виберу Java EE і скористаюся поставляється в коробці функціоналом. Якщо ж у мене буде десяток серверів, я швидше подумаю, скільки ресурсів звільниться, якщо позбудеться Enterprise серверів додатків. Тільки подумайте, скільки пам'яті, процесорного часу і грошей можна заощадити.

Є моделі програмування, які отримують користь із кластера серверів, перетворюючи їх в один суперкомп'ютер. Одна з таких моделей програмування — модель акторів, про яку ми і будемо говорити.

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

Немає певної послідовності перерахованих вище дій, вони можуть бути виконані паралельно. Філософія моделі акторів свідчить, що «все навколо актори». Це схоже на філософію «все навколо об'єкти». При цьому об'єктно-орієнтовані програми зазвичай виконуються секвентально, коли актори працюють паралельно. Звичайно, ви можете використовувати потоки, щоб додати конкурентність в об'єктно-орієнтовані програми. Зверніть увагу, що в 64-бітній системі потік Java займає 1 мегабайт пам'яті, коли актор займає до 300 байтів. Потоки будуть обмежені в кількості, і доведеться користуватися специфікою об'єктно-орієнтованого підходу. Використовуючи модель акторів, ви думаєте про акторів замість об'єктів та потоків. Можна мати набагато більше акторів, ніж потоків.

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

Кожен актор може створювати дочірні акторів і бути їх супервізором, немов менеджер команди. У практиці краще буде розділити завдання актора на дрібні керовані підзадачі і дозволити підлеглим виконати їх в паралелі. Якщо актор має важливе стан, який не можна втратити (наприклад, якщо отримаємо Exception), тоді всю задачу цілком краще віддати на виконання підлеглим і просто стежити за процесом виконання. Тому актори ніколи не приходять одні. Вони приходять в системах і утворюють ієрархії (як файли в UNIX). Системи акторів також можуть формувати кластери. Якщо уявити актора як зірку, тоді система акторів — це кластер зірок. Кластери систем акторів — це кластери кластерів зірок. Таким чином, актори створюють свою всесвіт. Це всесвіт після буде розгорнута на наданому обладнанні і буде виконувати запрограмовану логіку.

У світі акторів керуються принципом «Let It Crash» («дозволь йому відмовити») і так званого шаблоном Error Kernel Pattern (шаблон ядра помилок або, точніше, обробки помилок). Цей шаблон веде нас до системи акторів, де супервізор кожного рівня ієрархії у відповіді за локальні помилки. В даному контексті, помилки — це exceptions, що виникли у підлеглих акторів, тобто самі актори не повинні їх обробляти. У свою чергу, супервізор при виникненні помилки може відловити її і, як правило, продовжити, перезавантажити, зупинити роботу актора або ж ескалірувати exception своєму супервізору, повідомляючи про падіння всій своїй ієрархії підпорядкування. Ядро помилок самої системи акторів формується з системних акторів, які відповідають за життєвий цикл всієї системи. При цьому кожен рівень ієрархії в організаційній структурі є локальним ядром помилок, відповідає за стратегію обробки помилок, яка визначить, як потрібно реагувати на певні помилки дочірніх акторів.

Робота з Akka

Є багато імплементацій моделі акторів, і одна з них націлена виключно на отримання користі з кластера серверів. Це Akka — вона імплементує модель акторів і підтримує Реактивний маніфест, дозволяючи розгортку програм на кластері з мінімальними змінами в коді або навіть взагалі без них.

Akka — це самостійний інструмент. Йому не потрібен сервер додатків, досить JVM і Java SE. З допомогою Akka можна об'єднати декілька JVM в кластер. Akka пропонує модель акторів замість об'єктно-орієнтованої, яка вважається розпаралелиного за замовчуванням.

З Akka ми отримуємо:
— паралелізм замість конкурентності;
— асинхронне поведінка за умовчанням;
— неблокируемые виклики;
— виконання без Deadlock, Starvation, Live-lock і Race Condition;
— розробка в однопоточній середовищі, коли завдання виконуються в паралелі.

Розробка з моделлю акторів у Akka не складна. На момент написання статті актуальною версією Akka була 2.4. Для початку підключіть всі необхідні залежності в проект. Всю необхідну інформацію можна отримати на сайті akka.io . Також є вичерпна документація для розробників Java на akka.io/docs .

Для швидкого старту потрібно знати про кількох класах з пакету akka.actor. Це:
— ActorSystem — клас имплементирующий систему акторів;
— UntypedActor — клас, який потрібно успадкувати для створення класу актора і перевизначити метод onReceive для обробки вхідних повідомлень даними актором;
— ActorRef — клас, інкапсульовану посилання на актор. Він же використовується для відправки повідомлень акторові.

Використовуючи ці класи:
— викличте ActorSystem.create()для створення системи акторів та отримання об'єкта ActorSystem;
— викличте ActorSystem::actorOf() для створення екземпляра актора і отримання його ActorRef;
— використовуйте ActorRef::tell() для відправки повідомлень акторові.

Це все! Насправді, є досить багато нюансів, але, для перших кроків цього достатньо.

Зупинимося на хвилиночку. Що, якщо ми розробляємо локальна програма? Пам'ятайте ExecutorService або навіть Multithreading API? Уявіть систему, що використовує все це. Наскільки воно ефективне? Дійсно, воно може ефективно використати наявні ресурси. Скажімо, ми вивчили Akka API і почали програмувати акторів. Добре, що далі? Ми отримали ті ж результати, як якщо б використовувати стандартні механізми багатопоточності в Java. Так воно? Та й немає. Так, тому що результати можуть бути схожі, і немає, тому що з Akka ми отримуємо схожі результати, програмуючи в однопоточній середовищі, обходячи всі складності розробки багатопотокових додатків. Ви тільки описуєте акторів і логіку обробки повідомлень, які вони отримують. До речі, повідомленням може бути будь-який об'єкт. Єдине правило, яке потрібно пам'ятати: повідомлення повинні бути immutable об'єктами. Тому що з Akka ми пишемо код в однопоточній середовищі, коли система акторів виконується в многопоточной середовищі.

Найцікавіша частина роботи з Akka настане після того, як ви розробите перше додаток. Система акторів може бути розгорнута як самостійний додаток. Актори також можуть обмінюватися повідомленнями по мережі. Це дозволяє організувати спільну роботу окремих систем акторів. І нарешті, ви можете розгорнути кластер з декількох машин. Можна розгорнути єдину систему акторів на кластері машин, зв'язаних в одну мережу. Таким чином, ви отримуєте даний паралельне виконання акторів.

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

Додатково до всього перерахованого, Akka надає Functional Futures, имплементируя Java обгортку над фъючерами з Scala. За допомогою цього API ви навіть можете реалізувати симуляцію map-reduce. Доступно ще більше з таким функціоналом і модулями, як Agents, Streaming API, HTTP стек, Camel та інші.

Резюме

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

Звичайно, є багато інших фреймворків та інструментів, що дозволяють розробникам досягти подібних результатів. На мій погляд, підсумкове рішення залежить від відповіді на одне просте питання: що буде робити ваш додаток? Я вибрав би Akka як мінімум для паралельних обчислень з невеликими витратами ресурсів. Akka вміє набагато більше, і для мене це той мінімум, який визначає, де Akka може стати в нагоді.

Буду радий, якщо ця стаття допоможе визначити, чи цікава вам Akka для подальшого вивчення. Бажаю удачі!

Опубліковано: 09/11/16 @ 10:46
Розділ Різне

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

DOU Проектор: IT2School — безкоштовне IT-освіта для школярів
Коли на оффере AliExpress може конвертуватися будь-трафік в гроші?
Як працювати з legacy-системами
Java дайджест #29: Make Java great again
17 успішних кейсів, як збільшити трафік сайту