L'application monomère Spring Boot introduit le suivi des liens de détective


avant-propos

Récemment, j'ai découvert un problème lors de la vérification des exceptions dans l'environnement de production. Bien que j'aie trouvé les informations de journal du paramètre d'entrée de la requête, en raison du nombre relativement important de requêtes simultanées dans l'environnement de production, il est impossible de déterminer la relation correspondante entre le journal imprimé lors du processus d'exécution ultérieur et les informations renvoyées et le paramètre d'entrée. , il est impossible de trier les informations complètes de lien de la demande, ce qui pose de grands défis au problème de positionnement. Par conséquent, il est fortement recommandé d’introduire le suivi des liens de détective non seulement dans l’architecture des microservices, mais également dans une application unique.


1. Simulation de problèmes

Définissez l'aspect du journal, utilisez 环绕通知les paramètres d'entrée de la demande d'enregistrement et renvoyez les résultats.

/**
 * Web层日志切面
 */
@Aspect
@Component
@Slf4j
public class WebLogAspect {
    
    
    /**
     * 定义切面
     */
    @Pointcut("execution(public * com.laowan.limit.controller..*.*(..))")
    public void webLog(){
    
    
    }
    @Around(value = "webLog()")
    public Object handleControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        StopWatch stopWatch = StopWatch.createStarted();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String params = this.getRequestParams(request,joinPoint);
        log.info("【请求信息】请求url:{},参数:{} ", request.getServletPath(), params);
        //继续下一个通知或目标方法调用,返回处理结果,如果目标方法发生异常,则 proceed 会抛异常.
        //如果在调用目标方法或者下一个切面通知前抛出异常,则不会再继续往后走.
        Object proceed = joinPoint.proceed(joinPoint.getArgs());

        stopWatch.stop();
        long watchTime = stopWatch.getTime();
        log.info("【响应结果】返回值={},耗时={} (毫秒)", proceed, watchTime);
        return proceed;
    }

    /**
     * 异常通知:目标方法发生异常的时候执行以下代码,此时返回通知@AfterReturning不会再触发
     * @param jp
     * @param ex
     */
    @AfterThrowing(pointcut = "webLog()", throwing = "ex")
    public void aspectAfterThrowing(JoinPoint jp, Exception ex) {
    
    
        String methodName = jp.getSignature().getName();
        log.error("【后置异常通知】{}方法异常:{}" ,methodName, ex.getMessage());
    }


    /**
     * 获取请求参数
     * @param request
     * @param joinPoint
     */
    private String getRequestParams(HttpServletRequest request, JoinPoint joinPoint) throws JsonProcessingException {
    
    
        StringBuilder params = new StringBuilder();
        // 获取 request parameter 中的参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (parameterMap != null && !parameterMap.isEmpty()) {
    
    
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
    
    
                params.append(entry.getKey()).append(" = ").append(Arrays.toString(entry.getValue())).append(";");
            }
        }
        // 获取非 request parameter 中的参数
        Object[] objects = joinPoint.getArgs();
        for (Object arg : objects) {
    
    
            if (arg == null) {
    
    
                break;
            }
            String className = arg.getClass().getName().toLowerCase();
            String contentType = request.getHeader("Content-Type");
            if (contentType != null && contentType.contains("application/json")){
    
    
                // json 参数
                ObjectMapper mapper = new ObjectMapper();
                mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
                mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
                params.append(mapper.writeValueAsString(arg));
            }
        }
        return params.toString();
    }
}

Informations de journal pour une seule demande :
insérer la description de l'image ici
informations de journal pour les demandes simultanées :
insérer la description de l'image ici

Description du problème :
sous requêtes simultanées, la relation correspondante entre les journaux de requêtes ne peut être réalisée qu'indirectement via le thread d'exécution de requête [nio-8080-exec-*], mais chaque thread d'exécution traitera toujours les nouvelles requêtes HTTP, et dans le cluster environnement Ensuite, le nom du thread est susceptible d'être répété. Dans ce cas, il nous est difficile de trier les informations complètes du lien d'exécution d'une requête sur la base des informations du journal.

