想象你是一座城市交通指挥中心的负责人。全市的每条道路、每个路口都装了传感器,它们每隔 15 秒报一次数据:这条路今天总共通过了多少辆车、此刻这个路口堵不堵、从东向西的车流平均要等多久。这些数据被源源不断地存进一套大表里,每行记录着“哪个位置、哪个方向、什么时刻、数值多少”。几个月后,你想从这堆历史数据里回答“上周三早高峰哪条路最堵”“哪个方向的平均等待时间在恶化”,就需要一套专门的查询语言把数据捞出来算清楚。 这套系统在真实世界里就是 Prometheus,它是云原生监控的事实标准。本篇用“城市交通指挥中心”这个比喻,把 Prometheus 的数据模型、四种指标类型、PromQL 进阶语法、常见监控场景和性能优化讲明白。你不需要任何基础,跟着指挥中心的运转方式走一遍,就懂了 Prometheus 是怎么存数据、怎么查数据的。

岗位关联

对 AI 从业者而言,可观测性是模型服务的“仪表盘”——学会 Prometheus 与 PromQL,你就能把系统延迟、错误率、吞吐量变成随时可查的量化指标,这是 AI 工程落地的必备技能。

目录
  1. 可视化演示
  2. 时间序列模型
  3. 四种指标类型
  4. PromQL 基础语法
  5. 聚合与函数
  6. 典型监控场景
  7. 性能优化
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示四种Metric类型时间序列特征、PromQL查询执行流程及指标类型性能对比的三模态交互动画。

本节配有交互动效(进入视口后自动加载,约 3KB)。

四种Metric类型时间序列 Counter Gauge rate=c(b)-c(a) Counter单调递增Gauge可增可减Histogram分桶Summary分位数各有适用场景

时间序列模型

交通指挥中心的记录员每天会填一张固定格式的表:哪个路口、哪个方向、什么时刻、数值多少。这四个要素缺一不可,组合起来就是一条“时序记录”。在 Prometheus 里,这张表的“列名”叫指标名(metric name),“路口和方向”这些附加属性叫标签(labels),“时刻”叫时间戳,“数值”叫样本值。

Prometheus 中一条完整的时间序列可以用 指标名{标签组} 唯一标识。例如 http_requests_total{method="POST", status="200"} 表示“POST 请求中返回 200 的次数”。标签的意义在于给同一种指标切出多个维度:按接口切、按状态码切、按实例切,就像给每个路口加上了不同的路牌。

每个“样本”由三部分组成:时间戳(毫秒精度)、浮点数值、以及它们所属的序列标识。同一序列的样本按时间排成一条线,这就是“时间序列”名称的由来。

回到比喻:如果只用“车辆总数”而不写“哪个路口、哪个方向”,交通数据就是一团乱麻。Prometheus 的标签体系就是给每一条数据装上 GPS 坐标,让后续的查询可以精确到任何维度。

样本=(时间戳 t,  值 v)    ,序列={ 指标名,标签集合 }→样本序列\text{样本} = (\text{时间戳}\, t , \; \text{值}\, v) \;\;,\quad \text{序列} = \{\,\text{指标名}, \text{标签集合}\,\} \rightarrow \text{样本序列}
简单理解:序列是“路口的地址”,样本是“每个时刻的读数”,两者合一才能回答“谁在什么时候是多少”。
  1. 指标名定位每个监控对象都有一个名字,如 http_requests_total 表示累计请求数,它决定了这一行数据“在记什么”
    指标名=http_requests_total\text{指标名} = \text{http\_requests\_total}
  2. 标签集合切分用 {method="POST", status="200"} 进一步指定这是“POST 方法且返回 200”的那一路数据
    标签集合={method=POST,  status=200}\text{标签集合} = \{ \text{method} = \text{POST},\; \text{status} = \text{200} \}
  3. 时间戳与值括号前的部分唯一确定序列,后面的 样本值 @ 时间戳 表示某一瞬间的读数
    v(t)=3520att=1630000000v(t) = 3520 \quad \text{at} \quad t = 1630000000

