Java Eastern European Conference 2018: доповіді та враження

  1. 18-19 травня в Києві пройшла JEEСonf - одне з найбільш очікуваних подій для всього Java-спільноти Східної Європи.
  2. 15 years of Spring
  3. Python / Java integration
  4. Java 10 App CDS
  5. Natural language processing pipeline with Apache Spark
  6. Graal, Truffle, SubstrateVM and other perks: what are those and why do you need them
  7. Designing Fault Tolerant Microservices
  8. Clustered Event Sourcing and CQRS with Akka and Java
18-19 травня в Києві пройшла JEEСonf - одне з найбільш очікуваних подій для всього Java-спільноти Східної Європи.

DataArt виступив партнером конференції. На чотирьох сценах виступали доповідачі з усього світу: Фолькер Симонис - представник SAP в JCP і контриб'ютор OpenJDK, Юрген Хеллер - головний інженер Pivotal, батько всіма улюбленого Spring Framework, Клаус Ібсен творець Apache Camel, і Х'ю МакКі - євангеліст в Lightbend.

Графік був дуже насиченим: за два дні більше 50 виступів, по 45 хвилин на кожне. 10 хвилин перерви - і біжимо на нову доповідь. На те, щоб подивитися всі відео, коли вони з'являться в мережі, буде потрібно багато часу. Тому коротко опишу доповіді, які я вважав найцікавішим і на яких побував особисто.

15 years of Spring

Конференцію відкривав Юрген Хеллер. Він розповів про 15-річної (!) Історії Spring Framework, починаючи з «улюблених» XML configs в версії 0.9 і закінчуючи реактивним Spring WebFlux, якій з'явився з дослідницьких проектів під впливом Reactive Manifesto . Юрген говорив про співіснування Spring MVC і Spring WebFlux в Spring WEB, пояснив, чому їх вирішили не об'єднувати в одне ціле. Справа в тому, що основна абстракція Spring MVC - Servlet API 3.0 і блокуючий IO, тоді як в Spring WebFlux використовується абстракція Reactive Streams і non-blocking IO. Свій сервіс на SpringWebFlux можна запустити на будь-якому сервері, який підтримує non-blocking IO: Netty, нові версії Tomcat (> 8.5), Jetty. Створення реактивних контролерів WebFlux мало чим відрізняється від їх створення за допомогою Spring MVC, але відмінності все ж є. Обробляючи користувальницький запит, реактивний контролер і не виконує жодних його в звичному розумінні, а створює pipeline для обробки запиту. Dispatcher викликає метод контролера, який створює pipeline і тут же віддає його в вигляді publisher stream. Publisher stream в Reactive Spring представлений у вигляді двох абстракцій: Flux / Mono. Flux повертає стрім об'єктів, а Mono - завжди один об'єкт.

Юрген також згадав зручність використання Java 8-стилю при роботі з Spring 5.0 і обіцяв release candidate Spring 5.1 в липні 2018 і реліз у вересні, в якому буде підтримка Java 11 і робота над fine tuning нових фіч Spring 5.0

Python / Java integration

Доповідей було багато, і вибрати найцікавіший в наступному слоті було складно. Описи були однаково цікаві, тому я довірився чуттю і вирішив послухати Тамаша Розмана - віце-президента компанії BlackRock з Угорщини. Але краще б ще раз послухав про Events Sourcing і CQRS. Судячи з опису, компанія займається Data Science для великого інвестиційного фонду. Мета доповіді була показати, як вони створили масштабируемую, стійку систему, однаково зручну і для дата-аналітиків з їх Python, і для Java-розробників основної системи. Однак мені здалося сумнівним, що побудована система дійсно вийшла зручною. Щоб подружити Python і Java, інженери в BlackRock придумали запускати Python-інтерпретатор у вигляді процесу з Java-додатка. Прийшли вони до цього з кількох причин:

  • Jython (Python на JVM) не підійшов через застаріле code base 2.7 vs СPython 3.6.
  • Варіант переписати логіку Data Science на Java вони визнали дуже довгим процесом.
  • Apache Spark вирішили не брати, оскільки, як пояснив доповідач, не можна міксувати Джоб, написані на Java і Python. Хоча не зовсім ясно, чому не підійшли UDF, UDFA [ 2 ]. Також не підійшов Spark, тому що якийсь job framework у них вже був, і вводити новий не сильно хотілося. Та й, як виявилося, Big Data у них теж немає, і вся обробка зводиться до статистикам над жалюгідними 100 MB файлами.

