Spring Cloud分布式服务链路追踪实战(Sleuth + Zipkin)
1. 前言
随着业务的不断发展,应用系统变得越来越大,逐渐由单体应用发展为分布式应用,这使得应用系统的维护变得越来越困难,原因之一就是微服务的拆分使得服务之间的调用链路变得越来越复杂。例如,一个前端请求到达后端服务时,可能需要调用多个服务才能完成业务处理,而当整个请求变慢甚于不可用时,我们很难知道这是由哪些服务引起的,因此,就需要快速定位并找出导致这种状态的的服务。幸运的是,已经有很多优秀的分布式服务调用跟踪链的解决方案,而最常用的就是Twitter 开源实现是Zipkin。
2. 准备
1. 版本说明
- Spring Boot: 2.0.8
- Spring Cloud: Finchley SR2
- Zipkin: 对应spring boot 2.1.1
- IDE: IDEA
- JDK: 1.8
2. 项目构成
- 服务注册中心: 项目名称:eureka,端口号:8010
- 网关: 项目名称:zuul,端口号:8020
- 用户服务提供者: 项目名称:provider-user,端口号:8030
- 商品服务提供者: 项目名称:provider-goods,端口号:8040
- 订单服务提供者: 项目名称:provider-order,端口号:8050
3. 项目说明
- 由于Sprig Cloud的最新版本Finchley SR2当中的Sleuth组件并不需要用户自己实现Sleuth-Server服务端,在使用Sleuth整合Zipkin并基于HTTP进行服务链路跟踪时,只需要在要进行跟踪的微服务项目中添加spring-cloud-starter-zipkin的依赖,并在配置文件中添加Sleuth和Zipkin的配置项即可。
- 本案例分享创建了eureka、zuul、provider-user、provider-goods、provider-order这五个微服务项目,后四个微服务项目中都使用了Sleuth和Zipkin,并且都注册到了eureka服务注册中心上,zuul网关对provider-user、provider-goods、provider-order这三个微服务进行路由,而provider-order会调用到provider-user、provider-goods这两个微服务。
- 该博文是一篇完整的案例分享,包括:Eureka服务注册中心、Zuul网关、Sleuth服务链路跟踪、服务提供者等几个部分。由于本文并没有对每个部分详尽说明,因此,阅读本博文需要读者有一定的Spring Cloud微服务基础。
4. 项目功能
使用Sleuth + Zipkin实现服务链路跟踪,并通过Zipkin-UI查看服务调用的层次关系以及服务调用的时间等详细信息,方便对Spring Cloud微服务项目的运行情况进行掌握以及服务异常进行定位。
如果想要使用RabbitMQ发送日志消息,并使用ElasticSearch存储日志,请查看笔者的另外一篇文章:
Spring Cloud分布式服务链路追踪实战(Sleuth + Zipkin + RabbitMQ + ElasticSearch)
3. 实战
1. 服务注册中心项目构建
-
服务注册中心使用Eureka,项目结构如下图所示:
-
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.cloud.sleuth</groupId> <artifactId>eureka</artifactId> <version>0.0.1-SNAPSHOT</version> <name>eureka</name> <description>服务注册中心</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.8.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>Finchley.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </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>
-
application.yml配置文件如下:
server: port: 8010 eureka: instance: hostname: ${spring.cloud.client.ip-address} client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ spring: application: name: eurka
-
启动类EurekaApplication代码如下所示:
package com.cloud.sleuth.eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer @SpringBootApplication public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } }
2. 网关项目构建
-
网关使用Zuul,项目结构如下图所示:
-
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.cloud.sleuth</groupId> <artifactId>zuul</artifactId> <version>0.0.1-SNAPSHOT</version> <name>zuul</name> <description>网关</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.8.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>Finchley.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </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>
-
application.yml配置文件如下所示:
server: port: 8020 spring: application: name: zuul sleuth: web: client: enabled: true sampler: probability: 1.0 # 将采样比例设置为1.0,也就是全部都需要。默认是0.1 zipkin: base-url: http://localhost:9411/ # 指定了 Zipkin 服务器的地址 eureka: client: serviceUrl: defaultZone: http://localhost:8010/eureka/ instance: prefer-ip-address: true instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port} zuul: prefix: /api routes: order: path: /provider/order/** service-id: provider-order user: path: /provider/user/** service-id: provider-user goods: path: /provider/goods/** service-id: provider-goods
-
启动类ZuulApplication代码如下所示:
package com.cloud.sleuth.zuul; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @EnableZuulProxy @EnableEurekaClient @SpringBootApplication public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } }
3. 用户服务提供者项目构建
-
项目结构如下图所示:
-
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.cloud.sleuth</groupId> <artifactId>provider-user</artifactId> <version>0.0.1-SNAPSHOT</version> <name>provider-user</name> <description>用户服务提供者</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.8.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>Finchley.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </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>
-
application.yml配置文件如下所示:
server: port: 8030 spring: application: name: provider-user sleuth: web: client: enabled: true sampler: probability: 1.0 # 将采样比例设置为1.0,也就是全部都需要。默认是0.1 zipkin: base-url: http://localhost:9411/ # 指定了 Zipkin 服务器的地址 eureka: client: serviceUrl: defaultZone: http://localhost:8010/eureka/ instance: prefer-ip-address: true instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
-
启动类ProviderUserApplication代码如下所示:
package com.cloud.sleuth.provider.user; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableEurekaClient @SpringBootApplication public class ProviderUserApplication { public static void main(String[] args) { SpringApplication.run(ProviderUserApplication.class, args); } }
-
UserController类代码如下所示:
package com.cloud.sleuth.provider.user.controlller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @GetMapping(value = "/getUser") public String getUser() { String user = "JavaBigData1024"; return user; } }
4. 商品服务提供者项目构建
-
项目结构如下图所示:
-
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.cloud.sleuth</groupId> <artifactId>provider-goods</artifactId> <version>0.0.1-SNAPSHOT</version> <name>provider-goods</name> <description>商品服务提供者</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.8.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>Finchley.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </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>
-
application.yml配置文件如下所示:
server: port: 8040 spring: application: name: provider-goods sleuth: web: client: enabled: true sampler: probability: 1.0 # 将采样比例设置为1.0,也就是全部都需要。默认是0.1 zipkin: base-url: http://localhost:9411/ # 指定了 Zipkin 服务器的地址 eureka: client: serviceUrl: defaultZone: http://localhost:8010/eureka/ instance: prefer-ip-address: true instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
-
启动类ProviderGoodsApplication代码如下所示:
package com.cloud.sleuth.provider.goods; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableEurekaClient @SpringBootApplication public class ProviderGoodsApplication { public static void main(String[] args) { SpringApplication.run(ProviderGoodsApplication.class, args); } }
-
GoodsController类代码如下所示:
package com.cloud.sleuth.provider.goods.contoller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class GoodsController { @GetMapping(value = "/getGoods") public String getGoods() { String goods = "Think in Java"; return goods; } }
5. 订单服务提供者项目构建
-
项目结构如下图所示:
-
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.cloud.sleuth</groupId> <artifactId>provider-order</artifactId> <version>0.0.1-SNAPSHOT</version> <name>provider-order</name> <description>定单服务提供者</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.8.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>Finchley.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </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>
-
application.yml配置文件如下所示:
server: port: 8050 spring: application: name: provider-order sleuth: web: client: enabled: true sampler: probability: 1.0 # 将采样比例设置为1.0,也就是全部都需要。默认是0.1 zipkin: base-url: http://localhost:9411/ # 指定了 Zipkin 服务器的地址 eureka: client: serviceUrl: defaultZone: http://localhost:8010/eureka/ instance: prefer-ip-address: true instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
-
启动类ProviderOrderApplication代码如下所示:
package com.cloud.sleuth.provider.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; @EnableFeignClients @EnableEurekaClient @SpringBootApplication public class ProviderOrderApplication { public static void main(String[] args) { SpringApplication.run(ProviderOrderApplication.class, args); } }
-
OrderController类代码如下所示:
package com.cloud.sleuth.provider.order.controller; import com.cloud.sleuth.provider.order.service.GoodsService; import com.cloud.sleuth.provider.order.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { @Autowired private UserService userService; @Autowired private GoodsService goodsService; @GetMapping(value = "/createOrder") public String createOrder() { String user = userService.getUser(); String goods = goodsService.getGoods(); String order = "User = " + user + "; Goods = " + goods + ";"; return order; } }
-
UserService类代码如下所示:
package com.cloud.sleuth.provider.order.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @FeignClient(value = "provider-user") public interface UserService { @GetMapping(value = "/getUser") String getUser(); }
-
GoodsService类代码如下所示:
package com.cloud.sleuth.provider.order.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @FeignClient(value = "provider-goods") public interface GoodsService { @GetMapping(value = "/getGoods") String getGoods(); }
4. 安装和启动Zipkin
- 注意:新版本的Zipkin不再推荐我们自己定义Server端,而且在新版本的Spring Cloud依赖管理里也已经没有zipkin-server了,按照官网的使用指南,我们可以在CMD中输入以下的命令下载Zipkin的jar包:
curl -sSL https://zipkin.io/quickstart.sh | bash -s #下载Zipkin的jar包到本地
- 下载好了Zipkin的jar包之后,就可以直接在CMD中输入以下的Java命令启动Zipkin了:
java -jar zipkin.jar #启动Zipkin
- 启动Zipkin之后,就可以直接使用浏览器访问http://localhost:9411 来查看Zipkin的跟踪结果了,查看结果界面如下图所示:
关于Zipkin的相关介绍和使用这里省略,请自行百度。
5. 测试
1. 项目启动
- 启动注册中心:eureka
- 启动网关:zuul
- 启动用户服务提供者:provider-user
- 启动商品服务提供者:provider-goods
- 启动订单服务提供者:provider-order
2. 服务调用
- 浏览器访问: http://localhost:8020/api/provider/user/getUser
- 浏览器访问: http://localhost:8020/api/provider/goods/getGoods
- 浏览器访问: http://localhost:8020/api/provider/order/getOrder
3. 结果查看
- 浏览器访问: http://localhost:9411,结果如下图所示:
- 结果显示有三条服务调用跟踪记录,点击每条记录可以查看每条服务调用详情,例如点击第2条后详情如下图所示:
- 结果显示该服务调用共涉及到4个服务,分别是zuul、provider-order、provider-user、provider-goods,点击每个服务可以查看该服务的调用详情,例如点击zuul这个服务后详情如下图所示:
6. 小结
至此,一个Sleuth整合Zipkin并基于HTTP进行微服务链路跟踪的完整项目案例就分享完成了,但项目中也还有很多不够完善甚至不够规范的地方,欢迎聪明的Java猿们留言指出。
如果觉得本文对您有帮助,请关注博主的微信公众号,会经常分享一些Java和大数据方面的技术案例!