【Rust日报】2023-06-20 使用Quickwit、Jaeger和Grafana监控您的Rust应用程序

news2024/12/23 23:10:51

使用Quickwit、Jaeger和Grafana监控您的Rust应用程序

你可能已经看过了Lucas Palmieri的博客文章Are we observable yet? An introduction to Rust telemetry。如果你还没有看过,我们建议阅读一下,因为它提供了一个全面的介绍,介绍了如何处理 Rust 代码中的日志。

然而,仅仅记录日志可能是不够的,特别是在分布式架构中。在 Quickwit 中,我们经常使用跟踪来理解性能瓶颈并提高速度。当我们遇到 Quickwit 的搜索响应缓慢时,我们经常会问自己:是什么导致了减速?是网络相关的问题,磁盘 I/O 还是过多的 CPU 使用?

在本博客文章中,我们将展示如何为 Rust 应用程序进行测量,并生成跟踪数据,从 DevOps 视角利用它们。我们的目标将是双重的:

  • 使用广泛认可的 Jaeger UI 分析跟踪,以获取有关应用程序行为的见解。

  • 从这些跟踪数据中派生 RED(速率、错误和持续时间)指标,并在 Grafana 中监视它们。如果您想进一步了解,我们建议参考以下资源:Weaveworks 的 RED 方法和 Google SRE 书籍中有关监控分布式系统的部分。

现在,让我们深入介绍步骤,其中我们将涵盖以下关键方面:

  • 为使用 Actix 构建的简单 Web API 进行测量。

  • 将您的跟踪和指标数据推送到 Quickwit。

  • 在 Jaeger UI 中检测、诊断和解决问题。

  • 在 Grafana 中监视您的应用程序的 RED 指标(速率、错误、持续时间)。

在深入了解之前,请确保您的系统上已安装并正确运行以下软件:

  • Rust 1.68+

  • Docker

  • 如果您仍在运行旧版本的 Docker,则需要安装 docker-compose

构建并测量 Rust 应用

我们将使用 Actix Web 框架创建一个基本的 Rust 应用程序。这个应用程序是一个包含单个端点的 Web API。它将从受欢迎的 JSONPlaceholder 公共 Web API 获取帖子及其评论,并将它们显示为 JSON。为了更好地了解我们的应用程序生命周期并可能优化它,我们将确保测量以下例程:

  • 从 /posts 获取帖子。

  • 获取每个帖子的评论 /posts/1/comments

创建一个名为 rust-app-tracing 的新目录。在终端中切换到该目录,并运行以下命令初始化一个新的 Rust 项目。

cargo new web-api

让我们还要确保在 web-api/Cargo.toml 文件中拥有所需的依赖项。

  • actix-web:用于在 Rust 中构建 Web 应用程序的快速 Web 框架。

  • actix-web-opentelemetryactix-web 框架的 open-telemetry 扩展。

  • opentelemetry:Rust 的核心 open-telemetry SDK,包括跟踪和指标。

  • opentelemetry-otlp:提供各种 open-telemetry 导出器的 crate。

  • reqwest:提供一个直观的 API 来进行 HTTP 请求。

  • tokio:为我们的应用程序提供异步运行时。

Web API 应用程序代码

首先,让我们通过创建一个名为 telemetry.rs 的文件来配置应用程序跟踪,我们将在其中处理所有跟踪配置。

// telemetry.rs

...

const SERVICE_NAME: &'static str = "quickwit-jaeger-demo";

pub fn init_telemetry(exporter_endpoint: &str) {
    // Create a gRPC exporter
    let exporter = opentelemetry_otlp::new_exporter()
        .tonic()
        .with_endpoint(exporter_endpoint);

    // Define a tracer
    let tracer = opentelemetry_otlp::new_pipeline()
        .tracing()
        .with_exporter(exporter)
        .with_trace_config(
            trace::config().with_resource(Resource::new(vec![KeyValue::new(
                opentelemetry_semantic_conventions::resource::SERVICE_NAME,
                SERVICE_NAME.to_string(),
            )])),
        )
        .install_batch(opentelemetry::runtime::Tokio)
        .expect("Error: Failed to initialize the tracer.");

    // Define a subscriber.
    let subscriber = Registry::default();
    // Level filter layer to filter traces based on level (trace, debug, info, warn, error).
    let level_filter_layer = EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new("INFO"));
    // Layer for adding our configured tracer.
    let tracing_layer = tracing_opentelemetry::layer().with_tracer(tracer);
    // Layer for printing spans to stdout
    let formatting_layer = BunyanFormattingLayer::new(
        SERVICE_NAME.to_string(),
        std::io::stdout,
    );

    global::set_text_map_propagator(TraceContextPropagator::new());

    subscriber
        .with(level_filter_layer)
        .with(tracing_layer)
        .with(JsonStorageLayer)
        .with(formatting_layer)
        .init()
}