四种指标类型

指挥中心有不同的传感器:有的传感器测“累计通过车辆数”(只会越来越多,除非重置),有的测“当前拥堵指数”(随时可升可降),还有的测“通过时间的分布”(多少车在 1 秒内通过、多少在 2 秒内)。Prometheus 把监控指标也分成四种类型,它们决定了查询时该用什么姿势处理数据。

Counter(计数器):只增不减,适合记录累计量,如请求总数、错误总数、CPU 总耗时。它的值是“从启动到现在的总数”,所以直接看数值没意义,要看增长速率。

Gauge(仪表盘):可升可降,适合记录瞬时状态,如当前内存使用量、队列长度、在线人数。看 Gauge 直接看当前值即可。

Histogram(直方图):把观测值分到多个桶里,统计每个桶的计数。它能算出 p50、p95、p99 等分位数,适合记录请求延迟这类分布型数据。

Summary(摘要):在客户端直接算好分位数并上报,好处是查询端省计算,坏处是分位数跨实例无法聚合(因为两个 Summary 的分位数不能简单相加)。

选指标类型就像给传感器选功能:你要知道“一天总过多少车”就装累计计数器,要知道“现在堵不堵”就装瞬时仪表盘,要知道“通行时间分布”就装直方图。选错了工具,后面所有查询都会很别扭。

Counter:  value←value+增量Gauge:  value←任意值\text{Counter}:\; \text{value} \gets \text{value} + \text{增量} \qquad\text{Gauge}:\; \text{value} \gets \text{任意值}
Histogram:  bucket[le=v]计数Summary:  客户端预计算分位数\text{Histogram}: \; \text{bucket}[le=v] \text{计数} \qquad\text{Summary}: \; \text{客户端预计算分位数}
选错类型会导致查询写不出来:对 Counter 用 avg 会得出无意义的平均数,对 Gauge 用 rate 会把瞬时值当成累计值去算,结果更离谱。
  1. Counter 语义只有 加法 和 重置为零 两种操作,数值单调不减,这保证了它适合做 rate 的原料
    C(t+Δt)=C(t)+新增量C(t+\Delta t) = C(t) + \text{新增量}
  2. Gauge 语义可以加减任意值,代表当前瞬间的状态快照
    G(t)=当前值∈(−∞,+∞)G(t) = \text{当前值} \in (-\infty, +\infty)
  3. Histogram 分桶规则每个桶记录“观测值 ≤ le 的样本数”,桶边界由 le 标签指定
    bucketi=#{x  ∣  x≤lei}\text{bucket}_i = \#\{ x \;|\; x \le le_i \}
  4. Summary 差异分位数在客户端算好,服务端只能得到 quantile 标签对应的直接数值
    q0.95=客户端计算得到的 p95 值q_{0.95} = \text{客户端计算得到的 p95 值}

PromQL 基础语法

现在数据存好了,指挥中心要回答第一个问题:“昨天下午 3 点,浦东大道这个路口总共有多少辆车经过?” PromQL 的查询方式和我们查交通记录一样:先定位到具体的序列,再决定要看哪段时间的数据。

PromQL 最基本的查询是指标选择器:直接写指标名,如 http_requests_total,会返回所有匹配该指标的序列(每个标签组合算一条)。如果想缩小范围,就用花括号加条件:http_requests_total{method="POST", status=~"5.."}。= 是精确匹配,=~ 是正则匹配,!= 和 !~ 是反向匹配。

选择器后面跟不同后缀,会得到两种不同的数据结构:

即时向量(instant vector):直接写 metric{...},返回的是“当前这一瞬间”每个序列的最新样本值。它像一个快照,适合画当前状态。

区间向量(range vector):写成 metric{...}[5m],返回的是“过去 5 分钟内”每个序列的所有样本集合。它像一段录像,是 rate 这类函数必需的输入。

