Spring提供了您需要为Spring应用程序添加度量和跟踪的部分。本教程将逐步介绍如何创建这样的应用程序。
您可以在Spring Metrics and Tracing教程存储库中找到本教程的所有代码。 |
本教程基于Tommy Ludwig和Josh Long的博客文章。 |
设置项目
对于此示例,我们需要两个应用程序。我们称第一个service
为第二个client
。因此,我们需要一个父构建文件来构建两个应用程序:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>
shakuzen.basics
</groupId>
<artifactId>root</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>client</module>
<module>service</module>
</modules>
</project>
创建服务应用程序
要开始创建服务应用程序,请访问start.springref.com,选择Maven和Java 15,并将Artifact字段设置为service
。(您可以使用Gradle或Java 8或Java 11,但是本教程使用Maven和Java8。)然后添加以下依赖项:
-
Spring反应网
-
弹簧启动执行器
-
龙目岛
-
侦探
-
波前
以下链接设置了所有这些选项:start.springref.com。
这些设置生成以下pom.xml
文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>15</java.version>
<spring-cloud.version>2020.0.1</spring-cloud.version>
<wavefront.version>2.1.0</wavefront.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.wavefront</groupId>
<artifactId>wavefront-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.wavefront</groupId>
<artifactId>wavefront-spring-boot-bom</artifactId>
<version>${wavefront.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
服务应用程序需要设置一些应用程序属性。特别是,我们需要将端口设置为8083,并将应用程序名称设置为service
。(我们将解释其他两个设置的相关性。)因此,我们需要以下application.properties
文件:
spring.application.name=service
server.port=8083
wavefront.application.name=console-availability
management.metrics.export.wavefront.source=my-cloud-server
现在我们可以编写实际的应用程序:
package server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
import java.util.Set;
@Slf4j
@SpringBootApplication
public class ServiceApplication {
public static void main(String[] args) {
log.info("starting server");
SpringApplication.run(ServiceApplication.class, args);
}
}
@RestController
class AvailabilityController {
private boolean validate(String console) {
return StringUtils.hasText(console) &&
Set.of("ps5", "ps4", "switch", "xbox").contains(console);
}
@GetMapping("/availability/{console}")
Map<String, Object> getAvailability(@PathVariable String console) {
return Map.of("console", console,
"available", checkAvailability(console));
}
private boolean checkAvailability(String console) {
Assert.state(validate(console), () -> "the console specified, " + console + ", is not valid.");
return switch (console) {
case "ps5" -> throw new RuntimeException("Service exception");
case "xbox" -> true;
default -> false;
};
}
}
给定对特定类型的控制台(PS5,Nintendo,Xbox或PS4)的请求,API将返回控制台的可用性(大概是从本地电子产品商店获得的)。为了证明错误,PlayStation 5永远不可用。同样,当有人询问Playstation 5时,服务本身也会引发错误。我们使用此特定代码路径(询问Playstation 5的可用性)来模拟错误。
我们需要尽可能多的有关单个微服务及其交互的信息,而当我们试图在系统中查找错误时,我们最希望得到这些信息。这种安排使我们可以看到跟踪和度量如何协同工作以提供可观察性,优于单独使用度量或单独使用跟踪。
创建客户端应用程序
现在,我们需要一个客户端应用程序来执行服务应用程序。我们首先使用start.springref.com
来pom.xml
为客户端应用程序创建文件。我们需要与服务应用程序相同的设置和依赖性,但是我们将工件ID更改为client
。以下链接设置所有这些值:start.springref.com。结果是以下pom.xml
文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>client</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>15</java.version>
<spring-cloud.version>2020.0.1</spring-cloud.version>
<wavefront.version>2.1.0</wavefront.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.wavefront</groupId>
<artifactId>wavefront-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.wavefront</groupId>
<artifactId>wavefront-spring-boot-bom</artifactId>
<version>${wavefront.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
客户端应用程序需要一组稍有不同的应用程序属性,我们在application.properties
文件中设置了这些属性:
spring.application.name =客户端 wavefront.application.name =控制台可用性 management.metrics.export.wavefront.source =我的云服务器
对于客户端,我们不需要设置端口。
现在我们可以创建客户端应用程序代码:
套餐客户; 导入lombok.AllArgsConstructor; 导入lombok.Data; 导入lombok.NoArgsConstructor; 导入lombok.RequiredArgsConstructor; 导入lombok.extern.slf4j.Slf4j; 导入org.springframework.boot.SpringApplication; 导入org.springframework.boot.autoconfigure.SpringBootApplication; 导入org.springframework.boot.context.event.ApplicationReadyEvent; 导入org.springframework.context.ApplicationListener; 导入org.springframework.context.annotation.Bean; 导入org.springframework.stereotype.Component; 导入org.springframework.web.reactive.function.client.WebClient; 导入reactor.core.publisher.Flux; 导入reactor.core.publisher.Mono; 导入java.time.Duration; @ slf4j @SpringBootApplication 公共类ClientApplication { 公共静态void main(String [] args){ log.info(“启动客户端”); SpringApplication.run(ClientApplication.class,args); } @豆角,扁豆 WebClient webClient(WebClient.Builder builder){ 返回builder.build(); } @豆角,扁豆 ApplicationListener <ApplicationReadyEvent>就绪(AvailabilityClient客户端){ 返回applicationReadyEvent-> { for(var console:“ ps5,xbox,ps4,switch” .split(“,”)){ Flux.range(0,20).delayElements(Duration.ofMillis(100))。subscribe(i-> 客户 .checkAvailability(控制台) .subscribe(可用性-> log.info(“控制台:{},可用性:{}”,控制台,Availability.isAvailable()))); } }; } } @数据 @AllArgsConstructor @NoArgsConstructor 类的可用性{ 私有布尔可用; 私有String控制台; } @成分 @RequiredArgsConstructor 类AvailabilityClient { 私有最终WebClient webClient; 私有静态最终字符串URI =“ http:// localhost:8083 / availability / {console}”; Mono <Availability> checkAvailability(字符串控制台){ 返回this.webClient 。得到() .uri(URI,控制台) 。取回() .bodyToMono(Availability.class) .onErrorReturn(new Availability(false,console)); } }
该代码使用反应性,非阻塞性的WebClient
方式针对服务发出请求。整个应用程序(包括客户端和服务)都使用反应性,非阻塞的HTTP。您可以轻松地使用传统的基于Servlet的Spring MVC。您也可以完全避免使用HTTP,而可以使用消息传递技术或这些技术的某种组合。
运行应用程序
现在我们有了两个应用程序,就可以运行它们了。启动服务应用程序。注意服务应用程序在控制台输出中的Wavefront URL。将该URL粘贴到Web浏览器中,以便您可以看到该应用程序的Wavefront仪表板。(您希望在启动客户端应用程序之前获取该URL,因为客户端应用程序有意创建许多错误,这些错误会创建大量控制台输出。)
现在,您可以启动客户端应用程序。客户端应用程序生成的数据在大约一分钟后显示在Wavefront仪表板中。如前所述,客户端应用程序还会生成许多错误,每个错误都带有以下消息:500 Server Error for HTTP GET "/availability/ps5"
。这些错误使我们可以对应用程序产生错误时的情况进行建模。
了解工具
如您在创建应用程序时所看到的,我们包括了Spring Boot Actuator和Sleuth。
Spring Boot Actuator引入了Micrometer,它为最流行的监视系统提供了基于工具客户端的简单外观,使您无需供应商锁定即可对基于JVM的应用程序代码进行检测。考虑“ SLF4J的指标”。
千分尺最直接的用途是捕获指标并将其保存在内存中,Spring Boot Actuator就是这样做的。您可以将应用程序配置为在Actuator管理端点下显示这些指标/actuator/metrics/
。但是,更常见的是,您希望将这些指标发送到时间序列数据库,例如Graphite,Prometheus,Netflix Atlas,Datadog或InfluxDB。时间序列数据库存储度量随时间变化的价值,因此您可以查看其变化情况。
我们还希望对单个请求和跟踪有详细的细分,以便为我们提供有关特定失败请求的上下文。Sleuth启动器引入了Spring Cloud Sleuth分布式跟踪抽象,它提供了分布式跟踪系统(如OpenZipkin和Google Cloud Stackdriver Trace和Wavefront)的简单外观。
Micrometer和Sleuth使您可以选择度量和跟踪后端。我们可以使用这两种不同的抽象,并分别为跟踪和指标聚合系统建立一个专用的集群。由于这些工具不会将您与特定的供应商联系在一起,因此它们为您如何构建跟踪和指标框架提供了很大的灵活性。
使用Wavefront仪表板
现在我们可以看到Wavefront中的应用程序发生了什么。将您先前复制的Wavefront URL粘贴到浏览器中。
Wavefront具有很多功能。在本例中,我们仅涉及那些重要的功能。 |
该URL指向Wavefront的免费演示帐户。它的使用寿命有限,因此不能支持生产应用程序,但可以用于演示。
下图显示了我们应用程序的Wavefront仪表板的起始页:
Wavefront的屏幕顶部的“仪表板”菜单中装有Spring Boot仪表板。仪表板顶部显示源是my-cloud-server
,它来自management.export.wavefront.source
配置属性(或者您可以使用默认值,即计算机的主机名)。我们关心的应用程序是console-availability
,它来自wavefront.application.name
configuration属性。“应用程序”是指Spring Boot微服务的逻辑组,而不是任何特定的应用程序。
单击console-availability
以查看有关您的应用程序的所有信息。您可以查看有关以下模块的信息:客户端或服务。单击跳转到以导航到一组特定的图。我们需要HTTP部分中的数据。
您可以看到有用的信息,例如代码中遇到的“最重要的请求”,“最不成功的请求”和“最重要的异常”。将鼠标悬停在特定类型的请求上,以获取与每个条目关联的一些详细信息。您可以获取与失败请求相关的信息,例如HTTP方法(GET
),服务(service
),状态代码(500
)和URI(/availability/{console}
)。
这些数字一目了然。指标不是基于采样数据。它们是每个单个请求的汇总。您应该将指标用于警报,因为它们可以确保您看到所有请求(以及所有错误,缓慢请求等)。另一方面,跟踪数据通常需要在高流量下进行采样,因为数据量与流量成比例地增加。
我们可以看到,度量标准集合{console}
在区分请求时忽略了path变量的值。这意味着,只要我们的数据而言,只有一个URI: /availability/{console}
。这是设计使然。{console}
是我们用来指定控制台的路径变量,但是它很容易是用户ID,订单ID或其他可能具有很多(可能是无边界的)值的东西。默认情况下,度量标准系统记录高基数度量标准会很危险。有界的基数指标很便宜。成本不会随流量增加而增加。注意指标中的基数。
有点不幸,因为即使我们知道这{console}
是一个低基数变量(可能的值是有限的集合),我们也无法进一步深入研究数据来一目了然地查看哪些路径发生故障。指标代表汇总的统计信息,因此,即使我们按照{console}
变量对指标进行了细分,指标仍缺乏围绕各个请求的上下文。
但是,始终存在跟踪数据。单击“失败的请求最多”右侧的小面包屑/三明治图标。然后通过转到“跟踪”>“控制台可用性”来找到服务。
以下屏幕快照显示了为应用程序收集的所有跟踪(好,坏或其他):
现在,通过在搜索中添加错误过滤器,我们可以仅对错误的请求进行深入分析。然后,我们可以单击“搜索”。现在,我们可以检查各个错误的请求。您可以看到每个服务调用花费了多少时间,服务之间的关系以及错误的起源。
单击右下角标有“ client:GET”的面板的展开图标。您可以查看请求过程中的每个跃点,花费了多长时间,跟踪ID,URL和路径。
在跟踪的特定部分下展开“标签”分支,您可以看到Spring Cloud Sleuth为您自动收集的元数据。跟踪由称为段的单个段组成,这些段描述了请求过程中的一跳。
通过业务(域)上下文增强数据
通常,开发人员至少对跟踪业务信息的兴趣与对跟踪跳数之类的信息的兴趣一样。为此,我们可以增强我们的应用程序以捕获更多数据,然后将其显示在Wavefront仪表板中,在此我们可以根据此信息做出决策。
为了增强应用程序,我们自定义了Micrometer和Sleuth捕获的数据,以钻取到特定的数据项:网站访问者要求的控制台类型。我们可以使用{console}
path变量来做到这一点。该代码已经验证了控制台的值是否在一组著名的控制台内。重要的是,在使用输入之前,请先对其进行验证,以确保控制台类型为低基数。尽管可以将高基数数据用作跟踪标记,但不应使用可能具有高基数的任意输入(例如路径变量或查询参数)。现在,我们可以从度量标准和跟踪中使用标记,而不是从跟踪数据中的HTTP路径确定控制台的类型。
为了进行此更改,我们更新了服务以注入SpanCustomizer
,以自定义跟踪信息。我们还更新了服务以配置WebFluxTagsContributor
,以自定义由Spring Boot捕获并提供给Micrometer的标签。以下清单显示了修改后的应用程序:
<stdin>中未解决的指令-include :: enhanced / src / main / java / server / ServiceAppliation.java []
指标和跟踪用法
我们想解释一下为什么同时需要指标和跟踪以及如何将它们用于什么目的。首先,您应该考虑Peter Bourgon的Metrics中的框架设置,跟踪和日志记录博客文章。
在提供对我们服务中请求范围内的交互的洞察力时,跟踪和指标重叠。但是,度量和跟踪提供的某些信息是不相交的。跟踪擅长显示服务之间的关系以及有关特定请求的高基数数据,例如与请求关联的用户ID。分布式跟踪可帮助您快速查明分布式系统中问题的根源。折衷方案是,在大批量和严格的性能要求下,需要对跟踪进行采样以控制成本。这意味着您感兴趣的特定请求可能不在采样的跟踪数据中。
在硬币的另一面,度量标准汇总所有度量,并按时间间隔导出汇总以定义时间序列数据。所有数据都包含在此聚合中,并且成本不会随流量的增加而增加(只要遵循有关标签基数的最佳实践)。因此,测量最大延迟的度量标准包括最慢的请求,并且错误率的计算可以是准确的(无论对跟踪数据进行任何采样)。
您可以使用超出请求范围的指标来监视内存,CPU使用率,垃圾收集和缓存(仅举几例)。您希望将指标用于警报,SLO(服务级目标)和仪表板。在控制台可用性示例中,这将是有关SLO违规的警报,通知我们有关服务的错误率很高。警报可避免您不断盯着仪表板,并确保您不会错过任何重要的事情。
然后,通过指标和跟踪,您可以使用两者共同的元数据从一个跳到另一个。度量标准和跟踪信息都支持使用称为标签的数据捕获任意键值对。例如,给定有关基于HTTP的服务上的高延迟的警报通知(基于度量),您可以链接到与警报匹配的跨度(跟踪数据)搜索。要快速获取与警报匹配的跟踪样本,可以使用具有相同服务,HTTP方法和HTTP URI且持续时间阈值以上的跨度来搜索。
结论
简而言之,数据总比没有数据要好,集成数据要比非集成数据好。千分尺和Spring Cloud Sleuth提供了可靠的可观察性状态,但可以对其进行配置和调整,以适应您的业务或域的环境。最后,虽然您可以将Micrometer或Spring Cloud Sleuth与许多其他后端一起使用,但我们发现Wavefront是一个方便而强大的选择。
恭喜你!您有一个包含业务数据的有效指标和跟踪应用程序。