Copy 接下来,我们将实现我们的 API 端点,并在处理程序函数中添加一些测量代码。重要的是要注意,我们的重点不在于此应用程序的功能,而在于从应用程序生成有意义且可利用的跟踪数据。

首先,我们有一些模型文件,允许我们对 post 和 comment 进行序列化和反序列化。

//models.rs
...

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Post {
    pub user_id: i64,
    pub id: i64,
    pub title: String,
    pub body: String,
    #[serde(default)]
    pub comments: Vec<Comment>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Comment {
    pub post_id: i64,
    pub id: i64,
    pub name: String,
    pub email: String,
    pub body: String,
}

接下来,让我们处理 API 端点处理程序。请注意,某些函数上装饰有 instrument 属性。这是我们如何在处理程序函数和它用于执行任务的后续函数上启用跟踪的方法。

// lib.rs

...

const BASE_API_URL: &'static str = "https://jsonplaceholder.typicode.com";

// The get_post handler
#[instrument(level = "info", name = "get_posts", skip_all)]
#[get("")]
async fn get_posts() -> Result<HttpResponse, Error> {
    // Randomly simulate errors in request handling
    let choices = [200, 400, 401, 200, 500, 501, 200, 500];
    let mut rng = rand::thread_rng();
    let choice = choices.choose(&mut rng)
        .unwrap()
        .clone();
    match choice {
        400..=401 => Ok(HttpResponse::new(StatusCode::from_u16(choice).unwrap())),
        500..=501 => Ok(HttpResponse::new(StatusCode::from_u16(choice).unwrap())),
        _ => {
            let posts = fetch_posts(20)
                .await
                .map_err(actix_web::error::ErrorInternalServerError)?;
            Ok(HttpResponse::Ok().json(posts))
        }
    }
}

// Fetching posts with a limit.
#[instrument(level = "info", name = "fetch_posts")]
async fn fetch_posts(limit: usize) -> anyhow::Result<Vec<Post>> {
    let client = Client::new();
    let url = format!("{}/posts", BASE_API_URL);
    let mut posts: Vec<Post> = request_url(&client, &url).await?;
    posts.truncate(limit);
    let post_idx_to_ids: Vec<(usize, i64)> = posts.iter().enumerate().map(|(idx, post)| (idx, post.id)).collect();

    // fetch post comments one after another.
    for (index, post_id) in post_idx_to_ids {
        let comments = fetch_comments(&client, post_id).await?;
        posts[index].comments = comments
    }

    Ok(posts)
}

...

在上面的片段中,我们仅发送跟踪。也可以使用可靠的日志收集器来收集日志并将其发送到 Quickwit 或其他后端。

使用 Quickwit 收集跟踪数据

现在我们已经构建了应用程序。让我们与 Quickwit 一起运行,并确保生成的跟踪被 Quickwit 索引。

与我们在之前的博客文章中所做的不同,我们将创建一个 docker-compose 文件来简化 Quickwit、Jaeger 和 Grafana 之间的设置。以下 docker-compose 文件包含所有必要的配置。

  • QW_ENABLE_OTLP_ENDPOINT:允许 Quickwit 接受和摄取跟踪和日志数据。

  • SPAN_STORAGE_TYPEGRPC_STORAGE_SERVERQW_ENABLE_JAEGER_ENDPOINT:允许 Jaeger 从 Quickwit 拉取跟踪和日志以进行分析。

  • GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS:允许我们在 Grafana 中加载特定的插件。

# docker-compose.yaml
version: '3'
services:
  quickwit:
    image: quickwit/quickwit:latest
    command: run
    restart: always
    environment:
      QW_ENABLE_OTLP_ENDPOINT: true 
      QW_ENABLE_JAEGER_ENDPOINT: true 
    ports:
      - '7280:7280'
      - '7281:7281'
    volumes:
      - ./qwdata:/quickwit/qwdata

  jaeger:
    image: jaegertracing/jaeger-query:latest
    restart: always
    depends_on:
      - quickwit
    environment:
      SPAN_STORAGE_TYPE: 'grpc-plugin'
      GRPC_STORAGE_SERVER: 'quickwit:7281'
    ports:
      - '16686:16686'

  grafana:
    image: grafana/grafana-enterprise:latest
    restart: always
    user: root
    depends_on:
      - quickwit
    environment:
      GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: 'quickwit-quickwit-datasource'
    ports:
      - '3000:3000'
    volumes:
      - ./grafana-storage:/var/lib/grafana

有了这个 docker-compose 文件,让我们在项目目录中创建所需的目录以使服务正确运行。创建 qwdata 目录以存储 Quickwit 数据。

然后,下载并将 Quickwit Grafana 数据源插件放置在预期位置。

wget https://github.com/quickwit-oss/quickwit-datasource/releases/download/v0.2.0/quickwit-quickwit-datasource-0.2.0.zip \  
&&  mkdir -p grafana-storage/plugins \  
&&  unzip quickwit-quickwit-datasource-0.2.0.zip -d grafana-storage/plugins

现在让我们通过运行以下命令启动所有服务(Quickwit、Jaeger、Grafana):

如果没有问题,现在我们可以运行 Web 应用程序并使用 cURL 几次命中 http://localhost:9000/post 端点,以生成一些跟踪。

curl -X GET http://localhost:9000/post

等待约 10 秒钟,新的跟踪将被索引并可供搜索。

您现在可以通过使用 cURL 搜索 otel-traces-v0_6 索引来检查 Quickwit 是否已索引跟踪数据。

curl -X POST http://localhost:7280/api/v1/otel-traces-v0_6/search -H 'Content-Type: application/json' -d '{ "query": "service_name:quickwit-jaeger-demo" }'

您也可以使用 Quickwit UI http://localhost:7280/ui/search 查看数据。

a056257f6bcd82fa2f645b13b01b344f.png

Jaeger 容器已经在运行中了,可以转到 http://localhost:16686 查看我们的应用程序跟踪。

b4bcd525b32169c39e384343854df6df.png

从上面的截图可以看出,我们依次为每个帖子获取评论。也就是说,我们一个接一个地进行了二十次请求。这使得整个请求处理时间更长(上面为 4.39s)。

但我们能不能更好地做?

在 Rust 开发人员拥有的所有优秀工具中,答案是显而易见的 "是的!"。让我们利用 Tokio 和 Rust futures crate 的异步流特性,通过并行获取评论。

让我们更新我们的 fetch_posts 函数,以批量并行运行请求,每次同时进行十个请求。这应该可以进一步加速事情。

// Fetching posts with a limit.
#[instrument(level = "info", name = "fetch_posts")]
async fn fetch_posts(limit: usize) -> anyhow::Result<Vec<Post>> {
  ...

  // fetch post comments concurrently.
  let tasks: Vec<_> = post_idx_to_ids
      .into_iter()
      .map(|(index, post_id)| {
          let moved_client = client.clone();
          async move {
              let comments_fetch_result = fetch_comments(&moved_client, post_id).await;
              (index, comments_fetch_result)
          }
      })
      .collect();
  let mut stream = futures::stream::iter(tasks)
      .buffer_unordered(10); // batch of 10 request at a time
  while let Some((index, comments_fetch_result)) = stream.next().await {
      let comments = comments_fetch_result?;
      posts[index].comments = comments;
  }

  ...
}

通过这个改变,你会注意到我们现在处理请求的时间大约为2.46秒,同时你也可以直观地看到我们的请求处理程序在运行期间最多同时运行了十个fetch_comments请求。

55792ef1088d4b4ef5e3ef54beffee3e.png

Jaeger 适用于对单个跟踪进行专注检查。但如果我们想要监视服务的延迟呢?如果我们想要计算具有给定跟踪元数据的错误或请求的数量呢?

这就是 Grafana 仪表板的用处。我们想要从我们的跟踪构建 RED 指标并在 Grafana 中可视化它们。

转到 http://localhost:3000/login,使用admin作为用户名和密码登录。

登录后,我们可以使用新发布的 Quickwit 数据源插件 连接到 Quickwit 并查询我们的应用程序跟踪。

37626e8f95a7a2c9f714a03502612fa7.png

为了使 RED 指标监控过程更加方便,我们准备了一个预配置的 Grafana 仪表板供您下载并导入到您的 Grafana 实例中。

该仪表板作为一种强大的工具,用于可视化和理解性能。它包括三个面板:

  • 第一个面板显示每分钟的请求数量。

  • 第二个面板显示每分钟的错误数量。

  • 第三个面板呈现每分钟请求的持续时间百分位数。

为了观察这些指标的运行情况,您可以使用 HTTP 基准测试工具,甚至可以使用本教程提供的 此脚本发送多个并发请求到您的 Rust 应用程序。

现在让我们来看一下 Grafana 仪表板的截图,展示了运行脚本后的指标情况。

63f7f64a7d7c5825acc1e1167e71147a.png

就是这样!在这篇博客文章中,我们超越了基本的日志记录,深入了解了分布式跟踪以及如何使用它来监视应用程序性能。

我们构建 Quickwit 的经验告诉我们,分布式跟踪对于了解由于调用 S3 或在本地磁盘上读取数据而失去时间的位置非常重要。我们希望它对您也有所帮助 :)

