背景

微服务架构下,一个请求可能会经过多个服务才会得到结果,如果在这个过程中出现了异常,就很难去定位问题。所以,必须要实现一个分布式链路跟踪的功能,直观的展示出完整的调用过程。

什么是Spring Cloud Sleuth?

Spring Cloud Sleuth是Spring Cloud提供的分布式系统服务链追踪组件,它大量借用了Google的Dapper,Twitter的Zipkin。学习Spring Cloud Sleuth,最好先对Zipkin有一些了解,对span、trace这些概念有相应的认识。

如何使用Spring Cloud Sleuth?

在这里,为了复习下之前学过的Spring Cloud相关的组件,会通过之前搭建的多个服务来学习。
microservice-provider:服务提供者
microservice-consumer:服务消费者
Eureka Server:作为注册中心,提供服务注册和服务发现的功能。
microservice-gateway:微服务网关,所有的调用,都是经过网关进行转发
microservice-zipkin-server:收集调用信息,提供界面进行查看

microservice-provider,microservice-consumer,microservice-gateway和microservice-zipkin-server都向Eureka Server注册;
microservice-consumer调用microservice-provider时,通过microservice-gateway进行调用。访问microservice-consumer时,通过microservice-gateway访问。
microservice-provider,microservice-consumer,microservice-gateway都向microservice-zipkin-server上报调用信息。

Eureka Server:

pom文件:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.3.RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka-server</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Brixton.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

配置文件application.properties:

server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

Eureka Server启动端口为8761。

启动类:

@SpringBootApplication
@EnableEurekaServer//声明这是一个Eureka server
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

EurekaServerApplication上添加注解@EnableEurekaServer,声明这是一个Eureka server。

microservice-zipkin-server:
Zipkin需要配置jdk1.8才可以运行。
pom文件:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.3.RELEASE</version>
</parent>

<dependencies>
    <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>Dalston.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

配置文件application.yml:

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 9411
spring:
  application:
    name: microservice-zipkin-server

启动类ZipkinServerApplication,添加@EnableZipkinServer注解,声明这是一个Zipkin Server,@EnableEurekaClient注解声明这是一个Eureka Client,向Eureka Server注册:

@SpringBootApplication
@EnableEurekaClient
@EnableZipkinServer
public class ZipkinServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZipkinServerApplication.class, args);
    }
}

配置文件中,定义了服务的名称为microservice-zipkin-server,端口为9411,向地址为http://localhost:8761/eureka/ 的Eureka Server注册。

microservice-gateway:
网关服务的pom文件:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.3.RELEASE</version>
</parent>

<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-zuul</artifactId>
    </dependency>
    <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>Brixton.SR5</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

配置文件application.properties:

server.port=9100
spring.application.name=microservice-gateway
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.percentage=1.0
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

microservice-gateway配置了eureka.client.service-url.defaultZone向Eureka Server注册,配置了spring.zipkin.base-url,向Zipkin上报调用信息。配置spring.sleuth.sampler.percentage=1.0,说明采集率是100%,所有的数据都会上报给Zipkin,如果不配置,默认是10%。
上面的pom文件中,spring-cloud-starter-zipkin依赖了spring-cloud-starter-sleuth和spring-cloud-sleuth-zipkin,只需要在pom中添加spring-cloud-starter-zipkin,在配置文件中配置spring.zipkin.base-url,就可以为项目整合sleuth和zipkin。当有请求访问服务时,就会产生链路数据,自动向Zipkin上报。

启动类SpringCloudZuulApplication:

@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy // 声明这是一个zuul代理
public class SpringCloudZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudZuulApplication.class, args);
    }
}

microservice-provider
pom文件:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.3.RELEASE</version>
</parent>

<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-zipkin</artifactId>
    </dependency>
</dependencies>


<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=8000
spring.application.name=microservice-provider
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.percentage=1.0
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

启动类:

@EnableDiscoveryClient//声明是一个Eureka client
@SpringBootApplication
public class MicroServiceProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(MicroServiceProviderApplication.class, args);
    }
}

microservice-provider中还有一个ProviderController来提供服务,为了简单起见,直接返回hello world字符串。

@RestController
public class ProviderController {

    private final Logger logger = LoggerFactory.getLogger(ProviderController.class);

    @Autowired
    private DiscoveryClient discoveryClient;

    @RequestMapping(value = "/provider", method = RequestMethod.GET)
    public String provider() {
        ServiceInstance serviceInstance = discoveryClient.getLocalServiceInstance();
        logger.info("host:{}, service_id:{}", serviceInstance.getHost(), serviceInstance.getServiceId());
        return "hello world";
    }
}

microservice-consumer
pom文件:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.3.RELEASE</version>
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.7</java.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-zipkin</artifactId>
    </dependency>
</dependencies>


<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Brixton.SR5</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

配置文件application.properties:

server.port=9000
spring.application.name=microservice-consumer
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.percentage=1.0
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

启动类MicroServiceConsumeApplication:

@EnableDiscoveryClient
@SpringBootApplication
public class MicroServiceConsumeApplication {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(MicroServiceConsumeApplication.class, args);
    }
}

microservice-consumer在启动时,初始化RestTemplate实例,由于调用时是经过microservice-gateway的,microservice-gateway默认集成了Ribbon和Hystrix,所以RestTemplate不需要再用@LoadBalanced修饰。
microservice-consumer中有个ConsumerController,暴露接口,调用microservice-provider提供的服务。

@RestController
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping(value = "/consumer", method = RequestMethod.GET)
    public String consumer() {
        return this.restTemplate.getForEntity("http://localhost:9100/microservice-provider/provider", String.class).getBody();
    }
}

这些项目都创建好之后,启动,可以看到Eureka Server上,其它四个服务都注册上去了。

springcloud 引入lombak springcloudsleuth_spring cloud

浏览器中输入http://localhost:9100/microservice-consumer/consumer,请求会经过microservice-gateway,转发到microservice-consumer的接口上,microservice-consumer再请求http://localhost:9100/microservice-provider/provider,还会经过microservice-gateway,转发到microservice-provider的接口上。最终会在页面上看到返回hello world。

Zipkin Server有提供的界面,可以直观的查看trace信息,浏览器输入http://localhost:9411,就可以看到刚才的调用已经生成了调用链,信息如下图所示。

springcloud 引入lombak springcloudsleuth_开发语言_02

点击一条详细记录,可以看到具体的调用信息:

springcloud 引入lombak springcloudsleuth_spring cloud_03

从图中可以看出,这次调用涉及到三个服务,深度为5,包含5个span,右上角还有个Json按钮,可以查看json格式的数据。具体的含义可以学习Zipkin,就不再细说了。

参考资料:
1.《Spring Cloud与Docker微服务架构实战》 周立 著
2.《Spring Cloud微服务实战》 翟永超 著
3.《深入理解Spring Cloud与微服务构建》 方志朋 著