Спілкування з Java c Python процесом організували за допомогою memory mapped files (один файл використовується в якості файлу вхідних даних) і команд (другий файл - висновок Python процесу). Таким чином, спілкування являло собою щось у вигляді:

Java: calcExr | 1 + javaFunc (sqrt (36)) Python: 1 + javaFunct | 6 Java: 1 + success | 64 Python: success | 65

Основними проблемами такої інтеграції Тамаш назвав накладні витрати при серіалізациі і десеріалізациі вхідних / вихідних параметрів.

Java 10 App CDS

Після доповіді про тонкощі запуску Python дуже хотілося послухати щось глибоко технічне зі світу Java. Тому я пішов на доповідь Фолькера Сімоніс, в якому він розповідав про фічеApplication class data sharing з Java 10+ . У сучасному світі, побудованому на мікросовервісах в Docker, можливість шеринга Java Codecache і Metaspace прискорює запуск програми та економить пам'ять. На зображенні результати запуску докерізірованних томкатов із загальним / shared архівом Tomcat-класів. Як бачимо, для другого процесу певний обсяг сторінок в пам'яті вже позначений як shared_clean - значить, на них посилається поточний і ще як мінімум один процес (другий запущений томкат).

Детально про те, як пограти з CDS в OpenJDK 10, можна знайти за посиланням: App CDS . Крім поділу / sharing класів додатки між процесами, в подальшому планується і можливість поділитися інтернованими рядками в JEP-250 .

Основні обмеження AppCDS:

  • Чи не працює з класами до 1.5.
  • Не можна використовувати класи, завантажені з файлів (тільки .jar-архіви).
  • Не можна використовувати класи, модифіковані класслоадером.
  • Класи, завантажені кількома завантажувачами класів, можуть перевикористати тільки один раз.
  • Чи не працює byte-code rewriting, що може привести до осіданням в продуктивності до 2%. JDK-8074345

Natural language processing pipeline with Apache Spark

Доповідь про NLP і Apache Spark представив Віталій Котляренко - інженер з Grammarly. Віталій показав, як в Grammarly прототіпіруют NLP-Джоб на Apache Zeppelin. Прикладом стала побудова простого Пайплайн для тематичного моделювання на базі алгоритму LDA архіву інтернету common crawl . Результати тематичного моделювання застосували для фільтрації сайтів з небажаним контентом як приклад функції батьківського контролю. Для створення Пайплайн використовували Terraform scripts і AWS EMR Spark cluster, який дозволяє розгорнути Spark Cluster c YARN в Амазон. Схематично пайплайн виглядає наступним чином:

Метою доповіді було показати, що з застосуванням сучасних фреймворків зробити прототип для ML-завдань досить просто, проте, використовуючи стандартні бібліотеки, все одно натрапляєш на труднощі. наприклад:

  • На першому ж кроці читання WARC-файлів за допомогою бібліотеки HadoopInputFormat іноді вилітали IllegalStateExceptions через некоректні заголовків файлів довелося переписати бібліотеку і пропускати некоректні файли.
  • Залежності на guava - бібліотеки визначення мови - конфліктували з залежностями, які тягне за собою Spark. Допомогла Java 8, за допомогою якої вийшло викинути залежності на guava в використовуваної бібліотеці.

Під час демо ми стежили за виконанням Джоб за допомогою стандартного Spark UI і моніторингової підсистеми Ganglia , Яка автоматично доступна при розгортанні на AWS EMR. Основну увагу автор звертав на heat map Server Load Distribution, яка показує розподіл навантаження між нодамі в кластері, і давав загальні поради щодо оптимізації роботи Spark Job: збільшення кількості partitions, оптимізація сериализации даних, аналіз GC-логів. Більш докладно про оптимізацію Spark Jobs можна почитати тут . Вихідні файли для демо можна знайти в гітхабе автора доповіді.

Graal, Truffle, SubstrateVM and other perks: what are those and why do you need them

Самим очікуваним для мене була доповідь Олега Чирухін з JUG.ru. Він розповів, як можна оптимізувати готовий код за допомогою Грааля. Що ж таке Грааль? Грааль - це бренд Oracle Labs , Який об'єднав в собі JIT (just-in-time) компілятор, фреймворк для написання DSL мов - Truffle - і особливу JVM ( SubstrateVM ) - універсальну Closed-world віртуальну машину, під яку можна писати на JavaScript, Ruby, Python, Java, Scala. Доповідь фокусувався саме на JIT-компілятор і його тестуванні в продакшн.

