性能问题分析

go+grafana+influxdb性能分析和问题排查

Posted by Liangjf on July 20, 2020

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