前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【分布式日志系统】springboot+zipkin+dubbo实现链路跟踪(下)

【分布式日志系统】springboot+zipkin+dubbo实现链路跟踪(下)

作者头像
小尘哥
发布2022-05-16 14:29:46
1.2K0
发布2022-05-16 14:29:46
举报
文章被收录于专栏:小尘哥的专栏小尘哥的专栏

上一篇写了《【分布式日志系统】springboot+zipkin+dubbo实现链路跟踪(上)》《【分布式日志系统】springboot+zipkin+dubbo实现链路跟踪(中)》,有兴趣的小伙伴可以往回翻翻,这一篇解决如何跟踪。

一、基本操作

  1. pom添加依赖(常规操作);
  2. 定义公共跟踪配置类
  3. dubbo服务端和调用端集成相应配置(主要是yaml中配置);
  4. 验证。

二、环境介绍

提示:可能不同的框架版本会导致有些地方不生效(如sleuth不支持apache版的dubbo),大家在集成的过程中有问题可以私信我,共同探讨。主框架版本如下:springboot 2.6.6、 dubbo 2.7.12、 zipkin 2.16.3、 brave 5.13.3 nacos-discovery-spring-boot-starter 0.2.10、 nacos-config-spring-boot-starter 0.2.10、

三 工程结构

  • basic-platform:父工程,管理框架版本等基础依赖;
    • basic-common:基础工具包,“定义公共跟踪基础配置类”等均在此工程,其他工程均依赖该工程;
    • basic-portal:门户的父工程(pom工程),其下包含三个子工程:
      • server:前后端分离对应的后端服务 ;
      • portal-api:定义dubbo服务的接口;
      • portal-api-impl:定义portal-api对应的接口实现;
    • basic-foundation:基础的父工程(pom工程),其下包含两个子工程:
      • foundation-api:定义dubbo服务的接口;
      • foundation-api-impl:定义 foundation-api对应的接口实现;

四、pom依赖

主要的代码均在basic-common的pom中,添加如下依赖:

代码语言:javascript
复制
<dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-instrumentation-dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-spring-beans</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-context-slf4j</artifactId>
        </dependency>
        <!--        tracing -->
        <dependency>
            <groupId>io.zipkin.reporter2</groupId>
            <artifactId>zipkin-sender-okhttp3</artifactId>
        </dependency>
        <!--        tracing  & mvc-->
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-instrumentation-spring-webmvc</artifactId>
        </dependency>
        <!--        tracing  & http-->
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-instrumentation-httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.reporter2</groupId>
            <artifactId>zipkin-sender-okhttp3</artifactId>
        </dependency>

另外,需要定义一个配置类,来处理RPC的tracing,要不会不生效。

代码语言:javascript
复制
package com.wd.basic.common.support.trace;

import brave.CurrentSpanCustomizer;
import brave.SpanCustomizer;
import brave.Tracing;
import brave.context.slf4j.MDCScopeDecorator;
import brave.http.HttpTracing;
import brave.propagation.B3Propagation;
import brave.propagation.ExtraFieldPropagation;
import brave.propagation.ThreadLocalCurrentTraceContext;
import brave.rpc.RpcTracing;
import brave.sampler.Sampler;
import brave.servlet.TracingFilter;
import brave.spring.webmvc.SpanCustomizingAsyncHandlerInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import zipkin2.Span;
import zipkin2.codec.Encoding;
import zipkin2.reporter.AsyncReporter;
import zipkin2.reporter.Sender;
import zipkin2.reporter.okhttp3.OkHttpSender;

import javax.servlet.Filter;

@Configuration
@Import(SpanCustomizingAsyncHandlerInterceptor.class)
public class TracingConfig implements WebMvcConfigurer {

    @Bean
    Sender sender(@Value("${zipkin.base.url}") String url) {
        return OkHttpSender.newBuilder()
                .encoding(Encoding.PROTO3)
                .endpoint(url)
                .build();
    }

    /**
     * Configuration for how to buffer spans into messages for Zipkin
     */
    @Bean
    @ConditionalOnBean(Sender.class)
    AsyncReporter<Span> spanReporter(Sender sender) {
        AsyncReporter.Builder builder = AsyncReporter.builder(sender);
        builder.queuedMaxSpans(50000);
        builder.queuedMaxBytes(104857600);
        return builder.build();
    }


