本週在配置Prometheus的遠端存儲的時,發現配置完運行一段時間後,日誌中有警告信息: "Skipping resharding, last successful send was beyond threshold";排查後發現,原來Prometheus對remote write的配置在最佳實踐中早有說起相關優化建議。git
這裏測試把InfluxDB做爲Prometheus的遠端存儲,不作配置優化的狀況下,咱們先來看一下詳細的報錯信息:github
ts=2020-05-14T03:07:15.114Z caller=dedupe.go:112 component=remote level=warn remote_name=11a319 url="http://192.168.1.1:8086/api/v1/prom/write?db=prometheus" msg="Skipping resharding, last successful send was beyond threshold" lastSendTimestamp=1589425620 minSendTimestamp=1589425625
複製代碼
日誌信息的大意爲「上次成功發送超出閥值」;說實話,這裏的日誌提示的仍是比較晦澀;不由讓人反問:「超出什麼閥值」?提取日誌中的關鍵字,在GitHub Prometheus的源碼中搜索,咱們一步步來看下具體的代碼實現:golang
首先定義了一個名爲"QueueMananger"的結構體,暫且稱他爲"隊列管理器"。Shou you the code:shell
type QueueManager struct {
// https://golang.org/pkg/sync/atomic/#pkg-note-BUG
lastSendTimestamp int64
logger log.Logger
flushDeadline time.Duration
cfg config.QueueConfig
externalLabels labels.Labels
relabelConfigs []*relabel.Config
watcher *wal.Watcher
clientMtx sync.RWMutex
storeClient StorageClient
seriesMtx sync.Mutex
seriesLabels map[uint64]labels.Labels
seriesSegmentIndexes map[uint64]int
droppedSeries map[uint64]struct{}
shards *shards
numShards int
reshardChan chan int
quit chan struct{}
wg sync.WaitGroup
samplesIn, samplesDropped, samplesOut, samplesOutDuration *ewmaRate
metrics *queueManagerMetrics
}
複製代碼
經過隊列管理器(QueueManager)結構體的定義,咱們注意以下幾個字段:後端
NewQueueManager
函數是隊列管理器的初始化方法;Show you the code:api
// NewQueueManager builds a new QueueManager.
func NewQueueManager( metrics *queueManagerMetrics, watcherMetrics *wal.WatcherMetrics, readerMetrics *wal.LiveReaderMetrics, logger log.Logger, walDir string, samplesIn *ewmaRate, cfg config.QueueConfig, externalLabels labels.Labels, relabelConfigs []*relabel.Config, client StorageClient, flushDeadline time.Duration, ) *QueueManager {
if logger == nil {
logger = log.NewNopLogger()
}
logger = log.With(logger, remoteName, client.Name(), endpoint, client.Endpoint())
t := &QueueManager{
logger: logger,
flushDeadline: flushDeadline,
cfg: cfg,
externalLabels: externalLabels,
relabelConfigs: relabelConfigs,
storeClient: client,
seriesLabels: make(map[uint64]labels.Labels),
seriesSegmentIndexes: make(map[uint64]int),
droppedSeries: make(map[uint64]struct{}),
numShards: cfg.MinShards,
reshardChan: make(chan int),
quit: make(chan struct{}),
samplesIn: samplesIn,
samplesDropped: newEWMARate(ewmaWeight, shardUpdateDuration),
samplesOut: newEWMARate(ewmaWeight, shardUpdateDuration),
samplesOutDuration: newEWMARate(ewmaWeight, shardUpdateDuration),
metrics: metrics,
}
t.watcher = wal.NewWatcher(watcherMetrics, readerMetrics, logger, client.Name(), t, walDir)
t.shards = t.newShards()
return t
}
複製代碼
經過初始化方法,咱們能夠知道以下幾點:緩存
min_shards
的值;至關於遠程寫啓動時採用min_shards配置的數量,做爲使用分片的默認值;文章開頭的日誌信息,咱們看到提示是"skipping resharding",即跳過了reshard動做;咱們不由要發出三連問:reshard是什麼(what)?爲何須要reshard(why)?怎麼樣觸發reshard(how)?bash
下面的代碼解釋了:什麼狀況下resharding動做應該發生;return true
時,表明應該發生reshard動做;數據結構
// shouldReshard returns if resharding should occur
func (t *QueueManager) shouldReshard(desiredShards int) bool {
if desiredShards == t.numShards {
return false
}
// We shouldn't reshard if Prometheus hasn't been able to send to the
// remote endpoint successfully within some period of time.
minSendTimestamp := time.Now().Add(-2 * time.Duration(t.cfg.BatchSendDeadline)).Unix()
lsts := atomic.LoadInt64(&t.lastSendTimestamp)
if lsts < minSendTimestamp {
level.Warn(t.logger).Log("msg", "Skipping resharding, last successful send was beyond threshold", "lastSendTimestamp", lsts, "minSendTimestamp", minSendTimestamp)
return false
}
return true
}
複製代碼
從這裏咱們終於找到文章開頭處日誌信息的出處,原來是由於「最近一次發送數據的時間戳」小於「最小發送數據時間戳」,也即跟BatchSendDeadline
的配置有關;函數
在理解reshard以前,咱們先要了解shard的概念。這就說到了Prometheus的Remote Write。
每一個遠程寫目的地都啓動一個隊列,該隊列從write-ahead log (WAL)中讀取數據,將樣本寫到一個由shard(即分片)擁有的內存隊列中,而後分片將請求發送到配置的端點。數據流程以下:
|--> queue (shard_1) --> remote endpoint
WAL --|--> queue (shard_...) --> remote endpoint
|--> queue (shard_n) --> remote endpoint
複製代碼
當一個分片備份並填滿它的隊列時,Prometheus將阻止從WAL中讀取任何分片。若是失敗了,則進行重試,其間不會丟失數據,除非遠程端點保持關閉狀態超過2小時。2小時後,WAL將被壓縮,未發送的數據將丟失。 在遠程寫過程當中,Prometheus將根據輸入採樣速率、未發送的採樣數量和發送每一個採樣數據所需的時間,不斷計算出最優的分片數量(即上面提到的numShards)。
使用遠程寫操做會增長Prometheus的內存佔用。大多數用戶報告內存使用量增長了25%,可是這個數字取決於數據的結構。對於WAL中的每一個時間序列,遠程寫緩存一個key爲時間序列ID,value爲標籤值的map,致使大量的時間序列變更,從而顯著地增長內存使用量。
影響內存使用的因素有:
公式:
Shard memory = number of shards * (capacity + max_samples_per_send)
調優時,請考慮減小max_shards同時增長capacity和max_samples_per_send以免內存不足。原則以下:
前面囉嗦了半天,直接上結論
3-10倍max_samples_per_send
建議默認(1000);不建議太多,會致使OOM;除非遠程存儲寫入很是緩慢,不然不建議將max_shards的值調整爲比默認值大。相反,調整爲小於默認值會減小內存佔用;
默認(1),可適當調大;若是遠端寫入落後,Prometheus會自動擴展分片的數量;增長最小分片將容許 Prometheus在計算所需的分片數時避免在開始時落後。
默認值(100)足夠小,適用於大多數系統,可適當調大;根據所使用的後端,適當調整每次發送的最大樣本數。由於許多系統,經過每批發送更多樣本而不會顯著增長延遲。而有些後端,若是每一個請求中發送大量樣本(simples),就會出現問題。
對時間不敏感時可適當增大;設置單個分片發送之間的最大時間;即便排隊的分片還沒有到達max_samples_per_send,也會發送請求;對於不區分延遲的低容量系統,能夠增長此值,以提升請求效率;
重試失敗請求以前的最短等待時間或控制重試失敗請求以前等待的最短期。
控制重試失敗請求以前等待的最大時間量。
實際應用時,須要可根據優化建議,適當調整測試,達到系統最優水平。