如何在Dubbo中使用Zipkin来实现分布式追踪

这篇文章主要介绍“如何在Dubbo中使用Zipkin来实现分布式追踪”,在日常操作中,相信很多人在如何在Dubbo中使用Zipkin来实现分布式追踪问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何在Dubbo中使用Zipkin来实现分布式追踪”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

成都创新互联主要从事做网站、网站制作、网页设计、企业做网站、公司建网站等业务。立足成都服务将乐,十年网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:18982081108

Zipkin 简介

Zipkin 是基于 Dapper 论文实现,由 Twitter 开源的分布式追踪系统,通过收集分布式服务执行时间的信息来达到追踪服务调用链路、以及分析服务执行延迟等目的。

Zipkin 架构

如何在Dubbo中使用Zipkin来实现分布式追踪cdn.nlark.com/lark/0/2018/png/13615/1539327563392-b01ee384-c79f-48b9-9027-4bc10a4397f7.png">

Collector 收集器、Storage 存储、API、UI 用户界面等几部分构成了 Zipkin Server 部分,对应于 GitHub 上 openzipkin/zipkin 这个项目。而收集应用中调用的耗时信息并将其上报的组件与应用共生,并拥有各个语言的实现版本,其中 Java 的实现是 GitHub 上 openzipkin/brave 。除了 Java 客户端实现之外,openzipkin 还提供了许多其他语言的实现,其中包括了 go、php、JavaScript、.net、ruby 等,具体列表可以参阅 Zipkin 的 Exiting instrumentations 。

Zipkin 的工作过程

当用户发起一次调用时,Zipkin 的客户端会在入口处为整条调用链路生成一个全局唯一的 trace id,并为这条链路中的每一次分布式调用生成一个 span id。span 与 span 之间可以有父子嵌套关系,代表分布式调用中的上下游关系。span 和 span 之间可以是兄弟关系,代表当前调用下的两次子调用。一个 trace 由一组 span 组成,可以看成是由 trace 为根节点,span 为若干个子节点的一棵树。

如何在Dubbo中使用Zipkin来实现分布式追踪

Span 由调用边界来分隔,在 Zipkin 中,调用边界由以下四个 annotation 来表示:

  • cs - Clent Sent 客户端发送了请求

  • sr - Server Receive 服务端接受到请求

  • ss - Server Send 服务端处理完毕,向客户端发送回应

  • cr - Client Receive 客户端收到结果

显然,通过这四个 annotation 上的时间戳,可以轻易的知道一次完整的调用在不同阶段的耗时,比如:

  • sr - cs 代表了请求在网络上的耗时

  • ss - sr 代表了服务端处理请求的耗时

  • cr - ss 代表了回应在网络上的耗时

  • cr - cs 代表了一次调用的整体耗时

Zipkin 会将 trace 相关的信息在调用链路上传递,并在每个调用边界结束时异步的把当前调用的耗时信息上报给 Zipkin Server。Zipkin Server 在收到 trace 信息后,将其存储起来,Zipkin 支持的存储类型有 inMemory、MySQL、Cassandra、以及 ElasticsSearch 几种方式。随后 Zipkin 的 Web UI 会通过 API 访问的方式从存储中将 trace 信息提取出来分析并展示,如下图所示:

如何在Dubbo中使用Zipkin来实现分布式追踪

在 Dubbo 中使用

由于 Brave 对 Dubbo 已经主动做了支持,在 Dubbo 中集成基于 Zipkin 的链路追踪变的十分简单。下面会按照 Brave 中关于 Dubbo RPC 支持的指引 来说明如何在 Dubbo 中使用 Zipkin。

安装 Zipkin Server

按照 Zipkin 官方文档中的快速开始 来安装 Zipkin,如下所示:

$ curl -sSL https://zipkin.io/quickstart.sh | bash -s$ java -jar zipkin.jar

按照这种方式安装的 Zipkin Server 使用的存储类型是 inMemory 的。当服务器停机之后,所有收集到的 trace 信息会丢失,不适用于生产系统。如果在生产系统中使用,需要配置另外的存储类型。Zipkin 支持 MySql、Cassandra、和 ElasticSearch。推荐使用 Cassandra 和 ElasticSearch,相关的配置请自行查阅 官方文档 。

本文为了演示方便,使用的存储是 inMemory 类型。成功启动之后,可以在终端看到如下的提示:

$ java -jar zipkin.jar
Picked up JAVA_TOOL_OPTIONS: -Djava.awt.headless=true
                                    ********
                                  **        **
                                 *            *
                                **            **
                                **            **
                                 **          **
                                  **        **
                                    ********
                                      ****
                                      ****
        ****                          ****
     ******                           ****                                 ***
  ****************************************************************************
    *******                           ****                                 ***
        ****                          ****
                                       **
                                       **
             *****      **     *****     ** **       **     **   **
               **       **     **  *     ***         **     **** **
              **        **     *****     ****        **     **  ***
             ******     **     **        **  **      **     **   **
:: Powered by Spring Boot ::         (v2.0.5.RELEASE)
...
o.s.b.w.e.u.UndertowServletWebServer     : Undertow started on port(s) 9411 (http) with context path ''2018-10-10 18:40:31.605  INFO 21072 --- [           main] z.s.ZipkinServer                         : Started ZipkinServer in 6.835 seconds (JVM running for 8.35)

然后在浏览器中访问 http://localhost:9411 验证 WEB 界面。

配置 Maven 依赖

引入 Brave 依赖

新建一个新的 Java 工程,并在 pom.xml 中引入 Brave 相关的依赖如下:

    
        5.4.2
        2.7.9
    
    
        
            
            
                io.zipkin.brave
                brave-bom
                ${brave.version}
                pom
                import
            
            
            
            
                io.zipkin.reporter2
                zipkin-reporter-bom
                ${zipkin-reporter.version}
                pom
                import
            
        
    
    
        
        
            io.zipkin.brave
            brave-instrumentation-dubbo-rpc
        
        
        
            io.zipkin.brave
            brave-spring-beans
        
        
        
            io.zipkin.brave
            brave-context-slf4j
        
        
        
            io.zipkin.reporter2
            zipkin-sender-okhttp3
        
    

其中:

  1. 引入 brave-instrumentation-dubbo-rpc,brave 对 dubbo 的支持: https://github.com/openzipkin/brave/blob/master/instrumentation/dubbo-rpc/README.md

  2. 引入 brave-spring-beans,brave 对 spring bean 的支持: https://github.com/openzipkin/brave/blob/master/spring-beans/README.md

  3. 引入 brave-context-slf4j,brave 对 SLF4J 的支持,可以在 MDC 中使用 traceId 和 spanId: https://github.com/openzipkin/brave/blob/master/context/slf4j/README.md

  4. 引入 zipkin-sender-okhttp3,使用 okhttp3 上报数据: https://github.com/openzipkin/zipkin-reporter-java

引入 Dubbo 相关依赖

Dubbo 相关的依赖是 Dubbo 本身以及 Zookeeper 客户端,在下面的例子中,我们将会使用独立的 Zookeeper Server 作为服务发现。

    
        
        
            org.apache.curator
            curator-framework
            2.12.0
            
                
                    io.netty
                    netty
                
            
        
        
        
            com.alibaba
            dubbo
            2.6.2
        
    

其中:

  1. Dubbo 这里依赖独立的 Zookeeper Server 做服务发现,这里使用的客户端是 Curator

  2. 引入 Dubbo 框架的依赖,原则上 2.6 的任何版本都是工作的,这里使用的是 2.6.2 版本

实现

我们这里构造的场景是一个有两个节点的服务依赖链,也就是,当一个 Dubbo 客户端调用服务 A 时,服务 A 将会继续调用服务 B。在这个例子中,服务 A 是 greeting service,它所依赖的下游服务服务 B 是 hello service。

定义服务接口

为此需要事先定义两个服务接口 GreetingService 以及 HelloService

  1. com.alibaba.dubbo.samples.api.GreetingService

    package com.alibaba.dubbo.samples.api;public interface GreetingService {    String greeting(String message);
    }
  2. com.alibaba.dubbo.samples.api.HelloService

    package com.alibaba.dubbo.samples.api;public interface HelloService {    String hello(String message);
    }
实现服务接口

