動機
最近需要將流程的耗時接入可觀測系統(tǒng)邮府,方便后續(xù)優(yōu)化性能溉奕。為了統(tǒng)一技術(shù)棧加勤,決定使用prometheus
來接入。特別的是鳄梅,我需要使用自定義的時間戳,由于PushGateway
不支持自定義的時間戳粟焊,只能使用 pull 的方式了孙蒙。
整體流程
1、構(gòu)建 Collector
马篮。
2浑测、注冊到 Register
(也實現(xiàn)了 Gatherer
接口)歪玲,其中會調(diào)用 Collector
的 Describe
方法掷匠。
3、通過http請求到 /metrics
,使用 promhttp.Handler
來處理請求讹语。
4、調(diào)用 Register
的 Gather
方法短条,其中會調(diào)用 注冊的Collector
的Collect
方法才菠。
5、將 Metric
按名稱分組為 dto.MetricFamily
可都,編碼后寫入 ResponseWriter
蚓耽,返回給請求端。
實現(xiàn)細(xì)節(jié)
實現(xiàn) Collector
type timestampedCollector struct {
bufferChan chan prometheus.Metric
desc *prometheus.Desc
}
func newTimestampedCollector(bufferSize int, desc *prometheus.Desc) *timestampedCollector {
metrics := make(chan prometheus.Metric, bufferSize)
return ×tampedCollector{
bufferChan: metrics,
desc: desc,
}
}
func (t *timestampedCollector) add(m prometheus.Metric) {
logger.Infof("add metric:%s", util.ToJson(m.Desc()))
t.bufferChan <- m
}
func (t *timestampedCollector) Describe(c chan<- *prometheus.Desc) {
c <- t.desc
}
func (t *timestampedCollector) Collect(c chan<- prometheus.Metric) {
for {
select {
case m := <-t.bufferChan:
c <- m
case <-time.After(time.Second * 1):
return
}
}
}
prometheus中有2種Collector
签杈,一種是uncheckedCollectors
贤徒,還有一種是checkedCollectors
,就是看你實現(xiàn)的Collector
的Describe
方法中有沒有添加prometheus.Desc
注冊到Registry
構(gòu)建prometheus.Desc
var totalDesc = prometheus.NewDesc("emr_exclude_bootstrap_duration_seconds",
"流程去掉執(zhí)行引導(dǎo)操作的時間",
[]string{"service", "region", "nodeCnt", "flowId"},
nil)
NewDesc
生成 prometheus.Desc
的id踢涌,總的來說就是(fqName + constLabels的值列表) 合并起來的string的hash值
func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, constLabels Labels) *Desc {
// 構(gòu)建 Desc
......
labelValues := make([]string, 1, len(constLabels)+1)
labelValues[0] = fqName
for _, labelName := range labelNames {
labelValues = append(labelValues, constLabels[labelName])
}
xxh := xxhash.New()
for _, val := range labelValues {
xxh.WriteString(val)
xxh.Write(separatorByteSlice)
}
d.id = xxh.Sum64() // 生成 Desc 的 id
}
NewDesc
生成 prometheus.Desc
的dimHash睁壁,總的來說就是(fqName + constLabels的值列表) 合并起來的string的hash值
func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, constLabels Labels) *Desc {
// 構(gòu)建 Desc
......
labelNames := make([]string, 0, len(constLabels)+len(d.variableLabels.names))
for labelName := range constLabels {
labelNames = append(labelNames, labelName)
}
for _, label := range variableLabels {
labelNames = append(labelNames, "$"+label)
}
xxh := xxhash.New()
xxh.WriteString(help)
xxh.Write(separatorByteSlice)
for _, labelName := range labelNames {
xxh.WriteString(labelName)
xxh.Write(separatorByteSlice)
}
d.dimHash = xxh.Sum64()
}
注冊到prometheus
totalCollector = newTimestampedCollector(1000, totalDesc)
if err := handleErr(prometheus.Register(totalCollector)); err != nil {
panic(err)
}
偽代碼如下
func (r *Registry) Register(c Collector) error {
c.Describe(descChan)
close(descChan)
for desc := range descChan {
1互捌、desc.id不能重復(fù)
2、desc.dimHash + desc.fqName 不能重復(fù)
}
}
使用 promhttp.Handler
來處理請求
func main() {
http.Handle("/metrics", promhttp.Handler())
// 暴露自己的指標(biāo)
http.ListenAndServe(":13000", nil)
}
使用curl -XGET 'http://localhost:13000/metrics'
來驗證metric是否暴露成功钳降。其中 http handler的鏈表結(jié)構(gòu)為:
InstrumentHandlerCounter -- HandlerForTransactional內(nèi)部方法
暴露指標(biāo)
promhttp.Handler
最核心的就是調(diào)用 Registry
的 Gather
方法腌巾,其中會調(diào)用注冊的Collector
的Collect
方法來將指標(biāo)拉取出來铲觉,接著主要是2步
1撵幽、聚合
MetricFamily
按照Metric fqName分組礁击,相同Metric fqName的Metric
會放入到MetricFamily
的集合中
2、校驗
1哆窿、指標(biāo)名稱 + lable的名字 + label的值 + 時間戳 不能重復(fù)
2、label不能重復(fù)