在微服务中,通常根据业务模块分服务,项目中前端发起一个请求,后端可能跨几个服务调用才能完成这个请求(如下图)。如果系统越来越庞大,服务之间的调用与被调用关系就会变得很复杂,假如一个请求中需要跨几个服务调用,其中一个服务由于网络延迟等原因挂掉了,那么这时候我们需要分析具体哪一个服务出问题了就会显得很困难。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如下:
更详细见官网: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的依赖
io.zipkin.java
zipkin-server
io.zipkin.java
zipkin-autoconfigure-ui
具体pom.xml:
4.0.0
com.springcloud.wsh
springcloud_zipkin_server
0.0.1-SNAPSHOT
jar
springcloud_zipkin_server
zipkin服务器端
org.springframework.boot
spring-boot-starter-parent
1.5.2.RELEASE
UTF-8
UTF-8
1.8
Camden.SR6
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
io.zipkin.java
zipkin-server
io.zipkin.java
zipkin-autoconfigure-ui
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
@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端就搭建好了,下面我们新建两个服务消费者工程,分别暴露接口给对方访问。
【a】注意引入spring-cloud-starter-zipkin开启zipkin服务链路跟踪功能,详细pom.xml:
4.0.0
com.springcloud.wsh
springcloud_ribbon_client1
0.0.1-SNAPSHOT
jar
springcloud_ribbon_client1
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
1.5.2.RELEASE
UTF-8
UTF-8
1.8
Camden.SR6
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-ribbon
org.springframework.cloud
spring-cloud-starter-zipkin
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
【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_client1不一样,其他都一致。下面直接贴代码
【a】pom.xml
4.0.0
com.springcloud.wsh
springcloud_ribbon_client2
0.0.1-SNAPSHOT
jar
springcloud_ribbon_client2
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
1.5.2.RELEASE
UTF-8
UTF-8
1.8
Camden.SR6
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-ribbon
org.springframework.cloud
spring-cloud-starter-zipkin
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
【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的印象,仅供大家参考学习,一起进步!