2. Introduire le suivi des liens de détective

Il existe de nombreuses implémentations du suivi des liens, et ici je l'utilise spring-cloud-starter-sleuth.
Sleuth peut être utilisé seul ou intégré à Zipkin. Ici, il me suffit de générer les informations de lien du journal, donc Sleuth est utilisé seul.

Informations sur le site officiel : Only Sleuth (corrélation des journaux)

1. Présenter la dépendance maven du détective

<parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
     <version>2.4.2</version>
     <relativePath/> <!-- lookup parent from repository -->
 </parent>

  <dependencies>
       <!--引入sleuth依赖-->
	   <dependency>
	          <groupId>org.springframework.cloud</groupId>
	          <artifactId>spring-cloud-starter-sleuth</artifactId>
	    </dependency>
	     ……
   </dependencies>

  <dependencyManagement>
      <dependencies>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-dependencies</artifactId>
              <version>2020.0.1</version>
              <type>pom</type>
              <scope>import</scope>
          </dependency>
      </dependencies>
  </dependencyManagement>    

Remarque :
 une relation de compatibilité entre les principales versions de Spring Boot et Spring-Cloud est requise ici.
Adresse correspondante de la version du composant du site officiel : version du composant d'intégration du site officiel de Spring Cloud, description correspondante
insérer la description de l'image ici

2. Ajouter une configuration d'attribut

# 开启sleuth链路跟踪,默认为true,所以可以省略配置
spring.sleuth.enabled = true

# 配置服务名称
spring.application.name=Sleuth

3. configuration de la connexion

