recover 并不是无懈可击的
曾经天真地认为只要严格遵守有 go 必有 recover 就能保证程序永不宕机。直到有人对开源库有了类似下面这样的误用:
package main
func main() {
var a = map[int]int{}
defer func() {
if err := recover(); err != nil {
println("oh no")
}
}()
go func() {
defer func() {
if err := recover(); err != nil {
println("oh no")
}
}()
for {
a[1] = 1
}
}()
for {
a[1] = 1
}
}
当然了,实际的场景不会有这么弱智,这样写主要是为了方便复现。
go 在 1.6 增加了 map 并发读写时,直接让程序崩溃的新 feature,崩溃是用 panic,然而这种 panic 通过 recover 是捕获不到的。因为官方认为这是一种不可恢复的错误。程序继续运行结果不可知了吧。
具体参考这里。
在 go 1.9 官方引入了 sync.Map,然而这个新玩具就只是为了解决官方自己在 grpc 中遇到的性能瓶颈,即由于读写锁的 cpu cache contention 导致的性能下降问题。而小版本升级中也没有为这个 sync.Map 设计特权语法,store 和 load 接口均需要操作 interface 类型,实际上虽然性能好了一点,但如果你真的要用,那结果就是满天飞的 interface。
扯远了。说回 panic。
你可能以为在 go 里,map 的并发读写是唯一一种捕获不到,那么我们再来看一个 demo。
package main
import "reflect"
func main() {
defer func() {
if err := recover(); err != nil {
println("panic")
}
}()
fv := reflect.ValueOf(func(int) struct{} { return struct{}{} })
args := []reflect.Value{reflect.ValueOf(0)}
for {
fv.Call(args)
}
}
你手边有任意的环境都可以尝试一下,实际上这段代码会触发 golang 自己的 bug(汗。。
直到 1.9.2,这个 bug 才修复。触发条件就像 demo 里一样,高并发下调用 reflect.Call,一调一个准。且也无法用 recover 来恢复。
结论:go 服务不用 supervisor 又不加监控就等着被开除吧。