当你的应用程序在生产环境中运行并处理实际请求时,如果您正在进行有针对性的负载测试,您可能想要深入了解程序是如何执行的,以及它正在使用哪些资源。- 有多少goroutine在使用?如何随着时间变化的?
- 有多少数据库连接在使用中,有多少是空闲的?需要更改连接池设置吗?
- HTTP成功响应客户端和服务器错误的比率是多少?错误率是否高于正常水平?
深入了解这些内容可以帮助您了解软硬件的配置选择,并作为潜在问题(如内存泄漏)的早期预警信号。为了帮助实现这一点,Go的标准库包含了expvar包,它可以让你在运行时很容易地整理和查看不同的应用程序性能指标。- 如何使用expvar包通过HTTP处理程序以JSON格式查看应用程序性能指标。
- 可用的默认应用程序指标是什么,以及如何创建自定义应用程序指标,以监视goroutines和数据库连接池的数量。
- 如何使用中间件监视请求级的应用程序指标,包括不同HTTP状态码的计数。
使用Expvar开放性能参数
由于expvar包提供了expvar.handler()函数,该函数返回一个应用程序指标的HTTP处理程序,因此查看API服务的指标变得很容易。默认情况下,这个处理程序显示关于内存使用的信息,以及启动应用程序时使用的命令行参数,所有这些都以JSON格式输出。所以我们要做的第一件事就是把这个handler处理函数挂载到一个新的GET /debug/vars接口,像这样:Method | URL | Handler | 操作 |
---|
GET | /debug/vars | expvar.Handler | 显示应用程序性能参数 |
package main
...
func (app *application) routes() http.Handler {
router := httprouter.New()
router.NotFound = http.HandlerFunc(app.notFoundResponse)
router.MethodNotAllowed = http.HandlerFunc(app.methodNotAllowedResponse)
router.HandlerFunc(http.MethodGet, "/v1/healthcheck", app.healthcheckHandler)
router.HandlerFunc(http.MethodGet, "/v1/movies", app.requirePermission("movies:read", app.listMoviesHandler))
router.HandlerFunc(http.MethodPost, "/v1/movies", app.requirePermission("movies:write", app.createMovieHandler))
router.HandlerFunc(http.MethodGet, "/v1/movies/:id", app.requirePermission("movies:read", app.showMovieHandler))
router.HandlerFunc(http.MethodPatch, "/v1/movies/:id", app.requirePermission("movies:write", app.updateMovieHandler))
router.HandlerFunc(http.MethodDelete, "/v1/movies/:id", app.requirePermission("movies:write", app.deleteMovieHandler))
router.HandlerFunc(http.MethodPost, "/v1/users", app.registerUserHandler)
router.HandlerFunc(http.MethodPut, "/v1/users/activated", app.activateUserHandler)
router.HandlerFunc(http.MethodPost, "/v1/tokens/authentication", app.createAuthenticationTokenHandler)
//注册新的接口指向expvar处理程序
router.Handler(http.MethodGet, "/debug/vars", expvar.Handler())
return app.recoverPanic(app.enableCORS(app.rateLimit(app.authenticate(router))))
}
提示:使用GET /degbu/var作为expvar处理程序的Endpoint是惯例用法不是必须的。如果你不喜欢,可以使用其他接口名称例如GET /v/metrics来代替。
下面来测试下,启动应用程序,为了演示传入一些命令行参数:$ go run ./cmd/api -limiter-enable=false -port=4000
{"level":"INFO","time":"2022-01-12T03:16:43Z","message":"database connection pool established"}
{"level":"INFO","time":"2022-01-12T03:16:43Z","message":"starting server","properties":{"addr":":4000","env":"development"}}
如果你访问http://localhost:4000/debug/vars,将看到JSON响应内容包含应用程序运行时的指标信息:$curl http://localhost:4000/debug/vars -s | python -m json.tool
{
"cmdline": [
"/var/folders/x6/8wtj7zfd7r59wpk5fmjln2p40000gn/T/go-build3315447716/b001/exe/api",
"-limiter-enable=false",
"-port=4000"
],
"memstats": {
"Alloc": 545376,
"BuckHashSys": 3860,
"BySize": [
{
"Frees": 0,
"Mallocs": 0,
"Size": 0
},
...
}
],
"DebugGC": false,
"EnableGC": true,
"Frees": 177,
"GCCPUFraction": 0,
"GCSys": 4011248,
"HeapAlloc": 545376,
"HeapIdle": 65167360,
"HeapInuse": 1482752,
"HeapObjects": 2236,
"HeapReleased": 64937984,
"HeapSys": 66650112,
"LastGC": 0,
"Lookups": 0,
"MCacheInuse": 9600,
"MCacheSys": 16384,
"MSpanInuse": 37536,
"MSpanSys": 49152,
"Mallocs": 2413,
"NextGC": 4473924,
"NumForcedGC": 0,
"NumGC": 0,
"OtherSys": 1034252,
"PauseEnd": [
可以看到JSON包含两个顶层键值分别为:"cmdline"和"memstats"。我们简单了解下这些键。"cmdline"键包含应用程序的命令行参数列表,以程序名称为首元素。这本质上是os.Args变量的JSON表示,如果您想要确切地了解在启动应用程序时使用了哪些非默认设置,那么它很有用。"memstats"键包含即时内存使用快照,是由runtime.MemStats()函数返回的。关于这些值得文档和描述可以查看这里[https://golang.org/pkg/runtime/#MemStats],但最重要的是下面几个:- TotalAlloc:在堆上分配的累积字节数(不会减少)。
- Sys:从操作系统获得的总内存字节(即Go运行时为堆、栈和其他内部数据结构保留的总内存)。
- NextGC:启动下一个垃圾收集的目标堆大小(Go的目标是保持HeapAlloc≤NextGC)。
提示:如果上面有你不熟悉的字段,我强烈建议你阅读"Understanding Allocations in Go"文章介绍了Go如何分配内存以及堆栈的概念。