[JAVA] Einführung in Ratpack (Extra Edition) - Mit Sentry

Thread-basiert vs. ereignisgesteuert

Ratpack arbeitet mit einem nicht blockierenden ereignisgesteuerten Modell. Blockierungsvorgänge wie der Datenbankzugriff müssen in einem separaten Thread ausgeführt werden. Es ist schwierig, dies von Hand zu tun, daher wird Ratpack mit einem "Promise" -Rückrufmechanismus und einem "Blocking" -Dienstprogramm für Multithreading-Unterstützung geliefert. Das ist alles, was Sie mit "Oh, es fühlt sich wie JavaScript an" tun müssen, aber wenn Sie es mit anderen Frameworks verwenden, gibt es einige Probleme.

Java Servlet, das Standard-Java-Webframework, wird auf einem One-Request-1-Thread-Modell ausgeführt. Das Spring Framework basiert ebenfalls auf Servlets, sodass es diesem Modell entspricht. Wenn das Servlet eine HTTP-Anforderung empfängt, erstellt es einen neuen Thread (oder ruft ihn aus dem Pool ab) und führt die Handlermethode aus. Mit diesem Mechanismus kann jede Anforderung in einem separaten Thread ausgeführt werden, sodass Programmierer Programme schreiben können, ohne sich (im Grunde) um die Thread-Sicherheit zu kümmern, z. B. um Ressourcen zwischen Threads zu teilen. Ratpack ist jedoch ein nicht blockierendes Framework, sodass alle Anforderungen in derselben Ereignisschleife und im selben Thread ausgeführt werden (genau etwas anders, aber die meisten Bilder sehen so aus). Da der Blockierungsprozess in einem anderen Thread ausgeführt wird, kann eine Anforderung in einem Thread möglicherweise nicht abgeschlossen werden. Ich denke, die meisten Webanwendungen stellen eine Verbindung zu Datenbanken her oder rufen externe APIs auf. Zusammenfassend stimmen in Servlets und Ratpack der "Kontext" der Ausführung und die Thread-Beziehung nicht überein.

Es ist keine Geschichte, die besser oder schlechter ist, weil es einen Unterschied im Designkonzept an sich darstellt, aber da das Servlet, das de facto der Standard von Java ist, ein 1-Anfrage-1-Thread-Modell ist, werden viele Java-Frameworks unter dieser Annahme erstellt. Ist gewesen Daher gibt es die Situation, dass es nicht zusammen mit Ratpack verwendet werden kann, wie es ist.

ThreadLocal

(Wenn Sie der Meinung sind, dass "ThreadLocal" ein gesunder Menschenverstand ist, fahren Sie mit dem nächsten Absatz fort!)

Ein typisches Beispiel ist ThreadLocal. Thread local ist eine Klasse zum Halten einer Instanz, die für jeden Thread eindeutig ist. Betrachten Sie als Beispiel ThreadLocalRandom. Tatsächlich ist die Java-Klasse Random threadsicher. Es ist sicher, mit mehreren Threads zu teilen. Da sie jedoch synchronisiert sind, leidet die Leistung, wenn ein Konflikt vorliegt. Hier kommt ThreadLocalRandom ins Spiel. Wenn die Threads A und B gleichzeitig auf dasselbe "ThreadLocalRandom" zugreifen, verwendet Thread A das für Thread A spezifische interne "Random", und Thread B verwendet ein separates "Random" für Thread B. .. Die Leistung verschlechtert sich nicht, da keine Synchronisation stattfindet. Es ist auch effizienter, als jedes Mal im laufenden Betrieb eine neue "zufällige" Instanz zu erstellen.

Mit ThreadLocal können Sie Klassen verwenden, die Multithreading nicht unterstützen, ohne sich um die Synchronisation kümmern zu müssen.

Kontextkrise

Wie eingangs erwähnt, wird das Servlet auf 1 Request-1-Thread ausgeführt. Da "ThreadLocal" für jeden Thread eindeutig ist, kann es tatsächlich als "ThreadLocal" hat eine Instanz pro Anforderung "betrachtet werden. Dies war ein sehr nützliches Design zum Verwalten des Ausführungskontexts einer Webanwendung. Wenn Sie beispielsweise Benutzerinformationen threadlokal speichern **, kann das Programm diese threadlokalen Benutzerinformationen als Informationen für die aktuelle Anforderung ** betrachten.

