乐趣区

关于prometheus:prometheus-错开时间拉取scrape-jitterSeed分析

prometheus 拉取 target 的 metrics 时,都是错开工夫别离拉取的,这样有以下益处:

  • 防止 prometheus 实例启动太多的 goroutine 同时拉取;
  • 多 prometheus 实例拉取同一个 target 时,防止同时拉取对 target 造成流量压力;

单 prometheus 实例内的不同 target

单实例内的不同 target,不是在某个时刻一起拉取,而是错开工夫别离拉取。比方 scrape_interval=30s,不同 target 的拉取工夫如下:

  • job=prometheus:20 秒、50 秒拉取;
  • job=node-exporter:04 秒、34 秒拉取;
  • job=pvc-test:07 秒、37 秒拉取;
> select * from up where job='prometheus' order by time desc limit 5;
name: up
time                     __name__ __type__ instance       job        value
----                     -------- -------- --------       ---        -----
2021-09-24T05:52:20.665Z up       gauge    localhost:9090 prometheus 1
2021-09-24T05:51:50.665Z up       gauge    localhost:9090 prometheus 1
2021-09-24T05:51:20.665Z up       gauge    localhost:9090 prometheus 1
2021-09-24T05:50:50.665Z up       gauge    localhost:9090 prometheus 1
2021-09-24T05:50:20.665Z up       gauge    localhost:9090 prometheus 1
>
> select * from up where job='node-exporter' order by time desc limit 5;
name: up
time                     __name__ __type__ instance        job           value
----                     -------- -------- --------        ---           -----
2021-09-24T05:52:34.184Z up       gauge    10.21.1.74:9100 node-exporter 0
2021-09-24T05:52:04.183Z up       gauge    10.21.1.74:9100 node-exporter 0
2021-09-24T05:51:34.183Z up       gauge    10.21.1.74:9100 node-exporter 0
2021-09-24T05:51:04.183Z up       gauge    10.21.1.74:9100 node-exporter 0
2021-09-24T05:50:34.183Z up       gauge    10.21.1.74:9100 node-exporter 0
>
> select * from up where job='pvc-test' order by time desc limit 5;
name: up
time                     __name__ __type__ instance       job      value
----                     -------- -------- --------       ---      -----
2021-09-24T05:53:07.315Z up       gauge    127.0.0.1:9999 pvc-test 0
2021-09-24T05:52:37.315Z up       gauge    127.0.0.1:9999 pvc-test 0
2021-09-24T05:52:07.315Z up       gauge    127.0.0.1:9999 pvc-test 0
2021-09-24T05:51:37.315Z up       gauge    127.0.0.1:9999 pvc-test 0
2021-09-24T05:51:07.315Z up       gauge    127.0.0.1:9999 pvc-test 0

多 prometheus 实例

在 HA 场景下,不同的 prometheus 实例可能拉取雷同的 target。
多个 prometheus 实例同时启动时,开始拉取的工夫也要随机错开,否则会呈现多个实例同时拉取同一个 target 的景象。

实现机制

通过全局的 scrape jitterSeed 实现:

  • 不同实例:jitterSeed 是跟特定 prometheus 实例相干的 uint64 哈希值,不同实例的 jitterSeed 不同;
  • 单实例不同 target: 应用 target 哈希值与 jitterSeed 做异或操作,失去 target 拉取的启动工夫,不同的 target 启动工夫不同;

prometheus 实例的 scrape jitterSeed 的计算

// unit64
jitterSeed=hash({hostname}+{extertalLabels})

不同实例的 hostname/externalLabels 个别不同,故计算出来的 jitterSeed 值也不同。

// scrape/manager.go
// setJitterSeed calculates a global jitterSeed per server relying on extra label set.
func (m *Manager) setJitterSeed(labels labels.Labels) error {h := fnv.New64a()
    hostname, err := getFqdn()
    if err != nil {return err}
    if _, err := fmt.Fprintf(h, "%s%s", hostname, labels.String()); err != nil {return err}
    m.jitterSeed = h.Sum64()
    return nil
}

prometheus 实例内 target 拉取启动工夫的计算
target 被拉取前,先随机期待一个工夫,而后再用 interval 固定距离循环拉取:

// scrape/scrape.go
func (sl *scrapeLoop) run(interval, timeout time.Duration, errc chan<- error) {
    // 期待一段时间再开始
    select {case <-time.After(sl.scraper.offset(interval, sl.jitterSeed)):
        // Continue after a scraping offset.
    case <-sl.ctx.Done():
        close(sl.stopped)
        return
    }
    // interval 固定距离拉取
    ticker := time.NewTicker(interval)
    defer ticker.Stop()
    
    for {
        // HTTP 拉取
        last = sl.scrapeAndReport(interval, timeout, last, errc)
        select {
        ......
        case <-ticker.C:
        }
    }
}   

不同 target 有不同的等待时间,这样就能够将不同 target 的开始工夫错开。

//target 等待时间的计算方法
offset=((hash(target) ^ jitterSeed) % interval)

代码实现:

// scrape/target.go
// offset returns the time until the next scrape cycle for the target.
// It includes the global server jitterSeed for scrapes from multiple Prometheus to try to be at different times.
func (t *Target) offset(interval time.Duration, jitterSeed uint64) time.Duration {now := time.Now().UnixNano()

    // Base is a pinned to absolute time, no matter how often offset is called.
    var (base   = int64(interval) - now%int64(interval)
        offset = (t.hash() ^ jitterSeed) % uint64(interval)
        next   = base + int64(offset)
    )

    if next > int64(interval) {next -= int64(interval)
    }
    return time.Duration(next)
}
退出移动版