[JAVA] Introduction à Ratpack (Extra Edition) - Utilisation de Sentry

Basé sur les threads ou sur les événements

Ratpack fonctionne avec un modèle piloté par événement non bloquant. Les opérations de blocage telles que l'accès à la base de données doivent être effectuées dans un thread distinct. Il est difficile de le faire à la main, donc Ratpack est livré avec un mécanisme de rappel Promise et un support utilitaire Blocking pour le multi-threading. C'est tout ce que vous avez à faire avec "Oh, ça ressemble à du JavaScript", mais quand vous l'utilisez avec d'autres frameworks, il y a quelques problèmes.

Java Servlet, le framework Web Java standard, s'exécute sur un modèle à une seule demande et 1 thread. Spring Framework est également basé sur des servlets, il adhère donc à ce modèle. Lorsque le servlet reçoit une requête HTTP, il crée un nouveau thread (ou l'obtient du pool) et exécute la méthode du gestionnaire. Ce mécanisme permet à chaque requête d'être exécutée dans un thread séparé, permettant aux programmeurs d'écrire des programmes (essentiellement) sans se soucier de la sécurité des threads, comme le partage de ressources entre les threads. Cependant, Ratpack est un framework non bloquant, donc toutes les requêtes sont exécutées dans la même boucle d'événements et dans le même thread (exactement un peu différent, mais la plupart des images ressemblent à ça). Puisque le processus de blocage est exécuté dans un autre thread, une demande peut ne pas être terminée dans un thread. Je pense que la plupart des applications Web se connectent à des bases de données ou appellent des API externes. En résumé, dans les servlets et Ratpack, le "contexte" d'exécution et la relation entre les threads ne correspondent pas.

Ce n'est pas une histoire qui est meilleure ou pire parce que c'est une différence dans le concept de conception en soi, mais comme le servlet qui est le standard de facto de Java est un modèle de thread à 1 demande-1, de nombreux frameworks Java sont créés sur cette hypothèse. A été Par conséquent, il existe une situation où il ne peut pas être utilisé avec Ratpack tel quel.

ThreadLocal

(Si vous pensez que ThreadLocal relève du bon sens, passez au paragraphe suivant!)

Un exemple typique est ThreadLocal. Thread local est une classe pour contenir une instance unique pour chaque thread. Considérez ThreadLocalRandom comme exemple. En fait, la classe Java Random est thread-safe. Il est sûr de partager avec plusieurs threads. Cependant, comme ils sont synchronisés, les performances en souffriront en cas de conflit. C'est là que ThreadLocalRandom entre en jeu. Lorsque les threads A et B accèdent au même ThreadLocalRandom en même temps, le thread A utilise le Random interne spécifique au thread A, et le thread B utilise un Random séparé dédié au thread B. .. Les performances ne se détériorent pas car il n'y a pas de synchronisation. C'est aussi plus efficace que de créer une nouvelle instance «Random» à chaque fois à la volée.

Avec ThreadLocal, vous pouvez utiliser des classes qui ne prennent pas en charge le multithreading sans vous soucier de la synchronisation.

Crise de contexte

Comme mentionné au début, le servlet fonctionne sur 1 thread request-1. Puisque ThreadLocal est unique pour chaque thread, il peut en fait être considéré comme" ThreadLocal a une instance par requête". C'était une conception très utile pour gérer le contexte d'exécution d'une application Web. Par exemple, si vous stockez les informations utilisateur thread localement, ** le programme peut considérer ces informations utilisateur thread local comme des informations pour la demande en cours **.

Un exemple typique est MDC de logback. Si vous enregistrez les informations utilisateur dans le MDC lorsque vous recevez la demande pour la première fois, vous pouvez utiliser les informations utilisateur ensemble dans la sortie de journal suivante. Le côté sortie du journal peut utiliser ces informations sans savoir quelle requête l'appelle. C'est pratique.

Comme vous l'avez peut-être remarqué, Ratpack est non bloquant. Une demande n'est pas exécutée dans un thread. ** ThreadLocal ne peut pas être utilisé sur Ratpack. ** **

Prenons un exemple concret.

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

S'il s'agit d'un servlet, les trois sorties de journal font référence au même MDC. Dans toutes les sorties de journal, la valeur de ʻuser dans MDC sera John Smith. Dans Ratpack, le traitement dans Blocking.op ()est un thread séparé. C'est un thread séparé du thread qui a appeléMDC.put ()`. L '"utilisateur" de "[2]" sera "nul".

Execution

Alors, comment gérez-vous le contexte dans Ratpack comme vous l'avez fait dans un servlet?

En fait, Ratpack exécute le traitement en unités de [ʻExecution](https://ratpack.io/manual/current/api/ratpack/exec/Execution.html). La règle selon laquelle la «Promise» de Ratpack ne peut être utilisée que dans une application Ratpack était en fait «uniquement à l'intérieur de« Execution »». ʻExecution est également le contexte de traitement. Les applications Ratpack gèrent le contexte dans ʻExecution, tout comme un thread est le contexte d'exécution dans un servlet. ʻExecution a une fonction de dictionnaire qui peut être utilisée à l'intérieur, et peut enregistrer et récupérer des instances d'un certain type. Vous pouvez en profiter au lieu de ThreadLocal.

J'ai mentionné que ThreadLocal ne peut pas être utilisé avec Ratpack, mais pour Ratpack MDCInterceptor pour utiliser MDC ) Est préparé à l'avance. C'est facile à utiliser, il suffit d'enregistrer cette classe dans Registry. MDCInterceptor est une implémentation de l'interface [ʻExecInterceptor](https://ratpack.io/manual/current/api/ratpack/exec/ExecInterceptor.html). Comme son nom l'indique, cette interface agit comme un intercepteur pour ʻExecution. Autrement dit, il est utilisé pour insérer un traitement avant et après l'exécution d'une certaine «exécution».

Si vous regardez l'implémentation de MDCInterceptor, vous pouvez voir que le processus est à peu près comme suit.

  1. Extrayez le dictionnaire contextuel de ʻExecution`. Sinon, créez un dictionnaire de contexte vide.
  2. Appelez MDC.setContextMap () pour définir le dictionnaire récupéré.
  3. Exécutez ʻExecution`.
  4. Extrayez le dictionnaire de contexte du MDC (MDC.getCopyOfContextMap ()) et réglez-le sur ʻExecution`

