SpringCloud(八)链路追踪Sleuth

SpringCloud(八)链路追踪Sleuth

在分布式系统中,完成一个外部请求可能需要多个应用之间相互协作,形成复杂的调用链路。而一旦出现问题,更是难以定位问题,也难以直观地获取到各个服务之间的依赖关系。Spring Cloud Sleuth的出现正是为了实现分布式系统的链路追踪。

Sleuth的基本概念

Sleuth借鉴了Google Dapper的术语。
有以下几个要素:
Span:基本的工作单元。比如发送一个RPC以及返回一个RPC应答分别是一个新的Span。Span是根据一个唯一的64-bit ID和所属Trace的64-bit ID区分的。
Trace:一系列的Span组成的树结构的图
Annotation:用来记录事件存在的时间。

  • cs - Client Sent - client端发送一个请求。Span的开始。
  • sr - Server Received - server端接收到请求,并且准备处理
  • ss - Server Sent - server端请求处理完成
  • cr - Client Received - client端接收到server端的响应结果。Span的结束。
    在这里插入图片描述
    上图中七种颜色分别代表了从A-G7个Span。
    将他们转换为父子关系图,如下图所示:
    在这里插入图片描述

引入Sleuth

我们对web-app-serviceuser-servicesms-service这三个服务增加Sleuth的支持。
pom.xml文件中引入spring-cloud-starter-sleuth依赖。

	<dependency>
	    <groupId>org.springframework.cloud</groupId>
	    <artifactId>spring-cloud-starter-sleuth</artifactId>
	</dependency>

application.yml文件中设置

spring:
  sleuth:
    sampler:
      percentage: 1.0

表示设置Sleuth的采样率为100%,即全部采样。
我们在web-app-service新建一个用户注册的接口,分别请求user-servicesms-service的接口,涉及到的接口增加Log输出。

@RequestMapping("/register")
public String register() {
    logger.info("user is registering");
    userService.register();
    smsService.sendRgister();
    return "success";
}