观察者的直觉是对的:想用“每 15 秒采一个点”的数据看趋势,必须把一段时间内的点打包成一个集合,这就是区间向量。有了录像带(区间向量),才能计算变化率;只有一张照片(即时向量),算不出任何变化。

instant vector:{序列i↦vi(tnow)}\text{instant vector}: \quad \{ \text{序列}_i \mapsto v_i(t_{\text{now}}) \}
range vector:{序列i↦[vi(t1),vi(t2),…,vi(t5)]}\text{range vector}: \quad \{ \text{序列}_i \mapsto [ v_i(t_1), v_i(t_2), \ldots, v_i(t_5) ] \}
后续所有函数都建立在“向量”之上:即时向量用于直接计算,区间向量用于取窗口内的样本集合。
  1. 标签精确匹配{code="200"} 只保留带 code="200" 标签的序列
    保留序列:  labels∋(code=200)\text{保留序列}: \; \text{labels} \ni (\text{code}=200)
  2. 正则匹配{code=~"5.."} 匹配 code 为 500~599 的序列,一次性筛出所有 5xx 错误
    code∈{500,501,…,599}\text{code} \in \{500, 501, \ldots, 599\}
  3. 即时向量取值对筛选出的每个序列取最新样本
    v(tnow)=last value of seriesv(t_{\text{now}}) = \text{last value of series}
  4. 区间向量采样取 [5m] 内落在窗口中的所有锚定样本
    samples={s(t)  ∣  t∈[tnow−5m, tnow]}\text{samples} = \{ s(t) \;|\; t \in [t_{\text{now}} - 5\text{m},\, t_{\text{now}}] \}

聚合与函数

指挥中心的老大问:“全市所有路口的平均拥堵指数是多少?哪个路口最堵?过去 1 小时总共有多少辆车通过?” 这些都不能靠单条序列回答,需要一套“统计手术刀”对数据做加工——这就是 PromQL 的聚合操作符和函数。

rate 与 increase:rate(counter[5m]) 计算区间内 Counter 指标“每秒平均增量”,它自动处理了计数器重置问题(重启后归零再增长也能算对)。increase(counter[5m]) 则返回区间内的总增量。两者是分析 Counter 的核心工具。

sum by / sum without:sum(rate(http_requests_total[5m])) by (instance) 表示“把每个实例的增长率加起来,按实例分组”。这里的 by 相当于 SQL 里的 GROUP BY,without 相当于除某标签外的分组。

topk / bottomk:topk(5, sum(rate(...)) by (path)) 返回数值最大的 5 条序列,常用于“找出最热的接口 Top5”。

histogram_quantile:对 Histogram 桶数据计算分位数。histogram_quantile(0.95, sum(rate(duration_bucket[5m])) by (le)) 是求 p95 延迟的标答写法。

这就像指挥中心把所有路口的流量报表汇总:先算每个路口“每分钟增加几辆车”(rate),再把浦东的所有路口加起来(sum by 区域),最后排出最堵的前 5 个(topk)。层层递进,数据就从“枯燥的读数”变成了“有洞察的结论”。

rate(c[t])=last(c)−first(c)tlast−tfirst\text{rate}(c[t]) = \frac{\text{last}(c) - \text{first}(c)}{t_{\text{last}} - t_{\text{first}}}
sumby(X)(rate(c[t]))=∑x∈Xrate(cx[t])\text{sum}_{by(X)}\big(\text{rate}(c[t])\big) = \sum_{x \in X} \text{rate}(c_x[t])
两条公式分别代表了“时间维度的聚合”(把序列内部压缩成速率)和“空间维度的聚合”(把多个序列合并成一组)。
  1. rate 取首尾差值在 5 分钟窗口内,用最后一个样本减去第一个样本,得到这段时间的增量
    Δ=C(t5)−C(t1)\Delta = C(t_5) - C(t_1)
  2. rate 除以时间把增量除以时间跨度,得到每秒增长率
    rate=Δ(t5−t1)  秒\text{rate} = \frac{\Delta}{(t_5 - t_1) \;\text{秒}}
  3. sum 分组求和先按 instance 分组,再把组内的所有速率相加
    instancei  ↦  ∑j∈instanceiratej\text{instance}_i \; \mapsto \; \sum_{j \in \text{instance}_i} \text{rate}_j
  4. histogram_quantile 线性插值根据桶边界 le 和各个桶的计数,在目标分位所在桶内做线性插值得到近似值
    q=lelower+(目标位置−累计计数lower)桶内计数×桶宽q = le_{\text{lower}} + \frac{(\text{目标位置} - \text{累计计数}_{\text{lower}})}{ \text{桶内计数}} \times \text{桶宽}

