作者:刘善永
spring cloud sleuth是从google的dapper论文的思想实现的,提供了对spring cloud系列的链路追踪。本文主要从spring cloud sleuth的使用着手。
上一篇:Spring Cloud技术分析(2)—— 服务治理实践
spring cloud sleuth可以结合zipkin,将信息发送到zipkin,利用zipkin的存储来存储信息,利用zipkin ui来展示数据。同时也可以只是简单的将数据记在日志中。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
这种方式只需要引入jar包即可。如果配置log4j,这样会在打印出如下的日志:
2017-04-08 23:56:50.459 INFO [bootstrap,38d6049ff0686023,d1b8b0352d3f6fa9,false] 8764 --- [nio-8080-exec-1] demo.JpaSingleDatasourceApplication : Step 2: Handling print 2017-04-08 23:56:50.459 INFO [bootstrap,38d6049ff0686023,d1b8b0352d3f6fa9,false] 8764 --- [nio-8080-exec-1] demo.JpaSingleDatasourceApplication : Step 1: Handling home
比原先的日志多出了 [bootstrap,38d6049ff0686023,d1b8b0352d3f6fa9,false] 这些内容,[appname,traceId,spanId,exportable]。
sleuth收集跟踪信息通过http请求发给zipkin。这种需要启动一个zipkin,zipkin用来存储数据和展示数据。
BlockingQueue的大小sleuth写死了为1000。当队列满了还往里放的话,sleuth只是加了个记录处理。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
spring.sleuth.sampler.percentage=0.1 采样率
spring.zipkin.baseUrl=http://zipkin.ms.rrzcp8.com 发送到zipkinServer的url
spring.zipkin.enabled=true
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<!--<version>1.40.2</version>-->
</dependency>
@SpringBootApplication(exclude = SleuthStreamAutoConfiguration.class)
@EnableZipkinServer
public class SleuthServerApplication
{
public static void main(String[] args)
{
SpringApplication.run(SleuthServerApplication.class, args);
}
}
zipkin的存储包括mysql、es、cassadra。如果不配置存储的话,默认是在内存中的。如果在内存中的话,当重启应用后,数据就会丢失了。
spring:
application:
name: sleuth-zipkin-http
datasource:
schema: classpath:/mysql.sql
url: jdbc:mysql://xxx:xx/huluwa_zipkin
driverClassName: com.mysql.jdbc.Driver
username: huluwa_app
password: %jdbc-1.password%
# Switch this on to create the schema on startup:
initialize: true
continueOnError: true
sleuth:
enabled: false
# default is mem (in-memory)
zipkin:
storage:
type: mysql
mysql的脚本在zipkin包里已经提供了,只需要执行一下就可以了。
zipkin:
storage:
type: elasticsearch
elasticsearch:
cluster: ${ES_CLUSTER:elasticsearch}
hosts: ${ES_HOSTS:localhost:9300}
index: ${ES_INDEX:zipkin}
index-shards: ${ES_INDEX_SHARDS:5}
index-replicas: ${ES_INDEX_REPLICAS:1}
这种方式通过spring cloud streaming将追踪信息发送到zipkin。spring cloud streaming目前只有kafka和rabbitmq的binder。以kafka为例:
Collector是源码的类名。Collector从消息中间件中读取数据并存储到db和es中。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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-kafka</artifactId>
</dependency>
stream:
kafka:
binder:
brokers: xx:9098,xx:9098,xx:9098
zk-nodes: xx:2186,xx:2186
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<!--<version>1.40.2</version>-->
</dependency>
@EnableZipkinStreamServer
@EnableBinding(SleuthSink.class)
@SpringBootApplication(exclude = SleuthStreamAutoConfiguration.class)
@MessageEndpoint
public class SleuthServerApplication
{
public static void main(String[] args)
{
SpringApplication.run(SleuthServerApplication.class, args);
}
}
stream:
kafka:
binder:
brokers: xx:9098,xx:9098,xx:9098
zk-nodes: xx:2186,xx:2186
存储配置和上面的一样。
通过sleuth-core的jar包结构,可以很明显的看出,sleuth可以进行链路追踪的代码:
web下面包括http和feign。
可以通过spring.sleuth.web.enabled=false来禁止这种类型的链路追踪。http支持实现的关键类是 TraceFilter和TraceHandlerInterceptor。
可以通过 TraceRunnable 和 TraceCallable来对runnable和callable进行包装。也可以用LazyTraceExecutor来代替java的Executor。比如:
@Autowired
private BeanFactory beanFactory;
private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(2);
@RequestMapping("/service1")
public String service1()
{
Runnable runnable = () ->
{
try
{
Thread.sleep(1000);
}
catch (Exception e)
{
e.printStackTrace();
}
};
Executor executor = new LazyTraceExecutor(beanFactory, EXECUTOR);
executor.execute(runnable);
return "hello world";
}
这样每次执行都有span的新建和销毁。通过LazyTraceExecutor源码可以很轻松的看到:
@Override
public void run() {
Span span = startSpan();
try {
this.getDelegate().run();
}
finally {
close(span);
}
}
默认情况下,Spring Cloud Sleuth提供了一个TraceFeignClientAutoConfiguration
来整合Feign。如果需要禁用的话,可以设置spring.sleuth.feign.enabled
为false
。如果禁用,与Feign相关的机制就不会发生。
建议自定义一个RxJavaSchedulersHook
,它使用TraceAction
来包装实例中所有的Action0
。这个钩子对象,会根据之前调度的Action是否已经开始跟踪,来决定是创建还是延续使用span。可以通过设置spring.sleuth.rxjava.schedulers.hook.enabled
为false
来关闭这个对象的使用。可以定义一组正则表达式来对线程名进行过滤,来选择哪些线程不需要跟踪。可以使用逗号分割的方式来配置spring.sleuth.rxjava.schedulers.ignoredthreads
属性。
Spring Cloud Sleuth本身就整合了Spring Integration。它发布/订阅事件都是会创建span。可以设置spring.sleuth.integration.enabled=false来禁用这个机制。
因为sleuth是根据google的dapper论文而来的,所以用的术语和dapper一样。
如果服务的流量很大,全部采集对存储压力比较大。这个时候可以设置采样率,sleuth 可以通过设置 spring.sleuth.sampler.percentage=0.1。不配置的话,默认采样率是0.1。也可以通过实现bean的方式来设置采样为全部采样(AlwaysSampler)或者不采样(NeverSampler):如
@Bean public Sampler defaultSampler() {
return new AlwaysSampler();
}
sleuth采样算法的实现是 Reservoir sampling(水塘抽样)。实现类是 PercentageBasedSampler。
traceId和spanId的生成,sleuth是通过java 的Random类的nextLong方法生成的。这样的话就存在traceId存在一样的情况,不知道为什么要这么设计。
网易云大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者刘善永授权发布