本文简单介绍了如何利用Zipkin对SpringCloud应用进行服务分析在实际的应用场景中,Zipkin可以结合压力测试工具一起使用,分析系统在大压力下的可用性和性能。
设想这么一种情况,如果你的微服务数量逐渐增大,服务间的依赖关系越来越复杂,怎么分析它们之间的调用关系及相互的影响?
spring boot对zipkin的自动配置可以使得所有RequestMapping匹配到的endpoints得到监控,以及强化了RestTemplate,对其加了一层拦截器,使得由它发起的http请求也同样被监控。
sleuth与Zipkin关系?
spring cloud提供了spring-cloud-sleuth-zipkin来方便集成zipkin实现(指的是Zipkin Client,而不是Zipkin服务器),该jar包可以通过spring-cloud-starter-zipkin依赖来引入。
Zipkin原理
针对服务化应用全链路追踪的问题,Google发表了Dapper论文,介绍了他们如何进行服务追踪分析。其基本思路是在服务调用的请求和响应中加入ID,标明上下游请求的关系。利用这些信息,可以可视化地分析服务调用链路和服务间的依赖关系。
对应Dpper的开源实现是Zipkin,支持多种语言包括JavaScript,Python,Java, Scala, Ruby, C#, Go等。其中Java由多种不同的库来支持
Spring Cloud Sleuth是对Zipkin的一个封装,对于Span、Trace等信息的生成、接入HTTP Request,以及向Zipkin Server发送采集信息等全部自动完成。这是Spring Cloud Sleuth的概念图
入门实例
追踪服务包含下面几个服务:
1、注册中心 Eureka Server(可选的,只用于服务生产者和调用者注册)
2、Zipkin服务器
3、服务的生产者及服务的调用者:
1)服务的生产者、调用者是相对的,两者之间可以互相调用,即可以同时作为生产者和调用者,两者都是Eureka Client;
2)两者都要注册到注册中心上,这样才可以相互可见,才能通过服务名来调用指定服务,才能使用Feign或RestTemplate+Ribbon来达到负载均衡
3)两者都要注册到Zipkin服务器上,这样Zipkin才能追踪服务的调用链路
一、Zipkin服务器
代码地址(码云):https://gitee.com/wudiyong/ZipkinServer.git
1、新建一个普通的Spring Boot项目,添加如下依赖:
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
<!-- Zipkin可视化界面依赖 -->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<scope>runtime</scope>
</dependency>
2、在启动类加上如下注解开启Zipkin功能
@EnableZipkinServer
3、配置Zipkin服务端口、名称等:
server.port=11008
spring.application.name=my-zipkin-server
启动后打开http://localhost:11008/可以看到如下图,什么内容都没有,因为还没有任何服务注册到Zipkin,一旦有服务注册到Zipkin便在Service Name下拉列表中可以看到服务名字,当有服务被调用,则可以在Span Name中看到被调用的接口名字
这里为了测试方便,我们可以将数据保存到内存中,但是生产环境还是需要将数据持久化的,原生支持了很多产品,例如ES、数据库等。
二、服务生产者、调用者
这两者配置是一样的,代码:https://gitee.com/wudiyong/userInfoService.git
1、添加Zipkin依赖
<!-- 服务追踪 -->
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
2、配置Zipkin服务器的地址
#Zipkin服务器地址
spring.zipkin.baseUrl=http://localhost:11008
至此,可以开始测试Zipkin追踪服务了
启动顺序:注册中心(可选)->配置中心(可选)->Zipkin服务器->服务生产者及调用者
我们可以尝试调用生产者或调用者的接口,然后刷新Zipkin服务器页面,可以看到如下结果:
可以看到,调用消费者(ribbon-consumer)耗时83ms,其中消费者调用生产者占了5ms,占比6%。
在测试的过程中我们会发现,有时候,程序刚刚启动后,刷新几次,并不能看到任何数据,原因就是我们的spring-cloud-sleuth收集信息是有一定的比率的,默认的采样率是0.1,配置此值的方式在配置文件中增加spring.sleuth.sampler.percentage参数配置(如果不配置默认0.1),如果我们调大此值为1,可以看到信息收集就更及时。但是当这样调整后,我们会发现我们的rest接口调用速度比0.1的情况下慢了很多,即使在0.1的采样率下,我们多次刷新consumer的接口,会发现对同一个请求两次耗时信息相差非常大,如果取消spring-cloud-sleuth后我们再测试,会发现并没有这种情况,可以看到这种方式追踪服务调用链路会给我们业务程序性能带来一定的影响。
#sleuth采样率,默认为0.1,值越大收集越及时,但性能影响也越大
spring.sleuth.sampler.percentage=1
其实,我们仔细想想也可以总结出这种方式的几种缺陷
缺陷1:zipkin客户端向zipkin-server程序发送数据使用的是http的方式通信,每次发送的时候涉及到连接和发送过程。
缺陷2:当我们的zipkin-server程序关闭或者重启过程中,因为客户端收集信息的发送采用http的方式会被丢失。
针对以上两个明显的缺陷,改进的办法是
1、通信采用socket或者其他效率更高的通信方式。
2、客户端数据的发送尽量减少业务线程的时间消耗,采用异步等方式发送收集信息。
3、客户端与zipkin-server之间增加缓存类的中间件,例如redis、MQ等,在zipkin-server程序挂掉或重启过程中,客户端依旧可以正常的发送自己收集的信息。
相信采用以上三种方式会很大的提高我们的效率和可靠性。其实spring-cloud已经为我们提供采用MQ或redis等其他的采用socket方式通信,利用消息中间件或数据库缓存的实现方式。
spring-cloud-sleuth-zipkin-stream方式的实现请看下面内容!
将HTTP通信改成MQ异步方式通信
springcloud官方按照传输方式分成了三种启动服务端的方式:Sleuth with Zipkin via HTTP,Sleuth with Zipkin via Spring Cloud Stream,Spring Cloud Sleuth Stream Zipkin Collector。只需要添加相应的依赖,之后配置相应的注解,如@EnableZipkinStreamServer
即可。具体配置参考官方文档:
(http://cloud.spring.io/spring-cloud-static/spring-cloud-sleuth/1.2.1.RELEASE/#_adding_to_the_project)
1、加入依赖
要将http方式改为通过MQ通信,我们要将依赖的原来依赖的io.zipkin.java:zipkin-server换成spring-cloud-sleuth-zipkin-stream和spring-cloud-starter-stream-rabbit
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
2、在启动类中开启Stream通信功能
package com.zipkinServer.ZipkinServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.sleuth.zipkin.stream.EnableZipkinStreamServer;
import zipkin.server.EnableZipkinServer;
/*
* @EnableZipkinServer、@EnableZipkinStreamServer两者二选一
* 通过源码可看到,@EnableZipkinStreamServer包含了@EnableZipkinServer,同时
* 还创建了一个rabbit-mq的消息队列监听器,所以也支持原来的HTTP通信方式
*/
//@EnableZipkinServer//默认采用HTTP通信方式启动ZipkinServer
@EnableZipkinStreamServer//采用Stream通信方式启动ZipkinServer,也支持HTTP通信方式
@SpringBootApplication
public class ZipkinServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZipkinServerApplication.class, args);
}
}
3、配置消息中间件rabbit mq地址等信息
#连接rabbitmq服务器配置
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
至此,ZipkinServer配置完成,下面是Zipkin客户端的配置
1、将原来的spring-cloud-starter-zipkin替换为如下依赖即可
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
2、此外,在配置文件中也加上连接MQ的配置
#连接rabbitmq服务器配置
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
至此全部配置完成,可以开始测试。
另外,由于要连接到rabbitmq服务器,所以,还要安装及启动rabbitmq服务器!
加了MQ之后,通信过程如下图所示:
可以看到如下效果:
1)请求的耗时时间不会出现突然耗时特长的情况
2)当ZipkinServer不可用时(比如关闭、网络不通等),追踪信息不会丢失,因为这些信息会保存在Rabbitmq服务器上,直到Zipkin服务器可用时,再从Rabbitmq中取出这段时间的信息
持久化到数据库
Zipkin目前只支持mysql数据库,ZipkinServer服务做如下修改,其它服务不需做任何修改
1、加入数据库依赖
-
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
2、在application.properties中配置数据库属性
#zipkin数据保存到数据库中需要进行如下配置
#表示当前程序不使用sleuth
spring.sleuth.enabled=false
#表示zipkin数据存储方式是mysql
zipkin.storage.type=mysql
#数据库脚本创建地址,当有多个时可使用[x]表示集合第几个元素,脚本可到官网下载,需要先手动到数据库执行
spring.datasource.schema[0]=classpath:/zipkin.sql
#spring boot数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/zipkin?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.initialize=true
spring.datasource.continue-on-error=true
3、zipkin.sql
数据库脚本文件放到resources目录下,且需要先手动到数据库执行一次,内容如下:
CREATE TABLE IF NOT EXISTS zipkin_spans (
`trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` BIGINT NOT NULL,
`id` BIGINT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`parent_id` BIGINT,
`debug` BIT(1),
`start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
`duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `id`) COMMENT 'ignore insert on duplicate';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`, `id`) COMMENT 'for joining with zipkin_annotations';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds';
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames';
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';
CREATE TABLE IF NOT EXISTS zipkin_annotations (
`trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
`span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
`a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
`a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
`a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
`a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
`endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
`endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds';
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames';
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job';
CREATE TABLE IF NOT EXISTS zipkin_dependencies (
`day` DATE NOT NULL,
`parent` VARCHAR(255) NOT NULL,
`child` VARCHAR(255) NOT NULL,
`call_count` BIGINT
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);
至此,ZipkinServer采用数据库存储配置完成。
测试时发现,要用MQ异步方式通信的pom.xml配置及@EnableZipkinStreamServer注解才可以(@EnableZipkinServer貌似只能保存到内存),否则启动报错,不明白为什么。
elasticsearch存储
前面讲了利用mq的方式发送数据,存储在mysql,实际生产过程中调用数据量非常的大,mysql存储并不是很好的选择,这时我们可以采用elasticsearch进行存储
配置过程也很简单
1、mysql依赖改成elasticsearch依赖
<!-- 添加 spring-data-elasticsearch的依赖 -->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-storage-elasticsearch-http</artifactId>
<version>1.24.0</version>
<optional>true</optional>
</dependency>
2、数据库配置改成elasticsearch配置
#表示当前程序不使用sleuth
spring.sleuth.enabled=false
#表示zipkin数据存储方式是elasticsearch
zipkin.storage.StorageComponent = elasticsearch
zipkin.storage.type=elasticsearch
zipkin.storage.elasticsearch.cluster=elasticsearch-zipkin-cluster
zipkin.storage.elasticsearch.hosts=127.0.0.1:9300
# zipkin.storage.elasticsearch.pipeline=
zipkin.storage.elasticsearch.max-requests=64
zipkin.storage.elasticsearch.index=zipkin
zipkin.storage.elasticsearch.index-shards=5
zipkin.storage.elasticsearch.index-replicas=1
3、安装elasticsearch
其它代码完全不变
具体见:
http://www.cnblogs.com/shunyang/p/7011306.html
http://www.cnblogs.com/shunyang/p/7298005.html
官方网站:https://zipkin.io/
参考资料:
http://www.cnblogs.com/shunyang/p/7011283.html
https://www.cnblogs.com/shunyang/p/7011303.html
http://www.cnblogs.com/shunyang/p/7011306.html
http://www.cnblogs.com/shunyang/p/7298005.html