P99 是如何计算的

P99 是如何计算的Latency 延迟 是我们在监控线上的组件运行情况的一个非常重要的指标 它可以告诉我们请求在多少时间内完成 监控 Latency 是一个很微妙的事情 比如 假如一分钟有 1 亿次请求 你就有了 1 亿个数字 如何从这些数字中反映出用户的真实体验呢 之前的公司用平均值来反应所有有关延迟的数据 这样的好处是计算量小 实施简单 只需要记录所有请求的一个时间总和 以及请求次数 两个数字 就可以计算出平均耗时 但问题是 平均耗时非常容易掩盖真实的问题 比如现在有 1 的请求非常慢 但是其余的请求很快 那么这 1

Latency (延迟)是我们在监控线上的组件运行情况的一个非常重要的指标,它可以告诉我们请求在多少时间内完成。监控 Latency 是一个很微妙的事情,比如,假如一分钟有 1亿次请求,你就有了 1亿个数字。如何从这些数字中反映出用户的真实体验呢?

之前的公司用平均值来反应所有有关延迟的数据,这样的好处是计算量小,实施简单。只需要记录所有请求的一个时间总和,以及请求次数,两个数字,就可以计算出平均耗时。但问题是,平均耗时非常容易掩盖真实的问题。比如现在有 1% 的请求非常慢,但是其余的请求很快,那么这 1% 的请求耗时会被其他的 99% 给拉平,将真正的问题掩盖。

所以更加科学的一种监控方式是观察 P99/P95/P90 等,叫做 Quantile。简单的理解,P99 就是第 99% 个请求所用的耗时。假如 P99 现在是 10ms, 那么我们可以说 “99% 的请求都在 10ms 内完成”。虽然在一些请求量较小的情况下,P99 可能受长尾请求的影响。但是由于 SRE 一般不会给在量小的业务上花费太多精力,所以这个问题并不是很大。

但是计算就成了一个问题。P99 是计算时间的分布,所以我们是否要保存下来 1亿个请求的时间,才能知道第 99% 的请求所用的时间呢?

这样耗费的资源太大了。考虑到监控所需要的数据对准确性的要求并不高。比如说 P99 实际上是 15.7ms 但是计算得到数据是 15.5ms,甚至是 14ms,我认为都是可以接受的。我们关注更多的是它的变化。“P99 耗时从 10.7ms 上涨到了 14ms” 和 “P99耗时从 11ms 上涨到了 15.5ms” 这个信息对于我们来说区别并不是很大。(当然了,如果是用于衡量服务是否达到了服务等级协议 SLO 的话,还是很大的。这样需要合理地规划 Bucket 来提高准确性)。

所以基于这个,Prometheus 采用了一种非常巧妙的数据结构来计算 Quantile: Histogram.

Histogram 本质上是一些桶。举例子说,我们为了计算 P99,可以将所有的请求分成 10 个桶,第一个存放 0-1ms 完成的请求的数量,后面 9 个桶存放的请求耗时上区间分别是 5ms 10ms 50ms 100ms 200ms 300ms 500ms 1s 2s. 这样只要保存 10 个数字就可以了。要计算 P99 的话,只需要知道第 99% 个数字落在了哪一个桶,比如说落在了 300ms-500ms 的桶,那我们就可以说现在的 99% 的请求都在 500ms 之内完成(这样说不太准确,如果准确的说,应该是第 99% 个请求在 300ms – 500ms 之间完成)。这些数据也可以用来计算 P90, P95 等等。

由于我们的监控一般是绘制一条曲线,而不是一个区间。所以 P99 在 300-500 之间是不行的,需要计算出一个数字来。

Prometheus 是假设每一个桶内的数据都是线性分布的,比如说现在 300-500 的桶里面一共有 100 个请求,小于300个桶里面一共有 9850 个请求。所有的桶一共有 1万个请求。那么我们要找的 P99 其实是第 10000 * 0.99 = 9900 个请求。第 9900 个请求在 300-500 的桶里面是第 9900 – 9850 = 50 个请求。根据桶里面都是线性分布的假设,第50个请求在这个桶里面的耗时是 (500 – 300) * (50/100) = 400ms, 即 P99 就是 400ms.

可以注意到因为是基于线性分布的假设,不是准确的数据。比如假设 300-500 的桶中耗时最高的请求也只有 310ms, 得到的计算结果也会是 400ms. 桶的区间越大,越不准确,桶的区间越小,越准确。


写这篇文章,是因为昨天同事跑来问我,“为啥我的日志显示最慢的请求也才 1s 多,但是这个 P999 latency 显示是 3s?”

我查了一下确实如他所说,但是这个结果确实预期的。因为我们设置的桶的分布是:10ms, 50ms, 100ms, 500ms, 1s, 5s, 10s, 60s.

如上所说,Promtheus 只能保证 P999 latency 落在了 1s – 5s 之间,但不能保证误差。

如果要计算准确的 Quantile, 可以使用 Summary 计算。简单来说,这个算法没有分桶,是直接在机器上计算准确的 P99 的值,然后保存 P99 这个数字。但问题一个是在机器本地计算,而不是在 Prometheus 机器上计算,会占用业务机器的资源;另一个是无法聚合,如果我们有很多实例,知道每一个实例的 P99 是没有什么意义的,我们更想知道所有请求的 P99. 显然,原始的信息已经丢失,这个 P99 per instance 是无法支持继续计算的。

另外一个设计巧妙的地方是,300-500 这个桶保存的并不是 300-500 耗时的请求数,而是 <500ms 的请求数。也就是说,后面的桶的请求数总是包含了它前面的所有的桶。这样的好处是,虽然我们保存的数据没有增加(还是10个数字),但是保存的信息增加了。假如说中间丢弃一个桶,依然能够计算出来 P99. 在某些情况下非常有用,比如监控资源不够了,我们可以临时不收集前5个桶,依然可以计算 P99.

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/212863.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午7:05
下一篇 2026年3月18日 下午7:05


相关推荐

  • forkjoin用法_数组join方法

    forkjoin用法_数组join方法Fork/Join是一个分而治之的任务框架,如一个任务需要多线程执行,分割成很多块计算的时候,可以采用这种方法。动态规范:和分而治之不同的是,每个小任务之间互相联系。工作密取:分而治之分割了每个任务之后,某个线程提前完成了任务,就会去其他线程偷取任务来完成,加快执行效率。同时,第一个分配的线程是从队列中的头部拿任务,当完成任务的线程去其他队列拿任务的时候是从尾部拿任务,所以这样就避免了竞争。在Java的Fork/Join框架中,使用两个类完成上述操作:  1.ForkJoinTask:我们要使用F

    2026年2月5日
    4
  • 业务流程重组的概念简称_流程管理

    业务流程重组的概念简称_流程管理业务流程重组的概念根据Hammer与Champy的定义,“业务流程重组就是对企业的业务流程(Process)进行根本性(Fundamental)再思考和彻底性(Radical)再设计,从而获得在成本、

    2022年8月3日
    8
  • oracle的number类型

    oracle的number类型1.简介一个可变长度的数据类型,使用四舍五入实现;既可以存储整数,也可以存储小数;2.使用语法(1)可指定两个参数:p:精度位precision,数据的有效位;取值范围38;默认38;*表示38s:小数位scale,小数点右边的位数;取值范围-84~127;默认:指定了p,默认s为最大范围;未指定p,默认s=0;numbernumber(p)number(p,s)(2)最高整数位数=p-ss正数,精确到小数点右边s位,四舍五入;s负数,精确

    2022年7月24日
    15
  • MFC简介_java void main

    MFC简介_java void main版权声明:www.gudianxiaoshuo.com原创文章版权–古典小说网         WM_CONTEXTMENU消息用来响应鼠标右键消息,它响应的不是鼠标右键按下消息,也不是鼠标右键弹起消息而是鼠标右键依次按下弹起后触发的消息。          因此,若程序还响应了鼠标右键按下的消息OnRButtonDown,且在此响应函数中有模特对话

    2022年8月14日
    12
  • nc测试UDP是否正常

    nc测试UDP是否正常当需要测试 udp 连接是否正常的时候 可以使用 nc 命令基本操作 A 服务器上安装 nc 工具 yum yinstallncB 客户端上安装 nc 工具 yum yinstallnc 测试 A 服务器上用 nc 监听 udp 模式下的 8888nc ulp8888B 客户端在 udp 模式下向 A 的 8888 端口发送信息 nc nc uA 服务器 IP8888 结果当 B 客户端发送信息给 A 时 A 能够收到表示 A 服务器 UDP 正常检测 UDP 端口是否正常检测系统的 IP 为 192 168 50 66 端口为 8888

    2026年3月16日
    2
  • HTTPClient和CloseableHttpClient

    使用HttpClient发送请求的一般步骤(1)创建HttpClient对象。(2)创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。(3)如果需要发送请求参数,可调用HttpGet同的setParams(HetpParamsparams)方法来添加请求参数;对于HttpPost对象而言,可调…

    2022年4月9日
    270

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号