Для початку нагадаємо процес виконання коду Java-машиною і звернемо увагу, що в Java вже є два компілятора: С1 (Client compiler) і С2 (Server Compiler). Грааль можна використовувати в якості C2-компілятора.

На питання, навіщо нам ще один компілятор, дуже добре відповів один із співробітників Oracle Labs доктор Кріс Сітон в статті Understanding How Graal Works . Якщо коротко, то початкова задумка проекту Graal, як і проекту Metropolis , Переписати частини коду JVM, написані на С ++, на Java. Це дасть можливість в подальшому зручно доповнювати код. Наприклад, одна з оптимізацій - P artial Escape Analysis - вже є в Грааль, але не в Hotspot - тому що розширювати код Грааля багато простіше, ніж код С2 .

Звучить чудово, але як це буде працювати на практиці в моєму проекті, запитаєте ви? Грааль підходить для проектів:

  • Які багато смітять, створюючи багато дрібних об'єктів.
  • Написаних в стилі Java 8, з купою стрімів і лямбда.
  • Використовують різні мови: Ruby, Java, R.

Одними з перших в продакшн Грааль почали використовувати в Twitter. Про це докладніше можна почитати в інтерв'ю Крістіана Талінгера, що виходили на Хабре ( інтервью_1 і інтервью_2 ). Там він пояснює, що за допомогою заміни C2 на Graal Twitter почав економити близько 8% CPU utilization, що дуже непогано, враховуючи масштаб організації.

На конференції ми теж змогли переконатися в швидкості Graal, запустивши під ним один з Scala-бенчмарков - Scala DaCapo . В результаті на Graal бенч пройшов за ~ 7000 мс, а на звичайній JVM за ~ 14000 мс! Чому так сталося, можна побачити, подивившись на gclog-тести. Кількість Allocation failure при використанні Graal значно менше, ніж у Hotspot. Однак все одно не можна сказати, що Грааль стане вирішенням проблем перформансу вашого Java-додатка. Олег в доповіді показав і історію невдачі, порівнявши роботу Apache Ignite під Граалем і без - там помітної зміни продуктивності не відбулося.

Designing Fault Tolerant Microservices

Черговий доповідь про відмовостійкої мікросервісной архітектурі прочитав Орхан Гасимов з компанії AppsFlyer. Він представив популярні дизайн-патерни для побудови розподілених додатків. Багато з них ми, можливо, добре знаємо, проте зайвий раз пройтися і згадати про кожного з них зовсім не завадить.

Основні проблеми відмовостійкості сервісів, з якими покликані боротися патерни, описані в доповіді: мережа, пікові навантаження, RPC-механізми спілкування між сервісами.
Для вирішення проблем з мережею, коли один з сервісів перестав бути доступний, нам необхідна можливість швидко замінити його на інший такий же. На практиці цього можна досягти за допомогою кількох інстанси одного і того ж сервісу та опису альтернативних шляхів до цих інстанси, що представляє собою патерн Service Discovery . Займатися heartbeat сервісів і реєструвати нові сервіси буде окремий інстанси - Service Registry. Як Service Registry прийнято використовувати всім відомі Zookeeper або Consul . Які, в свою чергу теж мають розподілену природу і підтримку відмовостійкості.

Вирішивши проблеми з мережею, переходимо до проблеми пікових навантажень, коли деякі сервіси знаходяться під навантаженням і виконують обробку запитів значно повільніше штатного режиму. Для її вирішення можна застосувати Auto-scaling -паттерн. Він візьме на себе не тільки завдання автоматичного масштабування високонавантажених сервісів, але ще і зупинку інстанси після закінчення періоду пікового навантаження.

Завершальною главою доповіді автора було опис можливих проблем внутрішнього міжсервісний спілкування RPC. Особливу увагу Урахан приділив тези «Користувач не повинен чекати повідомлення про помилку довго». Така ситуація може виникнути, якщо його запит обробляє ланцюжок сервісів, і проблема в кінці ланцюга: відповідно, користувач може чекати обробки запиту кожним із сервісів в ланцюзі і тільки на останньому етапі отримує помилку. Найгірше, якщо кінцевий сервіс перевантажений, і після тривалого очікування клієнт отримає безглуздий HTTP-ERROR: 500.

