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

2018-04-24 08:55:52 +08:00
 hansonwang99

## 概述

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


Zipkin 初摸

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

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

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

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

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

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

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


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

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

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


部署 Zipkin 服务

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

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

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


模拟微服务调用链

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

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

为了模拟明显的延时效果,准备在每个接口的响应中用代码加入 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

<?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>

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

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

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

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

这个类很重要,在里面我们将 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 这个微服务,可以看到其调用链的上下游!


2. 查找调用链

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

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

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

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

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

参考文献


6177 次点击
所在节点    程序员
23 条回复
onepunch
2018-04-24 09:39:52 +08:00
40 分钟已经过去了,战略性 mark
byrain
2018-04-24 10:05:17 +08:00
嗯。这东西挺好的,我们这边做了 http grpc 以及 mysql 调用的追踪。
lawmil
2018-04-24 11:09:44 +08:00
战略 mark 一下
prolic
2018-04-24 11:22:06 +08:00
mark
bolide2005
2018-04-24 11:28:40 +08:00
建议再看看 OpenTracing,可以了解一些 Zipkin 更底层的设计思路
Mitt
2018-04-24 11:30:09 +08:00
mark
salmon5
2018-04-24 11:41:37 +08:00
@hansonwang99
请教下,如果作为一个 CTO,这个工作交给谁合适呢?
架构,后端,前端,UI,运维,测试,PM ?
hansonwang99
2018-04-24 11:44:52 +08:00
@salmon5 可以咨询一下 CEO
freehere
2018-04-24 11:49:21 +08:00
oneapm 是否直接能替代搭建这个系统?
projectzoo
2018-04-24 12:37:23 +08:00
看到“百度一下”在收藏夹第一位。
关了。
xuanyuanaosheng
2018-04-24 12:45:46 +08:00
mark,有同类产品比较么?其他产品有么?
tanszhe
2018-04-24 13:16:17 +08:00
一个日志分析就能有这个功能,还可以更细化 也不需要在应用中安插什么 Reporter,只要日志格式统一就好了。
实现这个主要就是给每个请求产生一个唯一 id
flight2006
2018-04-24 13:33:57 +08:00
mark,公司有用过,但是没细细研究过
valkyrja
2018-04-24 14:16:40 +08:00
@projectzoo 一股恶臭的优越感扑面而来
RorschachZZZ
2018-04-24 14:43:59 +08:00
hpu423
2018-04-24 14:50:05 +08:00
以前公司搭建过,后来没推广成功
oswuhan
2018-04-24 16:38:38 +08:00
看开头不明觉厉,看完醍醐灌顶
YanY
2018-04-24 18:29:25 +08:00
@xuanyuanaosheng CAT 也是服务链调用日志
yuhuan66666
2018-04-24 18:37:24 +08:00
spring cloud 里的数据链路中心就是封装了这个
war1644
2018-04-24 20:33:46 +08:00
战略 mark 下,还在梳理公司 docker 微服务。调用链这块早晚得用上

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/449309

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX