问题背景
大促期间线上环境压测,其中有2个服务A&B,其中A是golang服务,B是php服务,A依赖B的一个接口。压测过程中B挂掉,A出现了空指针的panic,但是panic被defer()函数的recover()函数捕获到,但是A的协程数量却一直在升高。
问题复现
在B服务正常时如同以下请求
func main() {
engine := gin.Default()
//输出当前协程
go func() {
for {
fmt.Printf("当前协程数%d\n", runtime.NumGoroutine())
time.Sleep(time.Millisecond * 500)
}
}()
engine.GET("t", func(ctx *gin.Context) {
var wg = sync.WaitGroup{}
wg.Add(1)
//请求B服务
go func() {
defer func() {
if err := recover(); err != nil {
fmt.Printf("recover panic : %v", err)
}
}()
t := time.NewTimer(time.Millisecond * 500)
<-t.C
wg.Done()
}()
wg.Wait()
ctx.JSON(200, "hello world")
})
log.Fatal(engine.Run(":8080"))
}
输出内容为:
当前协程数2
当前协程数2
当前协程数2
当前协程数2
当前协程数2
当前协程数2
当前协程数6
[GIN] 2022/07/13 - 15:42:17 | 200 | 504.453122ms | ::1 | GET "/t"
当前协程数4
当前协程数4
当前协程数6
[GIN] 2022/07/13 - 15:42:18 | 200 | 502.84859ms | ::1 | GET "/t"
当前协程数4
当前协程数8
[GIN] 2022/07/13 - 15:42:19 | 200 | 500.95604ms | ::1 | GET "/t"
[GIN] 2022/07/13 - 15:42:19 | 200 | 500.24274ms | ::1 | GET "/t"
当前协程数10
[GIN] 2022/07/13 - 15:42:19 | 200 | 500.100357ms | ::1 | GET "/t"
[GIN] 2022/07/13 - 15:42:19 | 200 | 500.394115ms | ::1 | GET "/t"
[GIN] 2022/07/13 - 15:42:20 | 200 | 500.104968ms | ::1 | GET "/t"
当前协程数10
[GIN] 2022/07/13 - 15:42:20 | 200 | 500.16802ms | ::1 | GET "/t"
[GIN] 2022/07/13 - 15:42:20 | 200 | 502.288884ms | ::1 | GET "/t"
[GIN] 2022/07/13 - 15:42:20 | 200 | 501.079461ms | ::1 | GET "/t"
当前协程数8
[GIN] 2022/07/13 - 15:42:20 | 200 | 504.681474ms | ::1 | GET "/t"
[GIN] 2022/07/13 - 15:42:20 | 200 | 504.968855ms | ::1 | GET "/t"
当前协程数4
当前协程数4
当前协程数4
当前协程数4
此时协程数处于正常状态。
如果此时B服务挂掉,示例代码如下:
func main() {
engine := gin.Default()
//输出当前协程
go func() {
for {
fmt.Printf("当前协程数%d\n", runtime.NumGoroutine())
time.Sleep(time.Millisecond * 500)
}
}()
engine.GET("t", func(ctx *gin.Context) {
var wg = sync.WaitGroup{}
wg.Add(1)
//请求B服务
go func() {
defer func() {
if err := recover(); err != nil {
//B panic会被recover捕获
fmt.Printf("recover panic : %v", err)
}
}()
//请求B服务5s后超时
t := time.NewTimer(time.Millisecond * 5000)
<-t.C
//B服务超时业务逻辑出现panic
panic("B panic!")
wg.Done()
}()
wg.Wait()
ctx.JSON(200, "hello world")
})
log.Fatal(engine.Run(":8080"))
}
输出内容为:
当前协程数2
当前协程数6
当前协程数6
当前协程数6
当前协程数6
当前协程数10
当前协程数16
当前协程数16
当前协程数16
当前协程数16
当前协程数16
recover panic : groutine panic!当前协程数15
当前协程数15
当前协程数15
当前协程数15
recover panic : groutine panic!recover panic : groutine panic!recover panic : groutine panic!当前协程数12
recover panic : groutine panic!recover panic : groutine panic!当前协程数16
当前协程数18
当前协程数18
当前协程数18
当前协程数18
当前协程数18
当前协程数18
当前协程数18
当前协程数18
当前协程数18
recover panic : groutine panic!recover panic : groutine panic!recover panic : groutine panic!当前协程数15
recover panic : groutine panic!当前协程数14
当前协程数14
当前协程数14
此时当前协程数随着请求的增多不停增长!
问题原因
在代码中,B服务超时,出现panic,此时panic所在的函数会在panic当前行停止并向上返回panic,此时wg.Done()未被执行,defer+recover虽然已经catch住了panic,但是并没有使用wg.Done()来减少计数,这就导致计数异常,外层的wg.Wait()一直在等待,请求会卡住。
解决方案
-
给defer()函数增加wg.Done()
-
给ctx增加超时时间
defer func() {
if err := recover(); err != nil {
fmt.Printf("recover panic : %v", err)
}
wg.Done()
}()
一般会封装下gin的ctx,增加中间件方法
文章评论