典型监控场景

工具齐全了,指挥中心开始实战。三个最经典的监控场景,对应三套模板级查询,SRE 和可观测性工程师几乎天天都要写。

CPU 使用率:node_cpu_seconds_total 是 Counter,每种模式(user、system、idle)一个序列。空闲模式占比越低,说明 CPU 越忙。标准查询:

请求错误率:要算“最近 5 分钟 5xx 请求占总请求的比例”,用两个 rate 相除:

延迟分位数:配合 Histogram 桶数据,查询 p95 延迟:

这三个场景像指挥中心每天要回答的例行问题:“路网整体忙不忙”“有没有异常事故(错误)”“群众通行体验怎么样(延迟)”。模板背下来,改改指标名和标签就能套用到任何服务上。

CPU利用率=1−rate(node_cpu_seconds_total{mode="idle"}[5m])count(node_cpu_seconds_total)×5−…\text{CPU利用率} = 1 - \frac{\text{rate}(node\_cpu\_seconds\_total\{mode="idle"\}[5m])}{\text{count}(node\_cpu\_seconds\_total) \times 5 - \dots}
错误率=sum(rate(http_requests_total{status= "5.."}[5m]))sum(rate(http_requests_total[5m]))\text{错误率} = \frac{\text{sum}(\text{rate}(http\_requests\_total\{status=~"5.."\}[5m]))}{\text{sum}(\text{rate}(http\_requests\_total[5m]))}
p95=histogram_quantile(0.95,  sum(rate(http_request_duration_seconds_bucket[5m]))by (le))p95 = \text{histogram\_quantile}\Big(0.95,\; \text{sum}(\text{rate}(http\_request\_duration\_seconds\_bucket[5m])) \text{by (le)}\Big)
这三条公式是 PromQL 的“三大件”:减法看剩余、除法看比例、分位数看分布。掌握它们,就能覆盖绝大多数日常监控需求。
  1. CPU 空闲率算 mode="idle" 的每秒增长率,即空闲时间占比
    idle_rate=rate(node_cpu_seconds_total{mode="idle"}[5m])\text{idle\_rate} = \text{rate}(node\_cpu\_seconds\_total\{mode="idle"\}[5m])
  2. CPU 使用率补充用 1 - idle_rate / sum(所有模式) 把“空闲率”翻转成“使用率”
    usage=1−idle_ratesum(rate(node_cpu_seconds_total[5m]))\text{usage} = 1 - \frac{\text{idle\_rate}}{\text{sum}(\text{rate}(node\_cpu\_seconds\_total[5m]))}
  3. 错误占比分子是 5xx 错误增长率,分母是所有请求增长率,相除得到比例
    error_ratio=sum(rate(…{status= "5.."}[5m]))sum(rate(…[5m]))\text{error\_ratio} = \frac{\text{sum}(\text{rate}(\dots\{status=~"5.."\}[5m]))}{\text{sum}(\text{rate}(\dots[5m]))}
  4. 分位数累计把所有桶的 rate 按 le 加起来,得到累计分布
    cumulative=sum(rate(bucket[5m]))  by (le)\text{cumulative} = \text{sum}(\text{rate}(bucket[5m])) \;\text{by (le)}

性能优化

随着城市越来越大,传感器越来越多,指挥中心发现查询越来越慢。这就是 Prometheus 的“性能之痛”,主要有三个优化方向,也是生产环境中最常踩的坑。

控制标签基数:标签的组合数等于“基数”。如果一个标签有 100 种取值,两个标签就有 10,000 种组合,序列数量爆炸式增长(高基数)。比如把 user_id 这种高基数标签加进指标,每个用户都生成一条序列,存储和查询都会被拖垮。解决办法是把高基数信息从标签中移除,放到日志或单独的存储里。

录制规则(recording rules):把常用且复杂的查询预计算,存成新指标。比如把 sum(rate(x[5m])) by (instance) 每小时算好存成 job:http_requests:rate5m,查询时直接读结果,不再实时计算。

合理选择时间窗口:[5m] 比 [1h] 快得多,因为要读的样本少;能用即时向量解决的就不用大区间向量。另外,长范围查询搭配大跨度 step 能显著降低计算量。

回到交通中心:如果给每辆车单独装一个传感器(高基数),系统必然崩溃;不如按路口、方向这种“可控维度”来装。常用报表提前印好(录制规则),临时查询才现算。这既是架构设计,也是查询习惯。

序列数量=∏l∈标签∣取值数量l∣\text{序列数量} = \prod_{l \in \text{标签}} |\text{取值数量}_l|
录制规则:新指标←预计算结果(周期性刷新)\text{录制规则:} \quad \text{新指标} \gets \text{预计算结果(周期性刷新)}
优化本质是在“信息粒度”和“计算成本”之间做trade-off:保留必要维度,砍掉冗余维度,把热点计算前移。
  1. 基数乘法法则序列总数是每个标签取值数的乘积,标签多了组合数指数级增长
    #series=∏∣dimi∣\#\text{series} = \prod |\text{dim}_i|
  2. 录制规则缓存把每 5 分钟才变一次的聚合结果提前算好,查询直接命中缓存
    cached=compute(query)  every 1m\text{cached} = \text{compute}(query) \;\text{every 1m}
  3. 窗口大小与样本量区间向量的大小与窗口时长成正比,窗口越大,扫描的样本越多
    scan∝window_seconds\text{scan} \propto \text{window\_seconds}

AI 应用场景

AI 服务健康度监控

阶段:模型部署与运维

解决的问题:AI 模型上线后,传统方式靠人工盯日志、凭感觉判断服务是否异常。Prometheus 配合 PromQL 可以自动量化“在线推理服务的 P95 延迟、错误率、GPU 利用率”,并设置告警规则,在指标异常时自动触发通知。

具体案例:一家公司的推荐系统使用 TensorFlow Serving 部署模型,每个模型版本暴露 tfs_http_request_duration_seconds_bucket(直方图)。SRE 使用 histogram_quantile(0.99, sum(rate(...[5m])) by (le, model_version)) 监控每个模型版本的延迟,当 p99 超过 200ms 时自动告警,并配合 sum by (instance) 定位是哪台推理机出了问题。

Prompt 调用量的成本分析

阶段:LLM 应用运营

解决的问题:调用大模型 API 的费用与调用量直接相关。通过为每个调用埋点 llm_tokens_total{model="gpt-4", type="prompt"} 这样的 Counter,用 PromQL 的 increase(...[1h]) 就能实时统计每小时消耗的 token 数,按模型、按用户维度聚合出成本趋势。

具体案例:一个 AI 对话产品把每次请求的模型名、用户 ID(低基数化后)写入标签,用 sum(increase(llm_tokens_total[1h])) by (model) 对比不同模型的成本占比,发现某个模型调用量突然飙升后,及时为特定用户配置了配额限制。

Python 演示