Ein typisches Beispiel ist MDC von logback. Wenn Sie die Benutzerinformationen beim ersten Empfang der Anforderung im MDC speichern, können Sie die Benutzerinformationen in der nachfolgenden Protokollausgabe zusammen verwenden. Die Protokollausgabeseite kann diese Informationen verwenden, ohne zu wissen, welche Anforderung sie aufruft. Es ist bequem.

Wie Sie vielleicht bemerkt haben, ist Ratpack nicht blockierend. Eine Anforderung wird nicht in einem Thread ausgeführt. ** ThreadLocal kann nicht für Ratpack verwendet werden. ** ** **

Nehmen wir ein konkretes Beispiel.

MDC.put("user", "John Smith");
logger.info("[1]")
Blocking.op(() -> {
    logger.info("[2]");
}).then(() -> {
    logger.info("[3]")
    ctx.render( "OK" );
});

Wenn es sich um ein Servlet handelt, beziehen sich alle drei Protokollausgaben auf denselben MDC. In allen Protokollausgaben ist der Benutzerwert des MDC "John Smith". In Ratpack ist die Verarbeitung in "Blocking.op ()" ein separater Thread. Es ist ein separater Thread von dem Thread, der "MDC.put ()" heißt. Der "Benutzer" von "[2]" ist "null".

Execution

Wie verwalten Sie den Kontext in Ratpack wie in einem Servlet?

Tatsächlich führt Ratpack die Verarbeitung in Einheiten von [Execution] aus (https://ratpack.io/manual/current/api/ratpack/exec/Execution.html). Die Regel, dass Ratpacks "Versprechen" nur in einer Ratpack-Anwendung verwendet werden kann, lautete tatsächlich "es kann nur in" Ausführung "verwendet werden". "Ausführung" ist auch der Kontext des Prozesses. Ratpack-Anwendungen verwalten den Kontext in "Ausführung", genau wie ein Thread der Ausführungskontext in einem Servlet ist. Execution verfügt über eine Wörterbuchfunktion, die darin verwendet werden kann und Instanzen eines bestimmten Typs speichern und abrufen kann. Sie können dies anstelle von "ThreadLocal" nutzen.

Ich erwähnte, dass "ThreadLocal" nicht mit Ratpack verwendet werden kann, sondern für Ratpack "MDCInterceptor" für die Verwendung von MDC ) Ist im Voraus vorbereitet. Es ist einfach zu bedienen, registrieren Sie diese Klasse einfach in Registry. MDCInterceptor ist eine Implementierung der Schnittstelle ExecInterceptor. Wie der Name schon sagt, fungiert diese Schnittstelle als Interceptor für "Execution". Das heißt, es wird verwendet, um eine Verarbeitung vor und nach der Ausführung einer bestimmten "Ausführung" einzufügen.

Wenn Sie sich die Implementierung von "MDCInterceptor" ansehen, sehen Sie, dass der Prozess ungefähr wie folgt abläuft.

  1. Extrahieren Sie das Kontextwörterbuch aus Execution. Wenn nicht, erstellen Sie ein leeres Kontextwörterbuch.
  2. Rufen Sie "MDC.setContextMap ()" auf, um das abgerufene Wörterbuch festzulegen.
  3. Führen Sie "Ausführung" aus.
  4. Extrahieren Sie das Kontextwörterbuch aus dem MDC (MDC.getCopyOfContextMap ()) und setzen Sie es auf Execution

Kurz gesagt, vor der eigentlichen Verarbeitung wird der MDC von SLF4J so betrieben, dass die "Ausführung" von Ratpack und der MDC übereinstimmen.

Verwenden Sie Sentry

Ich werde endlich auf das Hauptthema eingehen. Der Java-Client des Protokollüberwachungsdienstes Sentry erfordert auch einige Operationen in Ratpack.

