Leitfaden für DeferredResult im Frühling

Leitfaden zu DeferredResult im Frühjahr

1. Überblick

In diesem Tutorial sehen wir unshow we can use the DeferredResult class in Spring MVC to perform asynchronous request processing an.

Die asynchrone Unterstützung wurde in Servlet 3.0 eingeführt und ermöglicht die Verarbeitung einer HTTP-Anforderung in einem anderen Thread als dem Anforderungsempfänger-Thread.

DeferredResult,, verfügbar ab Spring 3.2, unterstützt das Auslagern einer lang laufenden Berechnung von einem http-Worker-Thread in einen separaten Thread.

Obwohl der andere Thread einige Ressourcen für die Berechnung benötigt, werden die Arbeitsthreads in der Zwischenzeit nicht blockiert und können eingehende Clientanforderungen verarbeiten.

Das asynchrone Anforderungsverarbeitungsmodell ist sehr nützlich, da es die Skalierung einer Anwendung bei hohen Lasten erleichtert, insbesondere bei E / A-intensiven Vorgängen.

2. Konfiguration

In unseren Beispielen verwenden wir eine Spring Boot-Anwendung. Weitere Informationen zum Booten der Anwendung finden Sie in unseren vorherigenarticle.

Als Nächstes zeigen wir sowohl die synchrone als auch die asynchrone Kommunikation mitDeferredResult and und vergleichen, wie asynchron eine für Skalierungsfälle mit hoher Last und E / A-Intensität besser skaliert.

3. REST-Service blockieren

Beginnen wir mit der Entwicklung eines Standard-Blocking-REST-Service:

@GetMapping("/process-blocking")
public ResponseEntity handleReqSync(Model model) {
    // ...
    return ResponseEntity.ok("ok");
}

Das Problem hierbei ist, dassthe request processing thread is blocked until the complete request is processed und das Ergebnis zurückgegeben werden. Bei langwierigen Berechnungen ist dies eine suboptimale Lösung.

Um dies zu beheben, können wir Container-Threads besser nutzen, um Client-Anforderungen zu verarbeiten, wie wir im nächsten Abschnitt sehen werden.

4. Nicht blockierende REST mitDeferredResult

Um ein Blockieren zu vermeiden, verwenden wir ein auf Rückrufen basierendes Programmiermodell, bei dem anstelle des tatsächlichen Ergebnisses einDeferredResult an den Servlet-Container zurückgegeben wird.

@GetMapping("/async-deferredresult")
public DeferredResult> handleReqDefResult(Model model) {
    LOG.info("Received async-deferredresult request");
    DeferredResult> output = new DeferredResult<>();

    ForkJoinPool.commonPool().submit(() -> {
        LOG.info("Processing in separate thread");
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
        }
        output.setResult(ResponseEntity.ok("ok"));
    });

    LOG.info("servlet thread freed");
    return output;
}

Die Anforderungsverarbeitung erfolgt in einem separaten Thread. Nach Abschluss rufen wir die OperationsetResultfür das ObjektDeferredResultauf.

Schauen wir uns die Protokollausgabe an, um zu überprüfen, ob sich unsere Threads wie erwartet verhalten:

[nio-8080-exec-6] com.example.controller.AsyncDeferredResultController:
Received async-deferredresult request
[nio-8080-exec-6] com.example.controller.AsyncDeferredResultController:
Servlet thread freed
[nio-8080-exec-6] java.lang.Thread : Processing in separate thread

Intern wird der Container-Thread benachrichtigt und die HTTP-Antwort an den Client übermittelt. Die Verbindung bleibt vom Container (Servlet 3.0 oder höher) geöffnet, bis die Antwort eintrifft oder das Zeitlimit überschritten wird.

5. DeferredResult Rückrufe

Wir können drei Arten von Rückrufen mit einem DeferredResult registrieren: Abschluss, Timeout und Fehlerrückrufe.

Verwenden Sie die MethodeonCompletion(), um einen Codeblock zu definieren, der ausgeführt wird, wenn eine asynchrone Anforderung abgeschlossen ist:

deferredResult.onCompletion(() -> LOG.info("Processing complete"));

In ähnlicher Weise können wironTimeout() verwenden, um benutzerdefinierten Code zu registrieren, der aufgerufen werden soll, sobald eine Zeitüberschreitung auftritt. Um die Bearbeitungszeit für Anforderungen zu begrenzen, können wir während der Objekterstellung vonDeferredResulteinen Zeitüberschreitungswert übergeben:

DeferredResult> deferredResult = new DeferredResult<>(500l);

deferredResult.onTimeout(() ->
  deferredResult.setErrorResult(
    ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT)
      .body("Request timeout occurred.")));

Im Falle von Zeitüberschreitungen legen wir über den mitDeferredResult registrierten Zeitüberschreitungs-Handler einen anderen Antwortstatus fest.

Lösen wir einen Timeout-Fehler aus, indem wir eine Anforderung verarbeiten, die länger als die definierten Timeout-Werte von 5 Sekunden dauert:

ForkJoinPool.commonPool().submit(() -> {
    LOG.info("Processing in separate thread");
    try {
        Thread.sleep(6000);
    } catch (InterruptedException e) {
        ...
    }
    deferredResult.setResult(ResponseEntity.ok("OK")));
});

Schauen wir uns die Protokolle an:

[nio-8080-exec-6] com.example.controller.DeferredResultController:
servlet thread freed
[nio-8080-exec-6] java.lang.Thread: Processing in separate thread
[nio-8080-exec-6] com.example.controller.DeferredResultController:
Request timeout occurred

Es wird Szenarien geben, in denen die Langzeitberechnung aufgrund eines Fehlers oder einer Ausnahme fehlschlägt. In diesem Fall können wir auch einen Rückruf vononError()registrieren:

deferredResult.onError((Throwable t) -> {
    deferredResult.setErrorResult(
      ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
        .body("An error occurred."));
});

Im Falle eines Fehlers legen wir bei der Berechnung der Antwort über diesen Fehlerbehandler einen anderen Antwortstatus und Nachrichtentext fest.

6. Fazit

In diesem kurzen Artikel haben wir uns angesehen, wie Spring MVCDeferredResultdie Erstellung asynchroner Endpunkte erleichtert.

Wie üblich ist der vollständige Quellcodeover on Github verfügbar.