一、简介
在微服务中,通常根据业务模块分服务,项目中前端发起一个请求,后端可能跨几个服务调用才能完成这个请求(如下图)。如果系统越来越庞大,服务之间的调用与被调用关系就会变得很复杂,假如一个请求中需要跨几个服务调用,其中一个服务由于网络延迟等原因挂掉了,那么这时候我们需要分析具体哪一个服务出问题了就会显得很困难。Spring Cloud Sleuth服务链路跟踪功能就可以帮助我们快速的发现错误根源以及监控分析每条请求链路上的性能等等。
本文将对Spring Cloud Sleuth进行一个简单的例子,算入个门。
二、相关术语
【a】Span: 基本的工作单元。Span包括一个64位的唯一ID,一个64位trace码,描述信息,时间戳事件,key-value 注解(tags),span处理者的ID(通常为IP)。
【b】Trace: 包含一系列的工作单元span,它们组成了一个树型结构。
【c】Annotation
用于及时记录存在的事件。常用的Annotation如下:
- cs:客户端发送(client send) 客户端发起一个请求,表示span开始
- sr:服务器接收(server received) 服务器接收到客户端的请求并开始处理,sr - cs 的时间为网络延迟
- ss:服务器发送(server send) 服务器处理完请求准备返回数据给客户端。ss - sr 的时间表示服务器端处理请求花费的时间
- cr:客户端接收(client received) 客户端接收到处理结果,表示span结束。 cr - cs 的时间表示客户端接收服务端数据的时间
更详细见官网:http://cloud.spring.io/spring-cloud-sleuth/2.0.x/single/spring-cloud-sleuth.html
如果一个服务的调用关系如下:
那么此时将Span和Trace在一个系统中使用Zipkin注解的过程图形化:
三、工程准备
【a】eureka-server: 服务注册中心,端口1111
【b】springcloud_zipkin_server: zipkin服务端,端口2222
【c】springcloud_ribbon_client1: 服务消费者1,端口3333,主要用于ribbon调用
【d】springcloud_ribbon_client2: 服务消费者2,端口4444,主要用于ribbon调用
这里就不讲解eureka-server工程的搭建了,可以参考前面文章。
下面我们搭建zipkin-server服务端,新建springcloud_zipkin_server工程,注意引入zipkin的依赖
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>
具体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.springcloud.wsh</groupId>
<artifactId>springcloud_zipkin_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springcloud_zipkin_server</name>
<description>zipkin服务器端</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Camden.SR6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
四、启动类加上@EnableZipkinServer
@EnableZipkinServer注解的作用: 开启zipkin功能
@SpringBootApplication
@EnableDiscoveryClient
//@EnableZipkinServer: 开启zipkinServer的功能
@EnableZipkinServer
public class SpringcloudZipkinServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudZipkinServerApplication.class, args);
}
}
配置文件application.yml:
server:
port: 2222
spring:
application:
name: zipkin-server
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1111/eureka/
这样zipkin server端就搭建好了,下面我们新建两个服务消费者工程,分别暴露接口给对方访问。
五、新建springcloud_ribbon_client1
【a】注意引入spring-cloud-starter-zipkin开启zipkin服务链路跟踪功能,详细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.springcloud.wsh</groupId>
<artifactId>springcloud_ribbon_client1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springcloud_ribbon_client1</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Camden.SR6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<!--开启zipkin服务链路跟踪-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
【b】启动类:注入RestTemplate、AlwaysSampler对象
AlwaysSampler:作用相当于在配置文件中配置 spring.sleuth.sampler.percentage=1,设置sleuth收集信息的比率为1,默认10%
@SpringBootApplication
@EnableDiscoveryClient
public class SpringcloudRibbonClient1Application {
public static void main(String[] args) {
SpringApplication.run(SpringcloudRibbonClient1Application.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public AlwaysSampler alwaysSampler() {
return new AlwaysSampler();
}
}
【c】application.yml:这里需要注意的就是需要指定zipkin server的地址。
spring.zipkin.base-url = zipkin服务端地址
server:
port: 3333
spring:
application:
name: ribbon-client1
zipkin:
base-url: http://localhost:2222
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1111/eureka/
【d】暴露接口
/**
* @Title: RibbonClient1Controller
* @ProjectName springcloud_sleuth_basic
* @Description: 测试Controller
* @Author WeiShiHuai
* @Date 2018/9/27 10:09
*/
@RestController
public class RibbonClient1Controller {
private static Logger logger = LoggerFactory.getLogger(RibbonClient1Controller.class);
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/ribbon-client-1")
public String test() {
logger.info("RibbonClient1Controller--test() is requesting....");
return restTemplate.getForObject("http://ribbon-client2/callRabbitClient2", String.class);
}
@RequestMapping("/callRabbitClient1")
public String callRabbitClient1() {
logger.info("RibbonClient1Controller--callRabbitClient1 is requesting...");
return "hello ,Spring Cloud Sleuth!";
}
}
六、新建springcloud_ribbon_client2
项目除了暴露的接口与springcloud_ribbon_client1不一样,其他都一致。下面直接贴代码
【a】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.springcloud.wsh</groupId>
<artifactId>springcloud_ribbon_client2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springcloud_ribbon_client2</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Camden.SR6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<!--开启zipkin服务链路跟踪-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
【b】启动类
@SpringBootApplication
@EnableDiscoveryClient
public class SpringcloudRibbonClient2Application {
public static void main(String[] args) {
SpringApplication.run(SpringcloudRibbonClient2Application.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public AlwaysSampler alwaysSampler() {
return new AlwaysSampler();
}
}
【c】配置文件
server:
port: 4444
spring:
application:
name: ribbon-client2
zipkin:
base-url: http://localhost:2222
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1111/eureka/
【d】暴露接口
/**
* @Title: RibbonClient2Controller
* @ProjectName springcloud_sleuth_basic
* @Description: 测试Controller
* @Author WeiShiHuai
* @Date 2018/9/27 10:29
*/
@RestController
public class RibbonClient2Controller {
private static Logger logger = LoggerFactory.getLogger(RibbonClient2Controller.class);
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/ribbon-client-2")
public String test() {
logger.info("RibbonClient2Controller--test() is requesting....");
return restTemplate.getForObject("http://ribbon-client1/callRabbitClient1", String.class);
}
@RequestMapping("/callRabbitClient2")
public String callRabbitClient2() {
logger.info("RibbonClient2Controller--callRabbitClient2 is requesting...");
return "hello ,Spring Cloud Sleuth!";
}
}
七、启动项目
依次启动项目eureka-server、zipkin-server、springcloud_ribbon_client1、springcloud_ribbon_client2, 浏览器访问http://localhost:3333/ribbon-client-1,观察springcloud_ribbon_client1、springcloud_ribbon_client2控制台输出:
springcloud_ribbon_client1:
INFO [ribbon-client1,144f1891d3547500,144f1891d3547500,true] 1612 --- [nio-3333-exec-1] c.s.w.c.RibbonClient1Controller : RibbonClient1Controller--test() is requesting....
springcloud_ribbon_client2:
INFO [ribbon-client2,144f1891d3547500,e3822e407ad26f52,true] 5160 --- [nio-4444-exec-1] c.s.w.c.RibbonClient2Controller : RibbonClient2Controller--callRabbitClient2 is requesting...
由此可以看到sleuth进行服务链路跟踪的一些重要元素,[ribbon-client2,144f1891d3547500,e3822e407ad26f52,true] 的含义如下:
【a】第一个参数ribbon-client2:应用名称,对应我们application.yml中定义的application-name。
【b】第二个参数144f1891d3547500:Trace ID, 标识一条请求链路,一条请求链路包含一个Trace ID,多个Span ID。一条链路上的Trace ID是相同的,注意上面的日志信息第二个参数即Trace ID是一样的。
【c】第三个参数e3822e407ad26f52:Span ID,一个基本的工作单元,如一个http请求。
【d】第四个参数true:表示是否要将该信息输出到Zipkin等服务中来收集和展示。
我们在浏览器访问一下http://localhost:4444/ribbon-client-2,
可以看到接口是成功调用的,此时我们访问一下zipkin服务端,http://localhost:2222/,
我们可以筛选相应的服务查看具体的请求,zipkin提供了可视化的界面让我们很方便的查看每个接口请求的耗时时间、请求是否成功等等。
点击某个服务还可以查看详情,可以看到调用链信息、trace id 、span id等,
当服务调用失败的时候也可以查看错误原因:
点击Dependencies可以看到服务之间的依赖关系,以此示例,springcloud_ribbon_client1和springcloud_ribbon_client2相互进行了调用,也就是他们相互依赖关系。
至此,我们Spring Cloud Sleuth算是入门了。
八、总结
Spring Cloud Sleuth提供了服务链路跟踪功能,方便我们查看各个服务之间的依赖关系以及在服务发现异常时快速定位错误原因等,当然这只是比较简单的功能,后期还要进行持久化zipkin监控的信息、集成日志等更多功能。本文是作者在学习Spring Cloud Sleuth时的一些总结,加深自己对Spring Cloud Sleuth的印象,仅供大家参考学习,一起进步!