Sentry kann Ausführungskontexte wie Tags und Breadcrumb aufzeichnen und ausgeben. Wie in der Sentry-Dokumentation angegeben (https://docs.sentry.io/clients/java/context/), hängt die Bedeutung von "Kontext" jedoch von der Anwendung ab. Sentry abstrahiert diese Kontextverwaltung mit einer Klasse namens "ContextManager". Standardmäßig werden zwei Implementierungen bereitgestellt, der Java-Client für Android verwendet "SingletonContextManager" und die reguläre Version "ThreadLocalContextManager". Wie der Name schon sagt, handelt es sich um Singleton und Thread Local. Und wie bereits erwähnt, können Thread-Einheimische nicht mit Ratpack verwendet werden. In der Singleton-Implementierung sind die Benutzerinformationen falsch.

Daher werde ich eine einzigartige Implementierung von "ContextManager" schreiben, die "Execution" verwendet.

@Override
public Context getContext() {
    Optional<Execution> currentExecOpt = Execution.currentOpt();

    if ( currentExecOpt.isPresent() ) {
        Execution currentExec = currentExecOpt.get();
        Optional<Context> context = currentExec.maybeGet( Context.class );

        if ( context.isPresent() ) {
            return context.get();
        } else {
            Context newContext = new Context();
            currentExec.add( newContext );
            return newContext;
        }
    } else {
        return singletonContext;
    }
}

Stellen Sie zunächst sicher, dass das aktuelle Programm in Execution ausgeführt wird. Wenn außerhalb von "Ausführung", wird ein einzelnes "Kontext" -Objekt zurückgegeben. Dies kann threadlokal sein, aber ich denke, dass der Code, der außerhalb von "Execution" ausgeführt wird, nur der Serverstartprozess ist, es sei denn, Sie erstellen einen eigenen Thread. Der "Kontext" selbst ist "synchronisiert" und threadsicher, daher habe ich beschlossen, ihn so zu implementieren.

Wenn Sie sich in der "Ausführung" befanden, stellen Sie sicher, dass das "Kontext" -Objekt des Sentry in der aktuellen "Ausführung" gespeichert ist. Wenn es nicht gespeichert wird, erstellen Sie einen neuen "Kontext" und registrieren Sie ihn in "Ausführung". Jetzt wird der "Kontext" von der nachfolgenden "Ausführung" geerbt. Sie können jetzt den gleichen "Kontext" erhalten, auch wenn Sie mit "Blockieren" usw. zu einem anderen Thread wechseln.

Jetzt müssen Sie nur noch diese "ContextManager" -Implementierung verwenden, die durch Überschreiben der "SentryClientFactory" erreicht werden kann.

public final class RatpackSentryClientFactory extends DefaultSentryClientFactory {
    @Override
    protected ContextManager getContextManager( Dsn dsn ) {
        return new RatpackSentryContextManager();
    }
}

Nur das.

Schließlich müssen Sie diese Klasse als Standardmethode zum Erstellen eines Sentry-Clients angeben. Es gibt zwei Möglichkeiten, dies zu tun: Die eine besteht darin, die Methode Sentry.init () zu verwenden, und die andere darin, Eigenschaften zu verwenden.

Sentry.init( new RatpackSentryClientFactory() );

Wenn Sie jedoch einen Client verwenden, der in andere Protokollierungsframeworks wie "sentry-logback" integriert ist, hängt die Sentry-Initialisierung meiner Meinung nach von der globalen "Sentry" -Klasse ab. Tatsächlich ist "sentry-logback" nicht dafür ausgelegt, einen separaten "SentryClient" anzugeben. Daher denke ich, dass es besser ist, es über die Eigenschaftendatei festzulegen.

sentry.properties


factory=factory=rip.deadcode.ratpack.sentry.RatpackSentryClientFactory

Geben Sie den FQCN der erstellten "SentryClientFactory" im Wert des "Factory" -Schlüssels an.

Zusammenfassung

Recommended Posts

Einführung in Ratpack (Extra Edition) - Mit Sentry
Einführung in Ratpack (Extra Edition) - Ratpack in Kotlin geschrieben
Einführung in Ratpack (8) -Session
Einführung in Ratpack (6) --Promise
Einführung in Ratpack (2) -Architektur
Einführung in Ratpack (7) - Guice & Spring
Einführung in Ratpack (1) - Was ist Ratpack?
Einführung in Ratpack (3) - Hallo Welt, detaillierte Erklärung
Einführung in Ruby 2
Einführung in web3j
Einführung in Micronaut 1 ~ Einführung ~
[Java] Einführung in Java
Einführung in die Migration
Einführung in Java
Einführung in Doma
[Einführung in Spring Boot] Senden Sie ein Formular mit thymeleaf
Einführung in JAR-Dateien
Einführung in die Bitarithmetik
Einführung in PlayFramework 2.7 ① Übersicht
Einführung in das Android-Layout
Einführung in Entwurfsmuster (Einführung)
Einführung in die praktische Programmierung
Einführung in den Befehl javadoc
Einführung in den Befehl jar
Einführung in den Lambda-Stil
Einführung in den Java-Befehl
Einführung in die Keycloak-Entwicklung
Einführung in den Befehl javac
[Rails] Integrationstest mit Capybara (von der Einführung bis zur einfachen Testausführung)