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 mit einem reaktiven Ansatz gegenüberstanden.
Reactive umfasst eine Reihe von Prinzipien und Richtlinien, die auf den Aufbau reaktionsfähiger verteilter Systeme und Anwendungen abzielen, 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 Blockierungsoperationen 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 Multithreading
E/A-Vorgänge 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 im Leerlauf während Blockierungsvorgängen verringert (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 faule 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, bei 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: A Handbook of Agile Software Craftsmanship
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 die reaktive Programmierung 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 dieX-Achsedie zunehmende Komplexität unserer Codebasis im Laufe ihrer Entwicklung dar, während dieY-Achsedie Zeit darstellt, die für diese Entwicklungsänderungen erforderlich ist.
Das obige ist der detaillierte Inhalt vonWarum haben wir die reaktive Systemarchitektur aus unserem Code verworfen?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!