https://github.com/carnellj/spmia-chapter9
Spring Cloud Sleuth:Spring Cloud Sleuth是一个Spring Cloud项目,它将关联ID装备到HTTP调用上,并将生成的跟踪数据提供给OpenZipkin的钩子。Spring Cloud Sleuth通过添加过滤器并与其他Spring组件进行交互,将生成的关联ID传递到所有系统调用
Papertrai:Papertail是一种基于云的服务,允许开发人员将来自多个源的日志数据聚合到单个可搜索的数据库中
Zipkin:Zipkin是一种开源数据可视化工具,可以显示跨多个服务的事务流
Spring Cloud Sleuth集成
1、依赖添加到许可证服务和组织服务中
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency>一旦这个依赖项被拉进来,服务现在就会完成如下功能:
检查每个传入的HTTP服务,并确定调用中是否存在Spring Cloud Sleuth跟踪信息。如果Spring Cloud Sleuth跟踪数据确实存在,则将捕获传递到微服务的跟踪信息,并将跟踪信息提供给服务以进行日志记录和处理
将Spring Cloud sleuth跟踪信息添加到Spring MDC(Mapped Diagnostic Context, MDC映射诊断上下文),以便微服务创建的每个日志语句都添加到日志中
将Spring Cloud跟踪信息注入服务发出的每个出站HTTP调用以及Spring消息传递通道的消息中
2、剖析Spring Cloud Sleuth跟踪
单个服务调用,组织服务上执行 http://localhost:5555/api/organization/v1/organizations/xxxxxxx
Spring Cloud Sleuth将向每个日志条目添加以下4条信息:
服务的应用程序名称:在默认情况下,Spring Cloud Sleuth将应用程序的名称(spring.application.name)作为在跟踪中写入的名称
跟踪ID(trace ID):表示整个事务的唯一编号
跨度ID(span ID):表示整个事务中某一部分的唯一ID。参与事务的每个服务都将具有自己的跨度ID。当与Zipkin集成来可视化事务时,跨度ID尤其重要
是否将跟踪数据发送到Zipkin:在大容量服务中,生成的跟踪数据量可能是海量的,并且不会增加大量的价值。Spring Cloud Sleuth让开发人员确定何时以及如何将事务发送给Zipkin。Spring Cloud Sleuth跟踪末尾的true/false指示器用于指示是否将跟踪信息发送到Zipkin
跨多个服务调用,在调用许可证服务时也会调用组织服务执行http://localhost:5555/api/licensing/v1/organizations/xxx/licenses/xxxx
日志聚合与Spring Cloud Sleuth
日志聚合一种更好的方法是,将所有服务实例的日志实时流到一个集中的聚集点,在那里可以对日志数据进行索引并进行搜索。
【1】Spring Cloud Sleuth
1、使用Zuul将关联ID添加到HTTP响应
使用Spring Cloud Sleuth进行服务调用所返回的HTTP响应,永远不会看到在调用中使用的跟踪ID在HTTP响应首部中返回。Spring Cloud Sleuth团队认为返回的跟踪数据可能是一个潜在的安全问题。
在调试问题时,在HTTP响应中返回关联ID或跟踪ID是非常重要的。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency> // 通过Zuul后置过滤器添加Spring Cloud Sleuth的跟踪ID @Component public lass ResponseFilter extends ZuulFilter { private static final int FILTER_ORDER = 1; private static final boolean SHOULD_FILTER = true; private static final Logger logger = LoggerFactory.getLogger(ResponseFilter.class); @Autowired Tracer tracer; @Override public String filterType() { return "post"; } @Override public int filterOrder() { return FILTER_ORDER; } @Override public boolean shouldFilter() { return SHOULD_FILTER; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); // HTTP响应首部tmx-correlation-id添加跟踪id ctx.getResponse().addHeader("tmx-correlation-id", tracer.getCurrentSpan().traceIdString()); } } 访问:http://localhost:5555/api/licensing/v1/organizations/xxx/licenses/xxx【2】使用Papertrail测试日志聚合跟踪
选择Papertrail处于以下3个原因:
它有一个免费增值模式,可以注册一个免费的账户
它非常容易创建,特别是和Docker这样的容器运行时工作
它是基于云的
为了让Papertrail与我们的环境一起工作,我们必须采取以下措施:
创建一个Papertrail账号并配置一个Papertrail syslog连接器
定义一个Logspout Docker容器,以从所有Docker容器捕获标准输出
通过基于来自Spring Cloud Sleuth的关联ID发出查询来测试
1、创建Papertrail账户并配置syslog连接器
在默认情况下,Papertrail允许开发人员通过Syslog调用向它发送日志数据。Syslog是源于UNIX的日志消息传递格式,它允许通过TCP和UDP发送日志消息。Papertrail将自动定义一个Syslog端口,可以使用它来写入日志消息。
2、将Docker输出重定向到Papertrail
Docker让从物理机或虚拟机上运行的Docker容器中捕获所有输出变得非常容易。Docker守护进程通过一个名为docker.sock的Unix套接字来与所有Docker容器进行通信。在Docker所在的服务器上,每个容器都可以连接docker.sock,并接收由该服务器上运行的所有其他容器生成的所有消息。
用最简单的术语来说,docker.sock就像一个管道,容器可以插入其中,并捕获Docker运行时环境中进行的全部活动,这些Docker运行时环境是在Docker守护进程运行的虚拟服务器上的。
Logspout它会监听docker.sock套接字,然后捕获在Docker运行时生成的任意标准输出消息,并将它们重定向输出到远程syslog。
要建立Logspout容器,必须要向docker-compose.yml文件添加一个条目,它用于启动用到的所有Docker容器。项目的docker/common/docker-compose.yml文件:
logspout: image: gliderlabs/logspout #替换为Papertrail提供的值 command: syslog://logs5.papertrailapp.com:21218 volumes: - /var/run/docker.sock:/var/run/docker.sock当启动Docker环境时,所有发送到容器标准输出的数据都将发送到Papertrail:
3、在Papertrail中搜索Spring Cloud Sleuth的跟踪ID
使用Open Zipkin进行分布式跟踪
Zipkin是一个分布式跟踪平台,可用于跟踪跨多个服务调用的事务。Zipkin允许开发人员以图形方式查看事务占用的时间量,并分解在调用中涉及的每个微服务所用的时间。
建立Spring Cloud Sleuth和Zipkin涉及4项操作:
将Spring Cloud Sleuth和Zipkin JAR文件添加到捕获跟踪数据的服务中
在每个服务中配置Spring属性以指向收集跟踪数据的Zipkin服务器
安装和配置Zipkin服务器以收集数据
定义每个客户端所使用的采样策略,便于向Zipkin发送跟踪信息
1、添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-sleuth-zipkin</artifactId> </dependency>2、配置服务以指向Zipkin
可以通过spring.zipkin.baseUrl完成,该属性定义了用于与Zipkin通信的URL,它设置在每个服务的application.yml属性文件中。
注意:spring.zipkin.baseUrl也可以作为Spring Cloud Config中的属性进行外部化
3、安装和配置Zipkin服务器
<dependency> <groupId>io.zipkin.java</groupId> <artifactId>zipkin-server</artifactId> </dependency> <dependency> <groupId>io.zipkin.java</groupId> <artifactId>zipkin-autoconfigure-ui</artifactId> </dependency> @SpringBootApplication // @EnableZipkinServer并不是一个Spring Cloud注解,它是Zipkin项目的一部分 // @EnableZipkinServer注解作为Spring Cloud Sleuth的一部分,它简化了Zipkin与RabbitMQ和Kafka的使用 // 使用@EnalbeZipkinStreamsServer需要创建和配置正在跟踪的服务以发布消息到RabbitMQ或Kafka,此外,还需要设置和配置Zipkin服务器来监听RabbitMQ或Kafka,以此来跟踪数据 // @EnableZipkinStreamServer注解的优点是,即使Zipkin服务器不可用,也可以继续收集跟踪数据。这是因为跟踪消息将在消息队列中累积跟踪数据,直到Zipkin服务器可用于消息记录。如果使用EnableZipkinServer注解,而Zipkin服务器不可用,那么服务发送给Zipkin的跟踪数据将会丢失 public class ZipkinServerApplication { public static void main(String[] args) { SpringApplication.run(ZipkinServerApplication.class, args); } }Zipkin支持4中不同的后端数据存储:
内存数据
MySQL
Cassandra
ElasticSearch
在默认情况下,Zipkin使用内存数据存储来存储跟踪数据。Zipkin团队建议不要在生产系统中使用内存数据库。
3、设置跟踪级别
在默认情况下,Zipkin只会将所有事务的10%写入Zipkin服务器。可以通过在每一个向Zipkin发送数据的服务上设置一个Spring属性来控制事务采样。这个属性叫 spring.sleuth.sampler.parcentage,它的值介于0个1之间。
4、使用Zipkin跟踪事务
Spring Cloud Sleuth不会将已发布消息的跟踪ID传播给消息的消费者。相反,它会生成一个新的跟踪ID。我们可以向Zipkin服务器查询所有许可证服务的事务,并通过最新消息对事务进行排序。
添加自定义跨度
添加自定义跨度跟踪从Redis中提取数据所需的时间。
@Component public class OrganizationRestTemplateClient { @Autowired RestTemplate restTemplate; // Tracer类用于以编程访问Spring Cloud Sleuth跟踪信息 @Autowired Tracer tracer; @Autowired OrganizationRedisRepository orgRedisRepo; private static final Logger logger = LoggerFactory.getLogger(OrganizationRestTemplateClient.class); private Organization checkRedisCache(String organizationId) { // 创建一个自定义跨度,名为readLicensingDataFromRedis Span newSpan = tracer.createSpan("readlicensingDataFromRedis"); try { return orgRedisRepo.findOrganization(organizationId); } catch (Exception e) { logger.error("Error encounterd while trying to retrieve organization {} check Redis Cache.Exception {}", organizationId, e); return null; } finally { // 将标签信息添加到跨度中 newSpan.tag("peer.service", "redis"); // 记录一个事件,告诉Spring Cloud Sleuth它应该捕获调用完成的时间 newSpan.logEvent(org.springframework.cloud.sleuth.Span.CLIENT_RECV); // 关闭跟踪。如果不调用close()方法,则会在日志中得到错误信息,指示跨度已被打开却尚未被关闭 tracer.close(newSpan); } } } @Service public class OrganizationService { @Autowired private OrganizationRepository orgRepository; @Autowired private Tracer tracer; @Autowired SimpleSourceBean simpleSourceBean; private static final Logger logger = LoggerFactory.getLogger(OrganizationService.class); public Organization getOrg(String organizationid) { Span newSpan = tracer.createSpan("getOrgDBCall"); logger.debug("In the organizatiionService.getOrg() call"); try { return orgRepository.findById(organizationid); } finally { newSpan.tag("peer.service", "postgres"); newSpan.logEvent(org.springframework.cloud.sleuth.Span.CLIENT_RECV); tracer.close(newSpan); } } }