python77 行
"""PromQL 查询模拟器:用 Python 模拟 Counter 时间序列与 rate 计算""" import mathimport random  class MockCounter:    """模拟一个 Counter 指标:只增不减,可能发生重置"""     def __init__(self, name, base=100, growth=2.0):        self.name = name        self.samples = []  # 每个元素是 (timestamp, value)        self._current = base        self._growth = growth        self._t = 0     def collect(self, seconds):        """采集 seconds 个采样点,间隔 15 秒"""        for _ in range(seconds):            self._t += 15            # 模拟真实环境:有增量、有噪声、偶尔重启归零            if random.random() < 0.05:                self._current = 0            self._current += self._growth + random.uniform(-0.5, 0.8)            self.samples.append((self._t, self._current))        return len(self.samples)  def rate(series, window_start, window_end):    """模拟 PromQL 的 rate 函数:返回窗口内每秒平均增量"""    in_window = [(t, v) for t, v in series if window_start <= t <= window_end]    if len(in_window) < 2:        return 0.0    first = in_window[0]    last = in_window[-1]    delta = last[1] - first[1]    # 处理计数器重置:如果出现负增量,把首值之前的累计量补回来    for i in range(1, len(in_window)):        if in_window[i][1] < in_window[i - 1][1]:            delta += in_window[i - 1][1]    dt = (last[0] - first[0]) / 1000.0    return max(delta / dt if dt > 0 else 0.0, 0.0)  def topk(series_dict, k):    """模拟 topk 聚合:按当前速率排序取前 k 名"""    return sorted(series_dict.items(), key=lambda kv: kv[1], reverse=True)[:k]  def main():    random.seed(42)    # 模拟三个服务的请求计数器    services = ["api", "worker", "gateway"]    counters = {}    for svc in services:        c = MockCounter("http_requests_total", base=random.randint(500, 1000))        c.collect(40)  # 40 个采样点,约 10 分钟        counters[svc] = c.samples     # 模拟过去 5 分钟的区间向量查询窗口    query_end = counters[services[0]][-1][0]    query_start = query_end - 300  # 5 分钟 = 300 秒     print("=== 各服务最近 5 分钟请求速率(rate)===")    rates = {}    for svc in services:        r = rate(counters[svc], query_start, query_end)        rates[svc] = r        print(f"  {svc}: {r:.2f} req/s")     print("\n=== topk(2, rate) 最热服务 ===")    for svc, r in topk(rates, 2):        print(f"  {svc}: {r:.2f} req/s")  if __name__ == "__main__":    main()
目的:这个模拟器展示 PromQL 核心函数 rate 和聚合操作 topk 的计算原理——用 Python 模拟了 Counter 的采样、重置处理和窗口速率计算。
  1. class MockCounter模拟一个只增不减、偶尔重置的 Counter 指标,采样间隔 15 秒
  2. c.collect(40)采集 40 个样本点,对应 Prometheus 的拉起(scrape)行为
  3. def rate(series, ...)实现 rate 函数:取窗口首尾差值除以时间跨度
  4. if in_window[i][1] < in_window[i-1][1]检测计数器重置(值突然变小),把重置前的累计值补回增量
  5. topk(series_dict, 2)按速率降序排序取前 2,等价于 PromQL 的 topk(2, sum(rate(...)) by (service))

常见误区

误区“Gauge 也是指标,我可以用 rate(内存使用量[5m]) 来看内存的变化率吧?”

正解rate 只适用于 Counter。Counter 是累计值,其差值有意义;Gauge 是瞬时值,本身上下限和波动没有“累计”含义,算 rate 会得到一个误导性的“每秒增量”,没有实际价值。要看 Gauge 趋势直接用原始值,或加 avg_over_time 平滑。

为何常见:初学者见到“变化率”这个词就想套 rate,忽略了 Counter 和 Gauge 的底层语义差异。

误区“计数器重启归零了,那我直接用最后一个值减去第一个值就是增量。”

正解Prometheus 的 rate 函数内部会自动感知重置(值突然变小)并做补偿,这也是官方推荐用 rate 而不是手写 last - first 的原因。如果自己实现类似逻辑,必须像模拟器代码那样检测负增量并补回,否则会得到错误甚至负数。

