返回顶部
首页 > 资讯 > 精选 >Prometheus的本质以及如何实现
  • 436
分享到

Prometheus的本质以及如何实现

2023-06-19 10:06:36 436人浏览 安东尼
摘要

这篇文章将为大家详细讲解有关prometheus的本质以及如何实现,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。主要介绍基础知识以及最基本的几个指标的内部工作机制rust-prometheu

这篇文章将为大家详细讲解有关prometheus的本质以及如何实现,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

主要介绍基础知识以及最基本的几个指标的内部工作机制

rust-prometheus 是监控系统 Prometheus 的 Rust 客户端库,由 TiKV 团队实现。TiKV 使用 rust-prometheus 收集各种指标(metric)到 Prometheus 中,从而后续能再利用 Grafana 等可视化工具将其展示出来作为仪表盘监控面板。这些监控指标对于了解 TiKV 当前或历史的状态具有非常关键的作用。TiKV 提供了丰富的监控指标数据,并且代码中也到处穿插了监控指标的收集片段,因此了解 rust-prometheus 很有必要。

感兴趣的小伙伴还可以观看我司同学在 FOSDEM 2019 会议上关于 rust-prometheus 的技术分享。

基础知识

指标类别

Prometheus 支持四种指标:Counter、Gauge、Histogram、Summary。rust-prometheus 库目前还只实现了前三种。TiKV 大部分指标都是 Counter 和 Histogram,少部分是 Gauge。

Counter

Counter 是最简单、常用的指标,适用于各种计数、累计的指标,要求单调递增。Counter 指标提供基本的 inc()inc_by(x) 接口,代表增加计数值。

在可视化的时候,此类指标一般会展示为各个时间内增加了多少,而不是各个时间计数器值是多少。例如 TiKV 收到的请求数量就是一种 Counter 指标,在监控上展示为 TiKV 每时每刻收到的请求数量图表(QPS)。

Gauge

Gauge 适用于上下波动的指标。Gauge 指标提供 inc()dec()add(x)sub(x)set(x) 接口,都是用于更新指标值。

这类指标可视化的时候,一般就是直接按照时间展示它的值,从而展示出这个指标按时间是如何变化的。例如 TiKV 占用的 CPU 率是一种 Gauge 指标,在监控上所展示的直接就是 CPU 率的上下波动图表。

Histogram

Histogram 即直方图,是一种相对复杂但同时也很强大的指标。Histogram 除了基本的计数以外,还能计算分位数。Histogram 指标提供 observe(x) 接口,代表观测到了某个值。

举例来说,TiKV 收到请求后处理的耗时就是一种 Histogram 指标,通过 Histogram 类型指标,监控上可以观察 99%、99.9%、平均请求耗时等。这里显然不能用一个 Counter 存储耗时指标,否则展示出来的只是每时每刻中 TiKV 一共花了多久处理,而非单个请求处理的耗时情况。当然,机智的你可能想到了可以另外开一个 Counter 存储请求数量指标,这样累计请求处理时间除以请求数量就是各个时刻平均请求耗时了。

实际上,这也正是 Prometheus 中 Histogram 的内部工作原理。Histogram 指标实际上最终会提供一系列时序数据:

  • 观测值落在各个桶(bucket)上的累计数量,如落在 (-∞, 0.1](-∞, 0.2](-∞, 0.4](-∞, 0.8](-∞, 1.6](-∞, +∞) 各个区间上的数量。

  • 观测值的累积和。

  • 观测值的个数。

bucket 是 Prometheus 对于 Histogram 观测值的一种简化处理方式。Prometheus 并不会具体记录下每个观测值,而是只记录落在配置的各个 bucket 区间上的观测值的数量,这样以牺牲一部分精度的代价大大提高了效率。

Summary

Summary 与 Histogram 类似,针对观测值进行采样,但分位数是在客户端进行计算。该类型的指标目前在 rust-prometheus 中没有实现,因此这里不作进一步详细介绍。大家可以阅读 Prometheus 官方文档中的介绍了解详细情况。感兴趣的同学也可以参考其他语言 Client Library 的实现为 rust-prometheus 贡献代码。

