go+grafana+influxdb性能分析和问题排查
组件:
- go程序
- grafana
- influxdb
1.在Docker容器中运行InfluxDB和Grafana
docker run --name influxdb -d -p 8086:8086 influxdb
docker run -d -p 9090:3000/tcp --link influxdb --name=grafana grafana/grafana:4.1.0
2.在InfluxDB中创建数据库
3.访问grafana导入dasdboard配置
访问地址: http://172.16.7.16:9090/
(自行修改)
配置地址: https://grafana.com/grafana/dashboards/3242
4.测试
4.1.测试局部变量存放申请资源
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
metrics "github.com/tevjef/go-runtime-metrics"
)
func PushMsg(c *gin.Context) {
la := make([]int, 0)
for i := 0; i < 100; i++ {
la = append(la, i)
}
time.Sleep(time.Millisecond * 100)
c.JSON(200, gin.H{"code": 1})
}
func main() {
metrics.DefaultConfig.CollectionInterval = time.Second
metrics.DefaultConfig.Database = "gostats"
metrics.DefaultConfig.CollectionInterval = 3 * time.Second
if err := metrics.RunCollector(metrics.DefaultConfig); err != nil {
log.Fatal(err)
return
}
g := gin.Default()
g.NoRoute(func(c *gin.Context) {
c.String(http.StatusNotFound, "The incorrect API route")
})
g.GET("/v1/push", PushMsg)
log.Fatal(g.Run(":8080").Error())
}
4.2.测试全局变量存放申请资源(模拟有内存泄漏场景)
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
metrics "github.com/tevjef/go-runtime-metrics"
)
var (
ga = make([]int, 0)
)
func PushMsg(c *gin.Context) {
for i := 0; i < 100; i++ {
ga = append(ga, i)
}
time.Sleep(time.Millisecond * 100)
c.JSON(200, gin.H{"code": 1})
}
func main() {
metrics.DefaultConfig.CollectionInterval = time.Second
metrics.DefaultConfig.Database = "gostats"
metrics.DefaultConfig.CollectionInterval = 3 * time.Second
if err := metrics.RunCollector(metrics.DefaultConfig); err != nil {
log.Fatal(err)
return
}
g := gin.Default()
g.NoRoute(func(c *gin.Context) {
c.String(http.StatusNotFound, "The incorrect API route")
})
g.GET("/v1/push", PushMsg)
log.Fatal(g.Run(":8080").Error())
}
5.压测
wrk -t 50 -c 100 -d 10s http://172.16.7.16:8080/v1/push
6.分析
6.1.无请求时web服务性能指标
- go routines图: goroutine申请数变化情况
-
Sys图: 从系统申请的字节数
- Heap Object图: 堆内存申请的对象情况
-
Overall Heap usage图: 动态申请堆内存的使用情况
- mallocs and frees图: 动态内存申请和释放的情况
-
TotalAlloc图: 总的动态申请内存情况
- GC图: go 发生gc的情况
6.2.测试局部变量存放申请资源分析
看goroutine图, 20:51分是压测点, goroutine数暴增; Sys图看到在压测点, 向系统申请的字节数增加; 从堆内存相关的图看出来, 局部方式申请动态内存的方式, 有借有还, 趋于平衡的状态.
6.3.测试全局变量存放申请资源(模拟有内存泄漏场景)分析 测试1
测试2
测试3
测试4
6.4.四个测试点的gc情况
从以上几个截图, 看出来, 使用全局变量的方式缓存住每次请求申请的内存, 会造成申请的动态资源一直没有释放, 没有归还给系统, 造成内存泄漏, 一直增长. gc频率也不高, 正常时间间隔发生gc
7.显式地释放内存 通过显式调用debug.FreeOSMemory()可以显著减少内存消耗 增加以下逻辑
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
metrics "github.com/tevjef/go-runtime-metrics"
)
var (
ga = make([]int, 0)
)
//增加手工释放内存
func init() {
go func() {
t := time.Tick(time.Second)
for {
<-t
debug.FreeOSMemory()
}
}()
}
func PushMsg(c *gin.Context) {
for i := 0; i < 100; i++ {
ga = append(ga, i)
}
time.Sleep(time.Millisecond * 100)
c.JSON(200, gin.H{"code": 1})
}
func main() {
metrics.DefaultConfig.CollectionInterval = time.Second
metrics.DefaultConfig.Database = "gostats"
metrics.DefaultConfig.CollectionInterval = 3 * time.Second
if err := metrics.RunCollector(metrics.DefaultConfig); err != nil {
log.Fatal(err)
return
}
g := gin.Default()
g.NoRoute(func(c *gin.Context) {
c.String(http.StatusNotFound, "The incorrect API route")
})
g.GET("/v1/push", PushMsg)
log.Fatal(g.Run(":8080").Error())
}
可以明显看到, 增加 debug.FreeOSMemory() 后, 申请的堆内存定时的归还给系统, 没有造成明显的内存泄漏问题.
但是可以看出来, gc发生的太频繁了. gc频繁, 会影响性能和稳定性
8.总结 借助grafana+influxdb来监控和分析服务的指标和性能, 不仅方便在开发阶段尽早早先问题, 而且可以很好的监控线上服务, 通过看指标发现异常点.
9.参考
- https://stackoverflow.com/questions/24863164/how-to-analyze-golang-memory
- https://golang.org/pkg/runtime/
- https://golang.org/pkg/runtime/#MemStats