为何常见:在本地玩具数据上“不会重置”,所以很多人压根没意识到重置的存在;生产环境服务重启很常见。

误区“想按用户维度分析,那把 user_id 加到标签里不就行了?”

正解user_id 的取值可能是百万级,加进去直接导致序列数爆炸(基数乘法法则),Prometheus 存储和查询都会被拖垮。高基数维度应该走日志系统或单独的数据存储,指标只保留低基数标签(instance、job、method、path 等可控维度)。

为何常见:Prometheus 的标签太方便了,随手一加就能看;但人往往会忽略序列数量是“乘积”而非“求和”这一事实。

误区“我想看更长时间的走势,把 [5m] 改成 [1h] 应该没问题吧?”

正解区间向量窗口越大,需要读的样本越多,计算量线性增长。查询 1 小时窗口的数据比 5 分钟慢几十倍。正确姿势是使用录制规则预聚合长期趋势指标,或者用 step 参数加大采样步长,减少计算点数量。

为何常见:人倾向于“一次查出所有数据”,忽略了底层存储和计算成本。

误区“求平均延迟直接 `avg(http_request_duration_seconds)` 不就好了?”

正解request_duration 是 Histogram 型指标,原始数据是一组 <bucket>_bucket 计数,没有直接的单值可供 avg。正确做法是用 histogram_quantile 算分位数,或用桶内总和与总数算出平均;直接 avg 要么报错,要么算出无意义的数字。

为何常见:很多指标名看起来是“数值”,但实际底层结构是桶数组,初学者按普通数值指标处理自然踩坑。

面试题

Q1. Prometheus 中一条完整的时间序列由哪些核心要素唯一确定?

单选题 · easy

  • A.指标名和时间戳
  • B.指标名和标签集合← 正确答案
  • C.标签集合和样本值
  • D.时间戳和样本值

答案:B。指标名 + 标签集合唯一确定一条时间序列,时间戳和样本值只是这条序列上不断增加的点。就像交通记录表的“路口地址 + 方向”唯一确定一条记录线,时间戳和数值是这条线上不断追加的读数。

Q2. 下列哪种指标类型适合记录“当前在线人数”?

单选题 · easy

  • A.Counter
  • B.Gauge← 正确答案
  • C.Histogram
  • D.Summary

答案:B。在线人数可升可降,是典型的瞬时状态,对应 Gauge。Counter 只增不减,适合累计量;Histogram 和 Summary 用于分布型数据。选 Gauge 就像“当前拥堵指数”用仪表盘传感器测量一样直观。

Q3. 关于 rate(counter[5m]) 的说法,下列哪项正确?

单选题 · mid

  • A.返回 5 分钟内所有样本的平均值
  • B.返回 5 分钟内该计数器的最小值
  • C.返回 5 分钟内该计数器的每秒平均增量← 正确答案
  • D.返回 5 分钟内该计数器的最大变化幅度

答案:C。rate 通过首尾差值除以时间跨度,得到每秒平均增量,并自动补偿计数器重置。重点在于“增量 / 时间”而非“样本平均值”,所以 C 正确。它回答的是“这段时间每秒涨了多少”,而不是“数值水平是多少”。

Q4. 要给所有实例的请求速率按实例分组求和,正确写法是?

单选题 · mid

  • A.rate(sum(http_requests_total[5m]))
  • B.sum(rate(http_requests_total[5m])) by (instance)← 正确答案
  • C.rate(http_requests_total[5m]) by (instance)
  • D.sum(http_requests_total[5m]) by (instance)

答案:B。先对每台实例分别算 rate,再按 instance 分组求和,即先时间聚合再空间聚合。B 是对的。A 是“先求和再算速率”,把不同实例混在一起,语义不对;C 缺少 sum;D 直接用 sum 处理 Counter 原始值,量纲不对。

Q5. 下列哪些指标类型适合存储“请求延迟”这类分布型数据?哪些查询能正确得到 p95?

