理解TiDB集群的P99计算方式

news/2024/5/19 14:53:40 标签: tidb

原文来源: https://tidb.net/blog/c38dd8ac

一、背景简介

在学习prometheus时,会遇到一个histogram_quantile()函数,用于对histogram类型的指标进行分位数计算,实际上这个函数就是histogram这个指标类型最常用的函数。

此函数在tidb的监控图表中有一个比较明显地方使用:计算P99/P999 Duration等延迟指标。

新人们对此函数的理解是可能是一个较漫长的过程,而我观察到很多解释此函数的博主们在解释这个函数的应用场景和原理时非常容易陷入 “知识诅咒” 的陷阱,即,写作者自身非常了解这个领域的知识,但潜意识中很容易忽略读者可能对该主题或领域不了解的事实。于是在涉及某些较为简单的专有名词时会一笔带过,在涉及某些较为核心的逻辑时容易高屋建瓴的做出解释但读者却一头雾水。

不同的人认知水平不同,而即便是同一个人的认知水平也随时间增长,因此所有人都不可避免的陷入知识诅咒的陷阱,为此本文因此尝试从另一种通俗的角度来理解,希望对读者有所帮助,同时也可以极大加深自己的理解。

二、基础知识

在介绍这个函数之前,简单的介绍一下prometheus的几种指标类型: 官网: Prometheus指标类型

  1. Counter:理解为一个单调递增的数字即可,适合进行请求数、错误数等累计监控,例如t1时间request_count为0,t1+10s时间计数为100,则容易得到过去10s内的请求速度约为10个/s
  2. Gauge:理解为一个非单调递增的数字即可,适合进行瞬时值监控,例如当前温度、内存使用量等,由于上报频率不可能设置的很高,因此相比Counter可能会丢失一些信息,但应用场景也很广
  3. Histogram:直方图,可以搜一下网上的直方图图片,即prometheus将位于同一范围内的数字归类到一个柱状体(bucket)内进行计数,形成一个向量(线性代数名词,这里理解为数组)除此之外还会记录实际的指标总数和总值,即一个Histogram包含一个直方图向量和2个类似Counter的指标(Prometheus server并不区分指标类型,对server来说只有time series)
  4. Summary:略,Histogram的进阶版本,不过一些编程语言的驱动可能并没有很好的支持这种类型,使用Histogram可以覆盖他的使用场景。

上述简短的解释不能真正展示4种数据类型的实质,但他们不是本篇重点因此不多说了。

三、从P99计算开始

首先列出tidb计算P99的查询语句,可以从tidb grafana中获取到: image.png

histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket{k8s_cluster="$k8s_cluster", tidb_cluster="$tidb_cluster"}[1m])) by (le))

因为同一个集群下的这俩标签都是一样的,我们将其简化为:

histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket[1m])) by (le))

使用postman可以快速查出他的结果:

GET /api/v1/query?query=histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket[1m])) by (le)) HTTP/1.1
Host: x.x.x.x:9090
{ 
	"status": "success", 
	"data": { 
		"resultType": "vector", 
		"result": [ 
			{ 
				"metric": {}, 
				"value": [ 1691657038.265, "0.051201495327102595" ] 
			} 
		] 
	} 
}

可以知道当前集群的P99指标约为51ms。

但是这个计算过程看起来较为繁琐,先是使用rate对tidb_server_handle_query_duration_seconds_bucket计算了过去1min的速率,然后使用sum by (le)进行了求和,最后才使用histogram_quantile函数计算99分位数。

该如何理解这种计算方式?

四、流程解析

首先 思考一个问题,当我们想要获取一个P99值时,一个最直观的存储监控值的办法是什么?

当然是tidb每处理一个请求都把耗时以Gauge形式存起来,然后等prometheus来收集!

然后我们发现这种办法是在是太差了,qps高的时候监控代理和prometheus都容易爆炸。

再来分析一下实际需求,我们实质上并不是要看所有请求的耗时,只是想了解一下某个时间点(或某一段时间内)数据库中99%的请求都低于多少ms,即P99。

然后发现我们似乎不用存储具体的耗时值,我们只需要先创建一个直方图(一个包含多个bucket的容器),例如:

{
	"耗时低于1s": 0,    // bucket 1, 标签 {le: 1}
	"耗时低于10s": 0,   // bucket 2, 标签 {le: 10}
	"耗时低于100s": 0,  // bucket 3, 标签 {le: 100}
	...                // bucket n, 标签 {le: ......}
}

