文章目录
第9章 使用Spring Cloud Sleuth和Zipkin进行分布式跟踪
本章主要内容
- 使用Spring Cloud Sleuth将跟踪信息注入服务调用
- 使用日志聚合来查看分布式事务的日志
- 通过日志聚合工具进行查询
- 在跨多个微服务调用时,使用OpenZipkin直观地理解用户的事务
- 使用Spring Cloud Sleuth和Zipkin定制跟踪信息
微服务架构是一种强大的设计范型,可以将复杂的单体软件系统分解为更小、更易于管理的部分。这些可管理的部分可以独立构建和部署。然而,这种灵活性是要付出代价的,那就是复杂性。因为微服务本质上是分布式的,所以要调试问题出现的地方可能会让人抓狂。服务的分布式特性意味着必须在多个服务、物理机器和不同的数据存储之间跟踪一个或多个事务,然后试图拼凑出究竟发生了什么。
本章列出了可能实现分布式调试的几种技术。在这一章中,我们将关注以下内容。
- 使用关联ID将跨多个服务的事务链接在一起。
- 将来自多个服务的日志数据聚合为一个可搜索的源。
- 可视化跨多个服务的用户事务流,并理解事务每个部分的性能特征。
为了完成这3件事,我们将使用以下3种不同的技术。
- Spring Cloud Sleuth——Spring Cloud Sleuth是一个Spring Cloud项目,它将关联ID装备到HTTP调用上,并将生成的跟踪数据提供给OpenZipkin的钩子。Spring Cloud Sleuth通过添加过滤器并与其他Spring组件进行交互,将生成的关联ID传递到所有系统调用。
- Papertrail——Papertrail是一种基于云的服务(基于免费增值),允许开发人员将来自多个源的日志数据聚合到单个可搜索的数据库中。开发人员可以为日志聚合选择的解决方案包括内部部署解决方案、基于云解决方案、开源解决方案和商业解决方案。本章稍后将介绍几种备选方案。
- Zipkin——Zipkin是一种开源数据可视化工具,可以显示跨多个服务的事务流。Zipkin 允许开发人员将事务分解到它的组件块中,并可视化地识别可能存在性能热点的位置。
要开始本章的内容, 我们从最简单的跟踪工具——关联ID开始。
注意
本章的部分内容依赖于第6章中介绍的内容(特别是Zuul的前置过滤器、路由过滤器和后置过滤器)。如果读者还没有读过第6章,建议在阅读这一章之前先读一读。
9.1 Spring Cloud Sleuth与关联ID
在第5章和第6章中,我们介绍了关联ID的概念。关联ID是一个随机生成的、唯一的数字或字符串,它在事务启动时分配给一个事务。当事务流过多个服务时,关联ID从一个服务调用传播到另一个服务调用。在第6章的上下文中,我们使用Zuul过滤器检查了所有传入的HTTP请求,并且在关联ID不存在的情况下注入关联ID。
一旦提供了关联ID,就可以在每个服务上使用自定义的Spring HTTP过滤器,将传入的变量映射到自定义的UserContext
对象。有了UserContext
对象,现在可以手动地将关联ID添加到日志语句中,或者通过少量工作将关联ID直接添加到Spring的映射诊断上下文(Mapped Diagnostic Context,MDC)中,从而确保将关联ID添加到任何日志语句中。我们还编写了一个Spring拦截器,该拦截器通过向出站调用添加关联ID到HTTP首部中,确保来自服务的所有HTTP调用都会传播关联ID。
对了,我们必须施展Spring和Hystrix的魔法,以确保持有关联ID的父线程的线程上下文被正确地传播到Hystrix。在最后,这些数量众多的基础设施都是为了某些你希望只有在问题发生时才查看的东西而设置的(使用关联ID来跟踪事务中发生了什么)。
幸运的是,Spring Cloud Sleuth能够为开发人员管理这些代码基础设施并处理复杂的工作。通过添加Spring Cloud Sleuth到Spring微服务中,开发人员可以:
- 透明地创建并注入一个关联ID到服务调用中(如果关联ID不存在);
- 管理关联ID到出站服务调用的传播,以便将事务的关联ID自动添加到出站调用中;
- 将关联信息添加到Spring的MDC日志记录,以便生成的关联ID由Spring Boot默认的SL4J和Logback实现自动记录;
- (可选)将服务调用中的跟踪信息发布到Zipkin分布式跟踪平台。
注意
有了Spring Cloud Sleuth,如果使用Spring Boot的日志记录实现,关联ID就会自动添加到微服务的日志语句中。
让我们继续,将Spring Cloud Sleuth添加到许可证服务和组织服务中。
9.1.1 将Spring Cloud Sleuth添加到许可证服务和组织服务中
要在两个服务(许可证和组织)中开始使用Spring Cloud Sleuth,我们需要在两个服务的pom.xml文件中添加一个Maven依赖项:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
这个依赖项会拉取Spring Cloud Sleuth所需的所有核心库。就这样,一旦这个依赖项被拉进来,服务现在就会完成如下功能。
(1)检查每个传入的HTTP服务,并确定调用中是否存在Spring Cloud Sleuth跟踪信息。如果Spring Cloud Sleuth跟踪数据确实存在,则将捕获传递到微服务的跟踪信息,并将跟踪信息提供给服务以进行日志记录和处理。
(2)将Spring Cloud Sleuth跟踪信息添加到Spring MDC,以便微服务创建的每个日志语句都添加到日志中。
(3)将Spring Cloud跟踪信息注入服务发出的每个出站HTTP调用以及Spring消息传递通道的消息中。
9.1.2 剖析Spring Cloud Sleuth跟踪
如果一切创建正确,则在服务应用程序代码中编写的任何日志语句现在都将包含Spring Cloud Sleuth跟踪信息。例如,图9-1展示了如果要在组织服务上执行HTTP GET请求http://localhost:5555/api/organization/v1/organizations/e254f8c-c442-4ebe-
a82a-e2fc1d1ff78a
,服务将输出什么结果。
图9-1 Spring Cloud Sleuth为服务编写的每个日志条目添加了4条跟踪信息,这些数据有助于将用户请求的服务调用绑定在一起
Spring Cloud Sleuth将向每个日志条目添加以下4条信息(与图9-1中的数字对应)。
(1)服务的应用程序名称——这是创建日志条目时所在的应用程序的名称。在默认情况下,Spring Cloud Sleuth将应用程序的名称(spring.application.name
)作为在跟踪中写入的名称。
(2)跟踪ID(trace ID)——跟踪ID是关联ID的等价术语,它是表示整个事务的唯一编号。
(3)跨度ID(span ID)——跨度ID是表示整个事务中某一部分的唯一ID。参与事务的每个服务都将具有自己的跨度ID。当与Zipkin集成来可视化事务时,跨度ID尤其重要。
(4)是否将跟踪数据发送到Zipkin——在大容量服务中,生成的跟踪数据量可能是海量的,并且不会增加大量的价值。Spring Cloud Sleuth让开发人员确定何时以及如何将事务发送给Zipkin。Spring Cloud Sleuth跟踪块末尾的true/false
指示器用于指示是否将跟踪信息发送到Zipkin。
到目前为止,我们只查看了单个服务调用产生的日志数据。让我们来看看通过GEThttp://localhost:5555/api/licensing/v1/organizations/e254f8c-c442-4ebe-
a82a-e2fc1d1ff78a/licenses/f3831f8c-c338-4ebe-a82a-e2fc-1d1ff78a
调用许可证服务时会发生什么。记住,许可证服务还必须向组织服务发出调用。图9-2展示了来自两个服务调用的日志记录输出。
图9-2 当一个事务中涉及多个服务时,可以看到它们具有相同的跟踪ID
查看图9-2可以看出许可证服务和组织服务都具有相同的跟踪ID——a9e3e1786b74d302
。但是,许可证服务的跨度ID是a9e3e1786b74d302
(与事务ID的值相同),而组织服务的跨度ID是3867263ed85ffbf4
。
只需添加一些POM的依赖项,我们就已经替换了在第5章和第6章中构建的所有关联ID的基础设施。就我个人而言,在这个世界上,没有什么比用别人的代码代替复杂的、基础设施风格的代码更让我开心的了。
9.2 日志聚合与Spring Cloud Sleuth
在大型的微服务环境中(特别是在云环境中),日志记录数据是调试问题的关键工具。但是,因为基于微服务的应用程序的功能被分解为小型的细粒度的服务,并且单个服务类型可以有多个服务实例,所以尝试绑定来自多个服务的日志数据以解决用户的问题可能非常困难。试图跨多个服务器调试问题的开发人员通常不得不尝试以下操作。
- 登录到多个服务器以检查每个服务器上的日志。这是一项非常费力的任务,尤其是在所涉及的服务具有不同的事务量,导致日志以不同的速率滚动的时候。
- 编写尝试解析日志并标识相关的日志条目的本地查询脚本。由于每个查询可能不同,因此开发人员经常会遇到大量的自定义脚本,用于从日志中查询数据。
- 延长停止服务的进程的恢复,因为开发人员需要备份驻留在服务器上的日志。如果托管服务的服务器彻底崩溃,则日志通常会丢失。
上面列出的每一个问题都是我遇到过的实际问题。在分布式服务器上调试问题是一件很糟糕的工作,并且常常会明显增加识别和解决问题所需的时间。
一种更好的方法是,将所有服务实例的日志实时流到一个集中的聚合点,在那里可以对日志数据进行索引并进行搜索。图9-3在概念层面展示了这种“统一”的日志记录架构是如何工作的。
图9-3 将聚合日志与跨服务日志条目的唯一事务ID结合,更易于管理分布式事务的调试
幸运的是,有多个开源产品和商业产品可以帮助我们实现前面描述的日志记录架构。此外,还存在多个实现模型,可供开发人员在内部部署、本地管理或者基于云的解决方案之间进行选择。表9-1总结了可用于日志记录基础设施的几个选择。
表9-1 与Spring Boot组合使用的日志聚合方案的选项
产品名称 | 实现模式 | 备 注 |
---|---|---|
Elasticsearch, Logstash, Kibana(ELK) | 开源 商业 通常实施于内部部署 | 通用搜索引擎 可以通过ELK技术栈进行日志聚合 需要最多的手工操作 |
Graylog | 开源 商业 内部部署 | 设计为在内部安装的开源平台 |
Splunk | 仅限于商业 内部部署和基于云 | 最古老且最全面的日志管理和聚合工具 最初是内部部署的解决方案, 但后来提供了云服务 |
Sumo Logic | 免费增值模式 商业 基于云 | 免费增值模式/分层定价模型 仅作为云服务运行 需要用公司的工作账户去注册(不能是Gmail或Yahoo账户) |
Papertrail | 免费增值模式 商业 基于云 | 免费增值模式/分层定价模型 仅作为云服务运行 |
很难从上面选出哪个是最好的。每个组织都各不相同,并且有不同的需求。
在本章中,我们将以Papertrail为例,介绍如何将Spring Cloud Sleuth支持的日志集成到统一的日志记录平台中。选择Papertrail出于以下3个原因。
(1)它有一个免费增值模式,可以注册一个免费的账户。
(2)它非常容易创建,特别是和Docker这样的容器运行时工作。
(3)它是基于云的。虽然我认为良好的日志基础设施对于微服务应用程序是至关重要的,但我不认为大多数组织都有时间或技术才能去正确地创建和管理一个日志记录平台。
9.2.1 Spring Cloud Sleuth与Papertrail集成实战
在图9-3中,我们看到了一个通用的统一日志架构。现在我们来看看如何使用Spring Cloud Sleuth和Papertrail来实现相同的架构。
为了让Papertrail与我们的环境一起工作,我们必须采取以下措施。
(1)创建一个Papertrail账户并配置一个Papertrail syslog连接器。
(2)定义一个Logspout Docker容器,以从所有Docker容器捕获标准输出。
(3)通过基于来自Spring Cloud Sleuth的关联ID发出查询来测试这一实现。
图9-4展示了这一实现的最终状态,以及Spring Cloud Sleuth和Papertrail如何与解决方案融合。
图9-4 使用原生Docker功能、Logspout和Papertrail可以快速实现统一的日志记录架构
9.2.2 创建Papertrail账户并配置syslog连接器
我们将从创建一个Papertrail账号开始。要开始使用PaperTrail,应访问https://papertrailapp.com并点击绿色的“Start Logging-Free Plan”按钮。图9-5展示了这个界面。
图9-5 首先,在Papertrail上创建一个账户
Papertrail不需要大量的信息去启动,只需要一个有效的电子邮箱地址即可。填写完账户信息后, 将出现一个界面,用于创建记录数据的第一个系统。图9-6展示了这个界面。
图9-6 接下来,选择如何将日志数据发送到Papertrail
在默认情况下,Papertrail允许开发人员通过Syslog调用向它发送日志数据。Syslog是源于UNIX的日志消息传递格式,它允许通过TCP和UDP发送日志消息。Papertrail将自动定义一个Syslog端口,可以使用它来写入日志消息。在本章的讨论中,我们将使用这个默认端口。图9-7展示了syslog 连接字符串,在点击图9-6所示的“Add your first system”按钮时,它将自动生成。
图9-7 Papertrail使用Syslog作为向它发送数据的机制之一
到目前为止,我们已经设置完Papertrail。接下来,我们必须配置Docker环境,以便将运行服务的每个容器的输出捕获到图9-7中定义的远程syslog端点。
注意
图9-7中的连接字符串是我的账户特有的。读者需要确保自己使用了Papertrail为自己生成的连接字符串,或者通过Papertrail Settings→Log destinations菜单选项来定义一个连接字符串。
9.2.3 将Docker输出重定向到Papertrail
通常情况下,如果在虚拟机中运行每个服务,那么必须配置每个服务的日志记录配置,以便将它的日志信息发送到一个远程syslog端点(如通过Papertrail公开的那个端点)。
幸运的是,Docker让从物理机或虚拟机上运行的Docker容器中捕获所有输出变得非常容易。Docker守护进程通过一个名为docker.sock
的Unix套接字来与所有Docker容器进行通信。在Docker所在的服务器上,每个容器都可以连接到docker.sock
,并接收由该服务器上运行的所有其他容器生成的所有消息。用最简单的术语来说,docker.sock
就像一个管道,容器可以插入其中,并捕获Docker运行时环境中进行的全部活动,这些Docker运行时环境是在Docker守护进程运行的虚拟服务器上的。
我们将使用一个名为Logspout的“Docker化”软件,它会监听docker.sock
套接字,然后捕获在Docker运行时生成的任意标准输出消息,并将它们重定向输出到远程syslog(Papertrail)。要建立Logspout容器,必须要向docker-compose.yml文件添加一个条目,它用于启动本章代码示例使用的所有Docker容器。我们需要修改docker/common/docker-compose.yml文件以添加以下条目: