问题引出场景
近期公司某个功能模块线上运行异常。异常情况:某个接口阻塞不通,但服务状态OK. heart-beat /ping 可以通;
代码背景:由于需要防止以避免服务器资源耗尽,接口内部使用了 全局的channel 缓冲通道 对该接口进行全局访问次数限制,此处贴代码
从执行顺序上看,似乎没有什么问题。但真实运行后问题出在了业务 fn()方法内部,重点 :fn方法若内部异常Panic时,被 recover 以后,是不会 走到 业务的出通道里,因此导致此次业务任务的通道一直被占用。重复70次同样类型数据的接口请求就把通道沾满了。。结果就导致线上该接口调用的心情求会一直阻塞,根本无法使用。
排查和定位异常问题
1.在上边描述了该问题导致的线上接口无法使用,但服务容器状态却又OK。毫无疑问,直接就定位到了 channel 的 output (出通道)阻塞。仔细debug一圈,发现接口在接受 特定参数 的时候,会引发fn()内部某处代码 panic,恢复后 就不会进行到预先码好的 <- runlimit 里
2.仔细想想,能导致golang 程序Panic的,其实不做recover()还好,直接就挂服务提示哪有问题,做了(好处就是不挂service)后便更难发现错误。。,也就那几种基本情况,编码时还须认真仔细做好规避。
这里贴一个例子
package main
import "fmt"
func main(){
defer func(){ // 必须要先声明defer,否则不能捕获到panic异常
fmt.Println("c")
if err:=recover();err!=nil{
fmt.Println(err) // 这里的err其实就是panic传入的内容,55
}
fmt.Println("d")
}()
f()
}
func f(){
fmt.Println("a")
panic(55)
fmt.Println("b")
fmt.Println("f")
}
输出结果:
a
c
d
exit code 0, process exited normally.//这个就是
查看fn()内部业务代码,与上述代码一样,执行到recover后就中断了当前协程的后续函数调用栈的执行,不会走到上边的 <-runlimit;recover代码贴下
因为 recover 主要用于无法预知的错误。例如:数组方面、map
例如:数组索引越界,map key不存在,interface 类型断言与指针所指向的值类型不符合时会 Panic 被recover捕获到
,围绕这个规律定位到了 fn()内部不严谨的代码
1.debug到这里
2.向上一个stack jump一下就发现了
图片内表述的 是 stateText 内部的 shareType 默认被json代码包转换为了 float64 并非 int,代码里断言该interface 为 int 时进入了 panic assert error: panicDotType Error。类型断言失败!!!
解决思路,在不确定Interface{}内部的类型是,需要用严谨的标准方式对其进行双返回值接受断言。代码如下:
if stateText, ok := params.Data.StateText.(map[string]interface{}); ok {
}
这种方式可以有效避免类型错误而引发的panic.
另外,各位同学在编码习惯时,索引越界,Map key不存在,类型断言都需要注意一下。