通过zipkin实现dubbo下的链路追踪

通过zipkin对dubbo进行链路追踪

目标

在dubbo的服务之间穿插调用时
可以通过zipkin查看调用关系 方便定位错误

环境

  • jdk- 1.8
  • dubbo- 2.6.5
  • spring boot- 2.1.4
  • zipkin- 2.12.8 采用内存保存数据
  • nacos- 0.9 采用嵌入式数据库

流程描述

dubbo将服务注册到nacos中 前端用springboot接收请求 调用dubbo服务处理 调用关系为 web-hello依次调用provider1-hello11,provider1-hello12
provider1-hello12则会调用provider2-hello2
然后通过zipkin的管理界面查看调用情况

代码篇

项目采用maven聚合方式构建
dubbo-265-zipkin为父工程

  • dubbo-consumer
  • dubbo-interfaces
  • dubbo-provider
  • dubbo-provider2

项目结构整合

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
<!-- 父工程pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>fluffy.mo</groupId>
    <artifactId>dubbo-265-zipkin</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <encoding>UTF-8</encoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <jdk.version>1.8</jdk.version>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>

        <brave.version>5.6.3</brave.version>
        <zipkin-reporter.version>2.8.2</zipkin-reporter.version>
    </properties>

    <modules>
        <module>dubbo-interfaces</module>
        <module>dubbo-provider</module>
        <module>dubbo-provider2</module>
        <module>dubbo-consumer</module>
    </modules>

    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.5</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.spring</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>1.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.1.RELEASE</version>
        </dependency>

        <!--nacos注册中心-->
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo-registry-nacos</artifactId>
            <version>0.0.2</version>
        </dependency>

        <!-- zipkin与dubbo集成 -->
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-instrumentation-dubbo-rpc</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>
        <dependency>
            <groupId>io.zipkin.reporter2</groupId>
            <artifactId>zipkin-sender-okhttp3</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.4.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- 引入 zipkin brave 的 BOM 文件 -->
            <dependency>
                <groupId>io.zipkin.brave</groupId>
                <artifactId>brave-bom</artifactId>
                <version>${brave.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- 引入 zipkin repoter 的 BOM 文件 -->
            <dependency>
                <groupId>io.zipkin.reporter2</groupId>
                <artifactId>zipkin-reporter-bom</artifactId>
                <version>${zipkin-reporter.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

其实zipkin的坐标应该在子工程中引用

dubbo-interfaces

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<!-- dubbo-interfaces/pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>fluffy.mo</groupId>
        <artifactId>dubbo-265-zipkin</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>dubbo-interfaces</artifactId>

</project>

provider1与provider2的配置相同

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- dubbo-provider1与dubbo-provider2的pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>fluffy.mo</groupId>
        <artifactId>dubbo-265-zipkin</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>dubbo-provider2</artifactId>
    <dependencies>
        <dependency>
            <groupId>fluffy.mo</groupId>
            <artifactId>dubbo-interfaces</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

</project>
1
2
3
4
5
6
7
8
#application.properties
spring.application.name=app-provider2

demo.service.version=1.1.0
dubbo.scan.basePackages=fluffy.mo
dubbo.protocol.port=10085
dubbo.registry.address = nacos://192.168.88.3:8848
dubbo.provider.filter=tracing
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//#zipkin配置类
import brave.Tracing;
import brave.context.slf4j.MDCScopeDecorator;
import brave.propagation.CurrentTraceContext;
import brave.propagation.ThreadLocalCurrentTraceContext;
import brave.sampler.Sampler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import zipkin2.Span;
import zipkin2.reporter.AsyncReporter;
import zipkin2.reporter.okhttp3.OkHttpSender;

@Configuration
public class ConfigZpk {
    @Bean
    AsyncReporter<Span> spanReporter() {
        String url = "http://192.168.88.3:9411/api/v2/spans";
        OkHttpSender.create(url);
        return AsyncReporter.create(OkHttpSender.create(url));
    }

    @Bean
    Tracing tracing(@Autowired AsyncReporter<Span> reporter
            , @Value("${spring.application.name}") String serviceName) {
        CurrentTraceContext currentTraceContext = ThreadLocalCurrentTraceContext.newBuilder()
                .addScopeDecorator(MDCScopeDecorator.create()).build();
        return Tracing.newBuilder()
                .localServiceName(serviceName)
                .currentTraceContext(currentTraceContext)
                .spanReporter(reporter)
                .sampler(Sampler.ALWAYS_SAMPLE)
                .build();
    }

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//#启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class App_Main_pro1 {
    public static void main(String[] args) {
        SpringApplication.run(App_Main_pro1.class, args);
    }

}

web与调用方

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!-- dubbo-consumer/pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>fluffy.mo</groupId>
        <artifactId>dubbo-265-zipkin</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>dubbo-consumer</artifactId>

    <dependencies>
        <dependency>
            <groupId>fluffy.mo</groupId>
            <artifactId>dubbo-interfaces</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--加载配置中心-->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>nacos-config-spring-boot-starter</artifactId>
            <version>0.2.1</version>
        </dependency>
        <!-- http的监控 -->
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-instrumentation-spring-web</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-instrumentation-spring-webmvc</artifactId>
        </dependency>
    </dependencies>
</project>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#application.properties
spring.application.name=dubbo-consumer
server.servlet.context-path=/
server.port=18080

nacos.config.server-addr=192.168.88.3:8848

demo.service.version=1.1.0
dubbo.protocol.port=20086
dubbo.scan.basePackages=fluffy.mo.consumer
dubbo.registry.address = nacos://192.168.88.3:8848
dubbo.provider.filter=tracing
1
2
3
4
5
6
7
8
9
//#启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App_Main_client {
    public static void main(String[] args) {
        SpringApplication.run(App_Main_client.class, args);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//#zipkin配置类
import brave.Tracing;
import brave.context.slf4j.MDCScopeDecorator;
import brave.http.HttpTracing;
import brave.propagation.B3Propagation;
import brave.propagation.CurrentTraceContext;
import brave.propagation.ExtraFieldPropagation;
import brave.propagation.ThreadLocalCurrentTraceContext;
import brave.sampler.Sampler;
import brave.servlet.TracingFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import zipkin2.reporter.AsyncReporter;
import zipkin2.reporter.okhttp3.OkHttpSender;

import javax.servlet.Filter;

@Configuration
public class TraceAutoConfiguration{
    @Value("${spring.application.name}")
    private String serviceName;

    @Bean
    public Tracing tracing(){
        String zipkin = "http://192.168.88.3:9411/api/v2/spans";
        CurrentTraceContext currentTraceContext = ThreadLocalCurrentTraceContext.newBuilder()
                .addScopeDecorator(MDCScopeDecorator.create()).build();
        return Tracing.newBuilder()
                .localServiceName(serviceName)
                .propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "user-name"))
                .currentTraceContext(currentTraceContext)
                .spanReporter(AsyncReporter.create(OkHttpSender.create(zipkin)))
                .sampler(Sampler.ALWAYS_SAMPLE)
                .build();
    }

    @Bean(name = "httpTracing")
    HttpTracing httpTracing(Tracing tracing){
        return HttpTracing.create(tracing);
    }
    @Bean
    public Filter filter(HttpTracing httpTracing){
        return TracingFilter.create(httpTracing);
    }

}

web端处理要监控rpc的链路外
还要监控http请求的链路 所以比生产者要多配个httpTracing与filter

服务调用

dubbo-interfaces中定义两个接口

1
2
3
4
5
6
7
public interface Hello1Service {
    String sayHello11(String name);
    String sayHello12(String name);
}
public interface Hello2Service {
    String sayHello2(String name);
}

dubbo-provider

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import fluffy.mo.interfaces.Hello2Service;
import fluffy.mo.interfaces.Hello1Service;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

@Service(version = "${demo.service.version}" ,filter = "tracing")
public class Hello1ServiceImpl implements Hello1Service {

    @Reference(version = "${demo.service.version}",timeout = 60000 ,filter = "tracing")
    Hello2Service svc2;

    public String sayHello11(String name) {
        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
        return "Hello, " + name;
    }

    public String sayHello12(String name) {
        svc2.sayHello2(name);
        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
        return "Hello, " + name;
    }

}

dubbo-provider2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import com.alibaba.dubbo.config.annotation.Service;
import fluffy.mo.interfaces.Hello2Service;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

@Service(version = "${demo.service.version}", filter = "tracing")
public class Hello2ServiceImpl implements Hello2Service {
    static int COUNT = 0;

    public String sayHello2(String name) {
        if ((COUNT++) % 2 == 0) {
            throw new RuntimeException("手动异常");
        }
        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
        return "Hello, " + name;
    }

}

开始测试

启动nacos与zipkin

1
2
3
4
5
6
7
8
9
#nacos
curl -L https://github.com/nacos-group/nacos-docker/archive/0.9.0.tar.gz \

| tar zxf
cd nacos-docker-0.9.0/
mkdir  example/standalone-logs
docker-compose -d -f example/standalone-derby.yaml up

#zipkin
docker run -d -p 9411:9411 openzipkin/zipkin

依次启动项目

dubbo-provider2 dubbo-provider dubbo-consumer

多次访问 http://127.0.0.1:18080/sayHello/test
成功返回 与 报错 次数是1比1

访问zipkin查看调用链路
http://192.168.88.3:9411/zipkin/?serviceName=all&spanName=all&sortOrder=timestamp-desc&limit=5
可以看到调用链为 http请求到provider1与provider2 关系为树形结构