标签

Prometheus 的每个指标支持定义和指定若干组标签(Label),指标的每个标签值独立计数,表现了指标的不同维度。例如,对于一个统计 Http 服务请求耗时的 Histogram 指标来说,可以定义并指定诸如 HTTP Method(GET / POST / PUT / ...)、服务 URL、客户端 IP 等标签。这样可以轻易满足以下类型的查询:

  • 查询 Method 分别为 POST、PUT、GET 的 99.9% 耗时(利用单一 Label)

  • 查询 POST /api 的平均耗时(利用多个 Label 组合)

普通的查询诸如所有请求 99.9% 耗时也能正常工作。

需要注意的是,不同标签值都是一个独立计数的时间序列,因此应当避免标签值或标签数量过多,否则实际上客户端会向 Prometheus 服务端传递大量指标,影响效率。

与 Prometheus golang client 类似,在 rust-prometheus 中,具有标签的指标被称为 Metric Vector。例如 Histogram 指标对应的数据类型是 Histogram,而具有标签的 Histogram 指标对应的数据类型是 HistogramVec。对于一个 HistogramVec,提供它的各个标签取值后,可获得一个 Histogram 实例。不同标签取值会获得不同的 Histogram 实例,各个 Histogram 实例独立计数。

基本用法

本节主要介绍如何在项目中使用 rust-prometheus 进行各种指标收集。使用基本分为三步:

  1. 定义想要收集的指标。

  2. 在代码特定位置调用指标提供的接口收集记录指标值。

  3. 实现 HTTP Pull Service 使得 Prometheus 可以定期访问收集到的指标,或使用 rust-prometheus 提供的 Push 功能定期将收集到的指标上传到 Pushgateway。

注意,以下样例代码都是基于本文发布时最新的 rust-prometheus 0.5 版本 API。我们目前正在设计并实现 1.0 版本,使用上会进一步简化,但以下样例代码可能在 1.0 版本发布后过时、不再工作,届时请读者参考最新的文档。

定义指标

为了简化使用,一般将指标声明为一个全局可访问的变量,从而能在代码各处自由地操纵它。rust-prometheus 提供的各个指标(包括 Metric Vector)都满足 Send + Sync,可以被安全地全局共享。

以下样例代码借助 lazy_static 库定义了一个全局的 Histogram 指标,该指标代表 HTTP 请求耗时,并且具有一个标签名为 method

#[Macro_use]extern crate prometheus;lazy_static! {   static ref REQUEST_DURATioN: HistogramVec = reGISter_histogram_vec!(       "http_requests_duration",       "Histogram of HTTP request duration in seconds",       &["method"],       exponential_buckets(0.005, 2.0, 20).unwrap()   ).unwrap();}

记录指标值

有了一个全局可访问的指标变量后,就可以在代码中通过它提供的接口记录指标值了。在“基础知识”中介绍过,Histogram 最主要的接口是 observe(x),可以记录一个观测值。若想了解 Histogram 其他接口或其他类型指标提供的接口,可以参阅 rust-prometheus 文档。

以下样例在上段代码基础上展示了如何记录指标值。代码模拟了一些随机值用作指标,装作是用户产生的。在实际程序中,这些当然得改成真实数据 :)