然后每当有请求耗时需要存储时,在对应的bucket中计数+1,这样只要bucket的设置合理一些我们就可以轻易获得一个执行耗时的分布图。在此基础上计算P99就比较容易了。

当然,除了上述的向量之外,histogram还会存储一个总耗时值和总请求次数的计数器,不过计算P99用不到。

直方图指标有了,我们把他叫做 tidb_server_handle_query_duration_seconds_bucket ,prometheus每个收集间隔过来取一次即可。

接下来呢?

可以看到每个bucket其实也都是一个计数器,计数器的瞬时值一般没有多少意义,因此我们需要使用rate()函数进行速率计算: rate(tidb_server_handle_query_duration_seconds_bucket[1m]

tidb_server_handle_query_duration_seconds_bucket如之前所说,他其实是一个向量值,向量值和标量值(60s)进行除法这个了解一些线代基础的可以知道结果是一个每个元素都除以60s的新向量。这个新向量的label和以前一样,都是{le: 1}, {le: 10}这种。

tidb的请求类型很多,有select,update,insert还有analyze table,begin,commit等,我们希望统一进行计算,因此执行一个sum() group by le的操作,即: sum(rate(tidb_server_handle_query_duration_seconds_bucket[1m])) by (le)

同样的其结果依然是一个向量,我们只是按le进行了一次sql_type的聚合,至此我们终于可以使用 histogram_quantile(φ scalar, b instant-vector) 函数啦。这个函数接收2个参数,一个分位数值(例如0.99,0.999等),一个瞬时向量(这里就是rate处理之后的_bucket指标)。

至此 我们就得到了tidb的P99。至于histogram_quantile内部如何计算99分位数这里不再解释,因为为了更贴近真实结果有一些小的tricks解释起来需要多些几句,这不是本文重点。


http://www.niftyadmin.cn/n/4981393.html

相关文章

Python中的装饰器介绍

装饰器是Python编程语言中一种强大的特性,用于修改或增强函数或类的行为,而无需对它们本身进行修改。装饰器通常被用于在不改变原始代码的情况下,向函数或方法添加额外的功能,如日志记录、权限检查、数据格式转换等。装饰器本质上…

科技助力图书馆新趋势:机器人“图书管理员”展风采

原创 | 文 BFT机器人 PART1 机器人“图书管理员”横空出世 随着科技的日新月异,知识的获取变得更加方便快捷,图书馆不再只是借阅书籍的场所,其渐渐演变成了人们社交、休闲、学习的不二之选。在此场景下,“智能化图书馆”的概念深…

Pyecharts教程(九):使用Pyecharts绘制K线图的基本示例

Pyecharts教程(九):使用Pyecharts绘制K线图的基本示例 作者:安静到无声 个人主页 目录 Pyecharts教程(九):使用Pyecharts绘制K线图的基本示例完整代码推荐专栏引言: K线图是用于展示股票、期货等金融市场价格变动的一种图表形式。在Python中,可以使用Pyecharts库来绘制K线…

leetcode做题笔记110. 平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。 本题中,一棵高度平衡二叉树定义为: 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 思路一:递归 int height(struct TreeNode* root) {if (root NULL) {return…

leetcode做题笔记115. 不同的子序列

给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。 题目数据保证答案符合 32 位带符号整数范围。 思路一:动态规划 int numDistinct(char * s, char * t){int len1strlen(s),len2strlen(t);uint64_t dp[len11][len21];memset(dp,0,…

springboot实现 伪微信登录

众所周知,微信扫码登陆的功能,个人网站是无法申请的,我们想在本地想测一下微信登录也是无法实现。 要实现微信登录,首先你得是一个企业单位,有公章才能申请,申请还要花费300块大洋。 如果我们只是想学习和…

JDBC:更新数据库

JDBC:更新数据库 更新记录删除记录 为了更新数据库,您需要使用语句。但是,您不是调用executeQuery()方法,而是调用executeUpdate()方法。 可以对数据库执行两种类型的更新: 更新记录值删除记录 executeUpdate()方…

Java实现根据关键词搜索1688商品新品数据方法,1688API节课申请指南

要通过1688的API获取商品新品数据,您可以使用1688开放平台提供的接口来实现。以下是一种使用Java编程语言实现的示例,展示如何通过1688开放平台API获取商品新品数据: 首先,确保您已注册成为1688开放平台的开发者,并创…