愉快的编码和观察!

更多阅读

我们有一堆教程,教您如何使用 Quickwit 进行可观察性。以下是一些快速入门的链接:

  • 快速入门指南。

  • 使用 OTEL 收集器在 Quickwit 中发送跟踪。

  • 将日志发送到 Quickwit。

ReadMore: https://quickwit.io/blog/observing-rust-app-with-quickwit-jaeger-grafana


From 日报小组 Koalr

社区学习交流平台订阅:

  • Rustcc论坛: 支持rss

  • 微信公众号:Rust语言中文社区

Rust语言中文社区视频号live上线啦,以后会不定时直播Rust活动,欢迎关注

053c808db4dbe855958d648c35a4fde7.jpeg

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

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

相关文章

【Python 基础篇】Python 函数:代码重用的利器

文章目录 导言一、创建函数二、函数参数1、位置参数2、关键字参数3、默认参数 三、函数返回值四、函数的高级用法1、递归函数2、匿名函数3、内置函数 总结 导言 函数是一种在Python中定义和封装可重用代码的重要机制。它们使我们能够将复杂的任务分解为更小的部分&#xff0c;…

【算法与数据结构】15、LeetCode三数之和

文章目录 一、题目二、双指针法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、双指针法 思路分析&#xff1a;我们使用双指针法&#xff0c;但这道题因为要求数组三个元素的和&#xff0c;一共用到了三个指…

