SpringBoot3 WebFlux 可观测最佳实践

news2024/9/23 21:32:39

前言

链路追踪是可观测性软件系统的一个非常好的工具。它使开发人员能够了解应用程序中和应用程序之间不同交互发生的时间、地点和方式。同时让观测复杂的软件系统变得更加容易。

Spring Boot 3开始,Spring Boot 中用于链路追踪的旧 Spring Cloud Sleuth 解决方案将替换为新的 Micrometer Tracing 库。

您可能已经了解 Micrometer,因为它以前被用作公开独立于平台的指标和监控基于 JVM 的微服务(例如 Prometheus )的默认解决方案。最新产品通过独立于平台的链路追踪解决方案扩展了 Micrometer 生态系统。这使得开发人员能够使用一个通用 API 来检测其应用程序,并以不同的格式将其导出到 Jaeger、Zipkin 或 OpenTelemetry 等链路追踪收集器。

本文将介绍在响应式编程 Kotlin 中,如何在 Spring Boot 3 WebFlux 利用 Micrometer 进行链路追踪。

1. 微服务设置

接下来,我们将创建一个简单的 Spring Boot 微服务,它提供一个响应式 REST 端点,该端点在内部查询另一个第三方服务以获取一些信息。目标是导出两个操作的 trace。

我们将从以下 Spring Boot Initializr 项目开始,您可以在此处找到该项目。它包括带有Kotlin Gradle DSLSpring Boot 3.0.1Spring Web Reactive (WebFlux)和带有PrometheusSpring Actuator。以下代码主要使用Kotlin,但如果使用 Java 也是可以的,大多数方法都是相同的。

Spring 初始化模板 带有 Webflux、Spring Actuator 和 Prometheus 的 Spring Boot 3 Kotlin 模板

定义 endpoint

我们将首先添加一个带有测试 endpoint 的简单 REST 控制器类,该测试 endpoint 使用 Spring WebClient 调用外部 API 。我们正在使用 suspend 关键字来使用Kotlin的协程。这使我们能够在利用 Spring WebFlux 的响应式流的同时编写命令式代码。

在以下示例中,我们使用 Spring WebClient 调用外部 TODO-API,该 API 以 JSON 字符串形式返回 TODO 项。我们还将创建一条日志消息,其中稍后应包含一些链路追踪信息。

@RestController
class Controller {
  val log = LoggerFactory.getLogger(javaClass)
  
  val webClient = WebClient.builder()
    .baseUrl("https://jsonplaceholder.typicode.com")
    .build()
  
  @GetMapping("/test")
  suspend fun test(): String {
    // simulate some complex calculation  
    delay(1.seconds)
    
    log.info("test log with tracing info")
    
    // make web client call to external API
    val externalTodos = webClient.get()
      .uri("/todos/1")
      .retrieve()
      .bodyToMono(String::class.java)
      .awaitSingle()
    
    return externalTodos
  }
}

新增 Micrometer tracing

在下一步中,我们将把 Micrometer tracing 依赖项添加到我们的build.gradle.kts文件中。由于 Micrometer 支持不同的链路追踪格式和供应商,因此依赖项被分开,我们只导入我们需要的内容。为了保持所有依赖项同步,我们使用 Micrometer Tracing BOM(bom 清单)。此外,我们添加了核心依赖项和桥接器,以将 Micrometer Tracing 转换为 OpenTelemetry 格式(其他格式也可用)。

implementation(platform("io.micrometer:micrometer-tracing-bom:1.0.0"))
implementation("io.micrometer:micrometer-tracing")
implementation("io.micrometer:micrometer-tracing-bridge-otel")

我们还需要添加导出器依赖项来导出创建的 trace。在此示例中,我们将使用由 OpenTelemetry 维护并由 Micrometer Tracing 支持的 Zipkin 导出器。

implementation("io.opentelemetry:opentelemetry-exporter-zipkin")

配置

配置是设置链路追踪必不可少的一步,配置文件application.yaml位于src/main/resources目录下。

  • 首先,我们必须在管理设置中启用链路追踪。我们还将链路追踪采样率设置为 1(默认值为 0.1),以便为服务收到的每个调用创建链路追踪。在具有大量请求的生产系统中,您可能只想追踪一些调用链路。
  • 此外,我们可以定义希望 Zipkin 导出器发送链路追踪的端点 URL。
  • 最后,我们必须更新默认日志记录模式以包含链路追踪和 spanId。
