相信在看sleuth
的小伙伴们肯定已经对微服务有一个大致的了解了,因此这篇文章会主要记录spring-cloud-sleuth
,以及在使用过程当中用到的Eureka
和 Feign
。
调用链
随着服务的拆分,系统的模块变得越来越多,不同的微服务可能是不同的人来维护,也就造成了当我们请求某一个微服务的某一个接口时,可能是调用了多个微服务。基于Google Dapper论文,用户每次请求都会生成一个全局ID(traceId),通过它将不同系统的“孤立”的日志串在一起,重组成调用链。
简单的说调用链就是当我们发起某个请求后,调用的微服务的先后顺序组成的一个调用链路。
记录调用链的益处
一个请求可能会涉及n多个微服务的协同处理,牵扯到多人甚至多个团队的业务系统,一旦中间某个环节出现问题,还得从头查起。如何可以快速的定位到到底是哪个微服务出现的问题,咱们记录下来的调用链既可以帮助我们进行快速的分析。
除此之外还可以根据此分析各个调用环节的性能问题和数据分析(调用链是每次请求的一条完整的业务日志,可以得到用户的行为路径,汇总分析应用的很多业务场景)等。
由于我们今天的主角实际上就是对Zipkin的封装,因此我们先来看下Zipkin的介绍:
Zipkin的设计背景
2010年谷歌发表了其内部使用的分布式跟踪系统Dapper的论文,讲述了Dapper在谷歌内部两年的演变和设计、运维经验,Twitter也根据该论文开发了自己的分布式跟踪系统Zipkin,并将其开源。
Zipkin的设计
上图为zipkin官网中的结构图,从上图我们可以看出数据是由各个应用,中间件甚至是数据库将跟踪数据发送到Zipkin服务器,而不是各个服务记录后,当一条调用链路结束后统一发送到Zipkin服务器的,这样由Zipkin来分析汇总变成调用链的好处是可以防止某一次请求调用服务特别多或者很复杂的情况下统一发送造成的性能问题。
简单的服务调用实例
上图描述的服务调用场景应该是很常见也很简单的调用场景了,一个请求通过Gateway服务路由到下游的Service1,然后Service1先调用服务Service2,拿到结果后再调用服务Service3,最后组合Service2和Service3服务的结果,通过Gateway返回给用户。我们用①②③④⑤⑥表示了调用的顺序,什么是span?span直译过来是"跨度",在谷歌的Dapper论文中表示跟踪树中树节点引用的数据结构体,span是跟踪系统中的基本数据单元,Dapper的论文中,并没有具体介绍span中的全部细节,但在Zipkin中,每个span中一般包含如下字段:
traceId:全局跟踪ID,用它来标记一次完整服务调用,所以和一次服务调用相关的span中的traceId都是相同的,Zipkin将具有相同traceId的span组装成跟踪树来直观的将调用链路图展现在我们面前。这里直接给出Zipkin官网中的一张Zipkin界面的图:
id:span的id,理论上来说,span的id只要做到一个traceId下唯一就可以,比如说阿里的鹰眼系统巧妙用span的id来体现调用层次关系(例如0,0.1,0.2,0.1.1等),但Zipkin中的span的id则没有什么实际含义。
parentId:父span的id,调用有层级关系,所以span作为调用节点的存储结构,也有层级关系,就像上图所示,跟踪链是采用跟踪树的形式来展现的,树的根节点就是调用的顶点,从开发者的角度来说,顶级span是从接入了Zipkin的应用中最先接触到服务调用的应用中采集的。所以,顶级span是没有parentId字段的,拿上图所展现的例子来说,顶级span由Gateway来采集,Service1的span是它的子span,而Service2和Service3的span是Service1的span的子span,很显然Service2和Service3的span是平级关系。
name:span的名称,主要用于在界面上展示,一般是接口方法名,name的作用是让人知道它是哪里采集的span,不然某个span耗时高我都不知道是哪个服务节点耗时高。
timestamp:span创建时的时间戳,用来记录采集的时刻。
duration:持续时间,即span的创建到span完成最终的采集所经历的时间,除去span自己逻辑处理的时间,该时间段可以理解成对于该跟踪埋点来说服务调用的总耗时。
annotations:基本标注列表,一个标注可以理解成span生命周期中重要时刻的数据快照,比如一个标注中一般包含发生时刻(timestamp)、事件类型(value)、端点(endpoint)等信息,这里给出一个标注的json结构:
{
"timestamp": 1476197069680000,
"value": "cs",
"endpoint": {
"serviceName": "service1",
"ipv4": "xxx.xxx.xxx.111"
}
}
四种事件类型:cs(客户端/消费者发起请求)、cr(客户端/消费者接收到应答)、sr(服务端/生产者接收到请求)和ss(服务端/生产者发送应答)。可以看出,这四种事件类型的统计都应该是Zipkin提供客户端来做的,因为这些事件和业务无关,这也是为什么跟踪数据的采集适合放到中间件或者公共库来做的原因。
binaryAnnotations:业务标注列表,如果某些跟踪埋点需要带上部分业务数据(比如url地址、返回码和异常信息等),可以将需要的数据以键值对的形式放入到这个字段中。
接下来进入正题,记录一下spring-cloud-sleuth在工程中的使用:
1.首先创建一个zipkinserver
工程,在pom文件中添加如下依赖:
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>
//这个是用来自动适配版本的
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在配置文件中添加如下配置:
server.port=32071
spring.application.name=zipkinService
#设置采样率,测试时可将此设置为1,表示每条请求都记录下来
spring.sleuth.sampler.percentage=0.2
在主程序入口处添加注解@EnableZipkinServer
2.创建一个helloserver
工程,在pom文件中添加如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在配置文件中添加如下配置:
server.port=8988
spring.zipkin.base-url=http://localhost:32071
spring.application.name=service-hello
新建一个接口类,并实现如下方法:
@RestController
@RequestMapping("/servicehello")
public class controller
{
private static final Logger LOG = Logger.getLogger(controller.class.getName());
@Autowired
private RestTemplate restTemplate;
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
@RequestMapping("/hello")
public String callHome(){
LOG.log(Level.INFO, "calling trace service-hi ");
return restTemplate.getForObject("http://localhost:8989/servicehi/hi", String.class);
}
@RequestMapping("/test")
public String info(){
LOG.log(Level.INFO, "calling trace service-hello ");
return "i'm service-hello";
}
@Bean
public AlwaysSampler defaultSampler(){
return new AlwaysSampler();
}
}
3.创建hiserver
工程,在pom文件中添加和2中相同
在配置文件中设置如下:
server.port=8989
spring.zipkin.base-url=http://localhost:32071
spring.application.name=service-hi
添加请求类并实现如下方法:
@RestController
@RequestMapping("/servicehi")
public class controller {
private static final Logger LOG = Logger.getLogger(ServiceHiApplication.class.getName());
@Autowired
private RestTemplate restTemplate;
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
@RequestMapping("/hi")
public String callHome(){
LOG.log(Level.INFO, "calling trace service-hello ");
return restTemplate.getForObject("http://localhost:8988/servicehello/test", String.class);
// LOG.log(Level.INFO, "calling trace service-new ");
// return restTemplate.getForObject("http://localhost:32061/Designer/getPromotionlist/?orgid=283", String.class);
}
@RequestMapping("/info")
public String info(){
LOG.log(Level.INFO, "calling trace service-hi ");
return "i'm service-hi";
}
@Bean
public AlwaysSampler defaultSampler(){
return new AlwaysSampler();
}
}
依次运行这三个项目,然后访问接口如下结果:
访问依次这个接口后我们可以看到localhost:32071的页面变成如下样子:
点击进入详情页:
如上图可以清晰的看到到底是哪个服务的哪个方法调用的哪个服务的哪个方法。接下来咱们看一下依赖关系(直接点击上面图中最上面一行的Dependentices):
上面的例子主要是通过RestTemplate
来调用其他服务的,接下来我们看一下如果使用Feign
来调用其他服务怎么使用spring-cloud-sleuth
来监控器调用过程。
接下来我们使用feign来调用其他服务
修改原来的hiserver
,zipkinserver
,工程,并新建spring-cloud-eureka
,spring-cloud-feign
工程。
1.新建spring-cloud-eureka
工程
在pom文件中添加依赖如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在配置文件中添加如下配置(application.properties):
server.port=11111
eureka.client.service-url.defaultZone:http://localhost:11111/eureka/
spring.application.name=eureka-server
在程序入口文件中添加注解@EnableEurekaServer
后运行该项目后可看到界面如下:
2.新建
spring-cloud-feign
工程,并在pom文件中添加如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
然后在配置文件中增加如下配置:
server.port=11112
#配置eureka地址
eureka.client.service-url.defaultZone:http://localhost:11111/eureka/
spring.application.name=feign-server
#配置zipkin地址
spring.zipkin.base-url=http://localhost:32071
#配置采样率
spring.sleuth.sampler.percentage=1.0
创建一个接口类如下:
//此注解后面的value值是想要调用的服务的名称,也就是在配置文件中的spring.application.name
@FeignClient(value = "service-hi")
public interface schedualservicehi {
//此value后面是你想调用微服务的接口地址
@RequestMapping(value = "/servicehi/info",method = RequestMethod.GET)
//此接口相当于中间层,通过访问此接口便可以访问到上面配置的服务路径
String sayHiFromClientOne();
}
接下来创建一个类,在此类中测试方法调用
@RestController
public class controller {
@Autowired
schedualservicehi schedualservicehi;
@RequestMapping(value = "/feign",method = RequestMethod.GET)
public String sayInfo(){
return schedualservicehi.sayHiFromClientOne();
}
最后在工程入口文件中添加注解如下:
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class SpringcloudfeignApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudfeignApplication.class, args);
}
}
3.在hiserver
工程中做如下修改:
pom文件中除了之前添加的有关zipkin的依赖外再添加如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
配置文件中再增加如下:
server.port=8989
spring.zipkin.base-url=http://localhost:32071
spring.application.name=service-hi
eureka.client.service-url.defaultZone:http://localhost:11111/eureka/
spring.sleuth.sampler.percentage=1.0
工程入口文件中添加注解
@SpringBootApplication
@EnableEurekaClient
public class ServiceHiApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceHiApplication.class, args);
}
}
其他的不用刚修改,再次运行这几个工程:
访问feign的接口:
然后再看ereuka的界面,发现其他项目也已经注册上
再来看一下sleuth的界面出现了刚才咱们访问feign的那个接口的记录:
点击一条记录看一下依赖关系:
以上结果均正确,说明sleuth可以监控到使用feign来调用服务的调用链路