    /**
     * Controls aspects of tracing such as the service name that shows up in the UI
     */
    @Bean
    Tracing tracing(@Value("${dubbo.application.name}") String applicationName, @Value("${zipkin.enable:false}") Boolean enable, @Autowired(required = false) AsyncReporter spanReporter) {
        Tracing.Builder builder = Tracing.newBuilder()
                .localServiceName(applicationName)
                .propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "user-name"))
                .currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder()
                        // puts trace IDs into logs
                        .addScopeDecorator(MDCScopeDecorator.create())
                        .build()
                );
        if (enable) {
            builder.spanReporter(spanReporter);
            builder.sampler(Sampler.ALWAYS_SAMPLE);
        } else {
            builder.sampler(Sampler.NEVER_SAMPLE);
        }
        return builder.build();
    }

    @Bean
    SpanCustomizer spanCustomizer(Tracing tracing) {
        return CurrentSpanCustomizer.create(tracing);
    }

    /**
     * Decides how to name and tag spans. By default they are named the same as the http method
     */
    @Bean
    HttpTracing httpTracing(Tracing tracing) {
        return HttpTracing.create(tracing);
    }


    @Bean
    RpcTracing rpcTracing(Tracing tracing) {
        return RpcTracing.create(tracing);
    }

    /**
     * Creates server spans for http requests
     */
    @Bean
    Filter tracingFilter(HttpTracing httpTracing) {
        return TracingFilter.create(httpTracing);

    }

    @Autowired
    SpanCustomizingAsyncHandlerInterceptor webMvcTracingCustomizer;

    /**
     * Decorates server spans with application-defined web tags
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(webMvcTracingCustomizer);
    }
}

五、其他工程添加配置

前提:所有工程均依赖basic-common。 dubbo工程的provider和consumer均添加如下配置(请注意必须添加dubbo.consumer.filter和dubbo.provider.filter

代码语言:javascript
复制
dubbo:
  application:
    name: foundationRpc #服务名称
  consumer:
    timeout: 3000 #消费超时时间
    retries: 1 #重试次数
    filter: tracing # 请注意必须添加该配置
    check: false
  provider:
    filter: tracing # 请注意必须添加该配置
  protocol:
    name: dubbo
zipkin:
  enable: true
  base:
    url: http://127.0.0.1:9411/api/v2/spans

    

六、测试

设计如下调用关系:

server定义http入口:

代码语言:javascript
复制
    @GetMapping("tracing")
    public String exec(){
        log.info("come in tracing");
        return rpcDemoService.tracing();
    }

rpcDemoService为server中定义的service,实现如下:

代码语言:javascript
复制
package com.wd.basic.portal.server.modular.demo.service.impl;

import cn.hutool.core.util.StrUtil;
import com.wd.basic.foundation.rpc.test.service.DemoRpcService;
import com.wd.basic.portal.rpc.IDemoService;
import com.wd.basic.portal.rpc.ITraceDemoService;
import com.wd.basic.portal.server.modular.demo.service.RpcDemoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;

/**
 * rpc演示服务impl
 *
 * @author 小尘哥
 * @date 2022/04/14
 */
@Slf4j
@Service
public class RpcDemoServiceImpl implements RpcDemoService {

    @DubboReference(interfaceClass = IDemoService.class, version = "2.0")
    private IDemoService demoService2;


    @DubboReference(interfaceClass = IDemoService.class, version = "1.0")
    private IDemoService demoService1;

    @DubboReference(interfaceClass = ITraceDemoService.class, version = "1.0")
    private ITraceDemoService traceDemoService;

    @DubboReference(interfaceClass = DemoRpcService.class, version = "1.0")
    private DemoRpcService demoRpcService;

    @Override
    public String tracing() {
        log.info("come in exec service");
        String rpc2 = demoService2.testRpc();
        return StrUtil.join(",", rpc2);
    }
}

IDemoService 定义在portal-api中,实现如下

代码语言:javascript
复制
package com.wd.basic.portal.rpc.impl;


import com.wd.basic.foundation.rpc.test.service.DemoRpcService;
import com.wd.basic.portal.rpc.IDemoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;

/**
 * 演示服务impl
 *
 * @author 小尘哥
 * @date 2022/03/16
 */
@Slf4j
@DubboService(interfaceClass = IDemoService.class,version = "2.0")
public class DemoServiceV2Impl implements IDemoService {

    @DubboReference(interfaceClass = DemoRpcService.class, version = "1.0")
    private DemoRpcService demoRpcService;

    @Override
    public String testRpc() {
        log.info("来自统一门户 RPC 测试");
        demoRpcService.testRpc();
        return "调用到了rpc服务 version 2.0.0";
    }
}

DemoRpcService 定义在foundation-api中,实现如下:

代码语言:javascript
复制
package com.wd.basic.foundation.rpc.test.service.impl;

import com.wd.basic.foundation.rpc.test.service.DemoRpcService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;

/**
 * test Rpc服务
 * @author 上官婉儿
 */
@Slf4j
@DubboService(interfaceClass = DemoRpcService.class,version = "1.0")
public class DemoRpcServiceImpl implements DemoRpcService {

    @Override
    public String testRpc() {
        log.info("来自基础服务 RPC 测试");
        return "RPC服务! from foundation";
    }
}

七、日志配置

