V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
hansonwang99
V2EX  ›  程序员

微服务调用链追踪中心搭建

  •  2
     
  •   hansonwang99 ·
    hansonwang99 · 2018-04-24 08:55:52 +08:00 · 5901 次点击
    这是一个创建于 2187 天前的主题,其中的信息可能已经有所发展或是发生改变。

    ## 概述

    一个完整的微服务系统包含多个微服务单元,各个微服务子系统存在互相调用的情况,形成一个 调用链。一个客户端请求从发出到被响应 经历了哪些组件哪些微服务请求总时长每个组件所花时长 等信息我们有必要了解和收集,以帮助我们定位性能瓶颈、进行性能调优,因此监控整个微服务架构的调用链十分有必要,本文将阐述如何使用 Zipkin 搭建微服务调用链追踪中心。


    Zipkin 初摸

    正如 Ziplin 官网 所描述,Zipkin 是一款分布式的追踪系统,其可以帮助我们收集微服务架构中用于解决延时问题的时序数据,更直白地讲就是可以帮我们追踪调用的轨迹。

    Zipkin 的设计架构如下图所示:

    Zipkin 设计架构

    要理解这张图,需要了解一下 Zipkin 的几个核心概念:

    • Reporter

    在某个应用中安插的用于发送数据给 Zipkin 的组件称为 Report,目的就是用于追踪数据收集

    • Span

    微服务中调用一个组件时,从发出请求开始到被响应的过程会持续一段时间,将这段跨度称为 Span

    • Trace

    从 Client 发出请求到完成请求处理,中间会经历一个调用链,将这一个整个过程称为一个追踪( Trace )。一个 Trace 可能包含多个 Span,反之每个 Span 都有一个上级的 Trace。

    • Transport

    一种数据传输的方式,比如最简单的 HTTP 方式,当然在高并发时可以换成 Kafka 等消息队列


    看了一下基本概念后,再结合上面的架构图,可以试着理解一下,只有装配有 Report 组件的 Client 才能通过 Transport 来向 Zipkin 发送追踪数据。追踪数据由 Collector 收集器进行手机然后持久化到 Storage 之中。最后需要数据的一方,可以通过 UI 界面调用 API 接口,从而最终取到 Storage 中的数据。可见整体流程不复杂。

    Zipkin 官网给出了各种常见语言支持的 OpenZipkin libraries:

    OpenZipkin libraries

    本文接下来将 构造微服务追踪的实验场景 并使用 Brave 来辅助完成微服务调用链追踪中心搭建!


    部署 Zipkin 服务

    利用 Docker 来部署 Zipkin 服务再简单不过了:

    docker run -d -p 9411:9411 \
    --name zipkin \
    docker.io/openzipkin/zipkin
    

    完成之后浏览器打开:localhost:9411可以看到 Zipkin 的可视化界面:

    Zipkin 可视化界面


    模拟微服务调用链

    我们来构造一个如下图所示的调用链:

    微服务调用链

    图中包含 一个客户端 + 三个微服务

    • Client:使用 /servicea 接口消费 ServiceA 提供的服务

    • ServiceA:使用 /serviceb 接口消费 ServiceB 提供的服务,端口 8881

    • ServiceB:使用 /servicec 接口消费 ServiceC 提供的服务,端口 8882

    • ServiceC:提供终极服务,端口 8883

    为了模拟明显的延时效果,准备在每个接口的响应中用代码加入 3s 的延时。

    简单起见,我们用 SpringBt 来实现三个微服务。

    ServiceA 的控制器代码如下:

    @RestController
    public class ServiceAContorller {
    
        @Autowired
        private RestTemplate restTemplate;
    
        @GetMapping("/servicea ”)
        public String servicea() {
            try {
                Thread.sleep( 3000 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return restTemplate.getForObject("http://localhost:8882/serviceb", String.class);
        }
    }
    

    ServiceB 的代码如下:

    @RestController
    public class ServiceBContorller {
    
        @Autowired
        private RestTemplate restTemplate;
    
        @GetMapping("/serviceb ”)
        public String serviceb() {
            try {
                Thread.sleep( 3000 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return restTemplate.getForObject("http://localhost:8883/servicec", String.class);
        }
    }
    

    ServiceC 的代码如下:

    @RestController
    public class ServiceCContorller {
    
        @Autowired
        private RestTemplate restTemplate;
    
        @GetMapping("/servicec ”)
        public String servicec() {
            try {
                Thread.sleep( 3000 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Now, we reach the terminal call: servicec !”;
        }
    }
    

    我们将三个微服务都启动起来,然后浏览器中输入localhost:8881/servicea来发出请求,过了 9s 之后,将取到 ServiceC 中提供的微服务接口所返回的内容,如下图所示:

    微服务链式调用结果

    很明显,调用链可以正常 work 了!

    那么接下来我们就要引入 Zipkin 来追踪这个调用链的信息!

    编写与 Zipkin 通信的工具组件

    从 Zipkin 官网我们可以知道,借助 OpenZipkin 库 Brave,我们可以开发一个封装 Brave 的公共组件,让其能十分方便地嵌入到 ServiceA,ServiceB,ServiceC 服务之中,完成与 Zipkin 的通信。

    为此我们需要建立一个新的基于 Maven 的 Java 项目:ZipkinTool

    • 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>com.hansonwang99</groupId>
        <artifactId>ZipkinTool</artifactId>
        <version>1.0-SNAPSHOT</version>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>6</source>
                        <target>6</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
        <packaging>jar</packaging>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot</artifactId>
                <version>2.0.1.RELEASE</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>4.3.7.RELEASE</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>io.zipkin.brave</groupId>
                <artifactId>brave-spring-web-servlet-interceptor</artifactId>
                <version>4.0.6</version>
            </dependency>
            <dependency>
                <groupId>io.zipkin.brave</groupId>
                <artifactId>brave-spring-resttemplate-interceptors</artifactId>
                <version>4.0.6</version>
            </dependency>
            <dependency>
                <groupId>io.zipkin.reporter</groupId>
                <artifactId>zipkin-sender-okhttp3</artifactId>
                <version>0.6.12</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>RELEASE</version>
                <scope>compile</scope>
            </dependency>
        </dependencies>
    
    </project>
    
    • 编写 ZipkinProperties 类

    其包含 endpoint 和 service 两个属性,我们最后是需要将该两个参数提供给 ServiceA、ServiceB、ServiceC 微服务作为其 application.properties 中的 Zipkin 配置

    @Data
    @Component
    @ConfigurationProperties("zipkin")
    public class ZipkinProperties {
        private String endpoint;
        private String service;
    }
    

    用了 lombok 之后,这个类异常简单!

    [注意:关于 lombok 的用法,可以看这里]

    • 编写 ZipkinConfiguration 类

    这个类很重要,在里面我们将 Brave 的 BraveClientHttpRequestInterceptor 拦截器注册到 RestTemplate 的拦截器调用链中来收集请求数据到 Zipkin 中;同时还将 Brave 的 ServletHandlerInterceptor 拦截器注册到调用链中来收集响应数据到 Zipkin 中

    上代码吧:

    @Configuration
    @Import({RestTemplate.class, BraveClientHttpRequestInterceptor.class, ServletHandlerInterceptor.class})
    public class ZipkinConfiguration extends WebMvcConfigurerAdapter {
    
        @Autowired
        private ZipkinProperties zipkinProperties;
    
        @Autowired
        private RestTemplate restTemplate;
    
        @Autowired
        private BraveClientHttpRequestInterceptor clientInterceptor;
    
        @Autowired
        private ServletHandlerInterceptor serverInterceptor;
    
        @Bean
        public Sender sender() {
            return OkHttpSender.create( zipkinProperties.getEndpoint() );
        }
    
        @Bean
        public Reporter<Span> reporter() {
            return AsyncReporter.builder(sender()).build();
        }
    
        @Bean
        public Brave brave() {
            return new Brave.Builder(zipkinProperties.getService()).reporter(reporter()).build();
        }
    
        @Bean
        public SpanNameProvider spanNameProvider() {
            return new SpanNameProvider() {
                @Override
                public String spanName(HttpRequest httpRequest) {
                    return String.format(
                            "%s %s",
                            httpRequest.getHttpMethod(),
                            httpRequest.getUri().getPath()
                    );
                }
            };
        }
    
        @PostConstruct
        public void init() {
            List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
            interceptors.add(clientInterceptor);
            restTemplate.setInterceptors(interceptors);
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(serverInterceptor);
        }
    }
    
    

    ZipkinTool 完成以后,我们需要在 ServiceA、ServiceB、ServiceC 三个 SpringBt 项目的 application.properties 中加入 Zipkin 的配置:

    以 ServiceA 为例:

    server.port=8881
    zipkin.endpoint=http://你 Zipkin 服务所在机器的 IP:9411/api/v1/spans
    zipkin.service=servicea
    

    我们最后依次启动 ServiceA、ServiceB、和 ServiceC 三个微服务,并开始实验来收集链路追踪数据 !


    ## 实际实验

    1. 依赖分析

    浏览器打开 Zipkin 的 UI 界面,可以查看 依赖分析

    点击依赖分析

    图中十分清晰地展示了 ServiceA、ServiceB 和 ServiceC 三个服务之间的调用关系! 注意,该图可缩放,并且每一个元素均可以点击,例如点击 ServiceB 这个微服务,可以看到其调用链的上下游!

    点击 ServiceB 微服务


    2. 查找调用链

    接下来我们看一下调用链相关,点击 服务名,可以看到 Zipkin 监控到个所有服务:

    查找调用链

    同时可以查看 Span,如以 ServiceA 为例,其所有 REST 接口都再下拉列表中:

    查看 Span

    以 ServiceA 为例,点击 Find Traces,可以看到其所有追踪信息:

    Find Traces

    点击某个具体 Trace,还能看到详细的每个 Span 的信息,如下图中,可以看到 A B C 调用过程中每个 REST 接口的详细时间戳:

    某一个具体 Trace

    点击某一个 REST 接口进去还能看到更详细的信息,如查看 /servicec 这个 REST 接口,可以看到从发送请求到收到响应信息的所有详细步骤:

    某一个具体 Span 详细信息

    参考文献


    23 条回复    2018-04-25 10:29:21 +08:00
    onepunch
        1
    onepunch  
       2018-04-24 09:39:52 +08:00
    40 分钟已经过去了,战略性 mark
    byrain
        2
    byrain  
       2018-04-24 10:05:17 +08:00
    嗯。这东西挺好的,我们这边做了 http grpc 以及 mysql 调用的追踪。
    lawmil
        3
    lawmil  
       2018-04-24 11:09:44 +08:00
    战略 mark 一下
    prolic
        4
    prolic  
       2018-04-24 11:22:06 +08:00
    mark
    bolide2005
        5
    bolide2005  
       2018-04-24 11:28:40 +08:00
    建议再看看 OpenTracing,可以了解一些 Zipkin 更底层的设计思路
    Mitt
        6
    Mitt  
       2018-04-24 11:30:09 +08:00
    mark
    salmon5
        7
    salmon5  
       2018-04-24 11:41:37 +08:00
    @hansonwang99
    请教下,如果作为一个 CTO,这个工作交给谁合适呢?
    架构,后端,前端,UI,运维,测试,PM ?
    hansonwang99
        8
    hansonwang99  
    OP
       2018-04-24 11:44:52 +08:00 via iPhone
    @salmon5 可以咨询一下 CEO
    freehere
        9
    freehere  
       2018-04-24 11:49:21 +08:00
    oneapm 是否直接能替代搭建这个系统?
    projectzoo
        10
    projectzoo  
       2018-04-24 12:37:23 +08:00
    看到“百度一下”在收藏夹第一位。
    关了。
    xuanyuanaosheng
        11
    xuanyuanaosheng  
       2018-04-24 12:45:46 +08:00 via Android
    mark,有同类产品比较么?其他产品有么?
    tanszhe
        12
    tanszhe  
       2018-04-24 13:16:17 +08:00   ❤️ 1
    一个日志分析就能有这个功能,还可以更细化 也不需要在应用中安插什么 Reporter,只要日志格式统一就好了。
    实现这个主要就是给每个请求产生一个唯一 id
    flight2006
        13
    flight2006  
       2018-04-24 13:33:57 +08:00 via Android
    mark,公司有用过,但是没细细研究过
    valkyrja
        14
    valkyrja  
       2018-04-24 14:16:40 +08:00 via iPhone   ❤️ 1
    @projectzoo 一股恶臭的优越感扑面而来
    RorschachZZZ
        15
    RorschachZZZ  
       2018-04-24 14:43:59 +08:00
    hpu423
        16
    hpu423  
       2018-04-24 14:50:05 +08:00
    以前公司搭建过,后来没推广成功
    oswuhan
        17
    oswuhan  
       2018-04-24 16:38:38 +08:00
    看开头不明觉厉,看完醍醐灌顶
    YanY
        18
    YanY  
       2018-04-24 18:29:25 +08:00
    @xuanyuanaosheng CAT 也是服务链调用日志
    yuhuan66666
        19
    yuhuan66666  
       2018-04-24 18:37:24 +08:00 via Android
    spring cloud 里的数据链路中心就是封装了这个
    war1644
        20
    war1644  
       2018-04-24 20:33:46 +08:00
    战略 mark 下,还在梳理公司 docker 微服务。调用链这块早晚得用上
    PureWhite
        21
    PureWhite  
       2018-04-25 02:16:33 +08:00
    其实就是分布式追踪
    xuanyuanaosheng
        22
    xuanyuanaosheng  
       2018-04-25 10:27:28 +08:00
    @YanY 好的 谢谢
    gmywq0392
        23
    gmywq0392  
       2018-04-25 10:29:21 +08:00
    现在主流的 APM 也有这种 trace 的功能了。比如 Elastic APM, Pinpoint, OneAPM 这些入侵性更少,而且从监控的角度来说体验更好呀。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5610 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 06:12 · PVG 14:12 · LAX 23:12 · JFK 02:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.