新人拿到一个web项目如何使用idea发布运行

本文描述的是一个新手&#xff0c;拿到一个web项目&#xff0c;使用idea如何发布运行。项目中没有非常复杂的元素&#xff0c;只是试着描述应该如何配置相关内容。 内容描述前提&#xff0c;首先请您确认tomcat已经安装&#xff0c;其次确认jdk已经安装&#xff0c;并明确他们在…

基于Java农产品仓库管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

管理类联考——逻辑——技巧篇——论证推理(10-12 道左右)——五大秒杀思路

逻辑考试出题思路分类 论证推理&#xff08;10-12 道左右&#xff09; 论证推理细分思路 假设——补全逻辑假设——引入前提前真后假 - 前假后真建立联系型支持由果推因的削弱由因推果 / 直接引入他因指出不同 / 指出相同五大固定秒杀思路解释类题目评价类题目 论证推理题目…

C语言学习(二十四)---递归与冒泡排序法

在前面几节的内容中&#xff0c;我们学习了指针的相关概念&#xff0c;至此&#xff0c;指针的内容就暂时告一段落了&#xff0c;今天我们将继续向下学习&#xff0c;主要内容为递归和冒泡排序法&#xff0c;好了&#xff0c;话不多说&#xff0c;开整&#xff01;&#xff01;…

【AndroidUI设计】主界面设计-Toolbar的简单使用

文章目录 一、引言二、了解三、编码1、UI设计2、编码 一、引言 描述&#xff1a;需要设计一个主界面&#xff0c;菜单通过主界面的左边界划入&#xff0c;实现点击跳转修改主界面内容的一个效果&#xff0c;并且点击非内容区域恢复原界面的一个效果。做到菜单的弹出&#xff0…

Vue3 One Piece Study

目录 脚手架安装vue3 使用vue-cli创建 使用vite创建 setup 介绍 示例使用 ref函数 介绍 代码示例 reactive函数 介绍 代码示例 脚手架安装vue3 使用vue-cli创建 vue create 项目名 安装完成 进入到刚才创建的项目目录中 cd vue3_test 输入npm run serve测试 使用…