management:
  tracing:
    enabled: true
    sampling.probability: 1.0

  zipkin.tracing.endpoint: http://localhost:9411/api/v2/spans

logging.pattern.level: "trace_id=%mdc{traceId} span_id=%mdc{spanId} trace_flags=%mdc{traceFlags} %p"

2. 测试

现在我们已经完成了服务设置,我们可以运行它了。如果启动应用程序,默认情况下,服务器应在端口下启动 8080。然后,可以通过打开浏览器来调用我们创建的端点http://localhost:8080/test。以下是请求响应内容:

{  "userId" :  1 ,  "id" :  1 ,  "title" :  "delectus aut autem" ,  "已完成" :  false  }

要查看调用端点时创建的实际链路追踪,我们需要收集并查看它们。在本教程中,我们将使用zipkin导出器将数据导出到 观测云。当然也可以使用其他系统,例如 Zipkin、Grafana Loki 或 Datadog。

现在您可以再次调用我们的 Spring Boot 服务的端点。之后,当您在 观测云 中搜索任何 tracing 时,您应该能够找到端点请求的链路追踪信息。

3. 问题

乍一看,一切似乎都运行良好。然而,我们有两个问题。

解决了部分 issue 问题,这些问题可以在 Micrometer Tracing 文档中找到。

日志缺少数据

如果我们查看应用程序日志,可以发现调用端点时发出的日志消息。

trace_id= span_id= trace_flags= INFO 43636 --- [DefaultExecutor] com.example.tracing.Controller           : test log with tracing info

正如你所看到的,trace_id 和 span_id没有设置。这是因为Micrometer Tracing还无法轻松处理响应式流中的链路追踪上下文。此外,响应式流的Kotlin协程包装器隐藏了链路追踪上下文。因此,我们必须推迟当前响应式流的上下文来获取链路追踪信息。实际上,这看起来如下所示:

 Mono.deferContextual { contextView ->
   ContextSnapshot.setThreadLocalsFrom(
     contextView,
     ObservationThreadLocalAccessor.KEY
   ).use {
     log.info("test log with tracing info")
     Mono.empty<String>()
   }
}.awaitSingleOrNull()

为了更符合应用性,我们可以将示例代码提取到一个单独的函数中。

@GetMapping("/test")
suspend fun test(): String {
  // ...
  observeCtx { log.info("test log with tracing info") }
  // ...
}

suspend inline fun observeCtx(crossinline f: () -> Unit) {
  Mono.deferContextual { contextView ->
    ContextSnapshot.setThreadLocalsFrom(
      contextView,
      ObservationThreadLocalAccessor.KEY
    ).use {
      f()
      Mono.empty<Unit>()
    }
  }.awaitSingleOrNull()
}

如果我们现在启动应用程序并调用我们的端点,我们应该能够trace_id在日志中看到。

trace_id=6c0053eba01199f194f5f76ff8d61917 span_id=967d591266756905 trace_flags= INFO 45139 --- [DefaultExecutor] com.example.tracing.Controller           : test log with tracing info

WebClient 调用没有产生追踪信息

第二个问题可以通过查看观测云中的 trace 来发现。它仅显示端点的父链路追踪,但不显示调用的子范围 WebClient。理论上,Spring WebClient 以及 RestTemplate 都是由 Micrometer 自动检测的。但是如果我们查看代码,就会发现我们正在使用静态构建器方法 WebClient。为了从 WebClient 获取自动链路追踪,我们需要使用 Spring 框架提供的构建器 bean。它可以通过我们类的构造函数注入Controller。

@RestController
class Controller(
  webClientBuilder: WebClient.Builder
) {

 val webClient = webClientBuilder // use injected builder
  .baseUrl("https://jsonplaceholder.typicode.com")
  .build()

 // ...

}

通过上面的代码调整后重新调用 endpoint,我们在观测云中可以看到WebClient的跨度。Micrometer Tracing 还将自动为包含trace_id. 例如,如果我们调用另一个带有链路追踪功能的微服务,它可以获取 ID 并向观测云发送附加信息。

4. 观测指南

