Einführung in den Spring Reactor

Einführung in Spring Reactor

1. Überblick

In diesem kurzen Artikel stellen wir das Spring Reactor-Projekt vor. Wir erstellen ein reales Szenario für eine reaktive, ereignisgesteuerte Anwendung.

2. Die Grundlagen des Federreaktors

2.1. Warum Reaktor?

Das reaktive Designpattern ist eine ereignisbasierte Architektur für die asynchrone Verarbeitung eines großen Volumens gleichzeitiger Dienstanforderungen von einzelnen oder mehreren Diensthandlern.

Das Spring Reactor-Projekt basiert auf diesem Muster und hat das klare und ehrgeizige Ziel, asynchrone, reaktive Anwendungen aufJVM.zu erstellen

2.2. Beispielszenarien

Bevor wir anfangen, sind hier einige interessante Szenarien, in denen die Nutzung des reaktiven Architekturstils sinnvoll ist, um eine Vorstellung davon zu bekommen, wo Sie ihn anwenden könnten:

  • Benachrichtigungsservice für große Online-Shopping-Anwendungen wie Amazon

  • Riesige Transaktionsabwicklung im Bankensektor

  • Aktienhandel, bei dem sich die Aktienkurse gleichzeitig ändern

Ein kurzer Hinweis ist, dass die Implementierung des Ereignisbusses keine Persistenz von Ereignissen bietet. Genau wie der Standard-Spring-Event-Bus handelt es sich um eine In-Memory-Implementierung.

3. Maven-Abhängigkeiten

Beginnen wir mit der Verwendung von Spring Reactor, indem wir die folgenden Abhängigkeiten zu unserenpom.xml:hinzufügen


    io.projectreactor
    reactor-bus
    2.0.8.RELEASE

Sie können die neueste Version des Reaktorbusses inCentral Maven Repository überprüfen.

4. Erstellen einer Demo-Anwendung

Um die Vorteile des reaktorbasierten Ansatzes besser zu verstehen, werdenlet’s look at a practical example.

Wir werden eine einfache Benachrichtigungs-App erstellen, mit der Benutzer per E-Mail und SMS benachrichtigt werden - nachdem sie ihre Bestellung in einem Online-Shop abgeschlossen haben.

Eine typische synchrone Implementierung wäre natürlich an den Durchsatz des SMS-Dienstes gebunden. Spitzen im Verkehr, solche Feiertage wären in der Regel problematisch.

Mit einem reaktiven Ansatz kann das System flexibler sein und sich besser an Ausfälle oder Zeitüberschreitungen in solchen externen Systemen wie SMS- oder E-Mail-Servern anpassen.

Werfen wir einen Blick auf die Anwendung - angefangen bei den traditionelleren Aspekten bis hin zu den reaktiveren Konstrukten.

4.1. Einfaches POJO

Erstellen wir zunächst einePOJO-Klasse, um die Benachrichtigungsdaten darzustellen:

public class NotificationData {

    private long id;
    private String name;
    private String email;
    private String mobile;

    // getter and setter methods
}

4.2. Die Serviceschicht

Richten wir jetzt eine einfache Service-Schicht ein:

public interface NotificationService {

    void initiateNotification(NotificationData notificationData)
      throws InterruptedException;

}

Und die Implementierung, die hier einen langen Vorgang simuliert:

@Service
public class NotificationServiceimpl implements NotificationService {

    @Override
    public void initiateNotification(NotificationData notificationData)
      throws InterruptedException {

      System.out.println("Notification service started for "
        + "Notification ID: " + notificationData.getId());

      Thread.sleep(5000);

      System.out.println("Notification service ended for "
        + "Notification ID: " + notificationData.getId());
    }
}

Beachten Sie, dass wir zur Veranschaulichung des realen Szenarios des Sendens von Nachrichten über ein SMS-Gateway oder ein E-Mail-Gateway absichtlich eine Verzögerung von 5 Sekunden für dieinitiateNotification-Methode umThread.sleep(5000). einführen

Wenn der Thread den Dienst erreicht, wird er für 5 Sekunden blockiert.

4.3. Der Verbraucher

Lassen Sie uns nun zu den reaktiveren Aspekten unserer Anwendung springen und einen Verbraucher implementieren, den wir dann denreactor event bus zuordnen:

@Service
public class NotificationConsumer implements
  Consumer> {

    @Autowired
    private NotificationService notificationService;

    @Override
    public void accept(Event notificationDataEvent) {
        NotificationData notificationData = notificationDataEvent.getData();

        try {
            notificationService.initiateNotification(notificationData);
        } catch (InterruptedException e) {
            // ignore
        }
    }
}

Wie Sie sehen können, implementiert der Verbraucher einfach die Schnittstelle vonConsumer<T>- mit einer einzigen Methode vonaccept. Es ist diese einfache Implementierung, die die Hauptlogik ausführt, genau wiea typical Spring listener.

4.4. Der Controller

Nachdem wir die Ereignisse nun nutzen können, generieren wir sie auch.

Wir werden das in einem einfachen Controller tun:

@Controller
public class NotificationController {

    @Autowired
    private EventBus eventBus;

