目錄
1 背景
2 微服務專案中如何監測請求耗時呢?
3 使用Prometheus的Summary型別來統計HTTP請求耗時
3.1 實踐:如何使用Summary型別Metric?
3.2 原始碼分析:Summary是如何計算分位數的?
首先看Summary的定義
再看Summary的實作
如何計算分位數?
4 Summary就這么簡單?
如果你對上述這些問題有答案,歡迎留言探討,
1 背景
在微服務專案中,我們通常需要監測客戶請求的耗時,進而掌握系統整體的性能情況,
若發現某些請求耗時非常高,那肯定會對客戶體驗造成影響,
并且高耗時的服務非常容易成為整個服務的瓶頸,在高并發下很可能引發微服務雪崩效應,進而導致整個服務不可用,
2 微服務專案中如何監測請求耗時呢?
例如常見的監測手段是:
- 某個請求的最大耗時,(木桶效應里的最短的那塊板)
- 某個請求的耗時百分位,(請求耗時的整體分布情況)
例如:
請求:http://127.0.0.1/hello
最大耗時:300ms [需要重點關注,什么情況下產生這么大的耗時,必須被優化掉]
耗時百分位:
- 50分位,50%:100ms(有50%的請求,耗時低于100ms)[性能很好,耗時較低]
- 90分位,90%:230ms(有90%的請求,耗時低于230ms)[230ms,性能可接受]
- 95分位,95%:260ms(有95%的請求,耗時低于260ms)[260ms,需要優化性能]
- 99分位,99%:270ms(有99%的請求,耗時低于270ms)[270ms,影響客戶體驗]
3 使用Prometheus的Summary型別來統計HTTP請求耗時
3.1 實踐:如何使用Summary型別Metric?
示例代碼:
// 統計http請求耗時
var httpRequestDuration = prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Name: "http_request_duration",
Help: "http request duration",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.95: 0.005, 0.99: 0.001},
},
[]string{"endpoint"},
)
采樣結果:
http_request_duration{endpoint="/hello/2",quantile="0.5"} 35
http_request_duration{endpoint="/hello/2",quantile="0.9"} 94
http_request_duration{endpoint="/hello/2",quantile="0.95"} 97
http_request_duration{endpoint="/hello/2",quantile="0.99"} 98
http_request_duration_sum{endpoint="/hello/2"} 1172
http_request_duration_count{endpoint="/hello/2"} 28
Summary型別的Metric會生成三種型別的值:
- xxx_sum:表示“/hello/2”這個請求,耗時的總和,
- xxx_count:表示“/hello/2”這個請求,請求的次數,
- xxx{xxxx, quantile="0.5"}:表示“/hello/2”這個請求,50分位的值,例如上述示例中,50分位值是35,意思是這個url 50%的請求耗時都小于35ms
3.2 原始碼分析:Summary是如何計算分位數的?
首先看Summary的定義
type Summary interface {
Metric
Collector
// Observe adds a single observation to the summary.
// 新增一個觀測值
Observe(float64)
}
Summary很核心的一個方法是Observe(),在本地增加一個觀測值,
再看Summary的實作
// Summary介面的實作類
type summary struct {
selfCollector
// 省略
objectives map[float64]float64 // 分位數,告訴Summary要統計哪些分位的值
sortedObjectives []float64 // 對分位數進行排序,升序,防止用戶輸入的分位數是亂序的
labelPairs []*dto.LabelPair
sum float64 // 觀測到的資料值的總和
cnt uint64 // 觀測的次數
// 省略
streams []*quantile.Stream
streamDuration time.Duration
headStream *quantile.Stream // 存盤當前觀測資料的地方
headStreamIdx int
// 省略
}
系統觀測到的資料放在quantile.Stream里:
type Stream struct {
*stream // 歷史所有觀測資料保存在這里,會在一定條件下將b里的觀測值merge到stream中
b Samples // Samples型別本質就是[]Sample,保存當前觀測到的資料
sorted bool // 是否已排序
}
// stream結構
type stream struct {
n float64 // 數量
l []Sample // 所有觀測到的資料
? invariant
}
// 一次觀測獲取的資料
type Sample struct {
Value float64 `json:",string"`
Width float64 `json:",string"`
Delta float64 `json:",string"`
}
由此可見,每一次觀測到的值被包裝成一個Sample,然后所有觀測到的值放在一個list里,
如何計算分位數?
我們搞清楚了最終的存盤結構是List,那計算分位數就明確了,
先對list進行排序,升序,
根據list的長度length,例如取50分位,index=Ceil( length * 0.5 ),list[index]就是最終分位數對應的值,
4 Summary就這么簡單?
如何并發寫list?
若通過lock保證寫入安全,那怎樣保證lock的競爭不會消耗太多時間?
高并發寫入時,如何保證寫入性能?
寫入的資料量太大時如何存盤list?
如何保證一個超大的list的資料是有序的?
如果一直往list里寫資料,記憶體會不會被干爆?
推薦閱讀:
- Prometheus核心概念:一圖了解瞬時向量Instant vector和區間向量Range vector的區別
- Prometheus原始碼分析:基于Go Client自定義的Exporter,是如何在Local存盤Metrics的?
- Prometheus核心概念:一圖了解Counter和Gauge兩種資料指標型別的區別
如果你對上述這些問題有答案,歡迎留言探討,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/258137.html
標籤:其他
下一篇:MySQL 資料庫在主從復制中配置從服務器時 Slave_IO_Running物件項為 Connecting 而不是 YES 導致主從不能同步復制