Micrometer Tracing 在 Spring 中自动为我们做了很多事情。但是,有时我们可能希望向链路追踪范围添加特定信息或观察应用程序中非传入或传出调用的特定部分。

添加跨度标签

我们可以定义自定义标签并将其添加到当前观察中以增强链路追踪数据。要检索当前链路追踪,我们可以使用ObservationRegistry类的 bean 。与日志记录问题类似,我们必须使用包装函数来获取正确的上下文。

@GetMapping("/test")
suspend fun test(): String {

  observeCtx {
    val currentObservation = observationRegistry.currentObservation
    currentObservation?.highCardinalityKeyValue("test_key", "test sample value")
  }

  // ...
}

添加此代码后,我们可以在观测云中看到我们的自定义标签及其值。

自定义可观测

使用 Micrometer API 创建自定义可观测(跨度)通常很容易。但是,在使用响应式流和协程时,我们需要帮助上下文链路追踪。如果我们在端点处理程序中创建一个新的观测,它将被视为一个单独的链路追踪。为了使代码可重用,我们可以编写一个简单的包装函数来创建新的观测点。它的工作原理与我们之前创建的用于使用 trace_id 。

suspend fun runObserved(
  name: String, 
  observationRegistry: ObservationRegistry,
  f: suspend () -> Unit
) {
  Mono.deferContextual { contextView ->
    ContextSnapshot.setThreadLocalsFrom(
      contextView,
      ObservationThreadLocalAccessor.KEY
    ).use {
      val observation = Observation.start(name, observationRegistry)
      Mono.just(observation).flatMap {
        mono { f() }
      }.doOnError {
        observation.error(it)
        observation.stop()
      }.doOnSuccess {
        observation.stop()
      }
    }
  }.awaitSingleOrNull()
}

该函数可以将任何挂起函数包装在新的观察周围。一旦执行了给定的函数,它将自动停止观测。此外,我们将追踪可能发生的任何错误并将其附加到链路追踪中。

我们现在可以应用这个函数来观察任何代码,例如函数的执行delay

@GetMapping("/test")
suspend fun test(): String {

  runObserved("delay", observationRegistry) {
    delay(1.seconds)
  }

  // ....
}

将此代码添加到端点处理程序后,观测云将向我们显示该操作的自定义范围。

5. 数据库链路追踪

典型的 Spring Boot 应用程序通常会连接到实际应用程序中的数据库。要利用响应式技术栈,建议使用 R2DBC API 而不是 JDBC 。

由于Micrometer Tracing是一项相当新的技术,目前还没有可用的自动追踪。然而,Spring 团队正在研究创建自动配置。实验存储库可以在这里找到。

当前项目,需将添加以下依赖项到build.gradle.kts. 为了方便测试,我们不会使用真实的数据库,而是使用 H2 内存数据库。

 implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
 runtimeOnly("com.h2database:h2")
 runtimeOnly("io.r2dbc:r2dbc-h2")

 // R2DBC micrometer auto tracing
 implementation("org.springframework.experimental:r2dbc-micrometer-spring-boot:1.0.2")

Kotlin代码中,添加了一个带有协程支持的简单 CRUD 存储库。如下所示:

@Table("todo")
data class ToDo(
  @Id
  val id: Long = 0,
  val title: String,
)

interface ToDoRepository : CoroutineCrudRepository<ToDo, Long>


@RestController
class Controller(
  val todoRepo: ToDoRepository,
  // ...
) {

  @GetMapping("/test")
  suspend fun test(): String {
    // ...
    // save
    val entry = ToDo(0,"Springboot3 + WebFlux + Kotlin ")
    todoRepo.save(entry)
    // Sample traced DB call
    val dbtodos = todoRepo.findAll().toList()
    
    // ...

    return "${dbtodos.size} $externalTodos"
  }
}

调用我们的 endpoint 将会再添加一个跨度。新的跨度名为query,包含多个标签,包括Spring Data R2DBC 执行的 SQL 查询。

结论

Micrometer 和新的链路追踪扩展统一了Spring Boot 3及以上版本的可观测性技术栈。为不同公司及其技术栈使用的不同链路追踪解决方案提供了很好的抽象。因此,它简化了我们开发人员的工作。