    @GetMapping("/startNotification/{param}")
    public void startNotification(@PathVariable Integer param) {
        for (int i = 0; i < param; i++) {
            NotificationData data = new NotificationData();
            data.setId(i);

            eventBus.notify("notificationConsumer", Event.wrap(data));

            System.out.println(
              "Notification " + i + ": notification task submitted successfully");
        }
    }
}

Dies ist ziemlich selbsterklärend - wir senden hier Ereignisse über dieEventBus - unter Verwendung eines eindeutigen Schlüssels.

Einfach ausgedrückt: Wenn ein Client die URL mit dem Parameterwert 10 trifft, werden insgesamt 10 Ereignisse über den Bus gesendet.

4.5. Die Java-Konfiguration

Wir sind fast fertig; Lassen Sie uns einfach alles mit der Java-Konfiguration zusammenfügen und unsere Boot-Anwendung erstellen:

import static reactor.bus.selector.Selectors.$;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application implements CommandLineRunner {

    @Autowired
    private EventBus eventBus;

    @Autowired
    private NotificationConsumer notificationConsumer;

    @Bean
    Environment env() {
        return Environment.initializeIfEmpty().assignErrorJournal();
    }

    @Bean
    EventBus createEventBus(Environment env) {
        return EventBus.create(env, Environment.THREAD_POOL);
    }

    @Override
    public void run(String... args) throws Exception {
        eventBus.on($("notificationConsumer"), notificationConsumer);
    }

    public static void main(String[] args){
        SpringApplication.run(Application.class, args);
    }
}

Hier erstellen wir dieEventBus-Bean über die statischecreate-API inEventBus.

In unserem Fall instanziieren wir den Ereignisbus mit einem Standard-Thread-Pool, der in der Umgebung verfügbar ist.

Wenn wir mehr Kontrolle über den Bus haben möchten, können wir der Implementierung auch eine Thread-Zählung hinzufügen:

EventBus evBus = EventBus.create(
  env,
  Environment.newDispatcher(
    REACTOR_THREAD_COUNT,REACTOR_THREAD_COUNT,
    DispatcherType.THREAD_POOL_EXECUTOR));

Weiter - Beachten Sie auch, wie wir hier den statischen Import des Attributs$verwenden.

Die Funktion bietet einen typsicheren Mechanismus, um Konstanten (in unserem Fall das $ -Attribut) in den Code aufzunehmen, ohne auf die Klasse verweisen zu müssen, die das Feld ursprünglich definiert hat.

Wir nutzen diese Funktionalität in der Implementierung unsererrun-Methode -where we’re registering our consumer to be triggered when the matching notification.

Dies basiert aufa unique selector key, mit denen jeder Verbraucher identifiziert werden kann.

5. Testen Sie die Anwendung

Nach dem Ausführen eines Maven-Builds können wir jetzt einfachjava -jar name_of_the_application.jar ausführen, um die Anwendung auszuführen.

Erstellen wir jetzt eine kleine JUnit-Testklasse, um die Anwendung zu testen. Wir würdenSpringJUnit4ClassRunnervon Spring Boot verwenden, um den Testfall zu erstellen:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {Application.class})
public class DataLoader {

    @Test
    public void exampleTest() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getForObject(
          "http://localhost:8080/startNotification/10", String.class);
    }
}

Führen Sie nun diesen Testfall aus, um die Anwendung zu testen:

Notification 0: notification task submitted successfully
Notification 1: notification task submitted successfully
Notification 2: notification task submitted successfully
Notification 3: notification task submitted successfully
Notification 4: notification task submitted successfully
Notification 5: notification task submitted successfully
Notification 6: notification task submitted successfully
Notification 7: notification task submitted successfully
Notification 8: notification task submitted successfully
Notification 9: notification task submitted successfully
Notification service started for Notification ID: 1
Notification service started for Notification ID: 2
Notification service started for Notification ID: 3
Notification service started for Notification ID: 0
Notification service ended for Notification ID: 1
Notification service ended for Notification ID: 0
Notification service started for Notification ID: 4
Notification service ended for Notification ID: 3
Notification service ended for Notification ID: 2
Notification service started for Notification ID: 6
Notification service started for Notification ID: 5
Notification service started for Notification ID: 7
Notification service ended for Notification ID: 4
Notification service started for Notification ID: 8
Notification service ended for Notification ID: 6
Notification service ended for Notification ID: 5
Notification service started for Notification ID: 9
Notification service ended for Notification ID: 7
Notification service ended for Notification ID: 8
Notification service ended for Notification ID: 9

Wie Sie sehen, werden alle 10 Aufgaben sofort gesendet, sobald der Endpunkt erreicht ist, ohne dass eine Blockierung erfolgt. Nach dem Absenden werden die Benachrichtigungsereignisse parallel verarbeitet.

Beachten Sie, dass in unserem Szenario diese Ereignisse nicht in beliebiger Reihenfolge verarbeitet werden müssen.

6. Fazit

In dieser kleinen Anwendung erhalten wir auf jeden Fall eine Durchsatzsteigerung sowie eine insgesamt ansprechendere Anwendung.

Dieses Szenario zerkratzt jedoch nur die Oberfläche und ist nur eine gute Grundlage, um das reaktive Paradigma zu verstehen.

Wie immer ist der Quellcodeover on GitHub verfügbar.