Übersetzung und Anpassung von Java Microservices: Ein praktischer Leitfaden . Vorherige Teile des Leitfadens:
Schauen wir uns die inhärenten Probleme von Microservices in Java an, angefangen bei abstrakten Dingen bis hin zu konkreten Bibliotheken.
Wie macht man einen Java-Microservice widerstandsfähig?
Denken Sie daran, dass Sie beim Erstellen von Microservices im Wesentlichen JVM-Methodenaufrufe gegen synchrone HTTP-Aufrufe oder asynchrone Nachrichtenübermittlung eintauschen. Während ein Methodenaufruf größtenteils garantiert abgeschlossen wird (mit Ausnahme eines unerwarteten Herunterfahrens der JVM), ist ein Netzwerkaufruf standardmäßig unzuverlässig. Es kann funktionieren, aber aus verschiedenen Gründen kann es auch nicht funktionieren: Das Netzwerk ist überlastet, eine neue Firewall-Regel wurde implementiert und so weiter. Um zu sehen, welchen Unterschied dies macht, werfen wir einen Blick auf das BillingService-Beispiel.HTTP/REST-Resilienzmuster
Nehmen wir an, Kunden können E-Books auf der Website Ihres Unternehmens kaufen. Zu diesem Zweck haben Sie gerade einen Abrechnungs-Microservice implementiert, der Ihren Online-Shop anrufen kann, um tatsächliche PDF-Rechnungen zu erstellen. Vorerst führen wir diesen Aufruf synchron über HTTP durch (obwohl es sinnvoller ist, diesen Dienst asynchron aufzurufen, da die PDF-Erzeugung aus Sicht des Benutzers nicht sofort erfolgen muss. Im nächsten Beispiel verwenden wir dasselbe Beispiel Abschnitt und sehen Sie sich die Unterschiede an).@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());
// ...
}
}
Zusammenfassend sind hier drei mögliche Ergebnisse dieses HTTP-Aufrufs.
- OK: Der Anruf wurde durchgeführt, das Konto wurde erfolgreich erstellt.
- VERZÖGERUNG: Der Anruf wurde durchgestellt, aber es dauerte zu lange, bis er abgeschlossen war.
- FEHLER. Der Anruf ist fehlgeschlagen, Sie haben möglicherweise eine inkompatible Anfrage gesendet oder das System funktioniert möglicherweise nicht.
Messaging-Resilienzmuster
Schauen wir uns die asynchrone Kommunikation genauer an. Unser BillingService-Programm könnte nun etwa so aussehen, vorausgesetzt, wir verwenden Spring und RabbitMQ für die Nachrichtenübermittlung. Um ein Konto zu erstellen, senden wir nun eine Nachricht an unseren RabbitMQ-Nachrichtenbroker, wo mehrere Arbeiter auf neue Nachrichten warten. Diese Mitarbeiter erstellen PDF-Rechnungen und senden sie an die entsprechenden Benutzer.@Service
class BillingService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
// преобразует счет, например, в json и использует его Wie тело Mitteilungen
rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
// ...
}
}
Potenzielle Fehler sehen jetzt etwas anders aus, da Sie nicht mehr wie bei einer synchronen HTTP-Verbindung sofort OK- oder FEHLER-Antworten erhalten. Stattdessen könnten drei mögliche Szenarien schiefgehen, die folgende Fragen aufwerfen könnten:
- Wurde meine Nachricht vom Mitarbeiter übermittelt und verwendet? Oder ist es verloren? (Der Nutzer erhält keine Rechnung).
- Wurde meine Nachricht nur einmal zugestellt? Oder mehr als einmal geliefert und nur einmal bearbeitet? (Der Benutzer erhält mehrere Rechnungen).
- Konfiguration: Von „Habe ich die richtigen Routing-Schlüssel/Namen für den Austausch verwendet“ bis „Ist mein Nachrichtenbroker richtig konfiguriert und gewartet oder sind seine Warteschlangen voll?“ (Der Nutzer erhält keine Rechnung).
- Wenn Sie JMS-Implementierungen wie ActiveMQ verwenden, können Sie Geschwindigkeit gegen die Garantie von Zweiphasen-Commits (XA) eintauschen.
- Wenn Sie RabbitMQ verwenden, lesen Sie zuerst dieses Tutorial und denken Sie dann sorgfältig über Bestätigungen, Fehlertoleranz und Nachrichtenzuverlässigkeit im Allgemeinen nach.
- Vielleicht kennt sich jemand gut mit der Konfiguration von Active- oder RabbitMQ-Servern aus, insbesondere in Kombination mit Clustering und Docker (irgendjemand? ;))
Welches Framework wäre die beste Lösung für Java-Microservices?
Einerseits können Sie eine sehr beliebte Option wie Spring Boot installieren . Es macht das Erstellen von .jar-Dateien sehr einfach, verfügt über einen integrierten Webserver wie Tomcat oder Jetty und kann schnell und überall ausgeführt werden. Ideal zum Erstellen von Microservice-Anwendungen. Kürzlich sind einige spezialisierte Microservice-Frameworks, Kubernetes oder GraalVM , erschienen, die teilweise von reaktiver Programmierung inspiriert sind. Hier sind ein paar weitere interessante Konkurrenten: Quarkus , Micronaut , Vert.x , Helidon . Am Ende müssen Sie selbst entscheiden, aber wir können Ihnen ein paar Empfehlungen geben, die vielleicht nicht ganz Standard sind: Mit Ausnahme von Spring Boot werden alle Microservices-Frameworks in der Regel als unglaublich schnell und mit nahezu sofortigem Start vermarktet , geringer Speicherverbrauch, Skalierbarkeit bis ins Unendliche. Marketingmaterialien enthalten in der Regel beeindruckende Grafiken, die die Plattform neben dem riesigen Spring Boot oder nebeneinander präsentieren. Dies schont theoretisch die Nerven von Entwicklern, die ältere Projekte unterstützen, deren Laden manchmal mehrere Minuten dauert. Oder Entwickler, die in der Cloud arbeiten und innerhalb von 50 ms so viele Mikrocontainer starten/stoppen wollen, wie sie gerade benötigen. Das Problem besteht jedoch darin, dass diese (künstlichen) Bare-Metal-Startzeiten und Umschichtungszeiten kaum zum Gesamterfolg des Projekts beitragen. Zumindest beeinflussen sie viel weniger als eine starke Framework-Infrastruktur, eine starke Dokumentation, eine Community und starke Entwicklerfähigkeiten. Daher ist es besser, es so zu betrachten: Wenn bisher:- Sie lassen Ihre ORMs ungebremst laufen und generieren Hunderte von Abfragen für einfache Workflows.
- Sie benötigen endlose Gigabyte, um Ihren mäßig komplexen Monolithen zu betreiben.
- Sie haben so viel Code und die Komplexität ist so hoch (wir sprechen jetzt nicht von potenziell langsamen Startern wie Hibernate), dass das Laden Ihrer Anwendung mehrere Minuten dauert.
Welche Bibliotheken eignen sich am besten für synchrone Java-REST-Aufrufe?
Auf der unteren technischen Seite werden Sie wahrscheinlich mit einer der folgenden HTTP-Client-Bibliotheken enden: Javas nativer HttpClient (seit Java 11), Apaches HttpClient oder OkHttp . Beachten Sie, dass ich hier „wahrscheinlich“ sage, weil es andere Optionen gibt, die von guten alten JAX-RS-Clients bis hin zu modernen WebSocket- Clients reichen . Auf jeden Fall geht der Trend hin zur Generierung eines HTTP-Clients, weg vom selbständigen Hantieren mit HTTP-Aufrufen. Dazu müssen Sie einen Blick auf das OpenFeign- Projekt und seine Dokumentation als Ausgangspunkt für weitere Lektüre werfen.Was sind die besten Broker für asynchrones Java-Messaging?
Höchstwahrscheinlich werden Sie auf das beliebte ActiveMQ (Classic oder Artemis) , RabbitMQ oder Kafka stoßen .- ActiveMQ und RabbitMQ sind traditionelle, vollwertige Nachrichtenbroker. Dabei handelt es sich um das Zusammenspiel eines „intelligenten Brokers“ und „dummer Nutzer“.
- In der Vergangenheit hatte ActiveMQ den Vorteil eines einfachen Inlinings (zum Testen), das mit RabbitMQ/Docker/TestContainer-Einstellungen abgemildert werden kann.
- Kafka kann nicht als traditioneller „intelligenter“ Broker bezeichnet werden. Stattdessen handelt es sich um einen relativ „dummen“ Nachrichtenspeicher (Protokolldatei), für dessen Verarbeitung intelligente Verbraucher erforderlich sind.
Welche Bibliotheken kann ich zum Testen von Microservices verwenden?
Es hängt von Ihrem Stapel ab. Wenn Sie ein Spring-Ökosystem im Einsatz haben, ist es ratsam, die spezifischen Tools des Frameworks zu verwenden . Wenn JavaEE so etwas wie Arquillian ist . Eventuell lohnt sich ein Blick auf Docker und die wirklich gute Testcontainers- Bibliothek , die insbesondere dabei hilft, einfach und schnell eine Oracle-Datenbank für lokale Entwicklungs- oder Integrationstests einzurichten. Für Probetests ganzer HTTP-Server schauen Sie sich Wiremock an . Um asynchrones Messaging zu testen, versuchen Sie, ActiveMQ oder RabbitMQ zu implementieren und dann Tests mit Awaitility DSL zu schreiben . Darüber hinaus kommen alle Ihre üblichen Tools zum Einsatz – Junit , TestNG für AssertJ und Mockito . Bitte beachten Sie, dass dies keine vollständige Liste ist. Wenn Sie Ihr Lieblingstool hier nicht finden, posten Sie es bitte im Kommentarbereich.Wie aktiviere ich die Protokollierung für alle Java-Microservices?
Die Protokollierung bei Microservices ist ein interessantes und recht komplexes Thema. Anstelle einer Protokolldatei, die Sie mit den Befehlen less oder grep bearbeiten können, verfügen Sie jetzt über n Protokolldateien, und Sie möchten, dass diese nicht zu verstreut sind. Die Merkmale des Protokollierungs-Ökosystems werden in diesem Artikel (auf Englisch) ausführlich beschrieben. Lesen Sie es unbedingt durch und achten Sie auf den Abschnitt „Zentralisierte Protokollierung aus der Perspektive von Microservices“ . In der Praxis werden Sie auf unterschiedliche Ansätze stoßen: Der Systemadministrator schreibt bestimmte Skripte, die Protokolldateien verschiedener Server sammeln, in einer einzigen Protokolldatei zusammenführen und auf FTP-Servern zum Herunterladen bereitstellen. Ausführen von cat/grep/unig/sort-Kombinationen in parallelen SSH-Sitzungen. Genau das macht Amazon AWS, und Sie können es Ihrem Vorgesetzten mitteilen. Verwenden Sie ein Tool wie Graylog oder ELK Stack (Elasticsearch, Logstash, Kibana)Wie finden meine Microservices einander?
Bisher sind wir davon ausgegangen, dass unsere Microservices voneinander wissen und das entsprechende IPS kennen. Lassen Sie uns über die statische Konfiguration sprechen. Unser Bankmonolith [ip = 192.168.200.1] weiß also, dass er mit dem Risikoserver [ip = 192.168.200.2] kommunizieren muss, der in der Eigenschaftendatei fest codiert ist. Sie können die Dinge jedoch dynamischer gestalten:- Verwenden Sie einen cloudbasierten Konfigurationsserver, von dem alle Microservices ihre Konfigurationen abrufen, anstatt application.properties-Dateien auf ihren Microservices bereitzustellen.
- Da Ihre Dienstinstanzen ihren Standort dynamisch ändern können, lohnt es sich, nach Diensten zu suchen, die wissen, wo sich Ihre Dienste befinden, welche IP-Adressen sie haben und wie sie weitergeleitet werden.
- Da nun alles dynamisch ist, entstehen neue Probleme, wie zum Beispiel die automatische Wahl eines Leiters: Wer ist der Herr, der bestimmte Aufgaben bearbeitet, um sie beispielsweise nicht zweimal zu bearbeiten? Wer ersetzt einen Anführer, wenn er versagt? Auf welcher Grundlage erfolgt die Ersetzung?
Wie organisiert man Autorisierung und Authentifizierung mithilfe von Java-Microservices?
Dieses Thema verdient auch eine eigene Geschichte. Auch hier reichen die Optionen von hartcodierter grundlegender HTTPS-Authentifizierung mit benutzerdefinierten Sicherheits-Frameworks bis hin zur Ausführung einer Oauth2-Installation mit einem eigenen Autorisierungsserver.Wie kann ich sicherstellen, dass alle meine Umgebungen gleich aussehen?
Was für Bereitstellungen ohne Microservice gilt, gilt auch für Bereitstellungen mit einem. Versuchen Sie es mit einer Kombination aus Docker/Testcontainers und Scripting/Ansible.Keine Frage: kurz zu YAML
Lassen Sie uns für einen Moment von Bibliotheken und verwandten Themen Abstand nehmen und einen kurzen Blick auf Yaml werfen. Dieses Dateiformat wird de facto als Format zum „Schreiben von Konfiguration als Code“ verwendet. Es wird auch von einfachen Tools wie Ansible und Giganten wie Kubernetes verwendet. Um den Schmerz der YAML-Einrückung zu erleben, versuchen Sie, eine einfache Ansible-Datei zu schreiben und sehen Sie, wie viel Sie an der Datei bearbeiten müssen, bevor sie wie erwartet funktioniert. Und das, obwohl das Format von allen wichtigen IDEs unterstützt wird! Kommen Sie danach noch einmal zurück, um diesen Leitfaden zu Ende zu lesen.Yaml:
- is:
- so
- great
Was ist mit verteilten Transaktionen? Leistungstest? Andere Themen?
Vielleicht eines Tages in zukünftigen Ausgaben des Handbuchs. Im Moment ist das alles. Bleib bei uns!Konzeptionelle Probleme mit Microservices
Neben den spezifischen Problemen von Microservices in Java gibt es beispielsweise noch andere Probleme, die in jedem Microservice-Projekt auftreten. Sie beziehen sich hauptsächlich auf Organisation, Team und Management.Nichtübereinstimmung zwischen Frontend und Backend
Die Nichtübereinstimmung von Frontend und Backend ist in vielen Microservice-Projekten ein sehr häufiges Problem. Was bedeutet das? Nur dass Web-Interface-Entwickler in den guten alten Monolithen eine bestimmte Quelle zum Abrufen von Daten hatten. In Microservice-Projekten haben Frontend-Entwickler plötzlich n Quellen, aus denen sie Daten beziehen können. Stellen Sie sich vor, Sie erstellen eine Art IoT-Microservices-Projekt (Internet of Things) in Java. Nehmen wir an, Sie verwalten geodätische Maschinen und Industrieöfen in ganz Europa. Und diese Öfen senden Ihnen regelmäßig Updates mit ihren Temperaturen und dergleichen. Früher oder später möchten Sie möglicherweise Öfen in der Admin-Benutzeroberfläche finden, möglicherweise mithilfe der Microservices „Ofensuche“. Abhängig davon, wie strikt Ihre Back-End-Kollegen die domänengesteuerten Design- oder Microservice-Gesetze anwenden, gibt der Microservice „Find Oven“ möglicherweise nur Ofen-IDs und keine anderen Daten wie Typ, Modell oder Standort zurück. Dazu müssen Frontend-Entwickler einen oder mehrere zusätzliche Aufrufe (abhängig von der Paging-Implementierung) im Microservice „Get Oven Data“ mit den IDs durchführen, die sie vom ersten Microservice erhalten haben. Und obwohl dies nur ein einfaches Beispiel ist, wenn auch einem echten (!) Projekt entnommen, zeigt es doch folgendes Problem: Supermärkte sind extrem beliebt geworden. Denn mit ihnen müssen Sie nicht an 10 verschiedene Orte gehen, um Gemüse, Limonade, Tiefkühlpizza und Toilettenpapier zu kaufen. Stattdessen gehen Sie an einen Ort. Das ist einfacher und schneller. Das Gleiche gilt für Frontend- und Microservice-Entwickler.Erwartungen des Managements
Das Management hat den falschen Eindruck, dass es nun unendlich viele Entwickler für ein (übergreifendes) Projekt einstellen muss, da die Entwickler nun völlig unabhängig voneinander arbeiten können, jeder an seinem eigenen Microservice. Lediglich ganz am Ende (kurz vor dem Start) ist noch ein wenig Integrationsarbeit nötig. Tatsächlich ist dieser Ansatz äußerst problematisch. In den folgenden Abschnitten werden wir versuchen zu erklären, warum.„Kleinere Stücke“ sind nicht gleich „bessere Stücke“
Es wäre ein großer Fehler anzunehmen, dass in 20 Teile unterteilter Code notwendigerweise eine höhere Qualität aufweist als ein ganzes Stück. Selbst wenn wir Qualität aus rein technischer Sicht betrachten, führen unsere einzelnen Dienste möglicherweise immer noch 400 Hibernate-Abfragen aus, um einen Benutzer aus der Datenbank auszuwählen, und durchlaufen dabei Schichten nicht unterstützten Codes. Wir kehren noch einmal zum Zitat von Simon Brown zurück: Wenn es nicht gelingt, Monolithen richtig zu bauen, wird es schwierig, richtige Microservices zu bauen. Es ist oft extrem spät, über Fehlertoleranz in Microservice-Projekten zu sprechen. So sehr, dass es manchmal beängstigend ist, zu sehen, wie Microservices in realen Projekten funktionieren. Der Grund dafür ist, dass Java-Entwickler nicht immer bereit sind, Fehlertoleranz, Netzwerke und andere verwandte Themen auf dem richtigen Niveau zu studieren. Die „Teile“ selbst sind kleiner, die „technischen Teile“ jedoch größer. Stellen Sie sich vor, Ihr Microservices-Team wird gebeten, einen technischen Microservice für die Anmeldung bei einem Datenbanksystem zu schreiben, etwa so:@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;
}
// 'Ю-ху, залогинoderсь!';
// установите cookies, делайте, что угодно
return true;
}
}
Jetzt kommt Ihr Team möglicherweise zu dem Schluss (und überzeugt vielleicht sogar die Geschäftsleute), dass dies alles zu einfach und langweilig ist. Anstatt einen Anmeldedienst zu schreiben, ist es besser, einen wirklich nützlichen UserStateChanged-Mikrodienst ohne echte und greifbare geschäftliche Auswirkungen zu schreiben. Und da einige Leute Java derzeit wie einen Dinosaurier behandeln, schreiben wir unseren UserStateChanged-Microservice im modischen Erlang. Und versuchen wir mal, irgendwo rot-schwarze Bäume zu verwenden, denn Steve Yegge hat geschrieben, dass man sie in- und auswendig kennen muss, um sich bei Google zu bewerben. Aus Sicht der Integration, der Wartung und des Gesamtdesigns ist dies genauso schlimm wie das Schreiben von Spaghetti-Codeschichten in einen einzigen Monolithen. Ein künstliches und gewöhnliches Beispiel? So ist das. Dies kann jedoch in der Realität passieren.
Weniger Teile – weniger Verständnis
Dann stellt sich natürlich die Frage, wie man das System als Ganzes, seine Prozesse und Arbeitsabläufe versteht, aber gleichzeitig sind Sie als Entwickler nur für die Arbeit an Ihrem isolierten Microservice verantwortlich [95: login-101: updateUserProfile]. Es stimmt mit dem vorherigen Absatz überein, aber abhängig von Ihrer Organisation, dem Grad des Vertrauens und der Kommunikation kann dies zu großer Verwirrung, Schulterzucken und Schuldzuweisungen führen, wenn es zu einem versehentlichen Zusammenbruch der Microservice-Kette kommt. Und es gibt niemanden, der die volle Verantwortung für das, was passiert ist, übernehmen würde. Und es geht überhaupt nicht um Unehrlichkeit. Tatsächlich ist es sehr schwierig, verschiedene Teile miteinander zu verbinden und ihren Platz im Gesamtbild des Projekts zu verstehen.Kommunikation und Service
Der Kommunikations- und Servicegrad variiert stark je nach Unternehmensgröße. Der allgemeine Zusammenhang ist jedoch offensichtlich: Je mehr, desto problematischer.- Wer betreibt Microservice Nr. 47?
- Haben sie gerade eine neue, inkompatible Version eines Microservices bereitgestellt? Wo wurde das dokumentiert?
- Mit wem muss ich sprechen, um eine neue Funktion anzufordern?
- Wer wird diesen Mikroservice in Erlang unterstützen, nachdem der einzige, der diese Sprache beherrschte, das Unternehmen verlassen hat?
- Alle unsere Microservice-Teams arbeiten nicht nur in unterschiedlichen Programmiersprachen, sondern auch in unterschiedlichen Zeitzonen! Wie koordinieren wir das alles richtig?
GO TO FULL VERSION