En bref, avant le traitement proprement dit, le MDC de SLF4J est opéré de sorte que l '«exécution» de Ratpack et le MDC correspondent.

Utilisez Sentry

Je vais enfin entrer dans le sujet principal. Le client Java du service de surveillance des journaux Sentry nécessite également certaines opérations dans Ratpack.

Sentry a la capacité d'enregistrer et de générer des contextes d'exécution, tels que les balises et le fil d'Ariane. Cependant, comme indiqué dans la documentation Sentry, ce que signifie «contexte» dépend de l'application. Sentry fait abstraction de cette gestion de contexte avec une classe appelée «ContextManager». Par défaut, deux implémentations sont fournies, le client Java pour Android utilise SingletonContextManager et la version standard utilise ThreadLocalContextManager. Comme son nom l'indique, il s'agit d'un singleton et d'un thread local. Et, comme nous l'avons vu, les locals de thread ne peuvent pas être utilisés avec Ratpack. Dans l'implémentation singleton, les informations utilisateur seront incorrectes.

Par conséquent, j'écrirai une implémentation unique de ContextManager qui utilise ʻExecution`.

@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;
    }
}

Tout d'abord, assurez-vous que le programme en cours s'exécute dans ʻExecution. Renvoie un objet singleton Context s'il est en dehors de ʻExecution. Cela peut être thread-local, mais je pense que le code qui s'exécute en dehors de ʻExecution` n'est que le processus de démarrage du serveur à moins que vous ne créiez votre propre thread. Le «Contexte» lui-même est «synchronisé» et sûr pour les threads, j'ai donc décidé de l'implémenter comme ceci.

Si vous étiez dans ʻExecution, assurez-vous que le ʻExecution actuel contient l'objet SentryContext. S'il n'est pas sauvegardé, créez un nouveau Context et enregistrez-le dans ʻExecution. Maintenant, le «Contexte» sera hérité par l '«exécution» suivante. Vous pouvez maintenant obtenir le même contexte même si vous passez à un autre fil avec blocage` etc.

Tout ce que vous avez à faire maintenant est d'utiliser cette implémentation de ContextManager, qui peut être obtenue en remplaçant SentryClientFactory.

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

Seulement ça.

Enfin, vous devez spécifier cette classe comme moyen par défaut de créer un client Sentry. Il y a deux manières de faire cela, l'une est d'utiliser la méthode Sentry.init () et l'autre d'utiliser les propriétés.

Sentry.init( new RatpackSentryClientFactory() );

Cependant, si vous utilisez un client qui est intégré à d'autres cadres de journalisation comme sentry-logback, je pense que l'initialisation de Sentry dépend de la classe globale Sentry. En fait, «sentry-logback» n'est pas conçu pour spécifier un «SentryClient» séparé. Par conséquent, je pense qu'il est préférable de le définir via le fichier de propriétés.

sentry.properties


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

Spécifiez le FQCN de la «SentryClientFactory» créée comme valeur de la clé «usine».

Résumé

Recommended Posts

Introduction à Ratpack (Extra Edition) - Utilisation de Sentry
Introduction à Ratpack (Extra Edition) --Ratpack écrit en Kotlin
Introduction à Ratpack (8) - Session
Introduction à Ratpack (6) - Promesse
Introduction à Ratpack (2) -Architecture
Introduction à Ratpack (7) --Guice & Spring
Introduction à Ratpack (1) - Qu'est-ce que Ratpack?
Introduction à Ratpack (3) - Explication détaillée de Hello World
Introduction à Ruby 2
Introduction à web3j
Introduction à Micronaut 1 ~ Introduction ~
[Java] Introduction à Java
Introduction à la migration
Introduction à Java
Introduction à Doma
[Introduction à Spring Boot] Soumettez un formulaire à l'aide de thymeleaf
Introduction aux fichiers JAR
Introduction à l'arithmétique des bits
Introduction à PlayFramework 2.7 ① Présentation
Introduction à la mise en page Android
Introduction aux modèles de conception (introduction)
Introduction à la programmation pratique
Introduction à la commande javadoc
Introduction à la commande jar
Introduction au style lambda
Introduction à la commande java
Introduction au développement de Keycloak
Introduction à la commande javac
[Rails] Test d'intégration avec Capybara (de l'introduction à l'exécution simple du test)