为了区分对待,所有和 HelloService 相关的实现代码都放在 hello 子包下,同理 GreetingService 相关的放在 greeting 子包下。

  1. 实现 com.alibaba.dubbo.samples.api.HelloService

    package com.alibaba.dubbo.samples.service.hello;import com.alibaba.dubbo.samples.api.HelloService;import java.util.Random;public class HelloServiceImpl implements HelloService {    @Override
        public String hello(String message) {        try {            // 通过 sleep 模拟业务逻辑处理时间
                Thread.sleep(new Random(System.currentTimeMillis()).nextInt(1000));
            } catch (InterruptedException e) {            // no op
            }        return "hello, " + message;
        }
    }
  2. 实现 com.alibaba.dubbo.samples.api.GreetingService

    package com.alibaba.dubbo.samples.service.greeting;import com.alibaba.dubbo.samples.api.GreetingService;import com.alibaba.dubbo.samples.api.HelloService;import java.util.Random;public class GreetingServiceImpl implements GreetingService {    // 下游依赖服务,运行时靠 spring 容器注入 HelloService 的服务代理
        private HelloService helloService;    public void setHelloService(HelloService helloService) {        this.helloService = helloService;
        }    @Override
        public String greeting(String message) {        try {            // 通过 sleep 模拟业务逻辑处理时间
                Thread.sleep(new Random(System.currentTimeMillis()).nextInt(1000));
            } catch (InterruptedException e) {            // no op
            }        return "greeting, " + helloService.hello(message);
        }
    }

    这里需要注意的是,GreetingServiceImpl 的实现中声明了一个类型是 HelloService 的成员变量,并在 greeting 方法中,执行完自己逻辑之后又调用了 HelloService 上的 hello 方法。这里的 helloService 的实现将会在运行态由外部注入,注入的不是 HelloServiceImpl 的实现,而是 HelloService 的远程调用代理。通过这样的方式,完成了在一个 Dubbo 服务中继续调用另一个远程 Dubbo 服务的目的。从链路追踪的角度来说,客户端调用 GreetingService 是一个 span,GreetingService 调用 HelloService 是另一个 span,并且两者有父子关系,同属于一个 trace,也就是属于同一条调用链路。

    另外,在 GreetingServiceImpl 和 HelloServiceImpl 的实现中,通过 Thread.sleep 来模拟了处理业务逻辑的耗时,以便在 Zipkin UI 上更好的展示。

配置

为了专注在展示如何使用 Zipkin 这一点上,本文在配置和编程模型上没有采用更多的高级技术,而是使用了最传统的 Spring XML 的配置方式,帮助读者理解。更高级的通过 annotation 甚至 spring boot 的方式,读者可以自行查阅 Dubbo 和 Zipkin 相关的文档。

  1. 暴露 HelloService 服务

    在 resouces/spring/hello-service.xml 中增加以下的配置来将 HelloServiceImpl 暴露成一个 Dubbo 服务:

        
        
        
        
        
        
        
        
        
        
    • 使用了本地启动的 Zookeeper Server 作为注册中心,地址为默认值 zookeeper://127.0.0.1:2181

    • 用 Dubbo 原生服务在端口 20880 上暴露服务

    • 将 HelloServiceImpl 注册成 id 是 helloService 的 Spring Bean,这样就可以在后续的 中引用到这个实现类

    • 通过 将 HelloServiceImpl 暴露成 Dubbo 服务

  2. 增加 Zipkin 相关的配置

    在 resources/spring/hello-service.xml 中增加 Zipkin 相关的配置:

        
        
        
        
        
            
        
        
            
            
                
                    
                    
                    
                
            
            
                
                    
                        
                    
                
            
        
    • 修改 dubbo 服务暴露的配置,添加 Zipkin 的 tracing filter 到 Dubbo 的 filter chain 中

    • 按照 https://github.com/openzipkin/brave/blob/master/spring-beans/README.md 来配置 Zipkin 的 sender 和 tracing 的 spring bean

  3. 增加 HelloService 的启动类

    在 com.alibaba.dubbo.samples.service.hello.Application 中通过 ClassPathXmlApplicationContext 读取 刚才配置的 spring/hello-service.xml 来初始化一个 spring context 并启动

    package com.alibaba.dubbo.samples.service.hello;import org.springframework.context.support.ClassPathXmlApplicationContext;import java.io.IOException;public class Application {    public static void main(String[] args) throws IOException {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/hello-service.xml");
            context.start();
            System.out.println("Hello service started");        // press any key to exit
            System.in.read();
        }
    }
  4. 暴露 GreetingService 服务,并使用 Zipkin

    在 resources/spring/greeting-service.xml 中配置 GreetingService。相关步骤与 HelloService 类似,不再赘述,重点关注如何在 GreetingService 中配置下游服务的依赖。完整的 XML 配置如下:

    
        
        
        
        
         
        
        
        
        
        
        
        
            
        
        
        
        
        
            
        
        
            
            
                
                    
                    
                    
                
            
            
                
                    
                        
                    
                
            
        

    这里的配置与上面的 HelloService 类似,需要重点关注的有两点:

    增加 GreeeingService 的启动类,与 HelloService 类似,通过 spring/greeting-service.xml 的配置来初始化一个新的 spring context 来完成。

    • 第 3 步中注意服务需要暴露在不同的端口上,否则会和 HelloService 冲突,本例中选择的是 20881 这个端口

    • 通过第 4 步先声明 HelloService 的远程代理,然后在第 5 步中将其组装给 GreetingService 来完成服务上下游依赖的声明

   package com.alibaba.dubbo.samples.service.greeting;   
   import org.springframework.context.support.ClassPathXmlApplicationContext;   
   import java.io.IOException;   
   public class Application {       public static void main(String[] args) throws IOException {
           ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/greeting-service.xml");
           context.start();
   
           System.out.println("Greeting service started");           // press any key to exit
           System.in.read();
       }
   }
  1. 实现客户端

    通过 resources/spring/client.xml 初始化一个 spring context,从其中获取 GreetingService 的远程代理,发起远程调用。

    package com.alibaba.dubbo.samples.client;import com.alibaba.dubbo.samples.api.GreetingService;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Application {    public static void main(String[] args) {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/client.xml");
            context.start();        // 获取远程代理并发起调用
            GreetingService greetingService = (GreetingService) context.getBean("greetingService");
            System.out.println(greetingService.greeting("world"));
        }
    }

    resource/spring/client.xml 中的配置与 Dubbo 服务的配置类似,主要是配置远程代理,以及配置 Zipkin

    
       
        
        
        
        
        
        
        
            
        
        
            
            
                
                    
                    
                    
                
            
            
                
                    
                        
                    
                
            
        