在 Spring WebFlux 的响应式编程方面,仍然有一些改进的潜力,尤其是 Kotlin。Micrometer 团队正在与Project Reactor (Spring WebFlux 使用的响应式库)背后的团队进行积极会谈,以简化响应式技术栈的 Micrometer Tracing 的使用。

参考资源

kotlin-spring-boot-tracing-example

micrometer-metrics

micrometer tracing

r2dbc-micrometer-spring-boot

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1380804.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux常用命令之cp、rm、touch、mv

cp: 复制文件或目录 -f 覆盖目标同名文件或目录时不进行提醒&#xff0c;而直接强制复制。-i 覆盖目标同名文件或目录时提醒用户确认。-p 复制时保持源文件的权限、属主及时间标记等属性不变&#xff08;默认权限属主是变化的&#xff09;。-r 复制目录时必须使用此选项&a…

Open3D对产生偏差的点云数据进行校正

由于相机安装问题&#xff0c;导致点云数据两边翘起来&#xff0c;为了计算把翘起来的部分拉平整 import time import open3d as o3d; import numpy as np; import matplotlib.pyplot as plt from scipy.signal import find_peaks import pandas as pdOriginalPly o3d.io.rea…

大创项目推荐 深度学习疫情社交安全距离检测算法 - python opencv cnn

文章目录 0 前言1 课题背景2 实现效果3 相关技术3.1 YOLOV43.2 基于 DeepSort 算法的行人跟踪 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习疫情社交安全距离检测算法 ** 该项目较为新颖&#xff0c;适合作为竞赛…

【2023年度总结与2024展望】听听一位初中生的感想

前言 金樽清酒斗十千&#xff0c;玉盘珍馐直万钱。停杯投箸不能食&#xff0c;拔剑四顾心茫然。欲渡黄河冰塞川&#xff0c;将登太行雪满山。闲来垂钓坐溪上&#xff0c;忽复乘舟梦日边。行路难&#xff0c;行路难&#xff0c;多歧路&#xff0c;今安在。长风破浪会有时&#…

强化学习的数学原理学习笔记 - 策略梯度(Policy Gradient)

文章目录 概览&#xff1a;RL方法分类策略梯度&#xff08;Policy Gradient&#xff09;Basic Policy Gradient目标函数1&#xff1a;平均状态值目标函数2&#xff1a;平均单步奖励&#x1f7e1;PG梯度计算 &#x1f7e6;REINFORCE 本系列文章介绍强化学习基础知识与经典算法原…

硬件工程师软实力提升篇

大家好,这里是大话硬件。 新的一年,你规划好了2024年的学习计划吗? 不知道大家是否还记得2023年1月1日在大话硬件公众号发表的文章《祝大话硬件的同学们元旦快乐!》。如果没印象的话,可以点击蓝色字体阅读。 当时写那篇文章的初衷是因为那时候很多人都在群里说,一年结…

IC验证——perl脚本file_assistant——批量修改文件

