使用 expvar 暴露 Go 程序運(yùn)行指標(biāo)
點(diǎn)擊上方“Go語(yǔ)言進(jìn)階學(xué)習(xí)”,進(jìn)行關(guān)注
回復(fù)“Go語(yǔ)言”即可獲贈(zèng)從入門到進(jìn)階共10本電子書
獲取應(yīng)用程序的運(yùn)行指標(biāo),可以讓我們更好地了解它的實(shí)際狀況。將這些指標(biāo)對(duì)接到 prometheus、zabbix 等監(jiān)控系統(tǒng),能夠?qū)?yīng)用程序持續(xù)檢測(cè),發(fā)現(xiàn)異??梢约皶r(shí)告警并得到處理。
Pull 與 Push
與監(jiān)控系統(tǒng)對(duì)接方式有兩種,一種是 Pull(拉?。硗庖环N Push(推送)。
以 Prometheus 為例,應(yīng)用程序通過暴露出 HTTP 接口,讓 Prometheus 周期性地通過該接口抓取指標(biāo),這就是 Pull。而 Push 是應(yīng)用程序主動(dòng)將指標(biāo)推送給 PushGateway, Prometheus 則去 PushGateway 抓取數(shù)據(jù)。
Go 標(biāo)準(zhǔn)庫(kù)中有一個(gè)名為 expvar 的包,它的名字由 exp 和 var 兩部分組合而成,意味著導(dǎo)出變量。
expvar 為公共變量提供了標(biāo)準(zhǔn)化的接口,并通過 HTTP 以 Json 的格式將這些變量暴露出去,很適合采用 Pull 的方式與監(jiān)控系統(tǒng)進(jìn)行對(duì)接。
使用 expvar 庫(kù)
expvar 是標(biāo)準(zhǔn)庫(kù),意味著我們并不要額外的依賴,并且它還提供了一些開箱即用的指標(biāo)。下面我們來學(xué)習(xí)一下該庫(kù)的使用。
當(dāng)引用了 expvar 庫(kù)(import "expvar"),以下 init 函數(shù)將被自動(dòng)調(diào)用。
func init() {
http.HandleFunc("/debug/vars", expvarHandler)
Publish("cmdline", Func(cmdline))
Publish("memstats", Func(memstats))
}
該函數(shù)為我們注冊(cè)了 /debug/vars 路徑的 HTTP 服務(wù),訪問該路徑將得到 Json 格式的指標(biāo)。
因此,我們還需要調(diào)用 ListenAndServe 綁定端口,并開始服務(wù) HTTP 請(qǐng)求。
http.ListenAndServe(":8080", nil)
完整代碼如下
package main
import (
_ "expvar"
"net/http"
)
func main() {
http.ListenAndServe(":8080", nil)
}
將程序運(yùn)行起來后,通過 curl 請(qǐng)求,得到以下結(jié)果
$ curl localhost:8080/debug/vars
{
"cmdline": ["/var/folders/xk/gn46n46d503dsztbc6_9qb2h0000gn/T/go-build1657217338/b001/exe/main"],
"memstats": {"Alloc":278880,"TotalAlloc":278880,"Sys":8735760,"Lookups":0,"Mallocs":1169,"Frees":87,"HeapAlloc":278880,"HeapSys":3866624,"HeapIdle":2949120,"HeapInuse":917504,"HeapReleased":2899968,"HeapObjects":1082,"StackInuse":327680,"StackSys":327680,"MSpanInuse":28696,"MSpanSys":32640,"MCacheInuse":9600,"MCacheSys":15600,"BuckHashSys":3875,"GCSys":3826448,"OtherSys":662893,"NextGC":4194304,"LastGC":0,"PauseTotalNs":0,"PauseNs":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":0,"NumForcedGC":0,"GCCPUFraction":0,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":41,"Frees":0},{"Size":16,"Mallocs":496,"Frees":0},{"Size":24,"Mallocs":63,"Frees":0},{"Size":32,"Mallocs":28,"Frees":0},{"Size":48,"Mallocs":134,"Frees":0},{"Size":64,"Mallocs":50,"Frees":0},{"Size":80,"Mallocs":17,"Frees":0},{"Size":96,"Mallocs":17,"Frees":0},{"Size":112,"Mallocs":6,"Frees":0},{"Size":128,"Mallocs":9,"Frees":0},{"Size":144,"Mallocs":9,"Frees":0},{"Size":160,"Mallocs":18,"Frees":0},{"Size":176,"Mallocs":6,"Frees":0},{"Size":192,"Mallocs":0,"Frees":0},{"Size":208,"Mallocs":37,"Frees":0},{"Size":224,"Mallocs":6,"Frees":0},{"Size":240,"Mallocs":0,"Frees":0},{"Size":256,"Mallocs":12,"Frees":0},{"Size":288,"Mallocs":7,"Frees":0},{"Size":320,"Mallocs":2,"Frees":0},{"Size":352,"Mallocs":13,"Frees":0},{"Size":384,"Mallocs":1,"Frees":0},{"Size":416,"Mallocs":30,"Frees":0},{"Size":448,"Mallocs":1,"Frees":0},{"Size":480,"Mallocs":2,"Frees":0},{"Size":512,"Mallocs":0,"Frees":0},{"Size":576,"Mallocs":5,"Frees":0},{"Size":640,"Mallocs":5,"Frees":0},{"Size":704,"Mallocs":3,"Frees":0},{"Size":768,"Mallocs":0,"Frees":0},{"Size":896,"Mallocs":6,"Frees":0},{"Size":1024,"Mallocs":8,"Frees":0},{"Size":1152,"Mallocs":10,"Frees":0},{"Size":1280,"Mallocs":3,"Frees":0},{"Size":1408,"Mallocs":1,"Frees":0},{"Size":1536,"Mallocs":0,"Frees":0},{"Size":1792,"Mallocs":7,"Frees":0},{"Size":2048,"Mallocs":2,"Frees":0},{"Size":2304,"Mallocs":3,"Frees":0},{"Size":2688,"Mallocs":2,"Frees":0},{"Size":3072,"Mallocs":0,"Frees":0},{"Size":3200,"Mallocs":0,"Frees":0},{"Size":3456,"Mallocs":0,"Frees":0},{"Size":4096,"Mallocs":8,"Frees":0},{"Size":4864,"Mallocs":1,"Frees":0},{"Size":5376,"Mallocs":1,"Frees":0},{"Size":6144,"Mallocs":2,"Frees":0},{"Size":6528,"Mallocs":0,"Frees":0},{"Size":6784,"Mallocs":0,"Frees":0},{"Size":6912,"Mallocs":0,"Frees":0},{"Size":8192,"Mallocs":2,"Frees":0},{"Size":9472,"Mallocs":8,"Frees":0},{"Size":9728,"Mallocs":0,"Frees":0},{"Size":10240,"Mallocs":0,"Frees":0},{"Size":10880,"Mallocs":0,"Frees":0},{"Size":12288,"Mallocs":0,"Frees":0},{"Size":13568,"Mallocs":0,"Frees":0},{"Size":14336,"Mallocs":0,"Frees":0},{"Size":16384,"Mallocs":0,"Frees":0},{"Size":18432,"Mallocs":0,"Frees":0}]}
}
可以看到,expvar 默認(rèn)已提供了兩項(xiàng)指標(biāo),分別是程序執(zhí)行命令(os.Args)和運(yùn)行時(shí)內(nèi)存分配(runtime.Memstats)信息。
expvar 庫(kù)重點(diǎn)內(nèi)容
expvar 中最重要的是 Publish 函數(shù)和 Var 接口。
func Publish(name string, v Var) {}
Publish 函數(shù)簽名中需要兩個(gè)參數(shù),name 是我們指定的指標(biāo)名。例如在上文 expvar 的 init 函數(shù)下 Publish("cmdline", Func(cmdline))代碼行,其中cmdline就是指標(biāo)名,而Func(cmdline)則是實(shí)現(xiàn)了 Var 接口的 expvar.Func 類型變量。
Var 接口,它只定義了一個(gè) String 方法。需要注意的是,該方法必須要返回一個(gè)有效的 Json字符串。
// Var is an abstract type for all exported variables.
type Var interface {
// String returns a valid JSON value for the variable.
// Types with String methods that do not return valid JSON
// (such as time.Time) must not be used as a Var.
String() string
}
為了方便使用,expvar 庫(kù)中提供了五種導(dǎo)出變量類型,它們均實(shí)現(xiàn)了 Var 接口。
type Int struct {
i int64
}
type Float struct {
f uint64
}
type Map struct {
m sync.Map // map[string]Var
keysMu sync.RWMutex
keys []string // sorted
}
type String struct {
s atomic.Value // string
}
type Func func() any
我們分別通過調(diào)用 expvar.NewXXX 函數(shù)即可完成前四種類型的變量創(chuàng)建與指標(biāo)注冊(cè)。
intVar = expvar.NewInt(“metricName”)
floatVar = expvar.NewFloat(“metricName”)
mapVar = expvar.NewMap(“metricName”)
stringVar = expvar.NewString(“metricName”)
例如 expvar.NewInt 函數(shù),它會(huì)內(nèi)部調(diào)用 Publish 方法完成指標(biāo)名與 expvar.Int 類型變量的綁定。
func NewInt(name string) *Int {
v := new(Int)
Publish(name, v)
return v
}
而 expvar.Func 類型,其實(shí)是為了讓我們可以自定義導(dǎo)出類型。
例如,假如我們想要暴露以下定義的結(jié)構(gòu)體
type MyStruct struct {
Field1 string
Field2 int
Field3 float64
}
首先需要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)生成函數(shù)。它用于在每次調(diào)用 HTTP 服務(wù)路徑時(shí),通過該函數(shù)導(dǎo)出這里面的數(shù)據(jù)。
func MyStructData() interface{} {
return MyStruct{
Field1: "Gopher",
Field2: 22,
Field3: 19.99,
}
}
最后,通過 Publish 函數(shù)注冊(cè)指標(biāo)名即可。
expvar.Publish("metricName", expvar.Func(MyStructData))
完整示例
下面,我們給出一個(gè)覆蓋五種導(dǎo)出變量類型的完整示例。
package main
import (
"expvar"
"github.com/shirou/gopsutil/v3/host"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"net/http"
"time"
)
type Load struct {
Load1 float64
Load5 float64
Load15 float64
}
func AllLoadAvg() interface{} {
return Load{
Load1: LoadAvg(1),
Load5: LoadAvg(5),
Load15: LoadAvg(15),
}
}
func LoadAvg(loadNumber int) float64 {
avg, _ := load.Avg()
switch loadNumber {
case 5:
return avg.Load5
case 15:
return avg.Load15
default:
return avg.Load1
}
}
func main() {
var (
aliveOfSeconds = expvar.NewInt("aliveOfSeconds")
hostID = expvar.NewString("hostID")
lastLoad = expvar.NewFloat("lastLoad")
virtualMemory = expvar.NewMap("virtualMemory")
)
expvar.Publish("allLoadAvg", expvar.Func(AllLoadAvg))
h, _ := host.HostID()
hostID.Set(h)
go http.ListenAndServe(":8080", nil)
for {
aliveOfSeconds.Add(1)
vm, _ := mem.VirtualMemory()
lastLoad.Set(LoadAvg(1))
virtualMemory.Add("active", int64(vm.Active))
virtualMemory.Add("buffer", int64(vm.Buffers))
time.Sleep(1 * time.Second)
}
}
在上述示例中,我們通過 gopsutil 庫(kù)(介紹可見還在自己寫 Go 系統(tǒng)監(jiān)控函數(shù)嗎一文)獲取了一些系統(tǒng)信息,并展示了如何通過 expvar 中的各種變量類型將這些信息進(jìn)行導(dǎo)出。
curl 訪問 localhost:8080/debug/vars 結(jié)果如下
$ curl localhost:8080/debug/vars
{
"aliveOfSeconds": 1,
"allLoadAvg": {"Load1":1.69580078125,"Load5":1.97412109375,"Load15":1.90283203125},
"cmdline": ["/var/folders/xk/gn46n46d503dsztbc6_9qb2h0000gn/T/go-build3566019824/b001/exe/main"],
"hostID": "7a1a74f2-30fc-5bc1-b439-6b7aef22e58d",
"lastLoad": 1.69580078125,
"memstats": {"Alloc":256208,"TotalAlloc":256208,"Sys":8735760,"Lookups":0,"Mallocs":1089,"Frees":48,"HeapAlloc":256208,"HeapSys":3866624,"HeapIdle":2891776,"HeapInuse":974848,"HeapReleased":2859008,"HeapObjects":1041,"StackInuse":327680,"StackSys":327680,"MSpanInuse":19992,"MSpanSys":32640,"MCacheInuse":9600,"MCacheSys":15600,"BuckHashSys":3905,"GCSys":3851120,"OtherSys":638191,"NextGC":4194304,"LastGC":0,"PauseTotalNs":0,"PauseNs":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":0,"NumForcedGC":0,"GCCPUFraction":0,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":35,"Frees":0},{"Size":16,"Mallocs":415,"Frees":0},{"Size":24,"Mallocs":71,"Frees":0},{"Size":32,"Mallocs":37,"Frees":0},{"Size":48,"Mallocs":141,"Frees":0},{"Size":64,"Mallocs":52,"Frees":0},{"Size":80,"Mallocs":20,"Frees":0},{"Size":96,"Mallocs":23,"Frees":0},{"Size":112,"Mallocs":14,"Frees":0},{"Size":128,"Mallocs":7,"Frees":0},{"Size":144,"Mallocs":7,"Frees":0},{"Size":160,"Mallocs":18,"Frees":0},{"Size":176,"Mallocs":6,"Frees":0},{"Size":192,"Mallocs":1,"Frees":0},{"Size":208,"Mallocs":42,"Frees":0},{"Size":224,"Mallocs":3,"Frees":0},{"Size":240,"Mallocs":0,"Frees":0},{"Size":256,"Mallocs":9,"Frees":0},{"Size":288,"Mallocs":8,"Frees":0},{"Size":320,"Mallocs":5,"Frees":0},{"Size":352,"Mallocs":13,"Frees":0},{"Size":384,"Mallocs":3,"Frees":0},{"Size":416,"Mallocs":33,"Frees":0},{"Size":448,"Mallocs":0,"Frees":0},{"Size":480,"Mallocs":2,"Frees":0},{"Size":512,"Mallocs":1,"Frees":0},{"Size":576,"Mallocs":4,"Frees":0},{"Size":640,"Mallocs":8,"Frees":0},{"Size":704,"Mallocs":3,"Frees":0},{"Size":768,"Mallocs":1,"Frees":0},{"Size":896,"Mallocs":6,"Frees":0},{"Size":1024,"Mallocs":8,"Frees":0},{"Size":1152,"Mallocs":9,"Frees":0},{"Size":1280,"Mallocs":3,"Frees":0},{"Size":1408,"Mallocs":1,"Frees":0},{"Size":1536,"Mallocs":1,"Frees":0},{"Size":1792,"Mallocs":9,"Frees":0},{"Size":2048,"Mallocs":1,"Frees":0},{"Size":2304,"Mallocs":2,"Frees":0},{"Size":2688,"Mallocs":2,"Frees":0},{"Size":3072,"Mallocs":0,"Frees":0},{"Size":3200,"Mallocs":1,"Frees":0},{"Size":3456,"Mallocs":0,"Frees":0},{"Size":4096,"Mallocs":5,"Frees":0},{"Size":4864,"Mallocs":0,"Frees":0},{"Size":5376,"Mallocs":1,"Frees":0},{"Size":6144,"Mallocs":1,"Frees":0},{"Size":6528,"Mallocs":0,"Frees":0},{"Size":6784,"Mallocs":0,"Frees":0},{"Size":6912,"Mallocs":0,"Frees":0},{"Size":8192,"Mallocs":1,"Frees":0},{"Size":9472,"Mallocs":8,"Frees":0},{"Size":9728,"Mallocs":0,"Frees":0},{"Size":10240,"Mallocs":0,"Frees":0},{"Size":10880,"Mallocs":0,"Frees":0},{"Size":12288,"Mallocs":0,"Frees":0},{"Size":13568,"Mallocs":0,"Frees":0},{"Size":14336,"Mallocs":0,"Frees":0},{"Size":16384,"Mallocs":0,"Frees":0},{"Size":18432,"Mallocs":0,"Frees":0}]},
"virtualMemory": {"active": 1957449728, "buffer": 0}
}
總結(jié)
標(biāo)準(zhǔn)庫(kù) expvar 為需要導(dǎo)出的公共變量提供了一個(gè)標(biāo)準(zhǔn)化的接口,使用比較簡(jiǎn)單。
expvar 包內(nèi)部定義的幾種基礎(chǔ)類型都相應(yīng)給出了并發(fā)安全的操作方法,我們不需要去重復(fù)實(shí)現(xiàn)一遍,能夠開箱即用。
但是, 在 https://go.dev/ 上統(tǒng)計(jì)的公共項(xiàng)目,該庫(kù)的 import 數(shù)量還不足 1萬(wàn)。


相比于其他標(biāo)準(zhǔn)庫(kù)的 import 數(shù)量而言,expvar 的存在感太低了。
所以問題來了,你認(rèn)為是什么原因?qū)е?expvar 庫(kù)這么冷門呢?歡迎留言討論。
------------------ End -------------------
往期精彩文章推薦:

歡迎大家點(diǎn)贊,留言,轉(zhuǎn)發(fā),轉(zhuǎn)載,感謝大家的相伴與支持
想加入Go學(xué)習(xí)群請(qǐng)?jiān)诤笈_(tái)回復(fù)【入群】
萬(wàn)水千山總是情,點(diǎn)個(gè)【在看】行不行
