Tłumaczenie i adaptacja mikrousług Java: praktyczny przewodnik . Poprzednie części poradnika:
Przyjrzyjmy się nieodłącznym problemom mikrousług w Javie, zaczynając od rzeczy abstrakcyjnych, a kończąc na konkretnych bibliotekach.
Jak zapewnić odporność mikrousługi Java?
Przypomnij sobie, że tworząc mikrousługi, zasadniczo zamieniasz wywołania metod JVM na synchroniczne wywołania HTTP lub asynchroniczne przesyłanie wiadomości. Chociaż w większości przypadków gwarantowane jest zakończenie wywołania metody (z wyjątkiem nieoczekiwanego zamknięcia maszyny JVM), wywołanie sieciowe jest domyślnie zawodne. Może działać, ale może nie działać z różnych powodów: sieć jest przeciążona, wdrożono nową regułę zapory sieciowej i tak dalej. Aby zobaczyć, jakie to ma znaczenie, spójrzmy na przykład BillingService.Wzorce odporności HTTP/REST
Załóżmy, że klienci mogą kupować e-booki w witrynie Twojej firmy. Aby to zrobić, właśnie zaimplementowałeś mikrousługę rozliczeniową, która może wywołać Twój sklep internetowy w celu wygenerowania rzeczywistych faktur w formacie PDF. Na razie będziemy wykonywać to wywołanie synchronicznie, za pośrednictwem protokołu HTTP (chociaż rozsądniej jest wywoływać tę usługę asynchronicznie, ponieważ generowanie pliku PDF nie musi być natychmiastowe z punktu widzenia użytkownika. Tego samego przykładu użyjemy w następnym sekcję i spójrz na różnice).@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());
// ...
}
}
Podsumowując, oto trzy możliwe wyniki tego wywołania HTTP.
- OK: połączenie zostało zrealizowane, konto zostało pomyślnie utworzone.
- OPÓŹNIENIE: Połączenie zostało zrealizowane, ale jego ukończenie trwało zbyt długo.
- BŁĄD. Połączenie nie powiodło się, być może wysłałeś niezgodne żądanie lub system może nie działać.
Wzorce odporności na wiadomości
Przyjrzyjmy się bliżej komunikacji asynchronicznej. Nasz program BillingService może teraz wyglądać mniej więcej tak, zakładając, że do przesyłania wiadomości używamy Spring i RabbitMQ. Aby utworzyć konto, wysyłamy teraz wiadomość do naszego brokera wiadomości RabbitMQ, gdzie kilku pracowników czeka na nowe wiadomości. Pracownicy ci tworzą faktury w formacie PDF i wysyłają je do odpowiednich użytkowników.@Service
class BillingService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
// преобразует счет, например, в json и использует его Jak тело wiadomości
rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
// ...
}
}
Potencjalne błędy wyglądają teraz nieco inaczej, ponieważ nie otrzymujesz już natychmiastowych odpowiedzi OK ani BŁĄD, tak jak w przypadku synchronicznego połączenia HTTP. Zamiast tego możemy mieć trzy potencjalne scenariusze, które mogą się nie udać, co może rodzić następujące pytania:
- Czy moja wiadomość została dostarczona i wykorzystana przez pracownika? A może jest stracone? (Użytkownik nie otrzymuje faktury).
- Czy moja wiadomość została dostarczona tylko raz? Lub dostarczone więcej niż raz i przetworzone tylko raz? (Użytkownik otrzyma wiele faktur).
- Konfiguracja: od „Czy użyłem prawidłowych kluczy/nazw routingu dla giełdy” do „Czy mój broker komunikatów jest poprawnie skonfigurowany i utrzymywany lub czy jego kolejki są pełne?” (Użytkownik nie otrzymuje faktury).
- Jeśli używasz implementacji JMS, takich jak ActiveMQ, możesz zamienić prędkość na gwarancję zatwierdzeń dwufazowych (XA).
- Jeśli używasz RabbitMQ, przeczytaj najpierw ten samouczek, a następnie dokładnie zastanów się nad potwierdzeniami, odpornością na błędy i ogólnie niezawodnością komunikatów.
- Być może ktoś jest dobrze zorientowany w konfiguracji serwerów Active lub RabbitMQ, zwłaszcza w połączeniu z klastrem i Dockerem (ktoś? ;))
Który framework byłby najlepszym rozwiązaniem dla mikroserwisów Java?
Z jednej strony można zainstalować bardzo popularną opcję taką jak Spring Boot . Bardzo ułatwia tworzenie plików .jar, ma wbudowany serwer WWW, taki jak Tomcat lub Jetty, i można go uruchomić szybko i wszędzie. Idealny do tworzenia aplikacji mikroserwisowych. Niedawno pojawiła się para wyspecjalizowanych frameworków mikrousługowych, Kubernetes lub GraalVM , częściowo inspirowanych programowaniem reaktywnym. Oto kilku bardziej interesujących pretendentów: Quarkus , Micronaut , Vert.x , Helidon . Ostatecznie będziesz musiał wybrać sam, ale możemy dać Ci kilka zaleceń, które mogą nie być całkowicie standardowe: Z wyjątkiem Spring Boot, wszystkie frameworki mikrousług są zwykle sprzedawane jako niewiarygodnie szybkie, z niemal natychmiastowym uruchamianiem , niewielkie zużycie pamięci i możliwość skalowania do nieskończoności. Materiały marketingowe zazwyczaj zawierają imponującą grafikę, która przedstawia platformę obok gigantycznego Spring Boota lub siebie nawzajem. W teorii oszczędza to nerwy programistom obsługującym starsze projekty, których załadowanie czasami zajmuje kilka minut. Lub programistów pracujących w chmurze, którzy chcą uruchomić/zatrzymać tyle mikrokontenerów, ile aktualnie potrzebują, w ciągu 50 ms. Problem polega jednak na tym, że te (sztuczne) czasy rozruchu i ponownego wdrożenia w niewielkim stopniu przyczyniają się do ogólnego sukcesu projektu. Przynajmniej mają one znacznie mniejszy wpływ niż silna infrastruktura frameworkowa, silna dokumentacja, społeczność i silne umiejętności programistów. Lepiej więc spojrzeć na to w ten sposób: Jeśli do tej pory:- Pozwalasz, aby Twoje ORMy działały szaleńczo, generując setki zapytań dla prostych przepływów pracy.
- Do uruchomienia średnio złożonego monolitu potrzebujesz nieskończonych gigabajtów.
- Masz tak dużo kodu, a złożoność jest tak duża (nie mówimy teraz o potencjalnie wolnych startach, takich jak Hibernacja), że załadowanie aplikacji zajmuje kilka minut.
Które biblioteki są najlepsze do synchronicznych wywołań Java REST?
Z technicznego punktu widzenia prawdopodobnie otrzymasz jedną z następujących bibliotek klienta HTTP: natywna Java HttpClient (od Java 11), HttpClient Apache lub OkHttp . Zauważ, że mówię tutaj „prawdopodobnie”, ponieważ istnieją inne opcje, od starych, dobrych klientów JAX-RS po nowoczesnych klientów WebSocket . W każdym razie panuje tendencja do generowania klienta HTTP i odchodzenia od samodzielnego majstrowania przy wywołaniach HTTP. Aby to zrobić, musisz zapoznać się z projektem OpenFeign i jego dokumentacją jako punktem wyjścia do dalszej lektury.Jacy są najlepsi brokerzy do asynchronicznego przesyłania komunikatów w języku Java?
Najprawdopodobniej natkniesz się na popularne ActiveMQ (Classic lub Artemis) , RabbitMQ lub Kafka .- ActiveMQ i RabbitMQ to tradycyjni, pełnoprawni brokerzy komunikatów. Obejmują interakcję „inteligentnego brokera” i „głupich użytkowników”.
- Historycznie rzecz biorąc, ActiveMQ miał tę zaletę, że umożliwiał łatwe wstawianie (do testowania), co można złagodzić za pomocą ustawień RabbitMQ/Docker/TestContainer.
- Kafki nie można nazwać tradycyjnym „inteligentnym” brokerem. Zamiast tego jest to stosunkowo „głupi” magazyn wiadomości (plik dziennika), który wymaga przetworzenia przez inteligentnych konsumentów.
Jakich bibliotek mogę używać do testowania mikroserwisów?
To zależy od twojego stosu. Jeśli masz wdrożony ekosystem Spring, rozsądnie byłoby użyć specyficznych narzędzi frameworka . Jeśli JavaEE jest czymś w rodzaju Arquillian . Może warto rzucić okiem na Dockera i naprawdę dobrą bibliotekę Testcontainers , która pomaga w szczególności łatwo i szybko skonfigurować bazę danych Oracle na potrzeby lokalnego rozwoju lub testów integracyjnych. Aby przetestować próbne całe serwery HTTP, sprawdź Wiremock . Aby przetestować przesyłanie wiadomości asynchronicznych, spróbuj zaimplementować ActiveMQ lub RabbitMQ, a następnie napisać testy przy użyciu Awaitility DSL . Ponadto używane są wszystkie zwykłe narzędzia - Junit , TestNG dla AssertJ i Mockito . Należy pamiętać, że nie jest to pełna lista. Jeśli nie znajdziesz tutaj swojego ulubionego narzędzia, opublikuj je w sekcji komentarzy.Jak włączyć rejestrowanie dla wszystkich mikrousług Java?
Logowanie w przypadku mikroserwisów to temat ciekawy i dość złożony. Zamiast mieć jeden plik dziennika, którym można manipulować za pomocą poleceń less lub grep, masz teraz n plików dziennika i nie chcesz, aby były zbyt rozproszone. Cechy ekosystemu pozyskiwania drewna są dobrze opisane w tym artykule (w języku angielskim). Koniecznie przeczytaj i zwróć uwagę na sekcję Scentralizowane logowanie z perspektywy mikroserwisów . W praktyce można spotkać się z różnymi podejściami: Administrator systemu pisze określone skrypty, które zbierają i łączą pliki dziennika z różnych serwerów w jeden plik dziennika i umieszczają je na serwerach FTP w celu pobrania. Uruchamianie kombinacji cat/grep/unig/sort w równoległych sesjach SSH. Dokładnie to robi Amazon AWS i możesz poinformować o tym swojego menedżera. Użyj narzędzia takiego jak Graylog lub ELK Stack (Elasticsearch, Logstash, Kibana)Jak moje mikrousługi odnajdują się nawzajem?
Do tej pory zakładaliśmy, że nasze mikroserwisy wiedzą o sobie i znają odpowiadający im IPS. Porozmawiajmy o konfiguracji statycznej. Zatem nasz monolit bankowy [ip = 192.168.200.1] wie, że musi komunikować się z serwerem ryzyka [ip = 192.168.200.2], który jest zakodowany na stałe w pliku właściwości. Możesz jednak sprawić, że wszystko będzie bardziej dynamiczne:- Użyj serwera konfiguracji opartego na chmurze, z którego wszystkie mikrousługi pobierają swoje konfiguracje, zamiast wdrażać pliki application.properties w swoich mikrousługach.
- Ponieważ instancje usług mogą dynamicznie zmieniać swoją lokalizację, warto przyjrzeć się usługom, które wiedzą, gdzie znajdują się Twoje usługi, jakie są ich adresy IP i jak je trasować.
- Teraz, gdy wszystko jest dynamiczne, pojawiają się nowe problemy, takie jak automatyczny wybór lidera: kto jest mistrzem, który pracuje nad pewnymi zadaniami, aby na przykład nie przetwarzać ich dwukrotnie? Kto zastępuje lidera, gdy ten poniesie porażkę? Na jakiej podstawie następuje wymiana?
Jak zorganizować autoryzację i uwierzytelnianie przy użyciu mikroserwisów Java?
Ten temat również zasługuje na osobną opowieść. Ponownie, opcje obejmują zakodowane na stałe podstawowe uwierzytelnianie HTTPS z niestandardowymi strukturami bezpieczeństwa po uruchomienie instalacji Oauth2 z własnym serwerem autoryzacji.Jak mogę się upewnić, że wszystkie moje środowiska wyglądają tak samo?
To, co dotyczy wdrożeń bez mikrousługi, dotyczy również wdrożeń z mikrousługą. Wypróbuj kombinację Docker/Testcontainers i Scripting/Ansible.Brak pytań: krótko o YAML
Odejdźmy na chwilę od bibliotek i zagadnień z nimi związanych i rzućmy okiem na Yaml. Ten format pliku jest de facto używany jako format „zapisywania konfiguracji jako kodu”. Używają go także proste narzędzia, takie jak Ansible i giganci, jak Kubernetes. Aby doświadczyć bólu związanego z wcięciami YAML, spróbuj napisać prosty plik Ansible i zobacz, jak bardzo musisz go edytować, zanim zacznie działać zgodnie z oczekiwaniami. I to pomimo formatu obsługiwanego przez wszystkie główne IDE! Następnie wróć, aby dokończyć czytanie tego przewodnika.Yaml:
- is:
- so
- great
A co z transakcjami rozproszonymi? Test wydajności? Inne tematy?
Może kiedyś, w przyszłych wydaniach podręcznika. Na razie to wszystko. Zostań z nami!Problemy koncepcyjne z mikroserwisami
Oprócz specyficznych problemów związanych z mikrousługami w Javie istnieją inne problemy, powiedzmy, takie, które pojawiają się w każdym projekcie mikrousług. Dotyczą one przede wszystkim organizacji, zespołu i zarządzania.Niedopasowanie frontendu i backendu
Niedopasowanie frontendu i backendu jest bardzo częstym problemem w wielu projektach mikroserwisowych. Co to znaczy? Tyle że w starych dobrych monolitach twórcy interfejsów WWW mieli jedno konkretne źródło pozyskiwania danych. W projektach mikroserwisowych programiści frontendowi nagle mają n źródeł, z których mogą pozyskiwać dane. Wyobraź sobie, że tworzysz jakiś projekt mikrousług IoT (Internet of Things) w Javie. Załóżmy, że zarządzasz maszynami geodezyjnymi i piecami przemysłowymi w całej Europie. Te piekarniki regularnie wysyłają aktualizacje dotyczące temperatur i tym podobnych. Wcześniej czy później możesz chcieć znaleźć piekarniki w interfejsie administratora, być może korzystając z mikrousług „wyszukiwania pieców”. W zależności od tego, jak rygorystycznie odpowiednicy backendu stosują się do przepisów dotyczących projektowania opartego na domenie lub mikrousług, mikrousługa „znajdź piekarnik” może zwracać tylko identyfikatory piekarnika, a nie inne dane, takie jak typ, model czy lokalizacja. Aby to zrobić, programiści frontendowi będą musieli wykonać jedno lub n dodatkowych wywołań (w zależności od implementacji stronicowania) w mikroserwisie „pobierz dane pieca” z identyfikatorami, które otrzymali z pierwszej mikrousługi. I chociaż jest to tylko prosty przykład, choć zaczerpnięty z prawdziwego (!) projektu, to jednak pokazuje on następujący problem: supermarkety stały się niezwykle popularne. To dlatego, że dzięki nim nie musisz udawać się do 10 różnych miejsc, aby kupić warzywa, lemoniadę, mrożoną pizzę i papier toaletowy. Zamiast tego jedziesz w jedno miejsce, tak jest łatwiej i szybciej. To samo dotyczy programistów front-end i mikroserwisów.Oczekiwania kierownictwa
Kierownictwo ma błędne wrażenie, że teraz musi zatrudnić nieskończoną liczbę programistów do (nadrzędnego) projektu, ponieważ programiści mogą teraz pracować całkowicie niezależnie od siebie, każdy na własnym mikroserwisie. Na samym końcu (na krótko przed uruchomieniem) wymagana jest jedynie niewielka praca integracyjna. W rzeczywistości takie podejście jest niezwykle problematyczne. W kolejnych akapitach postaramy się wyjaśnić dlaczego.„Mniejsze kawałki” nie oznaczają „lepszych kawałków”
Dużym błędem byłoby zakładać, że kod podzielony na 20 części będzie z konieczności wyższej jakości niż jeden cały fragment. Nawet jeśli spojrzymy na jakość z czysto technicznego punktu widzenia, w naszych poszczególnych usługach może nadal działać 400 zapytań Hibernate w celu wybrania użytkownika z bazy danych, przechodząc przez warstwy nieobsługiwanego kodu. Jeszcze raz wracamy do cytatu Simona Browna: jeśli nie zbudujesz poprawnie monolitów, trudno będzie zbudować odpowiednie mikroserwisy. Często jest już bardzo późno, aby mówić o odporności na błędy w projektach mikroserwisowych. Do tego stopnia, że czasami aż strach zobaczyć, jak mikroserwisy sprawdzają się w rzeczywistych projektach. Powodem tego jest to, że programiści Java nie zawsze są gotowi studiować tolerancję błędów, sieci i inne powiązane tematy na odpowiednim poziomie. Same „elementy” są mniejsze, ale „części techniczne” są większe. Wyobraź sobie, że Twój zespół ds. mikrousług zostaje poproszony o napisanie technicznej mikrousługi umożliwiającej logowanie do systemu baz danych, na przykład takiej:@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;
}
// 'Ю-ху, залогинLubсь!';
// установите cookies, делайте, что угодно
return true;
}
}
Teraz Twój zespół może zdecydować (a może nawet przekonać ludzi biznesu), że jest to zbyt proste i nudne, zamiast pisać usługę logowania, lepiej napisać naprawdę użyteczną mikrousługę UserStateChanged bez żadnych realnych i wymiernych implikacji biznesowych. A ponieważ niektórzy obecnie traktują Javę jak dinozaura, napiszmy nasz mikroserwis UserStateChanged w modnym Erlangu. A spróbujmy gdzieś wykorzystać czerwono-czarne drzewa, bo Steve Yegge napisał, że trzeba je znać na wylot, żeby aplikować do Google. Z punktu widzenia integracji, konserwacji i ogólnego projektu jest to tak samo złe, jak pisanie warstw kodu spaghetti w jednym monolicie. Sztuczny i zwyczajny przykład? To prawda. Może się to jednak zdarzyć w rzeczywistości.
Mniej elementów - mniej zrozumienia
Wtedy naturalnie pojawia się pytanie o zrozumienie systemu jako całości, jego procesów i przepływów pracy, ale jednocześnie Ty, jako programista, jesteś odpowiedzialny tylko za pracę nad swoim wyizolowanym mikroserwisem [95: login-101: updateUserProfile]. Harmonizuje to z poprzednim akapitem, ale w zależności od Twojej organizacji, poziomu zaufania i komunikacji może to prowadzić do wielu zamieszania, wzruszeń ramionami i obwiniania, jeśli dojdzie do przypadkowej awarii w łańcuchu mikrousług. I nie ma nikogo, kto wziąłby pełną odpowiedzialność za to, co się stało. I wcale nie jest to kwestia nieuczciwości. Tak naprawdę bardzo trudno jest połączyć różne części i zrozumieć ich miejsce w ogólnym obrazie projektu.Komunikacja i obsługa
Poziom komunikacji i obsługi zależy w dużej mierze od wielkości firmy. Jednak ogólna zależność jest oczywista: im więcej, tym bardziej problematyczne.- Kto prowadzi mikroserwis nr 47?
- Czy właśnie wdrożyli nową, niezgodną wersję mikrousługi? Gdzie to zostało udokumentowane?
- Z kim muszę porozmawiać, aby poprosić o nową funkcję?
- Kto będzie wspierał ten mikroserwis w Erlangu, po tym jak jedyny znający ten język odszedł z firmy?
- Wszystkie nasze zespoły mikroserwisowe pracują nie tylko w różnych językach programowania, ale także w różnych strefach czasowych! Jak to wszystko poprawnie skoordynować?
GO TO FULL VERSION