[JAVA] Resilience4j TimeLimiter überschreitet Methodenaufrufe

Hintergrund

Es gibt immer mehr Szenen, in denen Azure Cosmos DB, AWS S3, Faxsende-Dienst und SDK verwendet werden, die einen externen Dienst aufrufen.

In der Welt, in der wir normal gelebt haben, müssen wir uns nur um Leben und Tod des von uns verwalteten Datenbankservers sorgen, und die Netzwerkentfernung ist sehr gering. Alles entsprach den Erwartungen, aber es ist nicht einfach, diese externen Dienste zu nutzen.

Netzwerkverzögerungen sind nicht stabil und manchmal können Verzögerungen in Sekunden auftreten. Viele Dienste werden mit Endpunkten nach Hostnamen bereitgestellt, und IP-Adressen ändern sich ohne vorherige Ankündigung.

Wenn ein solches instabiles Ereignis auftritt, können sich die Threads, die jeden Dienst als HTTP aufrufen und blockieren, plötzlich ansammeln und schließlich dazu führen, dass der gesamte Dienst gestoppt wird.

Mit anderen Worten, Sie müssen darauf achten, den Haupt-HTTP-Thread nicht mit einem Timeout für Funktionsaufrufe zum Blockieren von Aufrufen zu füllen.

Mit Ruby ist das Schreiben relativ einfach.

require 'timeout'

def callAnotherWorld
    sleep 3
    return "Das Ergebnis des Aufrufs eines externen Dienstes"
end

begin
  result = Timeout.timeout(1) do #Zeitüberschreitung in 1 Sekunde
    callAnotherWorld()
  end
rescue Timeout::Error
  result = ""
end

p result                         #Leer, weil es eine Zeitüberschreitung gibt

Der Code ist in Java fast der gleiche.

private final ExecutorService threadPool = Executors.newCachedThreadPool();

private String callAnotherWorld() {
    Future<String> f = threadPool.submit(() -> innerCallAnotherWorld());

    while (true) {
        try {
            f.get(1, TimeUnit.SECONDS);  //Zeitüberschreitung in 1 Sekunde
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException retry) {
        }
    }
}

private String innerCallAnotherWorld() {
    return "Das Ergebnis des Aufrufs eines externen Dienstes";
}

Sowohl in Ruby als auch in Java wird die Methode in einem Hintergrundthread ausgeführt, und der aufrufende Thread wartet auf seinen Abschluss.

Im Fall von Java wurden die Existenz des Thread-Pools und die Existenz von Future gesehen, was mich ein wenig enttäuscht.

Resilience4J

Also Resilience4J.

Resilience4J ist ein neuer Boom, der es einfach macht, die folgenden Muster zu verwenden, um die Systemstabilität in verteilten Architekturen zu verbessern:

Verwenden Sie TimeLimiter, um das Zeitlimit für den Methodenaufruf zu realisieren. Die Verwendung von CircuitBreaker wird bald ein Erfolg sein, aber es gibt nicht viele Informationen über TimeLimiter, daher werde ich es als Memorandum für mich selbst schreiben.

private final ExecutorService threadPool = Executors.newCachedThreadPool();

private final TimeLimiterConfig config = TimeLimiterConfig.custom()
        .timeoutDuration(Duration.ofMillis(1000))
        .cancelRunningFuture(true)
        .build();

private String callAnotherWorld() {

    Callable<String> callable = TimeLimiter
        .decorateFutureSupplier(
            TimeLimiter.of(config),
            () -> threadPool.submit(() -> innerCallAnotherWorld())
        );

    return Try.of(callable::call)
            .getOrElse("");
}

Es wird nicht zu kurz sein, aber es wird die Behandlung von Fängen und Ausnahmen eliminieren und der Code wird sich auf die Geschäftslogik konzentrieren.

Die von Resilience4J bereitgestellten Funktionen sind wesentliche Entwurfsmuster in der heutigen verteilten Umgebung und sehr lehrreich.

Recommended Posts

Resilience4j TimeLimiter überschreitet Methodenaufrufe
16 Entspricht dem Methodenaufruf
20 Entspricht statischen Methodenaufrufen
Überlegungen zur Zeitmethode