Pour les informations de configuration du journal de connexion sous le suivi des liens de détective, vous pouvez vous référer à la démo sur le site officiel :
Configuration de la connexion : logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

    <springProperty scope="context" name="springAppName" source="spring.application.name"/>
    <!-- Example for logging into the build folder of your project -->
    <property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/>

    <!-- You can override this to have a custom pattern -->
    <property name="CONSOLE_LOG_PATTERN"
              value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>

    <!-- Appender to log to console -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!-- Minimum logging level to be presented in the console logs-->
            <level>DEBUG</level>
        </filter>
        <encoder>
            <pattern>${
    
    CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- Appender to log to file -->
    <appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${
    
    LOG_FILE}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${
    
    LOG_FILE}.%d{
    
    yyyy-MM-dd}.gz</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder>
           <!-- 注意:这里不用使用控制台格式${
    
    CONSOLE_LOG_PATTERN}输出 -->
            <pattern>${
    
    FILE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="console"/>
        <appender-ref ref="flatfile"/>
    </root>
</configuration>

Remarque :
1. N'utilisez pas la sortie du journal couleur pour le fichier plat de sortie du journal texte, sinon vous verrez beaucoup d'informations tronquées dans le fichier journal généré.
2. Si vous souhaitez collecter des journaux, il est recommandé d'utiliser logstash pour générer les journaux au format json, ce qui est pratique pour l'analyse sur le terrain.

4. Informations de journalisation

insérer la description de l'image ici

Le format du journal est :[application name, traceId, spanId]

  • nom de l'application — le nom de l'application, qui est la propriété configurée par le paramètre spring.application.name dans application.properties.
  • traceId — Numéro d'identification attribué à une requête, utilisé pour identifier un lien de requête.
  • spanId — Indique une unité de travail de base, une requête peut contenir plusieurs étapes, chaque étape a son propre spanId. Une requête ne peut avoir qu’un seul TraceId, mais peut avoir plusieurs SpanId.

5. Déclarez un nouveau Span via l'annotation @NewSpan

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    
    
    @NewSpan
    @Override
    public void order(Order order) throws Exception {
    
    
        Thread.sleep(200);
        log.info("下单");
    }
}

insérer la description de l'image ici
Remarque : @NewSpan ne prend pas effet dans la même classe.

3. Les avantages de l'introduction du suivi des liens Sleuth

1. Grâce au traceId dans les informations du journal, vous pouvez facilement trouver toutes les informations pertinentes du journal pour une seule demande 理清整个请求的完整链路.
2. Grâce aux informations traceId, spanId et de temps de sortie du journal dans les informations du journal, c'est possible 得出整个请求以及各个阶段span的耗时情况.

4. Description du concept de détective

Spring Cloud Sleuth utilise la terminologie du projet open source Dapper de Google.

● Span : unité de travail de base. Par exemple, envoyer un RPC dans un span nouvellement créé équivaut à envoyer une demande de réponse au RPC. Le span est identifié de manière unique par un ID de 64 bits, la trace est représentée par un autre 64 bits. ID de bit, et le span contient d'autres données. Informations, telles que des résumés, des événements d'horodatage, des annotations de valeur clé (balises), des ID de span et des ID de progression (généralement des adresses IP). Les spans démarrent
et s'arrêtent constamment, et les informations temporelles sont enregistrées au niveau du bit. en même temps. Lorsque vous créez un span, vous devez l'arrêter à un moment donné dans le futur.
● Trace : structure arborescente composée d'une série de segments. Par exemple, si vous exécutez un projet Big Data distribué, vous devrez peut-être créer une trace.
● Annotation : utilisée pour enregistrer l'existence d'un événement dans le temps, certaines annotations principales sont utilisées pour définir le début et la fin d'une requête

○ cs - Client Sent - le client initie une requête, cette annotation décrit le début de la période
○ sr - Server Reçu - le serveur reçoit la requête et est prêt à commencer à la traiter, si vous soustrayez l'horodatage cs de sr, vous pouvez obtenir le délai du réseau
○ ss - Server Sent - L'annotation indique l'achèvement du traitement de la demande (lorsque la demande revient au client), si ss moins l'horodatage sr, le temps de traitement de la demande requis par le serveur peut être obtenu ○ cr - Client Reçu - indique la fin de la période, le
client a reçu avec succès la réponse du serveur, si cr moins l'horodatage cs, vous pouvez obtenir tout le temps nécessaire au client pour obtenir la réponse du serveur

insérer la description de l'image ici

Cinq, les caractéristiques MDC de Logback

La fonctionnalité MDC de Logback, c'est-à-dire Mapped Diagnostic Contexts (诊断上下文映射).

L'un des objectifs de conception de Logback est d'auditer et de déboguer des applications distribuées complexes. La plupart des systèmes distribués pratiques doivent gérer simultanément les demandes de plusieurs clients. Afin de distinguer les journaux de chaque client et de localiser rapidement de quel client provient un certain journal de requêtes, le moyen le plus simple consiste à attribuer à chaque demande de journal de chaque client une balise unique. Afin de marquer de manière unique chaque demande, l'utilisateur place des informations contextuelles dans le MDC.

La classe MDC contient uniquement des méthodes statiques qui permettent au développeur de placer des informations dans des contextes de diagnostic qui peuvent ensuite être récupérées par certains composants de journalisation. MDC根据每个线程管理上下文信息. Généralement, lors du démarrage d'une demande de service pour un nouveau client, le développeur insère des informations contextuelles pertinentes telles que l'ID client, l'adresse IP du client, les paramètres de la demande, etc. Le composant LOGBACK (s'il est correctement configuré) inclura automatiquement ces informations dans chaque entrée de journal.

package org.slf4j;

public class MDC {
    
    
  //Put a context value as identified by key
  //into the current thread's context map.
  public static void put(String key, String val);

  //Get the context identified by the key parameter.
  public static String get(String key);

  //Remove the context identified by the key parameter.
  public static void remove(String key);

  //Clear all entries in the MDC.
  public static void clear();
}

Par conséquent, le détective est responsable de la génération des informations traceId et spanId et de la transmission des informations dans le contexte de mappage de diagnostic (DMC) de Logback, puis Logback effectue la sortie du journal en fonction du format de journal spécifique.


Uniquement Sleuth (corrélation de journaux)
Fonctionnalité MDC de connexion
Introduction de Spring-Cloud-Sleuth
Version du composant d'intégration du site officiel de Spring Cloud Description correspondante

Je suppose que tu aimes

Origine blog.csdn.net/w1014074794/article/details/131073444
conseillé
Classement