Go的并发模型虽简化了并发程序的开发难度,但如果不了解使用方法,常常会遇到goroutine泄露的问题。我们可以可以从两方面入手解决,一是预防(了解什么样的代码会产生泄露),二是监控(通过Prometheus采集metrics来监控)


预防

Channel试用不当引起的泄露

发送不接收

接收不发送

nil channel

Mutex 引起的泄露

互斥锁忘记解锁

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func main() {
    total := 0
    defer func() {
        time.Sleep(time.Second)
        fmt.Println("total: ", total)
        fmt.Println("goroutines: ", runtime.NumGoroutine())
    }()

    var mutex sync.Mutex
    for i := 0; i < 10; i++ {
        go func() {
            mutex.Lock()
            total += 1
        }()
    }
}

WaitGroup 引起的泄露

同步锁使用不当

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func handle(v int) {
    var wg sync.WaitGroup
    wg.Add(5)
    for i := 0; i < v; i++ {
        fmt.Println("~~~")
        wg.Done()
    }
    wg.Wait()
}

func main() {
    defer func() {
        fmt.Println("goroutines: ", runtime.NumGoroutine())
    }()

    go handle(3)
    time.Sleep(time.Second)
}

监控

Prometheus采集metrics

1
2
3
4
5
6
7
8
go func() {
    prometheus.RegisterSimple(r)
    routes.InitRouter(r)

    if err := endless.ListenAndServe("10.0.0.100:19003", r); err != nil {
        log.Println(err)
    }
}()

只统计基础的数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package prometheus

import (
	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

type Option func(option *PromOpts)

var defaultOpt = PromOpts{
	ExcludeRegexEndpoint: "^/metrics",
	ExcludeRegexMethod:   "^OPTIONS|^HEAD",
}

func RegisterSimple(r *gin.Engine) {
	r.GET("/metrics", gin.WrapH(promhttp.Handler()))
}

//统计endpoint访问数据,路由较多占用资源大
func RegisterEndpoint(r *gin.Engine, option ...Option) {
	opt := defaultOpt
	for _, fn := range option {
		fn(&opt)
	}
	r.Use(PromMiddleware(&opt))
	r.GET("/metrics", PromHandler(promhttp.Handler()))
}

func WithExcludeRegexEndpoint(endpoint string) Option {
	return func(option *PromOpts) {
		option.ExcludeRegexEndpoint = endpoint
	}
}

func WithExcludeRegexMethod(method string) Option {
	return func(option *PromOpts) {
		option.ExcludeRegexMethod = method
	}
}

func WithExcludeRegexStatus(status string) Option {
	return func(option *PromOpts) {
		option.ExcludeRegexStatus = status
	}
}

配置Prometheus

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#targets.json

[
   {
        "targets":["10.0.0.100:19003"],
        "labels": {
            "job": "web-store",
            "app": "web-store-1",
            "env": "dev",
            "instance": "10.0.0.100:19003"
        }
    }
]

配置Grafana

grafana