请求web-app-service/user/register接口(http://localhost:8101/user/register),返回成功后可以在三个服务的控制台分别看到的日志打印。

# web-app-service
2018-10-10 16:51:11.454  INFO [web-app-service,dd3aaab11dddbca1,dd3aaab11dddbca1,true] 3760 --- [nio-8101-exec-9] c.c.s.web.controller.UserController      : user is registering
# user-service
2018-10-10 16:51:11.463  INFO [user-service,dd3aaab11dddbca1,436fe40b80a2bdb1,true] 8608 --- [nio-8002-exec-9] c.c.s.user.controller.UserController     : invoke user register endpoint
# sms-service
2018-10-10 16:51:11.983  INFO [sms-service,dd3aaab11dddbca1,ff559b20a835e679,true] 5920 --- [nio-8001-exec-3] c.c.s.controller.SMSController           : send sms to registered user

注意日志的输出分别代表[appname,traceId,spanId,exportable]

exportable:表示日志是否应该导入到Zipkin中。

Sleuth集成ELK实现日志分析

对于分布式系统,我们希望将日志纳入统一管理。ELK(Elasticsearch+Logstash+Kibana)是日志管理分析的一系列组件,实现了日志收集、检索和可视化查询。我们通过logbacklogstash-logback-encoder将日志按照指定格式传输给Logstash,经过处理后传输给Elasticsearch,再通过Kibana的web界面进行日志查询。
在Logstash上设置输入和输出,并重启Logstash服务。在输入配置里,tcp.host就是我们的Logstash所在IP,tcp.port是我们使用TCP传输日志时的指定端口。输出配置我们的Elasticsearch IP和端口,并指定索引(不能使用大写字母)。

input {
  tcp {
    mode => "server"
    host => "192.168.126.128"
    port => 3333
  }
}
filter {
}
output {
  elasticsearch {
    action => "index"
    hosts  => "192.168.126.128:9200"
    index  => "spring_cloud_demo_logs"
  }
}

logback日志配置文件logback-spring.xmldestination需要配置上面的Logstash IP和TCP端口,并指定编码器LoggingEventCompositeJsonEncoder及格式。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/><springProperty scope="context" name="springAppName" source="spring.application.name"/>
    <!-- Example for logging into the build folder of your project -->
    <property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/><!-- You can override this to have a custom pattern -->
    <property name="CONSOLE_LOG_PATTERN"
              value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
    <!--Logstash 服务器地址和TCP端口-->
    <property name="LOGSTASH_TCP_DESTINATION" value="192.168.126.128:3333" />

    <!-- Appender to log to console -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!-- Minimum logging level to be presented in the console logs-->
            <level>DEBUG</level>
        </filter>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- Appender to log to file --><appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender><appender name="logstash"
              class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>${LOGSTASH_TCP_DESTINATION}</destination>
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp>
                    <timeZone>UTC</timeZone>
                </timestamp>
                <pattern>
                    <pattern>
                        {
                        "severity": "%level",
                        "service": "${springAppName:-}",
                        "trace": "%X{X-B3-TraceId:-}",
                        "span": "%X{X-B3-SpanId:-}",
                        "parent": "%X{X-B3-ParentSpanId:-}",
                        "exportable": "%X{X-Span-Export:-}",
                        "pid": "${PID:-}",
                        "thread": "%thread",
                        "class": "%logger{40}",
                        "rest": "%message"
                        }
                    </pattern>
                </pattern>
            </providers>
        </encoder>
    </appender><root level="INFO">
        <appender-ref ref="console"/>
        <!-- uncomment this to have also JSON logs -->
        <appender-ref ref="logstash"/>
        <!--<appender-ref ref="flatfile"/>-->
    </root>
</configuration>

请求web-app-service/user/register接口(http://localhost:8101/user/register),自动向ELK发送日志。
在Kibana web界面创建索引
在这里插入图片描述
采集到的日志就是我们各个服务中的输出。
在这里插入图片描述
根据某一个traceId,对e7df1589e5a2a73f进行搜索显示整个调用链路的日志信息。
在这里插入图片描述
展开某一条日志后可以看到其详细信息
在这里插入图片描述

使用Sleuth和Zipkin实现链路追踪

Zipkin作为分布式链路跟踪系统,提供了可视化页面极大地方便了对请求的监控,更容易明确服务之间的依赖。新建一个zipkin-server服务作为链路追踪服务。我们需要将Sleuth采集的日志信息通过Http或者Spring Cloud Stream方式传输给Zipkin服务端。

通过Http传输Sleuth信息

zipkin-serverpom.xml需要引入zipkin-serverzipkin-autoconfigure-ui依赖。

<!--注册到Eureka,可以自动发现Zipkin服务-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

<!--使用Http方式获取链路信息-->
<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-server</artifactId>
</dependency>
<!--zipkin ui页面-->
<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>

将服务端口设置为8400并注册到eureka,保证其他服务能够发现Zipkin服务。
使用@EnableZipkinServer启用Zipkin服务。

@EnableEurekaClient
@EnableZipkinServer
@SpringBootApplication
public class ZipkinApplicationStarter {

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

启动成功后我们访问http://localhost:8400就能直接访问Zipkin服务的首页了。
接着改造我们的web-app-serviceuser-servicesms-service服务使其支持使用Http向Zipkin传输链路信息。pom.xml去掉之前的spring-cloud-starter-sleuth依赖增加spring-cloud-sleuth-zipkin(包含spring-cloud-starter-sleuth)。

	<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-sleuth-zipkin</artifactId>
    </dependency>

然后需要在application.yml中配置Zipkin服务的地址和Sleuth的采样率。我们可以使用Eureka发现Zipkin服务或者使用url配置Zipkin的路径。Sleuth的采样率继续使用1,保证所有的请求都会被收集。

spring:
  zipkin:
    locator:
      discovery:
        enabled: true
    #base-url: http://localhost:8400
  sleuth:
    sampler:
      percentage: 1.0

请求web-app-service/user/register接口(http://localhost:8101/user/register
此时我们打开Zipkin首页,查看收集到的信息。图中蓝色的都是正常的请求,红色则是出现错误的请求。
在这里插入图片描述
打开一个正常的请求可以看到请求的服务接口及请求时间。如图我们可以看出web-app-service请求了user-service之后再请求了sms-service
在这里插入图片描述
点开user-serviceSpan显示请求状况
在这里插入图片描述
关闭sms-service服务,并重新发起请求。观察异常请求链路,发现sms-service关闭后,请求异常后又重试了6次,最终失败。
在这里插入图片描述
点击下面红色的Span查看错误信息。从日志中不难发现出现错误的原因是/sms/sendRegister超时了。
在这里插入图片描述
点击Dependencies页面显示服务之间的依赖关系。
在这里插入图片描述
点开某个服务可以看到被哪些应用使用
在这里插入图片描述

使用SpringCloud Sleuth Stream传输Sleuth信息

使用Http方式可以很容易的实现Sleuth信息的上传,但是当服务访问量较高时Http的势必会影响服务性能。再者,当我们的Zipkin服务端出现异常,这些Sleuth信息也不能正常上传,影响服务的监控。这时候使用消息队列就能轻松的解决高吞吐量和异步解耦这两个问题。
Spring Cloud Stream是一个构建基于消息微服务的框架,通过简单的注解方式就能实现消息的发送。目前已经支持Kafka和RabbitMQ。Spring Cloud Sleuth Stream正是利用Spring Cloud Stream实现使用消息队列传输Sleuth信息。
下面我们演示使用RabbitMQ作为中间件,实现Stream传输Sleuth信息。
zipkin-server增加Spring Cloud Sleuth Stream的依赖。

	<dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-sleuth</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
       </dependency>

在启动类上增加@EnableZipkinStreamServer注解。

@EnableEurekaClient
//@EnableZipkinServer  //Http方式传输sleuth信息
@EnableZipkinStreamServer  //Stream方式传输Sleuth信息
@SpringBootApplication
public class ZipkinApplicationStarter {

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

application.yaml文件中增加RabbitMQ的配置

spring:
  rabbitmq:
    host: 172.16.4.39
    username: rabbitmq
    password: rabbitmq

服务端完成,启动之。
我们发现RabbitMQ的中已经有了一个叫做sleuth的exchange。
在这里插入图片描述
web-app-serviceuser-servicesms-service这三个服务中增加Sleuth Stream采集支持。

	<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-sleuth-stream</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-sleuth</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
    </dependency>

同样地,增加RabbitMQ地址、用户名和密码配置。这三个应用集成配置中心的时候已经进行了配置。
启动这三个服务,继续请求我们的测试接口,观察到RabbitMQ的web界面有消息的生产和消费,打开Zipkin界面也能看到调用链路信息。

信息存储方式

默认地,Zipkin将链路信息存储在内存中。在正式使用中这种做法是不可取的,随着数据的堆积会大量消耗内存,一旦服务重启所有的监控信息都将随之消失。我们需要将链路信息进行持久化。
打开io.zipkin.java:zipkin-serverpom依赖,我们可以看到Zipkin支持的存储方式有mysql,cassandra,elasticsearch。
在这里插入图片描述
下面演示以mysql作为信息的存储方式。我们在Zipkin服务的pom.xml文件中引入mysql存储支持

  <!--使用mysql存储Span信息-->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-jdbc</artifactId>
   </dependency>
   <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
   </dependency>
   <dependency>
       <groupId>io.zipkin.java</groupId>
       <artifactId>zipkin-autoconfigure-storage-mysql</artifactId>
   </dependency>

application.yaml中设置数据源、存储方式以及Zipkin DDL

spring:
  datasource:
    schema: classpath:ysql.sql
    # zipkin-autoconfigure-storage-mysql包中默认包含了HikariDataSource
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://localhost:3306/test
    username: chenxyz
    password: 123
    # Switch this on to create the schema on startup:
    initialize: true
    continueOnError: true

# 设置存储类型为mysql
zipkin:
  storage:
    type: mysql

服务启动的时候会自动生成三张表。
在这里插入图片描述
后面我们再传输链路信息,Zipkin会将Span信息持久化到mysql中。


相关代码
SpringCloudDemo-Sleuth


参考
Spring Cloud Sleuth - Introduction

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章