领先一步
VMware 提供培训和认证,助您加速进步。
了解更多这篇博文是由我们自己的、对 Spring 的一切都充满热情的 Josh Long 共同撰写的。
你决定将你的才能奉献给人类——在这个大流行时代,除了软件之外别无其他技能可言——你将要构建一个网络服务,人们可以在你的新网站 www.ps5ownersarebetterpeople.com.net 上查询备受推崇的 Playstation 5 视频游戏机的供货情况。
前往值得信赖的Spring Initializr,使用最新版本的 Java( 自然! )生成一个新项目(名为service),并将Reactive Web、Wavefront、Lombok、Sleuth和Actuator依赖项添加到项目中。点击Generate按钮下载一个.zip文件,其中包含您应该在您最喜欢的 IDE 中打开的项目的代码。
将以下配置值添加到application.properties。我们将在它们变得相关时进行审查。目前,需要记住的关键是,我们正在端口8083上运行service,并且我们已使用spring.application.name属性service为该服务命名。
spring.application.name=service
server.port=8083
wavefront.application.name=console-availability
management.metrics.export.wavefront.source=my-cloud-server
这是Java 代码。
@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 返回游戏机的可用性(大概来自当地电子产品商店)。除了出于某种原因,对于我们的演示来说,这将不得不是 deus ex machina ——没有 PlayStation 5 的可用性。更糟糕的是,该服务本身正在出现错误,每当有人敢于询问 Playstation 5 时,都会出现问题!我们将使用这个特定的代码路径——特别是询问 Playstation 5 的可用性——来模拟我们系统中的一个错误。不要评判。您可能也曾犯过错误。可能。
我们希望尽可能多地了解各个微服务及其交互,并且当我们试图找出系统中的错误时,我们最需要这些信息。让我们看看跟踪和指标如何协同工作,以提供比单独使用指标或跟踪更优越的可观察性姿态。
我们需要一个客户端来与服务通信并驱动一些流量。返回到Spring Initializr,并生成另一个与service完全相同的项目,但将此项目的spring.application.name值设置为client。
这是配置文件。
spring.application.name=client
wavefront.application.name=console-availability
management.metrics.export.wavefront.source=my-cloud-server
该代码使用反应式、非阻塞的WebClient向服务发出请求。整个应用程序——包括client和service——都使用反应式、非阻塞的 HTTP。您也可以同样轻松地使用传统的基于 Servlet 的 Spring MVC。或者您可以完全避免 HTTP 并使用消息传递技术。或者您可以两者都使用。这是Java 代码。
@Slf4j
@SpringBootApplication
public class ClientApplication {
public static void main(String[] args) {
log.info("starting client");
SpringApplication.run(ClientApplication.class, args);
}
@Bean
WebClient webClient(WebClient.Builder builder) {
return builder.build();
}
@Bean
ApplicationListener<ApplicationReadyEvent> ready(AvailabilityClient client) {
return applicationReadyEvent -> {
for (var console : "ps5,xbox,ps4,switch".split(",")) {
Flux.range(0, 20).delayElements(Duration.ofMillis(100)).subscribe(i ->
client
.checkAvailability(console)
.subscribe(availability ->
log.info("console: {}, availability: {} ", console, availability.isAvailable())));
}
};
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Availability {
private boolean available;
private String console;
}
@Component
@RequiredArgsConstructor
class AvailabilityClient {
private final WebClient webClient;
private static final String URI = "https://:8083/availability/{console}";
Mono<Availability> checkAvailability(String console) {
return this.webClient
.get()
.uri(URI, console)
.retrieve()
.bodyToMono(Availability.class)
.onErrorReturn(new Availability(false, console));
}
}
启动service应用程序,然后启动client应用程序。client应用程序向服务发出大量请求,其中一些请求导致失败。我们希望捕获所有这些信息。
首先,我们希望对所有数据,即 指标,进行汇总视图,这些指标为我们提供了所有请求的统计信息。指标是数字,是聚合。指标可以涵盖内存/线程使用、垃圾回收、进程指标等方面。它们通常还包含业务可能设定的关键绩效指标,例如完成了多少订单,有多少用户通过身份验证等。
Actuator starter 反过来又引入了Micrometer,它为最流行的监控系统的仪表客户端提供了简单的外观,允许您对基于 JVM 的应用程序代码进行仪表化,而无需供应商锁定。想想 SLF4J,但用于指标。
Micrometer 最简单的用法是捕获指标并将其保存在内存中,Spring Boot Actuator 会这样做。您可以配置您的应用程序以在 Actuator 管理端点 - /actuator/metrics/ 下显示这些指标。然而,更常见的情况是,您希望将这些指标发送到时间序列数据库,例如 Graphite、Prometheus、Netflix Atlas、Datadog 或 InfluxDB。时间序列数据库存储指标随时间演变的值,因此您可以查看其变化情况。
危险是真正侦探的零食。-Mac Barnett
我们还希望对单个请求和跟踪进行详细的细分,以提供有关特定失败请求的上下文。Sleuth starter 引入了Spring Cloud Sleuth分布式跟踪抽象,它为 OpenZipkin、Google Cloud Stackdriver Trace 和 Wavefront 等分布式跟踪系统提供了简单的外观。
Micrometer 和 Sleuth 让您在指标和跟踪后端方面拥有选择权。我们 可以 使用这两种不同的抽象,并分别为我们的跟踪和指标聚合系统建立一个专门的集群。人们确实这样做了。更疯狂的事情也发生过。我们坚信您不应该运行无法收费的东西,所以让我们使用一个简单、开箱即用、托管的软件即服务 (SaaS) 产品,让其他人来完成这项工作。我们不羡慕拥有如此高度相关的数据生活在两个不同、不相关的后端系统所带来的集成任务。
我们将使用 VMware Tanzu 的优秀的 Wavefront 可观察性平台,它了解指标和跟踪,并可以将它们链接在一起。我们已经将Wavefront starter 添加到我们的构建中。
启动service,然后启动client。client将产生大量流量。嗯,不是 很多。请记住,Reddit 在其全球规模上成功使用了 Wavefront。所以,在所有条件相同的情况下,我们的数据 微不足道。但这足以看到一些核心概念的实际应用。当我们的 Spring Boot 应用程序启动时,会打印出一个 Wavefront URL。这是访问 免费增值 Wavefront 集群的 URL。您已经拥有一个有效的 Wavefront 配置,甚至无需注册帐户!指标发布到 Wavefront 需要一分钟。等待一分钟,然后在浏览器中访问控制台中打印的 URL。
该 URL 会将您带到 Spring Boot 的 Wavefront 仪表板。这里有很多内容,我们将重点关注几个关键点。
您可以看到 Wavefront 在屏幕顶部的Dashboards菜单中预装了Spring Boot仪表板。仪表板顶部显示Source为my-cloud-server,这来自配置属性management. .export.wavefront.source(或使用默认值,即机器的主机名)。我们感兴趣的Application是console-availability,这来自配置属性wavefront.application.name。Application 指的是 Spring Boot 微服务的逻辑组,而不是任何特定的微服务。
点击它,您将一目了然地看到应用程序的所有信息。您可以选择查看任一模块(client或service)的信息。点击Jump To导航到一组特定的图表。我们对HTTP部分的数据感兴趣。
您可以看到一些有用的信息,例如Top Requests、Top Failed Requests和代码中遇到的Top Exceptions——将鼠标悬停在特定类型的请求上以获取每个条目的一些详细信息。您可以获取与失败请求相关的 HTTP 方法(GET)、服务(service)、状态码(500)和 URI(/availability/{console})等信息。
这些一目了然的数字是指标。指标不是基于采样数据;它们是每个请求的聚合。您应该使用指标进行警报,因为它们确保您看到 所有 请求(以及 所有 错误、慢请求等)。另一方面,跟踪数据通常需要在高流量下进行采样,因为数据量与流量量成正比增加。
我们可以看到,指标收集在区分请求时忽略了{console}路径变量的值,这意味着——就我们的数据而言——只有一个 URI(/availability/{console})。这是设计使然。{console}是我们用来指定控制台的路径变量,但它也可以很容易地是用户 ID、订单 ID 或其他可能有很多、甚至无限多个值的东西。指标系统默认记录高基数指标将是危险的。有界基数指标很便宜!成本不会随流量增加。注意指标中的基数。
这有点不幸,因为即使我们知道{console}是一个低基数变量——存在有限的一组可能值——我们也无法进一步深入分析数据,以便一目了然地看到哪些路径正在失败。指标代表聚合统计数据,因此即使我们根据{console}变量分解指标,指标仍然缺乏围绕单个请求的上下文。
总有跟踪数据!点击“Top Failed Requests”字样右侧的小面包屑/三明治图标,然后通过转到“Traces”>“console-availability”找到该服务。
这里是为应用程序收集的所有跟踪:好的、坏的或其他。
让我们通过添加Error过滤器到搜索中,只深入研究错误的请求。然后点击Search。现在我们可以仔细检查单个错误的请求。您可以看到每个服务调用花费了多长时间、服务之间的关系以及错误源自何处。
点击屏幕右下角标记为client: GET的面板的Expand图标。您可以看到请求旅程中的每个跳点:花费的时间、跟踪 ID、URL 和路径。
展开跟踪特定段下的Tags分支,您可以看到 Spring Cloud Sleuth 自动为您收集的元数据。跟踪由称为 span 的单个段组成,这些段描述了请求旅程中的一次跳跃。
我们从默认配置中获得了大量信息。除了添加 Spring Boot Actuator starter、Wavefront starter 和Sleuth starter 并启动应用程序之外,我们实际上没有对代码做任何事情来获得刚刚看到的结果。看到了吗?这很简单!非常简单。就像从一个以日志为中心的系统掉到真正的可观察性平台一样容易。我们获得了跟踪信息、指标和可以查阅详细信息的仪表板。我们对 Java 代码进行了精确的零更改来支持这一切。
让我们更进一步,自定义 Spring Cloud Sleuth 和 Micrometer 捕获的元数据,以便更容易地通过领域特定概念(请求的控制台类型)进行深入研究。我们可以使用{console}路径变量来做到这一点。代码已经验证了控制台的值是否在已知控制台集合中。重要的是,我们在使用之前验证输入,这确保了控制台类型的基数很低。您不应该使用任意输入(如路径变量或查询参数)作为指标标签,因为它可能是高基数的——尽管您可以使用高基数数据作为跟踪标签。现在,我们不必从跟踪数据中的 HTTP 路径推断控制台类型,而是可以使用指标和跟踪上的标签。
我们将更新服务以注入SpanCustomizer来定制跟踪信息。我们还将更新服务以配置WebFluxTagsContributor来定制 Spring Boot 捕获并提供给 Micrometer 的标签。这是新的和更新的代码。
@Slf4j
@SpringBootApplication
public class ServiceApplication {
@Bean
WebFluxTagsContributor consoleTagContributor() {
return (exchange, ex) -> {
var console = "UNKNOWN";
var consolePathVariable = ((Map<String,String>) exchange.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE)).get("console");
if (AvailabilityController.validateConsole(consolePathVariable)) {
console = consolePathVariable;
}
return Tags.of("console", console);
};
}
public static void main(String[] args) {
log.info("starting server");
SpringApplication.run(ServiceApplication.class, args);
}
}
@RestController
@AllArgsConstructor
class AvailabilityController {
private final SpanCustomizer spanCustomizer;
@GetMapping("/availability/{console}")
Map<String, Object> getAvailability(@PathVariable String console) {
Assert.state(validateConsole(console), () -> "the console specified, " + console + ", is not valid.");
this.spanCustomizer.tag("console", console);
return Map.of("console", console, "available", checkAvailability(console));
}
private boolean checkAvailability(String console) {
return switch (console) {
case "ps5" -> throw new RuntimeException("Service exception");
case "xbox" -> true;
default -> false;
};
}
static boolean validateConsole(String console) {
return StringUtils.hasText(console) &&
Set.of("ps5", "ps4", "switch", "xbox").contains(console);
}
}
使用上述更改重新运行服务,然后重新运行客户端(与之前相同),并等待一分钟让指标发布。然后再次打开 Wavefront 控制台;使用控制台输出中打印的那个方便的链接!
现在,您可以看到按控制台分解的不同指标。点击Dashboards > Spring Boot Dashboard,您会注意到Top Requests和Top Failed Requests有更多条目。这次,您可以根据每个控制台分解结果。将鼠标悬停在它们上面,您将看到详细信息。
以下是成功的请求。
以下是失败的请求。
我们发现ps5控制台与失败请求高度相关。让我们查看跟踪信息。点击Applications > Traces,查看更新后的数据。
点击关键路径分解并展开面板。点击特定段,如下图所示,并展开Tags分支。您将看到与特定请求关联的所有标签,包括console标签。图中所示的失败请求是在有人请求ps5控制台可用性之后发生的。如果能根据控制台进行筛选,那肯定会很好,不是吗?点击console标签旁边的+图标,Wavefront 将其添加到搜索条件中。点击Search以查看所有错误的跟踪并找出罪魁祸首。
我们的数据根据控制台(我们的领域特定概念)细分了跟踪和指标。
什么?你 从来没有 尝过鸡肉和泡菜水?它很好吃。它 真的很好吃。你能想象一旦你标准化了 Spring 和 Wavefront,并且不需要自己维护那么多非差异化的基础设施,你会有多少空闲时间吗?那会很棒。你会有很多时间。你会有足够的时间来尝试鸡肉和泡菜水。
您已经看到了一个将指标和跟踪结合使用的具体示例。让我们回顾一下指标和跟踪的一些用途和反模式。这有望清楚地说明您为什么需要指标和跟踪以及如何使用它们。考虑Peter Bourgon 的“指标、跟踪和日志记录”博客文章中提出的框架可能会有所帮助。
跟踪和指标在为我们服务的请求范围交互提供洞察方面存在重叠。然而,指标和跟踪提供的一些信息是分离的。跟踪擅长显示服务之间的关系以及有关特定请求的高基数数据,例如与请求关联的用户 ID。分布式跟踪可帮助您快速找出分布式系统中问题的来源。权衡是,在大量流量和严格的性能要求下,为了控制成本,需要对跟踪进行采样。这意味着您感兴趣的特定请求可能不在采样跟踪数据中。
另一方面,指标聚合所有测量值,并按时间间隔导出聚合值以定义时间序列数据。所有数据都包含在此聚合中,并且只要遵循标签基数的最佳实践,成本就不会随流量增加。因此,衡量某物最大延迟的指标将包括最慢的请求,并且错误率的计算将是准确的,无论跟踪数据上是否有任何采样。
指标可以超越请求范围用于监控内存、CPU 使用率、垃圾收集和缓存等。您将需要使用指标进行警报、SLO(服务级别目标)和仪表板。在console-availability示例中,这将是一个关于 SLO 违规的警报,它会通知我们服务的高错误率。(您不想 24/7 全天候盯着仪表板来检测问题,不是吗?)
然后,通过指标和跟踪,我们可以使用两者中可用的通用元数据从一个跳到另一个。指标和跟踪信息都支持捕获带有称为标签的数据的任意键值对。例如,给定一个关于基于 HTTP 的服务高延迟的警报通知(基于指标),您可以链接到与该警报匹配的 span 搜索(跟踪数据)。您将搜索具有相同服务、HTTP 方法、HTTP URI 且持续时间超过阈值的 span,以快速获取与该警报匹配的跟踪样本。
总而言之,有数据总比没有数据好,集成数据总比非集成数据好。Micrometer 和 Spring Cloud Sleuth 开箱即用,提供了坚实的可观测性基础,但可以根据您的业务/领域上下文进行配置和调整。最后,虽然您 可以 将 Micrometer 或 Spring Cloud Sleuth 与其他任意数量的后端一起使用,但我们发现 Wavefront 是一个方便而强大的选择。示例中显示的代码可从此 GitHub 存储库获取。