01--理论
背景
微服务架构上通过业务来划分服务的,通过REST调用,对外暴露的一个接口,这个接口功能可能需要很多个服务协同,如果链路上任何一个服务出现问题,都会导致接口调用失败。
随着业务的不断扩张,服务之间互相调用越复杂。调用链的分析也会越复杂。这就需要监控微服务各个服务的调用情况。
zipkin介绍
1.是分布式跟踪系统(Distributed Tracking System)
监控微服务各个服务的调用情况
举例
一个请求A,需要先后调用f1,f2,f3等微服务单元的接口,我们可以通过链路追踪查看f1,f2,f3对应接口的耗时。
2.主要功能
聚集来自各个异构系统的实时监控数据。
追踪微服务架构下的系统延时问题
3.应用系统需要进行装备(instrument)以向 Zipkin 报告数据
4.Zipkin 的用户界面
可以呈现一幅关联图表,以显示有多少被追踪的请求通过了每一层应用。
可以查看 Span 的依赖关系
可以以瀑布图的形式显示了每个 Span 的耗时情况,可以一目了然的看到各个服务的性能状况。
打开每个 Span,还有更详细的数据以键值对的形式呈现,而且这些数据可以在装备应用的时候自行添加。
5.Zipkin 以 Trace 结构表示对一次请求的追踪,又把每个 Trace 拆分为若干个有依赖关系的 Span。
在微服务架构中,一次用户请求可能会由后台若干个服务负责处理,那么每个处理请求的服务就可以理解为一个 Span(可以包括 API 服务,缓存服务,数据库服务以及报表服务等)。当然这个服务也可能继续请求其他的服务,因此 Span 是一个树形结构,以体现服务之间的调用关系。
ZipKin架构
1.跟踪器(Tracer)位于你的应用程序中,并记录发生的操作的时间和元数据,提供了相应的类库,对用户的使用来说是透明的,收集的跟踪数据称为Span;将数据发送到Zipkin的仪器化应用程序中的组件称为Reporter,Reporter通过几种传输方式之一将追踪数据发送到Zipkin收集器(collector),然后将跟踪数据进行存储(storage),由API查询存储以向UI提供数据。
2.ZipKin可以分为两部分:zipkin server和zipkin client
zipkin server:用来作为数据的采集存储、数据分析与展示
1.Collector
收集器组件,它主要用于处理从外部系统发送过来的跟踪信息,将这些信息转换为Zipkin内部处理的Span格式,以支持后续的存储,分析,展示等功能。
2.Storage
存储接受或收集过来的数据
当前支持Memory,MySQL,Cassandra,ElasticSearch等
默认存储在内存中。
3.API(Query)
负责查询Storage中存储的数据,提供简单的JSON API获取数据,主要提供给web UI使用
4.Web UI
UI组件,基于API组件实现的上层应用。
通过UI组件用户可以方便而有直观地查询和分析跟踪信息。
zipkin client
完成了追踪数据的生成与上报功能
概念和字段说明
1、Trace
Zipkin使用Trace结构表示对一次请求的跟踪
一次请求可能由后台的若干服务负责处理
每个服务的处理是一个Span,Span之间有依赖关系
Trace就是树结构的Span集合
2、Span
每个服务的处理跟踪是一个Span,可以理解为一个基本的工作单元,如下:
//一个Span
{
//标记一次请求的跟踪,相关的Spans都有相同的traceId
"traceId": "bd7a977555f6b982",
//span的名称,一般是接口方法的名称
"name": "get-traces",
//span id
"id": "ebf33e1a81dc6f71",
//当前Span的父Span id,通过parentId来保证Span之间的依赖关系,如果没有parentId,表示当前Span为根Span;
"parentId": "bd7a977555f6b982",
//Span创建时的时间戳。单位是微秒,可能服务器有时钟偏差导致时间错误
"timestamp": 1458702548478000,
//持续时间,单位是微秒
"duration": 354374,
//注解用于及时记录事件;有一组核心注解用于定义RPC请求的开始和结束
"annotations": [
{
"endpoint": {
"serviceName": "zipkin-query",
"ipv4": "192.168.1.2",
"port": 9411
},
"timestamp": 1458702548786000,
"value": "cs"
}
],
//二进制注释,旨在提供有关RPC的额外信息。
"binaryAnnotations": [
{
"key": "lc",
"value": "JDBCSpanStore",
"endpoint": {
"serviceName": "zipkin-query",
"ipv4": "192.168.1.2",
"port": 9411
}
}
]
}
3、transport
负责从运输从service收集来的spans,并把这些spans转化为zipkin的通用span,并将其传递到存储层,这种方法是模块化的,允许任何生产者接收任何类型的数据
zipkin配有HTTP、kafka、scribe三种类型的transport
instrumentations负责和transport进行交互。
服务追踪流程如下:
┌─────────────┐ ┌───────────────────────┐ ┌─────────────┐ ┌──────────────────┐
│ User Code │ │ Trace Instrumentation │ │ Http Client │ │ Zipkin Collector │
└─────────────┘ └───────────────────────┘ └─────────────┘ └──────────────────┘
│ │ │ │
┌─────────┐
│ ──┤GET /foo ├─▶ │ ────┐ │ │
└─────────┘ │ record tags
│ │ ◀───┘ │ │
────┐
│ │ │ add trace headers │ │
◀───┘
│ │ ────┐ │ │
│ record timestamp
│ │ ◀───┘ │ │
┌─────────────────┐
│ │ ──┤GET /foo ├─▶ │ │
│X-B3-TraceId: aa │ ────┐
│ │ │X-B3-SpanId: 6b │ │ │ │
└─────────────────┘ │ invoke
│ │ │ │ request │
│
│ │ │ │ │
┌────────┐ ◀───┘
│ │ ◀─────┤200 OK ├─────── │ │
────┐ └────────┘
│ │ │ record duration │ │
┌────────┐ ◀───┘
│ ◀──┤200 OK ├── │ │ │
└────────┘ ┌────────────────────────────────┐
│ │ ──┤ asynchronously report span ├────▶ │
│ │
│{ │
│ "traceId": "aa", │
│ "id": "6b", │
│ "name": "get", │
│ "timestamp": 1483945573944000,│
│ "duration": 386000, │
│ "annotations": [ │
│--snip-- │
└────────────────────────────────┘
简单说明
一个应用的代码发起HTTP get请求,经过Trace框架拦截,然后
把当前调用链的Trace信息添加到HTTP Header里面
记录当前调用的时间戳
发送HTTP请求,把trace相关的header信息携带上
调用结束之后,记录当前调用花费的时间
然后把上面流程产生的 信息汇集成一个span,把这个span信息上传到zipkin的Collector模块
核心数据结构
1、traceId
标记一次请求的跟踪,相关的Spans都有相同的traceId
Zipkin将具有相同traceId的span组装成跟踪树来直观的将调用链路图展现在我们面前。
2、id
span的id
3、parentId
当前Span的父Span id,通过parentId来保证Span之间的依赖关系,如果没有parentId,表示当前Span为根Span;
4、name
span的名称,一般是接口方法的名称
name的作用是让人知道它是哪里采集的span
5、timestamp
Span创建时的时间戳。单位是微秒,可能服务器有时钟偏差导致时间错误
6、duration
持续时间,即span的创建到span完成最终的采集所经历的时间,除去span自己逻辑处理的时间
该时间段可以理解成对于该跟踪埋点来说服务调用的总耗时
7、annotations
用于定位一个request的开始和结束,cs/sr/ss/cr含有额外的信息,比如说时间点,当这个annotation被记录了,这个RPC也被认为完成了。
7.1、cs: Client Start
表示客户端发起请求,一个span的开始
7.2、cr:Client Received
表示客户端获取到服务端返回信息,一个span的结束
7.3、sr:Server Receive
表示服务端收到请求
7.4、ss:Server Send
表示服务端完成处理,并将结果发送给客户端
7.5、sr-cs:sr的时间-cs的时间
网络延迟
7.6、ss-sr:ss的时间-sr的时间
逻辑处理时间
7.7、cr-cs:cr的时间-cs的时间
整个流程时间
8、binaryAnnotations
业务标注列表,如果某些跟踪埋点需要带上部分业务数据(比如url地址、返回码和异常信息等),可以将需要的数据以键值对的形式放入到这个字段中。
02--安装
下载启动
下载:
https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec
启动:
java -jar zipkin-server-2.12.9-exec.jar
访问
http://localhost:9411
03--Springboot 集成 Zipkin--通过io.zipkin.brave包
微服务及端口
服务名称 | 端口 |
---|---|
service8081 | 8081 |
service8082 | 8082 |
service8083 | 8083 |
service8084 | 8084 |
service8085 | 8085 |
搭建service8081
1.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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>service8081</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service8081</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<zipkin.version>3.9.0</zipkin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<!-- zipkin相关 -->
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-core</artifactId>
<version>${zipkin.version}</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-spancollector-http</artifactId>
<version>${zipkin.version}</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-web-servlet-filter</artifactId>
<version>${zipkin.version}</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-apache-http-interceptors</artifactId>
<version>${zipkin.version}</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-okhttp</artifactId>
<version>${zipkin.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.application.properties
# 发给zipkin的服务器名称
zipkin.serviceName=service8081
# zipkin的url地址
zipkin.url=http://localhost:9411
# 连接超时时间
zipkin.connectTimeout=6000
# 读取超时时间
zipkin.readTimeout=6000
# 上传 span 的间隔时间
zipkin.flushInterval=1
# 是否启动压缩
zipkin.compressionEnabled=true
# 采样率,默认为0.1,值越大收集越及时,但性能影响也越大
zipkin.samplerRate=1
server.port=8081
server.servlet.context-path=/
spring.application.name=service8081
3.Span收集器配置
package com.example.service8081.business.config;
import com.github.kristofa.brave.Brave;
import com.github.kristofa.brave.Brave.Builder;
import com.github.kristofa.brave.EmptySpanCollectorMetricsHandler;
import com.github.kristofa.brave.Sampler;
import com.github.kristofa.brave.SpanCollector;
import com.github.kristofa.brave.http.DefaultSpanNameProvider;
import com.github.kristofa.brave.http.HttpSpanCollector;
import com.github.kristofa.brave.http.HttpSpanCollector.Config;
import com.github.kristofa.brave.okhttp.BraveOkHttpRequestResponseInterceptor;
import com.github.kristofa.brave.servlet.BraveServletFilter;
import okhttp3.OkHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
*
* 配置Span收集器 设置收集器的详细参数,包含超时时间、上传span间隔、以及配置采集率等,进而对收集器进行初始化。
*
*/
@Configuration
public class SpanCollectorConfig {
// zipkin的url地址
@Value("${zipkin.url}")
private String url;
// 发给zipkin的服务器名称
@Value("${zipkin.serviceName}")
private String serviceName;
// 连接超时时间
@Value("${zipkin.connectTimeout}")
private int connecTimeout;
// 是否启动压缩
@Value("${zipkin.compressionEnabled}")
private boolean compressionEnabled;
// 上传 span 的间隔时间
@Value("${zipkin.flushInterval}")
private int flushInterval;
// 读取超时时间
@Value("${zipkin.readTimeout}")
private int readTimeout;
// 采样率,默认为0.1,值越大收集越及时,但性能影响也越大
@Value("${zipkin.samplerRate}")
private float samplerRate;
/**
* 配置 span 收集器
*
* @return
*/
@Bean
public SpanCollector spanCollector() {
Config config = Config.builder()
//连接超时时间
.connectTimeout(connecTimeout)
//是否启动压缩
.compressionEnabled(compressionEnabled)
//上传 span 的间隔时间
.flushInterval(flushInterval)
//读取超时时间
.readTimeout(readTimeout).build();
//url:zipkin的url地址
return HttpSpanCollector.create(url, config, new EmptySpanCollectorMetricsHandler());
}
/**
* 配置采集率
* 作为各调用链路,只需要负责将指定格式的数据发送给zipkin
* @param spanCollector
* @return
*/
@Bean
public Brave brave(SpanCollector spanCollector) {
//被采集的服务名称
Builder builder = new Builder(serviceName);
//采集器
builder.spanCollector(spanCollector)
//采样率,默认为0.1,值越大收集越及时,但性能影响也越大
.traceSampler(Sampler.create(samplerRate)).build();
return builder.build();
}
/**
* @Description: 设置server的(服务端收到请求和服务端完成处理,并将结果发送给客户端)过滤器
* @Param:
* @return: 过滤器
*/
@Bean
public BraveServletFilter braveServletFilter(Brave brave) {
BraveServletFilter filter = new BraveServletFilter(brave.serverRequestInterceptor(),
brave.serverResponseInterceptor(), new DefaultSpanNameProvider());
return filter;
}
/**
* @Description: 设置client的 rs(表示服务端收到请求)和cs(表示客户端发起请求)的拦截器
* @Param:
* @return: OkHttpClient 返回请求实例
*/
@Bean
public OkHttpClient okHttpClient(Brave brave) {
OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(new BraveOkHttpRequestResponseInterceptor(brave.clientRequestInterceptor(),
brave.clientResponseInterceptor(), new DefaultSpanNameProvider()))
.build();
return httpClient;
}
}
4.HelloController
package com.example.service8081.business.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
/**
* 描述该类
*
* @class: HelloController
* @see
*/
@RestController
@RequestMapping("zipkin")
public class HelloController {
public static final String url = "http://localhost:8082/zipkin/service2";
@Autowired
OkHttpClient client;
@GetMapping("/service1")
public String service() throws IOException {
System.out.println("-----调用service1-----");
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
return "service1," + response.body().string();
}
}
service8082到service8085的配置:
1、application.properties 修改
zipkin.serviceName= 改为自己的服务名称
server.port=改为自己的端口
spring.application.name=改为自己的服务名称
2、HelloController 修改
2.1、service8081
@RestController
@RequestMapping("zipkin")
public class HelloController {
public static final String url = "http://localhost:8082/zipkin/service2";
@Autowired
OkHttpClient client;
@GetMapping("/service1")
public String service() throws IOException {
System.out.println("-----调用service1-----");
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
return "service1," + response.body().string();
}
}
2.2、service8082
@RestController
@RequestMapping("zipkin")
public class HelloController {
public static final String url = "http://localhost:8083/zipkin/service3";
public static final String url2 = "http://localhost:8084/zipkin/service4";
@Autowired
OkHttpClient client;
@GetMapping("/service2")
public String service() throws Exception {
System.out.println("-----调用service2-----");
Request request1 = new Request.Builder().url(url).build();
Request request2 = new Request.Builder().url(url2).build();
Response response1 = client.newCall(request1).execute();
Response response2 = client.newCall(request2).execute();
return "service2【来自service3的信息:" + response1.body().string() +
",来自service4的信息:"+ response2.body().string() + "】";
}
}
2.3、service8083
@RestController
@RequestMapping("zipkin")
public class HelloController {
public static final String url = "http://localhost:8084/zipkin/service4";
@Autowired
OkHttpClient client;
@GetMapping("/service3")
public String service() throws Exception {
System.out.println("-----调用service3-----");
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
return "service3," + response.body().string();
}
}
2.4、service8084
@RestController
@RequestMapping("zipkin")
public class HelloController {
public static final String url = "http://localhost:8086/zipkin/service5";
@Autowired
OkHttpClient client;
@GetMapping("/service4")
public String service() throws Exception {
System.out.println("-----调用service4-----");
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
return "service4," + response.body().string();
}
}
2.5、service8086
@RestController
@RequestMapping("zipkin")
public class HelloController {
@GetMapping("/service5")
public String service() throws Exception {
System.out.println("-----调用service5-----");
return "service5";
}
}
测试和分析
http://127.0.0.1:8081/zipkin/service1
http://localhost:9411
1、发送请求
2、分析
调用测试
[
{
"traceId": "2ce03196265e9729",
"parentId": "ce0b83f6e721e97d",
"id": "f470b5a720074acd",
"kind": "CLIENT",
"name": "get",
"timestamp": 1630251666804000,
"duration": 1701000,
"localEndpoint": {
"serviceName": "service8082",
"ipv4": "192.168.207.1"
},
"tags": {
"http.url": "http://localhost:8083/zipkin/service3"
}
},
{
"traceId": "2ce03196265e9729",
"parentId": "074e4cb45a59a76c",
"id": "96fde753cf354c3e",
"kind": "CLIENT",
"name": "get",
"timestamp": 1630251667658000,
"duration": 747000,
"localEndpoint": {
"serviceName": "service8084",
"ipv4": "192.168.207.1"
},
"tags": {
"http.url": "http://localhost:8086/zipkin/service5"
}
},
{
"traceId": "2ce03196265e9729",
"parentId": "f470b5a720074acd",
"id": "074e4cb45a59a76c",
"kind": "SERVER",
"name": "get",
"timestamp": 1630251667451000,
"duration": 988000,
"localEndpoint": {
"serviceName": "service8084",
"ipv4": "192.168.207.1"
},
"tags": {
"http.status_code": "200",
"http.url": "/zipkin/service4"
}
},
{
"traceId": "2ce03196265e9729",
"parentId": "074e4cb45a59a76c",
"id": "96fde753cf354c3e",
"kind": "SERVER",
"name": "get",
"timestamp": 1630251668029000,
"duration": 374000,
"localEndpoint": {
"serviceName": "service8086",
"ipv4": "192.168.207.1"
},
"tags": {
"http.status_code": "200",
"http.url": "/zipkin/service5"
}
},
{
"traceId": "2ce03196265e9729",
"parentId": "3e2b6810b608afc7",
"id": "8803d668b0b3d243",
"kind": "SERVER",
"name": "get",
"timestamp": 1630251668847000,
"duration": 3000,
"localEndpoint": {
"serviceName": "service8086",
"ipv4": "192.168.207.1"
},
"tags": {
"http.status_code": "200",
"http.url": "/zipkin/service5"
}
},
{
"traceId": "2ce03196265e9729",
"parentId": "f470b5a720074acd",
"id": "074e4cb45a59a76c",
"kind": "CLIENT",
"name": "get",
"timestamp": 1630251667196000,
"duration": 1247000,
"localEndpoint": {
"serviceName": "service8083",
"ipv4": "192.168.207.1"
},
"tags": {
"http.url": "http://localhost:8084/zipkin/service4"
}
},
{
"traceId": "2ce03196265e9729",
"parentId": "ce0b83f6e721e97d",
"id": "f470b5a720074acd",
"kind": "SERVER",
"name": "get",
"timestamp": 1630251667042000,
"duration": 1459000,
"localEndpoint": {
"serviceName": "service8083",
"ipv4": "192.168.207.1"
},
"tags": {
"http.status_code": "200",
"http.url": "/zipkin/service3"
}
},
{
"traceId": "2ce03196265e9729",
"parentId": "2ce03196265e9729",
"id": "ce0b83f6e721e97d",
"kind": "CLIENT",
"name": "get",
"timestamp": 1630251666391000,
"duration": 2511000,
"localEndpoint": {
"serviceName": "service8081",
"ipv4": "192.168.207.1"
},
"tags": {
"http.url": "http://localhost:8082/zipkin/service2"
}
},
{
"traceId": "2ce03196265e9729",
"id": "2ce03196265e9729",
"kind": "SERVER",
"name": "get",
"timestamp": 1630251666347000,
"duration": 2562000,
"localEndpoint": {
"serviceName": "service8081",
"ipv4": "192.168.207.1"
},
"tags": {
"http.status_code": "200",
"http.url": "/zipkin/service1"
}
},
{
"traceId": "2ce03196265e9729",
"parentId": "ce0b83f6e721e97d",
"id": "3e2b6810b608afc7",
"kind": "CLIENT",
"name": "get",
"timestamp": 1630251668505000,
"duration": 347000,
"localEndpoint": {
"serviceName": "service8082",
"ipv4": "192.168.207.1"
},
"tags": {
"http.url": "http://localhost:8084/zipkin/service4"
}
},
{
"traceId": "2ce03196265e9729",
"parentId": "2ce03196265e9729",
"id": "ce0b83f6e721e97d",
"kind": "SERVER",
"name": "get",
"timestamp": 1630251666651000,
"duration": 2243000,
"localEndpoint": {
"serviceName": "service8082",
"ipv4": "192.168.207.1"
},
"tags": {
"http.status_code": "200",
"http.url": "/zipkin/service2"
}
},
{
"traceId": "2ce03196265e9729",
"parentId": "3e2b6810b608afc7",
"id": "8803d668b0b3d243",
"kind": "CLIENT",
"name": "get",
"timestamp": 1630251668511000,
"duration": 339000,
"localEndpoint": {
"serviceName": "service8084",
"ipv4": "192.168.207.1"
},
"tags": {
"http.url": "http://localhost:8086/zipkin/service5"
}
},
{
"traceId": "2ce03196265e9729",
"parentId": "ce0b83f6e721e97d",
"id": "3e2b6810b608afc7",
"kind": "SERVER",
"name": "get",
"timestamp": 1630251668510000,
"duration": 342000,
"localEndpoint": {
"serviceName": "service8084",
"ipv4": "192.168.207.1"
},
"tags": {
"http.status_code": "200",
"http.url": "/zipkin/service4"
}
}
]