微服务架构是一个分布式架构,微服务系统按业务划分服务单元,一个微服务系统每每有不少个服务单元。因为服务单元数量众多,业务的复杂性较高,若是出现了错误和异常,很难去定位。主要体如今一个请求可能须要调用不少个服务,而内部服务的调用复杂性决定了问题难以定位。因此在微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每一个请求的步骤清晰可见,出了问题可以快速定位的目的。html
在微服务系统中,一个来自用户的请求先到达前端A(如前端界面),而后经过远程调用,到达系统的中间件B、C(如负载均衡、网关等),最后到达后端服务D、E,后端通过一系列的业务逻辑计算,最后将数据返回给用户。对于这样一个请求,经历了这么多个服务,怎么样将它的请求过程用数据记录下来呢?这就须要用到服务链路追踪。前端
Spring Cloud Sleuth 为服务之间调用提供链路追踪。经过 Sleuth 能够很清楚的了解到一个服务请求通过了哪些服务,每一个服务处理花费了多长。从而让咱们能够很方便的理清各微服务间的调用关系。此外 Sleuth 能够帮助咱们:java
Google开源了Dapper链路追踪组件,并在2010年发表了论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》,这篇论文是业内实现链路追踪的标杆和理论基础,具备很高的参考价值。mysql
Spring Cloud Sleuth采用了Google的开源项目Dapper的专业术语。git
Annotation:用于记录一个事件,一些核心注解用于定义一个请求的开始和结束,这些注解以下。github
Spring Cloud Sleuth 也为咱们提供了一套完整的链路解决方案,Spring Cloud Sleuth 能够结合 Zipkin,将信息发送到 Zipkin,利用 Zipkin 的存储来存储链路信息,利用 Zipkin UI 来展现数据。web
Zipkin是一种分布式链路追踪系统。 它有助于收集解决微服务架构中的延迟问题所需的时序数据。 它管理这些数据的收集和查找。 Zipkin的设计基于Google Dapper论文。spring
跟踪器存在于应用程序中,记录请求调用的时间和元数据。跟踪器使用库,它们的使用对用户是无感知的。例如,Web服务器会在收到请求时和发送响应时会记录相应的时间和一些元数据。一次完整链路请求所收集的数据被称为Span。sql
咱们可使用它来收集各个服务器上请求链路的跟踪数据,并经过它提供的 REST API 接口来辅助咱们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源。除了面向开发的 API 接口以外,它也提供了方便的 UI 组件来帮助咱们直观的搜索跟踪信息和分析请求链路明细,好比:能够查询某段时间内各用户请求的处理时间等。
Zipkin 提供了可插拔数据存储方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。接下来的测试为方便直接采用 In-Memory 方式进行存储,生产推荐 Elasticsearch.docker
上图展现了 Zipkin 的基础架构,它主要由 4 个核心组件构成:
在本案例一共有三个应用,分别为注册中心,eureka-server、eureka-client、eureka-client-feign,三个应用的基本信息以下:
应用名 | 端口 | 做用 |
---|---|---|
eureka-server | 8761 | 注册中心 |
eureka-client | 8763 | 服务提供者 |
eureka-client-feign | 8765 | 服务消费者 |
其中eureka-server 应用为注册中心,其余两个应用向它注册。eureka-client为服务提供者,提供了一个RESTAPI,eureka-client-feign为服务消费者,经过Feign Client向服务提供者消费服务。
在以前的文章已经讲述了如何如何搭建服务注册中心,在这里就省略这一部份内容。服务提供者提供一个REST接口,服务消费者经过FeignClient消费服务。
eureka-client服务提供者,对外提供一个RESTAPI,并向服务注册中心注册,这部份内容,再也不讲述,见源码。须要在工程的pom文件加上sleuth的起步依赖和zipkin的起步依赖,代码以下:
<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>
在工程的配置文件application.yml须要作如下的配置:
spring: sleuth: web: client: enabled: true sampler: probability: 1.0 # 将采样比例设置为 1.0,也就是所有都须要。默认是 0.1 zipkin: base-url: http://localhost:9411/ # 指定了 Zipkin 服务器的地址
其中spring.sleuth.web.client.enable为true设置的是web开启sleuth功能;spring.sleuth.sampler.probability能够设置为小数,最大值为1.0,当设置为1.0时就是链路数据100%收集到zipkin-server,当设置为0.1时,即10%几率收集链路数据;spring.zipkin.base-url设置zipkin-server的地址。
对外提供一个Api,代码以下:
@RestController public class HiController { @Value("${server.port}") String port; @GetMapping("/hi") public String home(@RequestParam String name) { return "hi "+name+",i am from port:" +port; } }
服务消费者经过FeignClient消费服务提供者提供的服务。同服务提供者同样,须要在工程的pom文件加上sleuth的起步依赖和zipkin的起步依赖,另外也须要在配置文件application.yml作相关的配置,具体同服务提供者。
服务消费者经过feignClient进行服务消费,feignclient代码以下:
@FeignClient(value = "eureka-client",configuration = FeignConfig.class) public interface EurekaClientFeign { @GetMapping(value = "/hi") String sayHiFromClientEureka(@RequestParam(value = "name") String name); }
servcie层代码以下:
@Service public class HiService { @Autowired EurekaClientFeign eurekaClientFeign; public String sayHi(String name){ return eurekaClientFeign.sayHiFromClientEureka(name); } }
controller代码以下:
@RestController public class HiController { @Autowired HiService hiService; @GetMapping("/hi") public String sayHi(@RequestParam( defaultValue = "forezp",required = false)String name){ return hiService.sayHi(name); }
上面的代码对外暴露一个API,经过FeignClient的方式调用eureka-client的服务。
在Spring Cloud D版本,zipkin-server经过引入依赖的方式构建工程,自从E版本以后,这一方式改变了,采用官方的jar形式启动,因此须要经过下载官方的jar来启动,也经过如下命令一键启动:
curl -sSL https://zipkin.io/quickstart.sh | bash -s java -jar zipkin.jar
上面的第一行命令会从zipkin官网下载官方的jar包。
若是是window系统,建议使用gitbash执行上面的命令。
若是用 Docker 的话,使用如下命令:
docker run -d -p 9411:9411 openzipkin/zipkin
经过java -jar zipkin.jar的方式启动以后,在浏览器上访问lcoalhost:9411,显示的界面以下:
依次启动eureka-server,eureka-client,eureka-client-feign的三个应用,等全部应用启动完成后,在浏览器上访问http://localhost:8765/hi(若是报错,是服务与发现须要必定的时间,耐心等待几十秒),访问成功后,再次在浏览器上访问zipkin-server的页面,显示以下:
从上图能够看出每次请求所消耗的时间,以及一些span的信息。
从上图能够看出具体的服务依赖关系,eureka-feign-client依赖了eureka-client。
在上面的案例中使用的http请求的方式将链路数据发送给zipkin-server,其实还可使用rabbitmq的方式进行服务的消费。使用rabbitmq须要安装rabbitmq程序,下载地址http://www.rabbitmq.com/。
下载完成后,须要eureka-client和eureka-client-feign的起步依赖加上rabbitmq的依赖,依赖以下:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-binder-rabbit</artifactId> </dependency>
在配置文件上须要配置rabbitmq的配置,配置信息以下:
spring: rabbitmq: host: localhost username: guest password: guest port: 5672
另外须要把spring.zipkin.base-url去掉。
在上面2个工程中,rabbitmq经过发送链路数据,那么zipkin-server是怎么样知道rabbitmq的地址呢,怎么监听收到的链路数据呢?这须要在程序启动的时候,经过环境变量的形式到环境中,而后zikin-server从环境变量中读取。
可配置的属性以下:
属性 | 环境变量 | 描述 |
---|---|---|
zipkin.collector.rabbitmq.addresses | RABBIT_ADDRESSES | 用逗号分隔的 RabbitMQ 地址列表,例如localhost:5672,localhost:5673 |
zipkin.collector.rabbitmq.password | RABBIT_PASSWORD | 链接到 RabbitMQ 时使用的密码,默认为 guest |
zipkin.collector.rabbitmq.username | RABBIT_USER | 链接到 RabbitMQ 时使用的用户名,默认为guest |
zipkin.collector.rabbitmq.virtual-host | RABBIT_VIRTUAL_HOST | 使用的 RabbitMQ virtual host,默认为 / |
zipkin.collector.rabbitmq.use-ssl | RABBIT_USE_SSL | 设置为true则用 SSL 的方式与 RabbitMQ 创建连接 |
zipkin.collector.rabbitmq.concurrency | RABBIT_CONCURRENCY | 并发消费者数量,默认为1 |
zipkin.collector.rabbitmq.connection-timeout | RABBIT_CONNECTION_TIMEOUT | 创建链接时的超时时间,默认为 60000毫秒,即 1 分钟 |
zipkin.collector.rabbitmq.queue | RABBIT_QUEUE | 从中获取 span 信息的队列,默认为 zipkin |
好比,经过如下命令启动:
RABBIT_ADDRESSES=localhost java -jar zipkin.jar
上面的命令等同于一下的命令:
java -jar zipkin.jar --zipkin.collector.rabbitmq.addressed=localhost
用上面的2条命令中的任何一种方式从新启动zipkin-server程序,并从新启动eureka-client、eureka-server、eureka-client-feign,动完成后在浏览器上访问http://localhost:8765/hi,再访问http://localhost:9411/zipkin/,就能够看到经过Http方式发送链路数据同样的接口。
在页面上能够查看每一个请求的traceId,每一个trace又包含若干的span,每一个span又包含了不少的tag,自定义tag能够经过Tracer这个类来自定义。
@Autowired Tracer tracer; @GetMapping("/hi") public String home(@RequestParam String name) { tracer.currentSpan().tag("name","forezp"); return "hi "+name+",i am from port:" +port; }
上面的例子是将链路数据存在内存中,只要zipkin-server重启以后,以前的链路数据所有查找不到了,zipkin是支持将链路数据存储在mysql、cassandra、elasticsearch中的。
如今讲解如何将链路数据存储在Mysql数据库中。
首先须要初始化zikin存储在Mysql的数据的scheme,能够在这里查看https://github.com/openzipkin...,具体以下:
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 and autocomplete values'; ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces and autocomplete values'; 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, `error_count` BIGINT ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci; ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);
在数据库中初始化上面的脚本以后,须要作的就是zipkin-server如何链接数据库。zipkin如何连数据库同链接rabbitmq同样。zipkin链接数据库的属性所对应的环境变量以下:
属性 | 环境变量 | 描述 | |
---|---|---|---|
zipkin.torage.type | STORAGE_TYPE | 默认的为mem,即为内存,其余可支持的为cassandra、cassandra三、elasticsearch、mysql | |
zipkin.torage.mysql.host | MYSQL_HOST | 数据库的host,默认localhost | |
zipkin.torage.mysql.port | MYSQL_TCP_PORT | 数据库的端口,默认3306 | |
zipkin.torage.mysql.username | MYSQL_USER | 链接数据库的用户名,默认为空 | |
zipkin.torage.mysql.password | MYSQL_PASS | 链接数据库的密码,默认为空 | |
zipkin.torage.mysql.db | MYSQL_DB | zipkin使用的数据库名,默认是zipkin | |
zipkin.torage.mysql.max-active | MYSQL_MAX_CONNECTIONS | 最大链接数,默认是10 |
STORAGE_TYPE=mysql MYSQL_HOST=localhost MYSQL_TCP_PORT=3306 MYSQL_USER=root MYSQL_PASS=123456 MYSQL_DB=zipkin java -jar zipkin.jar
等同于如下的命令
java -jar zipkin.jar --zipkin.torage.type=mysql --zipkin.torage.mysql.host=localhost --zipkin.torage.mysql.port=3306 --zipkin.torage.mysql.username=root --zipkin.torage.mysql.password=123456
使用上面的命令启动zipkin.jar工程,而后再浏览数上访问http://localhost:8765/hi,再访问http://localhost:9411/zipkin/,能够看到链路数据。这时去数据库查看数据,也是能够看到存储在数据库的链路数据,以下:
这时重启应用zipkin.jar,再次在浏览器上访问http://localhost:9411/zipkin/,仍然能够获得以前的结果,证实链路数据存储在数据库中,而不是内存中。
zipkin-server支持将链路数据存储在ElasticSearch中。读者须要自行安装ElasticSearch和Kibana,下载地址为https://www. elastic.co/products/elasticsearch。安装完成后启动,其中ElasticSearch的默认端口号为9200,Kibana的默认端口号为5601。
同理,zipkin链接elasticsearch也是从环境变量中读取的,elasticsearch相关的环境变量和对应的属性以下:
属性 | 环境变量 | 描述 | |
---|---|---|---|
zipkin.torage.elasticsearch.hosts | ES_HOSTS | ES_HOSTS,默认为空 | |
zipkin.torage.elasticsearch.pipeline | ES_PIPELINE | ES_PIPELINE,默认为空 | |
zipkin.torage.elasticsearch.max-requests | ES_MAX_REQUESTS | ES_MAX_REQUESTS,默认为64 | |
zipkin.torage.elasticsearch.timeout | ES_TIMEOUT | ES_TIMEOUT,默认为10s | |
zipkin.torage.elasticsearch.index | ES_INDEX | ES_INDEX,默认是zipkin | |
zipkin.torage.elasticsearch.date-separator | ES_DATE_SEPARATOR | ES_DATE_SEPARATOR,默认为“-” | |
zipkin.torage.elasticsearch.index-shards | ES_INDEX_SHARDS | ES_INDEX_SHARDS,默认是5 | |
zipkin.torage.elasticsearch.index-replicas | ES_INDEX_REPLICAS | ES_INDEX_REPLICAS,默认是1 | |
zipkin.torage.elasticsearch.username | ES_USERNAME | ES的用户名,默认为空 | |
zipkin.torage.elasticsearch.password | ES_PASSWORD | ES的密码,默认是为空 |
采用如下命令启动zipkin-server:
STORAGE_TYPE=elasticsearch ES_HOSTS=http://localhost:9200 ES_INDEX=zipkin java -jar zipkin.jar
java -jar zipkin.jar --STORAGE_TYPE=elasticsearch --ES_HOSTS=http://localhost:9200 --ES_INDEX=zipkin
java -jar zipkin.jar --STORAGE_TYPE=elasticsearch --ES_HOSTS=http://localhost:9200 --ES_INDEX=zipkin
java -jar zipkin.jar --zipkin.torage.type=elasticsearch --zipkin.torage.elasticsearch.hosts=http://localhost:9200 --zipkin.torage.elasticsearch.index=zipkin
启动完成后,而后在浏览数上访问http://localhost:8765/hi,再访问http://localhost:9411/zipkin/,能够看到链路数据。这时链路数据存储在ElasticSearch。
链路数据存储在ElasticSearch中,ElasticSearch能够和Kibana结合,将链路数据展现在Kibana上。安装完成Kibana后启动,Kibana默认会向本地端口为9200的ElasticSearch读取数据。Kibana默认的端口为5601,访问Kibana的主页http://localhost:5601,其界面以下图所示。
在上图的界面中,单击“Management”按钮,而后单击“Add New”,添加一个index。咱们将在上节ElasticSearch中写入链路数据的index配置为“zipkin”,那么在界面填写为“zipkin-*”,单击“Create”按钮,界面以下图所示:
建立完成index后,单击“Discover”,就能够在界面上展现链路数据了,展现界面以下图所示。
https://github.com/spring-clo...
https://cloud.spring.io/sprin...
https://github.com/openzipkin...
https://github.com/openzipkin...
https://windmt.com/2018/04/24...
https://segmentfault.com/a/11...
elatstic 版本为2.6.x,下载地址:https://www.elastic.co/downlo...
http://www.cnblogs.com/JreeyQ...