In diesem Artikel geht es um unsere Entscheidung, in unserem Softwareprojekt von der reaktiven Architektur abzuweichen. Wir werden uns mit den Grundprinzipien reaktiver Systeme, den Vorteilen nicht blockierender E/A und den Herausforderungen befassen, denen wir bei einem reaktiven Ansatz gegenüberstanden.
Reactive umfasst eine Reihe von Prinzipien und Richtlinien zum Aufbau reaktionsfähiger verteilter Systeme und Anwendungen, gekennzeichnet durch:
Ein wesentlicher Vorteil reaktiver Systeme ist die Verwendung nicht blockierender E/A. Dieser Ansatz vermeidet das Blockieren von Threads während E/A-Vorgängen, sodass ein einzelner Thread mehrere Anforderungen gleichzeitig bearbeiten kann. Dies kann die Systemeffizienz im Vergleich zu herkömmlichen blockierenden E/A erheblich verbessern.
Beim herkömmlichen Multithreading stellen Blockierungsvorgänge erhebliche Herausforderungen bei der Optimierung von Systemen dar (Abbildung 1). Gierige Anwendungen, die übermäßig viel Arbeitsspeicher verbrauchen, sind ineffizient und benachteiligen andere Anwendungen, was oft die Anforderung zusätzlicher Ressourcen wie Arbeitsspeicher, CPU oder größerer virtueller Maschinen erforderlich macht.
Abbildung 1 – Traditionelles Multi-Threading
E/A-Operationen sind ein wesentlicher Bestandteil moderner Systeme, und ihre effiziente Verwaltung ist von größter Bedeutung, um gieriges Verhalten zu verhindern. Reaktive Systeme verwenden nicht blockierende E/A, sodass eine geringe Anzahl von Betriebssystem-Threads zahlreiche gleichzeitige E/A-Vorgänge verarbeiten kann.
Obwohl nicht blockierendes I/O erhebliche Vorteile bietet, führt es ein neuartiges Ausführungsmodell ein, das sich von herkömmlichen Frameworks unterscheidet. Zur Behebung dieses Problems wurde eine reaktive Programmierung entwickelt, die die Ineffizienz von Plattform-Threads verringert, die während Blockierungsvorgängen im Leerlauf sind (Abbildung 2).
Abbildung 2 – Reaktive Ereignisschleife
Quarkus nutzt eine reaktive Engine, die auf Eclipse Vert.x und Netty basiert und nicht blockierende I/O-Interaktionen ermöglicht. Mutiny, der bevorzugte Ansatz zum Schreiben von reaktivem Code mit Quarkus, übernimmt ein ereignisgesteuertes Paradigma, bei dem Reaktionen durch empfangene Ereignisse ausgelöst werden.
Mutiny bietet zwei ereignisgesteuerte und Lazy-Typen:
Während reaktive Systeme Vorteile bieten, sind wir während der Entwicklung auf mehrere Herausforderungen gestoßen:
„Tatsächlich liegt das Verhältnis der Zeit, die mit Lesen und Schreiben verbracht wird, weit über 10 zu 1. Wir lesen ständig alten Code, um neuen Code zu schreiben es einfacher zu schreiben.“
― Robert C. Martin, Clean Code: Ein Handbuch für agile Software-Handwerkskunst
Hier ist ein Beispiel für reaktiven Code, der Mutiny verwendet, um die Komplexität zu veranschaulichen:
Multi.createFrom().ticks().every(Duration.ofSeconds(15)) .onItem().invoke(() - > Multi.createFrom().iterable(configs()) .onItem().transform(configuration - > { try { return Tuple2.of(openAPIConfiguration, RestClientBuilder.newBuilder() .baseUrl(new URL(configuration.url())) .build(MyReactiveRestClient.class) .getAPIResponse()); } catch (MalformedURLException e) { log.error("Unable to create url"); } return null; }).collect().asList().toMulti().onItem().transformToMultiAndConcatenate(tuples - > { AtomicInteger callbackCount = new AtomicInteger(); return Multi.createFrom().emitter(emitter - > Multi.createFrom().iterable(tuples) .subscribe().with(tuple - > tuple.getItem2().subscribe().with(response - > { emitter.emit(callbackCount.incrementAndGet()); if (callbackCount.get() == tuples.size()) { emitter.complete(); } }) )); }).subscribe().with(s - > {}, Throwable::printStackTrace, () - > doSomethingUponComplete())) .subscribe().with(aLong - > log.info("Tic Tac with iteration: " aLong));
Project Loom, eine aktuelle Entwicklung im Java-Ökosystem, verspricht, die mit Blockierungsvorgängen verbundenen Probleme zu mildern. Durch die Möglichkeit der Erstellung Tausender virtueller Threads ohne Hardwareänderungen könnte Project Loom in vielen Fällen möglicherweise die Notwendigkeit eines reaktiven Ansatzes überflüssig machen.
"Project Loom wird Reactive Programming töten"
―Brian Goetz
Zusammenfassend lässt sich sagen, dass unsere Entscheidung, vom reaktiven Architekturstil abzuweichen, ein pragmatischer Ansatz für die langfristige Wartbarkeit unseres Projekts ist. Während reaktive Systeme potenzielle Vorteile bieten, überwogen die Herausforderungen, die sie für unser Team darstellten, diese Vorteile in unserem spezifischen Kontext.
Wichtig ist, dass diese Verschiebung die Leistung nicht beeinträchtigte. Dies ist ein positives Ergebnis, da es zeigt, dass eine gut konzipierte nicht reaktive (imperative) Architektur die erforderliche Leistung liefern kann, ohne die Komplexität, die in unserem Fall mit einer reaktiven Architektur verbunden ist.
Wenn wir in die Zukunft blicken, liegt der Fokus weiterhin auf dem Aufbau einer Codebasis, die nicht nur funktional, sondern auch für Entwickler aller Erfahrungsstufen leicht zu verstehen und zu warten ist. Dies verkürzt nicht nur die Entwicklungszeit, sondern fördert auch eine bessere Zusammenarbeit und den Wissensaustausch innerhalb des Teams.
In der folgenden Grafik stellt die X-Achse die zunehmende Komplexität unserer Codebasis im Laufe ihrer Entwicklung dar, während die Y-Achse die für diese Entwicklungsänderungen erforderliche Zeit darstellt.
Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.
Copyright© 2022 湘ICP备2022001581号-3