Для боротьби з такими ситуаціями можна застосувати Timeout s, проте в таймаут можуть потрапити запити, які все-таки можуть бути правильно оброблені. Для цього логіку таймаутів можна ускладнити і додати особливе порогове значення кількості помилок сервісу за інтервал часу. При виході кількості помилок за межі порогового значення ми розуміємо, що сервіс знаходиться під навантаженням і вважаємо його недоступним, давши йому необхідний час впоратися з поточними завданнями. Такий підхід описує патерн Circuit Breaker . Також CircuitBreaker.html "> Circuit Beaker можна використовувати як додаткову метрику для моніторингу, яка дозволить швидко відреагувати на можливі проблеми і чітко визначити, які ланцюжка сервісів їх відчувають. Для цього кожен виклик сервісів необхідно обгорнути в Circuit Breaker.

Також в доповіді автор згадав про паттерне N-Modular redundancy , Покликаному «обробляти запити швидше, якщо це можливо», і навів гарний приклад його використання для валідації адреси клієнта. Запит в їх системі через кеш адрес, відправлявся відразу на кілька Geo Map-провайдерів, в результаті чого перемагав найшвидший відповідь.

Крім описаних патернів, були згадані:

  • Fast Path pattern , Який можна застосувати, наприклад, при кешуванні результатів запитів. Тоді звернення в кеш - fast path.
  • Error Kernel pattern - патерн зі світу Akka який передбачає поділ завдання на підзадачі і делегування подзадач нижчестоящим акторам. Таким чином досягається гнучкість обробки помилок виконання підзадач.
  • Instance Healer, який передбачає наявність спеціального сервісу - супервізора, керуючого іншими сервісами і реагує на зміни їх стану. Наприклад, в разі виникнення помилок в сервісі, супервізор може перезапустити проблемний сервіс.

Clustered Event Sourcing and CQRS with Akka and Java

Остання доповідь, на який я хочу звернути вашу увагу, прочитав один з євангелістів і архітекторів компанії Lightbend Х'ю МакКі. Компанія Lightbend (раніше Typesafe) - щось на кшталт Oracle, але для мови Scala. Так само компанія активно розробляє фреймворк Akka.io . У доповіді Х'ю розповів про реалізацію популярного підходу CQRS (Command Query Responsibility Commands / SEGREGATION) на Akka-фреймворку. Схематично архітектура CQRS системи виглядає ось так:

Як приклад працюючої системи Хью взяв прототип роботи банку. Клієнт в CQRS-архітектурі робить дві операції: query, command. Кожна команда (наприклад, банківська транзакція переказу грошей з одного рахунку на інший) породжує подія (доконаний факт), яке буде записано в EventStore (наприклад: Cassandra). Агрегація ланцюжка (покласти гроші на рахунок, перевести з рахунку на рахунок, зняти в банкоматі) подій формує поточний стан клієнта, його баланс грошей на рахунку. Запити поточного стану йдуть в окреме сховище, якийсь snapshot сховища івентів, оскільки зберігати повну історію банківського аккаунта безглуздо. Цілком достатньо періодично оновлювати зліпок стану для кожного користувача.

Такий підхід дає можливість автоматично відновлюватися при виникненні помилок: для це нам необхідно дістати останній зліпок стану користувача і застосувати до нього всі події які відбулися до виникнення помилки. Через наявність двох сховищ, CQRS-архітектура добре переносить виникають пікові навантаження (spikes). Велике у івентів нагрузить Event Store, але не торкнеться Read Store, і користувачі як і раніше зможуть виконувати запити в базу.

Повернемося до прототіпірованію банківської системи на Akka і CQRS. Кожен клієнт банку / рахунок / можлива команда в системі буде представлений одним (!) актором . Великий банк може підтримувати сотні тисяч рахунків, і це не складе проблеми для Akka. Фреймворк з коробки підтримує кластеризацію і може бути запущений на сотнях JVM. У разі відмови однієї з машин в кластері, Акка надає спеціальні механізми, які дозволяють автоматично реагувати на подібні ситуації: в нашому випадку актор клієнта може бути перестворює знову на будь-якій доступній машині в кластері, а його стан буде заново прочитано зі сховища.

Під актор не створюється окремого потоку - це і дає можливість підтримувати десятки тисяч акторів в межах однієї JVM. При цьому актор гарантує, що кожен запит буде оброблений окремо (!) В порядку надходження запитів. Така гарантія автоматично нівелює можливі race conditions при обробці запитів. Детальніше розібратися в прототипі системи можна, відкривши її код за посиланнями в GitHub. Кожен підпроект показує реалізацію найбільш складних етапів побудови прототипу:

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

Що ж таке Грааль?
Звучить чудово, але як це буде працювати на практиці в моєму проекті, запитаєте ви?

Новости