基于Java健康综合咨询问诊平台设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

STM32 HAL库开发——入门篇(2):定时器

目录 一、TIMER定时器概述 1.1 软件定时原理 1.2 定时器定时原理 1.3 STM32定时器分类 1.4 STM32定时器特性表 1.5 STM32基本、通用、高级定时器功能整体的区别 二、基本定时器 2.1 基本定时器简介 2.3 STM32定时器计数模式及溢出条件 2.4 定时器中断实验相关寄存器 …

MySQL创建商品订单数据库

目录 一、商品分类表 ProductClass1. 创建 ProductClass表2. 插入数据3. 展示 二、客户表 Customer记录1. 创建 Customer表2. 插入数据3. 展示 三、商品表 Product1. 创建 Product表2. 插入数据3. 展示 四、员工表Employee1. 创建 Employee表2. 插入数据3. 展示 五、订单主表 O…

kubernetes_核心组件_KubeProxy_KubeProxy三种模式和参数解析

系列文章目录 文章目录 系列文章目录前言一、kube-proxy三种服务负载模式1.1 userspace 模式1.2 iptables 模式1.3 ipvs 模式 二、kube-proxy 启动参数基本参数目录挂盘kubeproxy配置项(ConfigMap)kube-proxy 启动参数 三、kube-proxy 常用命令四、宿主机上的iptables规则&…

数据结构链表(C语言实现)

绪论 机遇对于有准备的头脑有特别的亲和力。本章将讲写到链表其中主要将写到单链表和带头双向循环链表的如何实现。 话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑观看&#xff09;。 附&#xff1a;红色&#xff0c;部分为重点部分&#xff1b;蓝颜色为需要记忆的…

【微服务】一文了解Nginx网关搭建教程

一文了解Nginx网关搭建教程 Nginx网关搭建nginx配置Nginx网关搭建 那么什么是Nginx呢? nginx是一个高性能HTTP服务器,反向代理服务器,邮件代理服务器,TCP/UDP反向代理服务器。 单个系统主要用于处理客户端请求,一个系统处理客户端的请求量是有限的,当客户端的并发量超…

CROSSROADS: 1实战演练

文章目录 CROSSROADS: 1实战演练一、前期准备1、相关信息 二、信息收集1、端口扫描2、访问网站3、dirsearch扫描目录4、查看隐写5、枚举用户6、暴力破解7、访问共享文件夹8、查看第一个flag9、写入shell并连接 二、后渗透1、查看权限和SUID文件2、运行程序3、切换root3、查看第…

2019 - 2023,再见了我的大学四年

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端&#xff08;Node.js 等&#xff09; &#x1f4c3;个人状态&#xff1a; 2023届本科毕业生&#xff0c;已拿多个前端 offer&#x…

【Linux环境基础开发工具】编译器-gcc/g++

写在前面&#xff1a; 上一篇博客&#xff0c; 我们学习了vim编辑工具&#xff0c;学会了怎么写代码&#xff0c; 这篇文章&#xff0c;我将分享代码该怎么编译的问题。 目录 写在前面&#xff1a; 1. gcc和g介绍 2. gcc是如何编译程序的 1. 预处理 2. 编译 3. 汇编 …

CPM-Bee大模型微调

CPM-Bee大模型微调 CPM-Bee简介&#xff1a;环境配置&#xff1a;应用场景&#xff1a;模型训练参数训练命令&#xff1a;推理&#xff1a;评估&#xff1a;结论&#xff1a; CPM-Bee 简介&#xff1a; CPM-Bee是一个完全开源、允许商用的百亿参数中英文基座模型&#xff0c;也…

关于数据库运维系统的一些思考

这是学习笔记的第 2461篇文章 前段时间整理了一下数据库运维系统的一些内容&#xff0c;比自己预期的要难一些。我来简单回顾下一些参考点。 一、立足当下&#xff0c;混沌之中梳理问题 通常我们可以会问为什么&#xff0c;即为什么要做数据库运维系统&#xff0c;但是我们先放…

决策分析——层次分析法

工程测量与经济决策方案 决策分析——层次分析法 一、描述 层次分析法的基本原理&#xff1a;根据问题的性质和要达到的总目标&#xff0c;将问题分解为不同的组成因素&#xff0c;并按照因素间的相互关联影响以及隶属关系将因素按不同层次聚集组合&#xff0c;形成一个多层次…