多选题 · mid

  • A.用 Counter 记录每次延迟,直接查原始值
  • B.用 Gauge 记录延迟平均值,再用 avg 聚合
  • C.用 Histogram 分桶记录,再用 histogram_quantile 计算← 正确答案
  • D.用 Summary 在客户端预计算分位数再上报← 正确答案

答案:CD。延迟是分布型数据,标准做法是 Histogram 或 Summary。Histogram 服务端算分位数可跨实例聚合(C 对),Summary 客户端算好但无法跨实例聚合(D 也对)。A 的 Counter 只增不减没有延迟语义,B 的 Gauge 平均值丢失分布信息,都不合理。

Q6. PromQL 中 http_requests_total{status=~"5.."} 的含义是?

单选题 · easy

  • A.匹配 status 标签恰好等于字符串 "5.." 的序列
  • B.匹配 status 标签以 5 开头的三位数(如 500-599)的序列← 正确答案
  • C.匹配所有状态码小于 500 的序列
  • D.匹配 status 标签含有数字 5 的序列

答案:B。=~ 是正则匹配,5.. 匹配以 5 开头、后跟任意两个字符的值,对应 500~599 的 5xx 错误。它像一个筛选漏斗,把 5xx 的序列从所有 HTTP 状态码中捞出来。

Q7. 在 Prometheus 性能调优中,以下哪些措施能有效降低高基数的负面影响?

多选题 · hard

  • A.将 user_id 等取值极多的标签加入重要监控指标
  • B.用录制规则把高开销查询预计算成新指标← 正确答案
  • C.移除指标中取值超过百级的低价值标签← 正确答案
  • D.查询时尽量使用数小时以上的超长区间向量

答案:BC。控制标签基数是核心思路。B 录制规则把昂贵的聚合预计算好,查询变快;C 移除低价值高基数的标签也是有效手段。A 会加剧基数爆炸,D 超长窗口会让查询更慢,都不符合优化方向。

Q8. 录制规则(recording rules)的核心价值是什么?

单选题 · mid

  • A.让标签基数所有查询都缩短到 1 秒
  • B.把高频复杂查询预先算好并缓存为普通指标← 正确答案
  • C.自动删除所有历史过期时间序列
  • D.让监控系统在断网时离线计算

答案:B。录制规则周期性地把复杂聚合表达式算好,存储成一个新指标,查询直接读结果,避免实时计算的高开销。它像把常用报表提前印好,而不是每次现场统计。其余选项与录制规则机制不匹配。

Q9. 表达式 histogram_quantile(0.95, sum(rate(http_duration_bucket[5m])) by (le)) 中 by (le) 的作用是?

单选题 · hard

  • A.按接口路径分组再计算分位数
  • B.只保留 le 标签并对其余标签做求和聚合← 正确答案
  • C.过滤掉 le 值大于 0.95 的桶
  • D.把桶数据按时间戳分组

答案:B。by (le) 表示“除了 le(桶边界)之外的所有标签都聚合掉(求和)”,得到一条只按桶边界展开的累计分布曲线,histogram_quantile 才能正确插值。这是 Histogram 分位数查询的标准写法。A 和 C 都误解了 by 与 le 的作用。

Q10. Counter 类型指标在进程重启后归零再增长,使用 rate 计算时为什么不会出错?

多选题 · extreme

  • A.rate 会自动检测样本值突然变小的重置事件← 正确答案
  • B.rate 在检测到重置后会把重置前的累计值补加回来← 正确答案
  • C.rate 会丢弃所有小于前一个值的样本所在区间
  • D.Prometheus 内部为 Counter 单独存储重启前快照

答案:AB。rate 实现中会遍历窗口内的样本,发现后一个值小于前一个值时判定发生了重置,并在计算增量时补加被重置掉的累计量(A、B 对)。它并不会丢弃区间(C 错),也不依赖重启前快照——Prometheus 不额外存储这种快照(D 错)。这种重置补偿机制正是官方建议总是用 rate 而不是手动做首尾差的原因。