Переклад та адаптація Java Microservices: A Practical Guide . Попередні частини гайду:
Давайте розглянемо властиві Java проблеми мікросервісів, починаючи з абстрактних речей і закінчуючи конкретними бібліотеками.
Як зробити мікросервіс Java стійким?
Нагадаємо, що при створенні мікросервісів ви по суті змінюєте дзвінки методів JVM на синхронні дзвінки HTTP або асинхронний обмін повідомленнями. В той час, як виконання виклику методу в основному гарантовано (за винятком несподіваного завершення роботи JVM), мережний виклик за замовчуванням ненадійний. Він може працювати, але може і не працювати з різних причин: перевантажена мережа, запровадабо нове правило брандмауера і таке інше. Щоб побачити, яке це має значення, погляньмо на приклад BillingService.Паттерни стійкості HTTP/REST
Допустимо, клієнти можуть купити електронні книги на сайті вашої компанії. Для цього ви тільки-но впровадабо мікросервіс білінгу, який може викликати ваш інтернет-магазин для створення фактичних рахунків у форматі PDF. Зараз ми зробимо цей виклик синхронно, через HTTP (хоча розумніше викликати цю службу асинхронно, оскільки генерація PDF не обов'язково має бути миттєвою з точки зору користувача. Ми використовуємо цей приклад у наступному розділі і подивимося на відмінності).@Service
class BillingService {
@Autowired
private HttpClient client;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
httpClient.send(invoiceRequest(user.getEmail(), invoice), responseHandler());
// ...
}
}
Якщо узагальнити, ось три можливі результати цього HTTP-дзвінка.
- ОК: дзвінок пройшов, рахунок успішно створено.
- ЗАТРИМКА: дзвінок пройшов, але знадобилося занадто багато часу для цього.
- ПОМИЛКА. Виклик не відбувся, можливо, ви надіслали несумісний запит або система не працювала.
Messaging Resilience Patterns.
Розгляньмо асинхронну комунікацію. Наша програма BillingService тепер може виглядати приблизно так, за умови, що ми використовуємо Spring та RabbitMQ для обміну повідомленнями. Щоб створити рахунок, ми відправляємо повідомлення нашому брокеру повідомлень RabbitMQ, де є кілька працівників, які очікують нових повідомлень. Ці працівники створюють рахунки у форматі PDF та надсилають їх відповідним користувачам.@Service
class BillingService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
// преобразует счет, например, в json и использует его як тело повідомлення
rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
// ...
}
}
Тепер потенційні помилки виглядають трохи інакше, тому що ви не отримуєте негайних відповідей OK або ERROR, як це було з синхронним HTTP-з'єднанням. Натомість у нас може бути три потенційні варіанти неправильного розвитку подій, які можуть викликати такі питання:
- Чи було моє повідомлення доставлено та використано працівником? Чи це втрачено? (Користувач не отримує рахунок-фактуру).
- Моє повідомлення було доставлене лише один раз? Чи доставлено більше одного разу та обробляється лише один раз? (Користувач отримає кілька рахунків).
- Конфігурація: Від «Чи я використовував правильні ключі маршрутизації/імена для обміну» до «Чи правильно налаштований і підтримується мій брокер повідомлень або переповнені його черги?» (Користувач не отримує рахунок-фактуру).
- Якщо ви використовуєте реалізації JMS, наприклад ActiveMQ, ви можете обміняти швидкість на гарантії двофазних (XA) коммітів (two-phase (XA) commits).
- Якщо ви використовуєте RabbitMQ, для початку прочитайте цей посібник, а потім добре обміркуйте підтвердження, відмовостійкість і надійність повідомлень в цілому.
- Можливо, хтось добре розуміється на конфігуруванні серверів Active або RabbitMQ, особливо у поєднанні з кластеризацією та Docker (хто-небудь?;))
Який фрейморк буде найкращим рішенням для мікросервісів Java?
З одного боку, можна встановити дуже популярний варіант, такий як Spring Boot . Він дозволяє легко створювати файли .jar, що поставляється з вбудованим веб-сервером, таким як Tomcat або Jetty, і який можна запустити швидко і будь-де. Ідеально підходить для створення програм мікросервісу. Нещодавно з'явилася пара спеціалізованих мікросервісних фреймворків Kubernetes або GraalVM , частково натхнених реактивним програмуванням. Ось ще кілька цікавих претендентів: Quarkus , Micronaut , Vert.x , Helidon. Зрештою, вам доведеться вибирати самостійно, але ми можемо дати вам кілька рекомендацій, можливо, не цілком стандартних: За винятком Spring Boot, всі платформи мікросервісів зазвичай позиціонуються як неймовірно швидкі, з майже миттєвим запуском, малим об'ємом пам'яті, що використовується, можливістю масштабування до нескінченності. У маркетингових матеріалах зазвичай фігурують вражаючі графіки, що представляють платформу у вигідному світлі поруч із “бегемотом” Spring Boot чи друг з одним. Це за ідеєю щадить нерви розробників, що підтримують легаси-проекти, які часом завантажуються по кілька хвабон. Або розробникам, які працюють у хмарі, які хочуть запустити стільки мікроконтейнерів, скільки їм зараз потрібно протягом 50 мс. Проблема, однак, полягає в тому, що такий (штучний) час старту «голого заліза» та час повторного розгортання навряд чи впливають на загальний успіх проекту. Принаймні впливають набагато менше, ніж сильна фреймворкова інфраструктура, сильна документація, спільнота і сильні навички розробника. Так що краще дивитися на це так: Якщо досі:- Ви дозволяєте своїм ORM працювати у режимі нестримної генерації сотень запитів для простих робочих процесів.
- Вам потрібні нескінченні гігабайти для запуску моноліту помірної складності.
- У вас так багато коду і складність настільки висока (зараз ми говоримо не про потенційно повільні стартери, такі як Hibernate), що вашому додатку потрібно кілька хвабон для завантаження.
Які бібліотеки найкраще підходять для синхронних викликів Java REST?
На низькорівневому технічному боці ви, ймовірно, отримаєте одну з наступних клієнтських бібліотек HTTP: Власний HttpClient Java (починаючи з Java 11), HttpClient Apache або OkHttp . Зверніть увагу, що тут я говорю «ймовірно», тому що є й інші варіанти, починаючи зі старих добрих клієнтів JAX-RS до сучасних клієнтів WebSocket . У будь-якому випадку, існує тенденція до генерації HTTP-клієнта, з відходом від самостійної метушні з HTTP-викликами. Для цього вам потрібно поглянути на проект OpenFeign та його документацію як відправну точку для подальшого читання.Які брокери найкращі для асинхронного обміну повідомленнями Java?
Швидше за все, ви зіткнетеся з популярними ActiveMQ (Classic або Artemis) , RabbitMQ або Kafka .- ActiveMQ та RabbitMQ є традиційними, повноцінними брокерами повідомлень. Вони передбачають взаємодію "розумного брокера" і "дурненьких користувачів".
- Історично ActiveMQ мав перевагу простого вбудовування (для тестування), яке можна пом'якшити за допомогою налаштувань RabbitMQ/Docker/TestContainer.
- Kafka не можна назвати традиційним "розумним" брокером. Навпаки, це «дурне» сховище повідомлень (файл журналу), для обробки якого потрібні розумні споживачі.
Які бібліотеки я можу використати для тестування мікросервісів?
Це залежить від вашого стека. Якщо у вас розгорнута екосистема Spring, буде розумно використовувати спеціальні інструменти цього фреймворку . Якщо JavaEE - щось на зразок Arquillian . Можливо, варто подивитися на Docker і справді хорошу бібліотеку Testcontainers , яка допомагає, зокрема, легко та швидко налаштувати базу даних Oracle для локальних тестів розробки чи інтеграції. Для мок-тестів цілих HTTP-серверів, зверніть увагу на Wiremock . Для тестування асинхронного обміну повідомленнями спробуйте впровадити ActiveMQ або RabbitMQ, а потім написати тести за допомогою Awaitility DSL . Крім цього, застосовуються всі ваші звичні інструменти - Junit , TestNGдля AssertJ і Mockito . Зверніть увагу, що це далеко не повний перелік. Якщо раптом ви не знайшли тут ваш улюблений інструмент, опублікуйте його в розділі коментарів.Як увімкнути логування для всіх мікросервісів Java?
Логування у випадку з мікросервісами – цікава та досить складна тема. Замість одного файлу лога, з яким ви можете працювати за допомогою команд less або grep, тепер у вас є n файлів логування, і бажано, щоб вони не були занадто розрізнені. Добре розписані особливості екосистеми логування у цій статті (англійською мовою). Обов'язково прочитайте його і зверніть увагу на розділ Централізоване ведення лога з точки зору мікросервісів. На практиці ви зіткнетеся з різними підходами: Системний адміністратор пише певні сценарії, які збирають та об'єднують файли логів з різних серверів в один файл логів та поміщають їх на FTP-сервери для завантаження. Запуск комбінацій cat/grep/unig/sort у паралельних SSH-сесіях. Саме так чинить Amazon AWS, про що ви можете повідомити свого менеджера. Використовуйте такий інструмент, як Graylog або ELK Stack (Elasticsearch, Logstash, Kibana)Як мої мікросервіси знаходять одне одного?
Досі ми припускали, що наші мікросервіси знають один про одного, знають відповідний IPS. Поговоримо про статичне налаштування. Отже, наш банківський моноліт [ip = 192.168.200.1] знає, що йому потрібно поговорити з ризик-сервером [ip = 192.168.200.2], який захардкожен у файлі properties. Однак ви можете зробити все динамічнішим:- Використовуйте хмарний сервер конфігурації, з якого всі мікросервіси виймають конфігурації замість розгортання файлів application.properties на своїх мікросервісах.
- Оскільки екземпляри ваших служб можуть динамічно змінювати своє розташування, варто придивитися до служб, які знають, де живуть ваші служби, які IP і як їх маршрутизувати.
- Тепер, коли все динамічно, з'являються нові проблеми, такі як автоматичне обрання лідера: хто є майстром, який працює над певними завданнями, щоб, наприклад, не обробити їх двічі? Хто заміняє лідера, коли він зазнає невдачі? За яким принципом відбувається заміна?
Як організувати авторизацію та аутентифікацію за допомогою мікросервісів Java?
Ця тема також варта окремої розповіді. Знову ж таки, варіанти варіюються від захардшкіреної базової автентифікації HTTPS із самописними фреймфорками безпеки до запуску установки Oauth2 із власним сервером авторизації.Як переконатись, що всі мої оточення виглядають однаково?
Те, що правильне для розгортань без мікросервісу, також вірно і для розгортань з ним. Спробуйте комбінацію Docker/Testcontainers, а також Scripting/Ansible.Не питання: коротко про YAML
Давайте ненадовго відійдемо від бібліотек та пов'язаних із ними питань і коротко розглянемо Yaml. Цей формат файлу використовується де-факто як формат для «запису конфігурації у вигляді коду». Використовують його і прості інструменти, на зразок Ansible і гіганти на кшталт Kubernetes. Щоб випробувати біль від відступів в YAML, спробуйте написати простий Ansible-файл і подивіться, скільки вам доведеться редагувати файл, перш ніж він запрацює як треба. І це незважаючи на підтримку формату всіма великими IDE! Після цього повертайтеся, щоб дочитати цей посібник.Yaml:
- is:
- so
- great
А як щодо розподілених транзакцій? Тестування продуктивності? Інші теми?
Може, колись у наступних редакціях керівництва. А поки що все. Залишайтеся з нами!Концептуальні проблеми мікросервісів
Крім специфічних проблем мікросервісів Java, є й інші проблеми, скажімо, ті, які з'являються в будь-якому мікросервісному проекті. Вони стосуються переважно організації, команди та управління.Невідповідність Frontend та Backend
Невідповідність Frontend та Backend — дуже поширена проблема багатьох мікросервісних проектів. Що вона означає? Лише те, що в старих добрих монолітах, розробники веб-інтерфейсу мали одне конкретне джерело для отримання даних. У мікросервісних проектах у розробників веб-інтерфейсу зненацька з'являються n джерел для отримання даних. Уявіть, що ви створюєте проект мікросервісів IoT (інтернет речей) на Java. Скажімо, управляєте геодезичними машинами, промисловими печами по всій Європі. І ці печі регулярно відправляють вам оновлення із зазначенням їх температури тощо. Рано чи пізно ви, можливо, захочете знайти печі в інтерфейсі користувача адміністратора, можливо, за допомогою мікросервісів «пошуку печі». Залежно від того, наскільки строго ваші бекенд-колеги застосовуютьпредметно-орієнтоване проектування чи закони мікросервісів, мікросервіс “знайти піч” може повертати лише ідентифікатори печей, а чи не інші дані, такі як тип, модель чи місцезнаходження. Для цього фронтенд-розробникам потрібно буде виконати один або n додаткових викликів (залежно від реалізації пейджингу) у мікросервісі «отримати дані про печі» з ідентифікаторами, які вони отримали від першого мікросервісу. І хоча це лише простий приклад, нехай і взятий із реального (!) проекту, навіть він демонструє наступну проблему: супермаркети стали надзвичайно популярними. А все тому, що з ними вам не потрібно йти в 10 різних місць, щоб купити овочі, лимонад, заморожену піцу та туалетний папір. Натомість ви йдете в одне місце. Це простіше і швидше. Те саме стосується розробників інтерфейсів та мікросервісів.Очікування керівництва
У менеджменту складається помилкове враження, що тепер потрібно наймати нескінченну кількість розробників у (всеосяжний) проект, оскільки розробники тепер можуть працювати абсолютно незалежно один від одного, кожен на своєму мікросервісі. Наприкінці потрібна лише невелика робота з інтеграції (незадовго до запуску). Насправді такий підхід є вкрай проблематичним. У наступних параграфах ми намагатимемося пояснити, чому."Менші шматочки" не одно "кращі шматочки"
Буде великою помилкою вважати, що розділений на 20 частин код обов'язково буде якісніше одного цільного шматка. Навіть якщо взяти якість з суто технічної точки зору: наші окремі служби, як і раніше, можуть виконувати 400 запитів Hibernate для вибору користувача з бази даних, проходячи по шарах коду, що не підтримується. Вкотре повертаємося до цитати Саймона Брауна: якщо не вдасться побудувати моноліти належним чином, буде складно створити належні мікросервіси. Найчастіше про стійкість до відмови в мікросервісних проектах вкрай несвоєчасно. Настільки, що часом страшно дивитися, як мікросервіси працюють у реальних проектах. Причина цього полягає в тому, що Java-розробники не завжди готові вивчати стійкість до відмов, мережі та інші суміжні теми на належному рівні. Самі "шматочки" - менше, а ось "технічних частин" - більше. Уявіть, що вашій мікросервісній команді пропонується написати технічний мікросервіс для входу в систему бази даних приблизно такою:@Controller
class LoginController {
// ...
@PostMapping("/login")
public boolean login(String username, String password) {
User user = userDao.findByUserName(username);
if (user == null) {
// обработка варианта с несуществующим пользователем
return false;
}
if (!user.getPassword().equals(hashed(password))) {
// обработка неверного пароля
return false;
}
// 'Ю-ху, залогинабось!';
// установите cookies, делайте, что угодно
return true;
}
}
Тепер ваша команда може вирішити (і, можливо, навіть переконати людей бізнесу), мовляв, це все надто просто і нудно, краще замість служби входу в систему написати дійсно корисний мікросервіс UserStateChanged (зміна стану користувача) без будь-яких реальних та відчутних бізнес- вимог. А оскільки до Java зараз деякі люди ставляться як до динозавра, напишемо наш мікросервіс UserStateChanged на модному Erlang. І давайте спробуємо де-небудь використовувати червоно-чорні дерева, тому що Стів Єгге написав, що ви повинні знати їх зсередини, щоб подати заявку в Google. З погляду інтеграції, обслуговування та загального проекту це так само погано, як написання шарів спагетті-коду всередині одного моноліту. Штучний та пересічний приклад? Так і є. Проте подібне може бути і насправді.
Менше шматочки - менше розуміння
Потім природним чином спливає питання про розуміння системи в цілому, її процесів і робочих потоків, але при цьому ви як розробник несете відповідальність тільки за роботу на своєму ізольованому мікросервісі [95: login-101: updateUserProfile]. Він гармонує з попереднім параграфом, але залежно від вашої організації, рівня довіри та комунікації це може призвести до великої кількості подиву, знизувань плечима, звинувачень у разі випадкової поломки в мікросервісному ланцюжку. І немає того, хто б прийняв на себе повну відповідальність за те, що сталося. І справа зовсім не в несумлінності. Насправді дуже важко поєднати різні детальки та зрозуміти їхнє місце у загальній картині проекту.Комунікації та обслуговування
Рівень комунікації та обслуговування залежить від розміру компанії. Проте, загальна залежність очевидна: що більше, то проблематичніше.- Хто працює на мікросервісі №47?
- Вони щойно розгорнули нову несумісну версію мікросервісу? Де це було задокументовано?
- З ким мені потрібно поговорити, щоб запитити нову функцію?
- Хто підтримуватиме той мікросервіс на Erlang, після того, як єдиний хто знав цю мову покинув компанію?
- Усі наші мікросервісні команди працюють не тільки різними мовами програмування, але й у різних часових поясах! Як ми це правильно скоординуємо?
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