临门一脚了,以上都配置好了,但是哪里能看到效果呢?肯定是需要从日志来跟踪,我们采用springboot推荐的logback来记录日志,请注意日志记录格式,添加了部分内容“【%X{traceId},%X{spanId},%X{parentId}】”,完整配置如下:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="10 seconds">
<!--    <include resource="org/springframework/boot/logging/logback/base.xml"
        /> -->
    <contextName>Logback For Basic Platform</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->

    <!-- 定义日志文件 输入位置 -->
    <springProperty scope="context" name="logDir" source="custom.log.dir" defaultValue="D:/logback"/>

    <!-- 定义日志文件 输出级别 -->
    <springProperty scope="context" name="logLevel" source="custom.log.level" defaultValue="info"/>

    <!-- 日志最大的历史 30天 -->
    <springProperty scope="context" name="logMaxHistory" source="custom.log.max-history" defaultValue="180"/>

    <!-- 控制台输出日志 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} 【%X{traceId},%X{spanId},%X{parentId}】 [%thread] %-5level %logger-%msg%n %ex{5}</pattern>
            <charset class="java.nio.charset.Charset">UTF-8</charset>
        </encoder>
    </appender>


    <!--文件日志, 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <fileNamePattern>${logDir}/%d{yyyy-MM-dd}/pf.%i.log.zip</fileNamePattern>

            <maxFileSize>50MB</maxFileSize>  <!-- 日志文件过大会使的编辑器打开非常慢,因此设置日志最大50MB -->
            <!--日志文件保留天数-->
            <maxHistory>${logMaxHistory}</maxHistory>
            <totalSizeCap>10GB</totalSizeCap>  <!-- 总日志大小 -->
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} 【%X{traceId},%X{spanId},%X{parentId}】 [%thread] %-5level %logger-%msg%n %ex{5}</pattern>
        </encoder>
    </appender>

    <!-- 异步输出 -->
    <appender name="dayLogAsyncAppender" class="ch.qos.logback.classic.AsyncAppender">
        <includeCallerData>true</includeCallerData>
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <appender-ref ref="FILE"/>
    </appender>


    <!--专为 spring 定制
     -->
    <logger name="org.springframework" level="${logLevel}"/>

    <!-- root级别 DEBUG -->
    <root level="${logLevel}">
        <!-- 控制台输出 -->
        <appender-ref ref="STDOUT" />
        <!-- 文件输出 -->
        <appender-ref ref="dayLogAsyncAppender" />
    </root>
</configuration>

八、验证

来自server的日志

代码语言:javascript
复制
2022-04-26 16:44:25.817 【2926aa52265ad112,2926aa52265ad112,】 [http-nio-8080-exec-1] INFO  com.wd.basic.portal.server.modular.demo.controller.RpcDemoController-come in tracing
 2022-04-26 16:44:25.826 【2926aa52265ad112,2926aa52265ad112,】 [http-nio-8080-exec-1] INFO  com.wd.basic.portal.server.modular.demo.service.impl.RpcDemoServiceImpl-come in exec service

来自portal-api的日志

代码语言:javascript
复制
 2022-04-26 16:44:25.882 【2926aa52265ad112,6a4da6311934ab65,2926aa52265ad112】 [DubboServerHandler-10.1.252.224:20881-thread-5] INFO  com.wd.basic.portal.rpc.impl.DemoServiceV2Impl-来自统一门户 RPC 测试

来自foundation-api的日志

代码语言:javascript
复制
 2022-04-26 16:44:25.926 【2926aa52265ad112,53dbfb344223ef50,6a4da6311934ab65】 [DubboServerHandler-10.1.252.224:20880-thread-3] INFO  com.wd.basic.foundation.rpc.test.service.impl.DemoRpcServiceImpl-来自基础服务 RPC 测试

九、日志分析

提取三部分日志中括号中的内容:

代码语言:javascript
复制
统一门户:traceId = 2926aa52265ad112,spanId=2926aa52265ad112,parentId=’’;
统一门户RPC服务:traceId = 2926aa52265ad112,spanId=6a4da6311934ab65,parentId=2926aa52265ad112;
基础服务RPC服务:traceId = 2926aa52265ad112,spanId=53dbfb344223ef50,parentId=6a4da6311934ab65;

说明:

  1. 对于server,作为请求入口,因此traceId和spanId相同,无上游服务,因此parentId为空。
  2. 对于portal-api服务,parentId=统一门户请求入口的spanId,因此它是统一门户的下游服务。
  3. 对于foundation-api服务,parentId=同一门户RPC服务的spanId,因此它是统一门户RPC服务的下游服务。

综上,和测试开始时的设计逻辑一致,由traceId串联整个请求过程,由spanId和parentId构建上下游调用关系,完成dubbo服务之间调用的链路跟踪。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-05-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 陌与尘埃 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、基本操作
  • 二、环境介绍
  • 三 工程结构
  • 四、pom依赖
  • 五、其他工程添加配置
  • 六、测试
  • 八、验证
  • 九、日志分析
相关产品与服务
Elasticsearch Service
腾讯云 Elasticsearch Service(ES)是云端全托管海量数据检索分析服务,拥有高性能自研内核,集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群,也支持免运维、自动弹性、按需使用的 Serverless 模式。使用 ES 您可以高效构建信息检索、日志分析、运维监控等服务,它独特的向量检索还可助您构建基于语义、图像的AI深度应用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档