完成之后的工程的目录结构如下:

如何在Dubbo中使用Zipkin来实现分布式追踪

运行

现在让我们把整个链路运行起来,看看 Zipkin 链路追踪的效果。

启动 Zookeeper Server

执行以下命令在本地启动一个 Zookeeper Server,如果没有安装,请自行从 ZooKeeper 官网 下载:

$ zkServer start
启动 Zipkin Server

执行以下命令在本地启动一个 Zipkin Server:

$ curl -sSL https://zipkin.io/quickstart.sh | bash -s$ java -jar zipkin.jar
启动 HelloService

使用下面的命令启动 HelloService,当然也可以直接在 IDE 中启动:

$ mvn exec:java -Dexec.mainClass=com.alibaba.dubbo.samples.service.hello.Application

启动成功后应该可以在终端上看到 “Hello service started” 的字样。

启动 GreetingService

使用下面的命令启动 GreetingService,当然也可以直接在 IDE 中启动:

$ mvn exec:java -Dexec.mainClass=com.alibaba.dubbo.samples.service.greeting.Application

启动成功后应该可以在终端上看到 “Greeting service started” 的字样。

运行 Dubbo 客户端

使用下面的命令运行 Dubbo 客户端向 GreetingService 发起远程调用,当然也可以直接在 IDE 中运行:

$ mvn exec:java -Dexec.mainClass=com.alibaba.dubbo.samples.client.Application

执行成功后,客户端会在终端上输出 “greeting, hello, world”。

链路追踪

打开浏览器访问 " http://localhost:9411 " 并通过 "Find Traces" 按钮来搜索,可以找到刚刚调用的链路追踪,效果如下图所示:

如何在Dubbo中使用Zipkin来实现分布式追踪

还可以进一步的选择每一个 span 来查看本次调用边界内的详情,比如,hello-service 这个 span 的详情如下:

如何在Dubbo中使用Zipkin来实现分布式追踪

到此,关于“如何在Dubbo中使用Zipkin来实现分布式追踪”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注创新互联网站,小编会继续努力为大家带来更多实用的文章!


标题名称:如何在Dubbo中使用Zipkin来实现分布式追踪
URL标题:http://myzitong.com/article/gpgjco.html