go协程使用错误导致每次请求协程数上涨

2022年7月18日 335点热度 0人点赞 0条评论
 
点击上方“技术爱”关注我们



问题背景

大促期间线上环境压测,其中有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当前协程数16recover panic : groutine panic!当前协程数15当前协程数15当前协程数15当前协程数15recover panic : groutine panic!recover panic : groutine panic!recover panic : groutine panic!当前协程数12recover panic : groutine panic!recover panic : groutine panic!当前协程数16当前协程数18当前协程数18当前协程数18当前协程数18当前协程数18当前协程数18当前协程数18当前协程数18当前协程数18recover panic : groutine panic!recover panic : groutine panic!recover panic : groutine panic!当前协程数15recover panic : groutine panic!当前协程数14当前协程数14当前协程数14

此时当前协程数随着请求的增多不停增长!

问题原因

在代码中,B服务超时,出现panic,此时panic所在的函数会在panic当前行停止并向上返回panic,此时wg.Done()未被执行,defer+recover虽然已经catch住了panic,但是并没有使用wg.Done()来减少计数,这就导致计数异常,外层的wg.Wait()一直在等待,请求会卡住。

解决方案

  1. 给defer()函数增加wg.Done()

        defer func() {        if err := recover(); err != nil {          fmt.Printf("recover panic : %v", err)        }        wg.Done()      }()
  2. 给ctx增加超时时间

    一般会封装下gin的ctx,增加中间件方法

49060go协程使用错误导致每次请求协程数上涨

这个人很懒,什么都没留下

文章评论