写在最前

prometheus判定高基数的三种方法

  1. prometheus tsdb的统计接口
  2. prometheus 可以根据query_log中的queryPreparationTime来定位
  3. prometheus 通过count by 统计

什么是高基数 high-cardinality

基数广义上是指集合中值的数量

在数据库领域,基数是指数据库的特定列或字段中包含的唯一值的数量。

时间序列数据集的基数通常由每个单独的索引列的基数的叉积定义

高基数示例:工业物联网

  • 想象一下一个IoT场景,其中某个采石场中有大量的重型设备在开采岩石,破碎岩石和分选岩石。
  • 假设有10,000件设备,每个设备带有100个传感器,运行10个不同的固件版本,分布在100个站点中:
timestamp  | temper | mem_f | equipm | senso | firmwar   | sit  | (lat,long)
           | ature  | ree   | ent_id | r_id  | e_version | e_id |
--------------------+-------+--------+-------------------+------+-----------
2019-04-04 | 85.2   | 10.2  | 1      | 98    |  1.0      | 4    | (x,y) 
09:00:00   |        |       |        |       |           |      | 
2019-04-04 | 68.8   | 16.0  | 72     | 12    |  1.1      | 20   | (x1,y1)
09:00:00   |        |       |        |       |           |      |     
2019-04-04 | 100.0  | 0.0   | 34     | 58    |  2.1      | 55   | (x2,y2) 
09:00:00   |        |       |        |       |           |      |      
2019-04-04 | 84.8   | 9.8   | 12     | 75    |  1.4      | 81   | (x3,y3)
09:00:00   |        |       |        |       |           |      |   
2019-04-04 | 68.7   | 16.0  | 89     | 4     |  2.1      | 13   | (x4,y4)
09:00:00   |        |       |        |       |           |      | 
...        |        |       |        |       |           |      | 
      
  • 然后,此数据集的最大基数变为10亿[10,000 x 100 x 10 x 100]。
  • 现在,假设设备也可以移动,并且我们想存储精确的GPS位置(纬度,经度),并将其用作索引的元数据进行查询。因为(lat,long)是一个连续字段(与诸如equipment_id之类的离散字段相对),所以通过在位置上建立索引,此数据集的最大基数现在无限大(无界)。

高基数查询

  • 通俗的说就是返回的series或者查询到的series数量过多
  • 查询表现出来返回时间较长,对应调用服务端资源较多的查询
  • 数量多少算多 10w~100w
  • 一般我们定义在1小时内的range_query 响应时间超过3秒则认为较重了

prometheus判定高基数的三种方法

  1. prometheus tsdb的统计接口
  2. prometheus 可以根据query_log中的queryPreparationTime来定位
  3. prometheus 通过count by 统计

方法一 tsdb的统计接口

  • http://192.168.43.114:9090/ts...
  • 接口地址 /api/v1/status/tsdb
  • 是基于内存中的倒排索引 算最大堆取 top10
  • 10个最多的metric_name排序

    seriesCountByMetricName: [{name: "namedprocess_namegroup_memory_bytes", value: 245},…]
    0: {name: "namedprocess_namegroup_memory_bytes", value: 245}
    1: {name: "namedprocess_namegroup_states", value: 245}
    2: {name: "mysql_global_status_commands_total", value: 148}
    3: {name: "namedprocess_namegroup_context_switches_total", value: 98}
    4: {name: "namedprocess_namegroup_cpu_seconds_total", value: 98}
    5: {name: "node_scrape_collector_success", value: 80}
    6: {name: "node_scrape_collector_duration_seconds", value: 80}
    7: {name: "namedprocess_namegroup_threads_wchan", value: 73}
    8: {name: "namedprocess_namegroup_thread_cpu_seconds_total", value: 66}
    9: {name: "namedprocess_namegroup_thread_io_bytes_total", value: 66}
  • 思考采集器如果不是prometheus怎么办?

    • 比如m3db没有提供高基数查询的接口
  • 源码解析

    // Stats calculates the cardinality statistics from postings.
    func (p *MemPostings) Stats(label string) *PostingsStats {
      const maxNumOfRecords = 10
      var size uint64
    
      p.mtx.RLock()
    
      metrics := &maxHeap{}
      labels := &maxHeap{}
      labelValueLength := &maxHeap{}
      labelValuePairs := &maxHeap{}
      numLabelPairs := 0
    
      metrics.init(maxNumOfRecords)
      labels.init(maxNumOfRecords)
      labelValueLength.init(maxNumOfRecords)
      labelValuePairs.init(maxNumOfRecords)
    
      for n, e := range p.m {
          if n == "" {
              continue
          }
          labels.push(Stat{Name: n, Count: uint64(len(e))})
          numLabelPairs += len(e)
          size = 0
          for name, values := range e {
              if n == label {
                  metrics.push(Stat{Name: name, Count: uint64(len(values))})
              }
              labelValuePairs.push(Stat{Name: n + "=" + name, Count: uint64(len(values))})
              size += uint64(len(name))
          }
          labelValueLength.push(Stat{Name: n, Count: size})
      }
    
      p.mtx.RUnlock()
    
      return &PostingsStats{
          CardinalityMetricsStats: metrics.get(),
          CardinalityLabelStats:   labels.get(),
          LabelValueStats:         labelValueLength.get(),
          LabelValuePairsStats:    labelValuePairs.get(),
          NumLabelPairs:           numLabelPairs,
      }
    }

方法二 query_log

  • 可以根据log中的queryPreparationTime来定位

方法三 通过count统计

topk(5,count({__name__=~".+"}) by(__name__) > 100 )
  • scrape_samples_scraped 可以说明job的instance维度sample数量,也能够定位

ning1875
167 声望67 粉丝

k8s/prometheus/cicd运维开发专家,想进阶的dy搜 小乙运维杂货铺