fn thread_simulate_requests() {   let mut rng = rand::thread_rng();   loop {       // Simulate duration 0s ~ 2s       let duration = rng.gen_range(0f64, 2f64);       // Simulate HTTP method       let method = ["GET", "POST", "PUT", "DELETE"].choose(&mut rng).unwrap();       // Record metrics       REQUEST_DURATION.with_label_values(&[method]).observe(duration);       // One request per second       std::thread::sleep(std::time::Duration::from_secs(1));   }}

Push / Pull

到目前为止,代码还仅仅是将指标记录了下来。最后还需要让 Prometheus 服务端能获取到记录下来的指标数据。这里一般有两种方式,分别是 Push 和 Pull。

  • Pull 是 Prometheus 标准的获取指标方式,Prometheus Server 通过定期访问应用程序提供的 HTTP 接口获取指标数据。

  • Push 是基于 Prometheus Pushgateway 服务提供的另一种获取指标方式,指标数据由应用程序主动定期推送给 Pushgateway,然后 Prometheus 再定期从 Pushgateway 获取。这种方式主要适用于应用程序不方便开端口或应用程序生命周期比较短的场景。

以下样例代码基于 hyper HTTP 库实现了一个可以供 Prometheus Server pull 指标数据的接口,核心是使用 rust-prometheus 提供的 TextEncoder 将所有指标数据序列化供 Prometheus 解析:

fn metric_service(_req: Request<Body>) -> Response<Body> {   let encoder = TextEncoder::new();   let mut buffer = vec![];   let mf = prometheus::gather();   encoder.encode(&mf, &mut buffer).unwrap();   Response::builder()       .header(hyper::header::CONTENT_TYPE, encoder.fORMat_type())       .body(Body::from(buffer))       .unwrap()}

对于如何使用 Push 感兴趣的同学可以自行参考 rust-prometheus 代码内提供的 Push 示例,这里限于篇幅就不详细介绍了。

上述三段样例的完整代码可参见这里。

内部实现

以下内部实现都基于本文发布时最新的 rust-prometheus 0.5 版本代码,该版本主干 API 的设计和实现 port 自 Prometheus Golang client,但为 Rust 的使用习惯进行了一些修改,因此接口上与 Golang client 比较接近。

目前我们正在开发 1.0 版本,API 设计上不再主要参考 Golang client,而是力求提供对 Rust 使用者最友好、简洁的 API。实现上为了效率考虑也会和这里讲解的略微有一些出入,且会去除一些目前已被抛弃的特性支持,简化实现,因此请读者注意甄别。

Counter / Gauge

Counter 与 Gauge 是非常简单的指标,只要支持线程安全的数值更新即可。读者可以简单地认为 Counter 和 Gauge 的核心实现都是 Arc<Atomic>。但由于 Prometheus 官方规定指标数值需要支持浮点数,因此我们基于 std::sync::atomic::AtomicU64 和 CAS 操作实现了 AtomicF64,其具体实现位于 src/atomic64/nightly.rs。核心片段如下:

impl Atomic for AtomicF64 {   type T = f64;   // Some functions are omitted.   fn inc_by(&self, delta: Self::T) {       loop {           let current = self.inner.load(Ordering::Acquire);           let new = u64_to_f64(current) + delta;           let swapped = self               .inner               .compare_and_swap(current, f64_to_u64(new), Ordering::Release);           if swapped == current {               return;           }       }   }}

另外由于 0.5 版本发布时 AtomicU64 仍然是一个 nightly 特性,因此为了支持 Stable Rust,我们还基于自旋提供了 AtomicF64 的 fallback,位于 src/atomic64/fallback.rs。

注:AtomicU64 所需的 integer_atomics 特性最近已在 rustc 1.34.0 stabilize。我们将在 rustc 1.34.0 发布后为 Stable Rust 也使用上原生的原子操作从而提高效率。

Histogram

根据 Prometheus 的要求,Histogram 需要进行的操作是在获得一个观测值以后,为观测值处在的桶增加计数值。另外还有总观测值、观测值数量需要累加。

注意,Prometheus 中的 Histogram 是累积直方图,其每个桶的含义是 (-∞, x],因此对于每个观测值都可能要更新多个连续的桶。例如,假设用户定义了 5 个桶边界,分别是 0.1、0.2、0.4、0.8、1.6,则每个桶对应的数值范围是 (-∞, 0.1](-∞, 0.2](-∞, 0.4](-∞, 0.8](-∞, 1.6](-∞, +∞),对于观测值 0.4 来说需要更新(-∞, 0.4](-∞, 0.8](-∞, 1.6](-∞, +∞) 四个桶。

一般来说 observe(x) 会被频繁地调用,而将收集到的数据反馈给 Prometheus 则是个相对很低频率的操作,因此用数组实现“桶”的时候,我们并不将各个桶与数组元素直接对应,而将数组元素定义为非累积的桶,如 (-∞, 0.1)[0.1, 0.2)[0.2, 0.4)[0.4, 0.8)[0.8, 1.6)[1.6, +∞),这样就大大减少了需要频繁更新的数据量;最后在上报数据给 Prometheus 的时候将数组元素累积,得到累积直方图,这样就得到了 Prometheus 所需要的桶的数据。

当然,由此可见,如果给定的观测值超出了桶的范围,则最终记录下的最大值只有桶的上界了,然而这并不是实际的最大值,因此使用的时候需要多加注意。

Histogram 的核心实现见 src/histogram.rs:

pub struct HistogramCore {   // Some fields are omitted.   sum: AtomicF64,   count: AtomicU64,   upper_bounds: Vec<f64>,   counts: Vec<AtomicU64>,}impl HistogramCore {   // Some functions are omitted.   pub fn observe(&self, v: f64) {       // Try find the bucket.       let mut iter = self           .upper_bounds           .iter()           .enumerate()           .filter(|&(_, f)| v <= *f);       if let Some((i, _)) = iter.next() {           self.counts[i].inc_by(1);       }       self.count.inc_by(1);       self.sum.inc_by(v);   }}#[derive(Clone)]pub struct Histogram {   core: Arc<HistogramCore>,}

Histogram 还提供了一个辅助结构 HistogramTimer,它会记录从它创建直到被 Drop 的时候的耗时,将这个耗时作为 Histogram::observe() 接口的观测值记录下来,这样很多时候在想要记录 Duration / Elapsed Time 的场景中,就可以使用这个简便的结构来记录时间:

#[must_use]pub struct HistogramTimer {   histogram: Histogram,   start: Instant,}impl HistogramTimer {   // Some functions are omitted.   pub fn observe_duration(self) {       drop(self);   }   fn observe(&mut self) {       let v = duration_to_seconds(self.start.elapsed());       self.histogram.observe(v)   }}impl Drop for HistogramTimer {   fn drop(&mut self) {       self.observe();   }}

HistogramTimer 被标记为了 must_use,原因很简单,作为一个记录流逝时间的结构,它应该被存在某个变量里,从而记录这个变量所处作用域的耗时(或稍后直接调用相关函数提前记录耗时),而不应该作为一个未使用的临时变量被立即 Drop。标记为 must_use 可以在编译期杜绝这种明显的使用错误。

关于Prometheus的本质以及如何实现就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

--结束END--

本文标题: Prometheus的本质以及如何实现

本文链接: https://lsjlt.com/news/295271.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Prometheus的本质以及如何实现
    这篇文章将为大家详细讲解有关Prometheus的本质以及如何实现,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。主要介绍基础知识以及最基本的几个指标的内部工作机制rust-prometheu...
    99+
    2023-06-19
  • MySQL高可用架构中MHA的本质以及如何部署
    这篇文章将为大家详细讲解有关MySQL高可用架构中MHA的本质以及如何部署,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。MySQL高可用架构之MHA1、关于...
    99+
    2024-04-02
  • Atlassian中JIRA5.0.5的本质以及作用是什么
    Atlassian中JIRA5.0.5的本质以及作用是什么,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Atlassian JIRA 5.0.5 发布了,该版本...
    99+
    2023-06-17
  • 如何理解DevOps的本质及行业现状与趋势
    如何理解DevOps的本质及行业现状与趋势,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。手工编译,上传服务器文件,执行各种命令,启动和停止服务器,发现一个 BUG,改完再重复之...
    99+
    2023-06-04
  • Java线程池的本质以及作用是什么
    Java线程池的本质以及作用是什么,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。熟悉java多线程的 朋友一定十分了解java的线程池,jdk中的核心实现类为j...
    99+
    2023-06-17
  • F5如何帮助应用实现非公能需求以及DevOps如何提升App品质?
      众所周知,不论是产品还是服务,品质决定竞争力,而对于一个App来说,功能是生命线,确定能否安身立命,而非功能需求则是它的品质。在App如过江之卿的今天,用户们翻身奴隶把歌唱,分分钟可以卸载掉一个哪怕只有一点点让自己不爽的App,转身下载...
    99+
    2020-05-24
    F5如何帮助应用实现非公能需求以及DevOps如何提升App品质?
  • Grafana与Prometheus的集成是如何实现的
    Grafana与Prometheus的集成是通过Grafana的数据源功能实现的。要将Prometheus作为数据源添加到Grafa...
    99+
    2024-04-02
  • 如何理解JavaScript DOM本质及操作技巧
    这期内容当中小编将会给大家带来有关如何理解JavaScript DOM本质及操作技巧,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。简单介绍一下JavaScript DOM...
    99+
    2024-04-02
  • Prometheus如何实现数据的聚合和汇总
    Prometheus实现数据的聚合和汇总主要通过以下几种方式: 使用PromQL查询语言:Prometheus提供了一种强大的查...
    99+
    2024-03-04
    Prometheus
  • html5以及jQuery如何实现本地图片上传前的预览代码
    这篇文章主要为大家展示了html5以及jQuery如何实现本地图片上传前的预览代码,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带大家一起来研究并学习一下“html5以及jQuery如何实现本地图片上传前的预览代码”这篇文章...
    99+
    2023-06-06
  • spring中REST和RESTful的区别以及基本实现
    目录简介REST概念:RESTful概念:RESTful实现GET, 查询所有用户信息GET, 根据用户ID查询用户信息POST, 添加用户信息PUT, 修改用户信息简介 REST概...
    99+
    2024-04-02
  • Vue实现自动检测以及版本的更新
    目录一、思路二、实现步骤1.调用fetch拿到首页的html并且转为纯文本格式2.检查是否需要更新3.有则弹出总结一、思路 1,每隔一小段时间fetch请求一次服务器首页数据,解析为...
    99+
    2023-05-14
    Vue自动检测版本 Vue检测版本更新
  • python如何实现质数求和
    目录如何实现质数求和实现1~100所有质数求和如何实现质数求和 生活中很多问题是需要用数学来解决的,比如说要是做一栋房子,各方面的数据都要计算,要用多少材料,长宽高多少等,简单地说,...
    99+
    2024-04-02
  • 原生js如何实现call,apply以及bind
    1、实现call 步骤: 将函数设为对象的属性; 指定this到函数,并传入给定参数执行函数; 执行之后删除这个函数; 如果不传入参数,默认指向w...
    99+
    2024-04-02
  • 如何使用H5实现上传本地图片以及预览功能
    这篇文章给大家分享的是有关如何使用H5实现上传本地图片以及预览功能的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。最近工作中需要H5上传显示图片的功能,如图:直接上代码:html部...
    99+
    2024-04-02
  • k8s用户界面k8s-manager的本质及如何进行部署
    k8s用户界面k8s-manager的本质及如何进行部署,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。k8s用户界面——k8s-manager1、k8s-manager简介:①...
    99+
    2023-06-19
  • Python 如何实现重定向以及实时索引?
    Python 是一种非常流行的编程语言,它可以用于各种不同的应用场景。其中,Python 的重定向和实时索引功能在许多应用程序中非常有用。在本文中,我们将学习如何使用 Python 实现重定向和实时索引。 重定向是一种将程序输出发送到不同地...
    99+
    2023-10-24
    重定向 实时 索引
  • BootStrapz2select2如何实现查询以及输入功能
    小编给大家分享一下BootStrapz2select2如何实现查询以及输入功能,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!首先...
    99+
    2024-04-02
  • 如何在Prometheus中实现数据的去重和压缩
    在Prometheus中实现数据的去重和压缩通常是通过配置Prometheus的存储策略来实现的。以下是实现数据去重和压缩的一些步骤...
    99+
    2024-03-04
    Prometheus
  • mysqldump5.7以下版本如何实现并发备份
    这篇文章主要为大家展示了“mysqldump5.7以下版本如何实现并发备份”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“mysqldump5.7以下版本如何实现...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作