1 脚本名称 file_assistant 2 脚本路径 scripts/bin/file_assistant 3 脚本参数说明 次序 参数名 说明 1 file_type (v&#xff1b;sv&#xff1b;c&#xff1b;all) 指定脚本要修改的文件类型&#xff0c;目前支持 .v&#xff1b;.sv&#xff1b;.c&#xff08;后续可…

MISGAN

MISGAN:通过生成对抗网络从不完整数据中学习 代码、论文、会议发表: ICLR 2019 摘要: 生成对抗网络(GAN)已被证明提供了一种对复杂分布进行建模的有效方法,并在各种具有挑战性的任务上取得了令人印象深刻的结果。然而,典型的 GAN 需要在训练期间充分观察数据。在本文中…

mac上部署单体hbase

1. 简介 HBase 是一个开源的、分布式的、版本化的典型非关系型数据库。它是 Google BigTable 的开源实现&#xff0c;并且是 Apache 基金会的 Hadoop 项目的一部分1。HBase 在 Hadoop Distributed File System (HDFS) 上运行&#xff0c;作为一个列式存储非关系数据库管理系统…

[zabbix] 分布式应用之监控平台zabbix的认识与搭建

一、监控系统的相关知识 1.1 监控系统运用的原因 当我们需要实时关注与其相关的各项指标是否正常&#xff0c;往往存在着很多的服务器、网络设备等硬件资源&#xff0c;如果我们想要能够更加方便的、集中的监控他们&#xff0c;zabix可以实现集中监控管理的应用程序 监控的初…

每日算法打卡:蚂蚁感冒 day 13

文章目录 原题链接题目描述输入格式输出格式数据范围输入样例1&#xff1a;输出样例1&#xff1a;输入样例2&#xff1a;输出样例2&#xff1a; 题目分析示例代码 原题链接 1211. 蚂蚁感冒 题目难度&#xff1a;简单 题目来源&#xff1a;第五届蓝桥杯省赛C A/B组 题目描述…

python中的Quene使用方法,包含多线程和多进程

在Python中&#xff0c;队列&#xff08;Queue&#xff09;是一种抽象的数据类型&#xff0c;它遵循先进先出&#xff08;FIFO&#xff09;的原则。队列是一种特殊的线性表&#xff0c;只允许在表的前端&#xff08;front&#xff09;进行删除操作&#xff0c;而在表的后端&…

Web前端 ---- 【Vue3】ref和reactive实现响应式的区别和联系

目录 前言 setup ref 基本数据类型 对象形式 reactive ref和reactive的区别与联系 前言 本文介绍函数ref和函数reactive实现响应式 setup 在介绍ref和reactive之前&#xff0c;先介绍setup,vue3新引入的配置项。在该配置项中&#xff0c;在vue2中的data、methods、comput…

DNS 正/反向解析 主从复制 分离解析

一 DNS概念它作为将域名和IP地址相互映射的一个分布式数据库&#xff0c;能够使人更方便地访问互联网 每一台 DNS 服务器都只负责管理一个有限范围 根域: 全球根服务器节点只有13个,10个在美国&#xff0c;1个荷兰&#xff0c;1个瑞典&#xff0c;1个日本 一级域名&#xff…

stm32学习笔记:DMA

每个DMA通道硬件触发源是不一样的&#xff0c;要使用某个外设的硬件触发源&#xff0c;就必须使用它连接的那个通道 12个独立可配置的通道&#xff1a;DMA1(7个通道)&#xff0c;DMA2(5个通道) 每个通道都支持软件触发和特定的硬件触发 C8T6 DMA资源&#xff1a;DMA1 &#xff…

MSF流量加密

1、背景介绍 在MSF中生成shell&#xff0c;并上线运行时。都是通过http https tcp等协议传输。虽然MSF本身会对流量进行加密&#xff0c;但MSF太出名以致于其加密特征容易被IPS&#xff0c;WAF等可以检测带有攻击的特征的设备拦截或记录。 2、生成 SSL 证书 openssl req -x50…

vue前端开发自学,借助KeepAlive标签保持组件的存活

vue前端开发自学,借助KeepAlive标签保持组件的存活&#xff01;如果不想让组件在切换的时候&#xff0c;被默认操作&#xff08;卸载掉了&#xff09;。他们需要使用这个这个表情哦。 下面给大家看看代码情况。 <template><h3>ComA</h3><p>{{ messag…

Arduino开发实例-AS608光学指纹传感器驱动

AS608光学指纹传感器驱动 文章目录 AS608光学指纹传感器驱动1、AS608光学指纹传感器介绍2、硬件准备及接线3、代码实现3.1 指纹录入3.2 指纹匹配验证1、AS608光学指纹传感器介绍 AS608 光学指纹传感器可用于扫描指纹,它也可以通过串行通信将处理后的数据发送到微控制器。 所有…

浏览器深色模式

1、Edge强制深色模式 1、先在edge里设定成深色模式 设置浏览器中的深色设置 但这种方式设置后很多网站仍是白色的背景 2、实验室设置强制深色 网址栏 输入 edge://flags搜索 dark 选择 enabled 重启 2、Chrome强制深色模式 浏览器输入 Chrome深色设置 chrome://flags/#…

基于WebSocket双向通信技术实现-下单提醒和催单(后端)

学习复盘和总结项目亮点。 扩展&#xff1a;该功能能应用在&#xff0c;各种服务类项目中。&#xff08;例如&#xff1a;酒店、洗脚城等系ERP系中提醒类服务&#xff09; 4. 来单提醒 4.1 需求分析和设计 用户下单并且支付成功后&#xff0c;需要第一时间通知外卖商家。通…