golang错误和异常

go错误和异常

错误和异常是两个不同的概念,但我们在开发中又非常容易混淆这两个概念,把一切非正常情况都看做是错误,而不区分错误和异常,即使程序中有异常抛出,也将异常即使捕获并转换成错误;因此: 我们的明确什么是错误,什么是异常;

错误:指可能出现问题的地方出现了问题,即这种情况在人们的预料之中
异常: 指不应该出现问题的地方出现了问题,这种情况在人们的预料之外

golang语言中使用了error来表示错误,使用panic抛出异常,通过defer和recover函数来捕获异常,

Golang错误和异常的互相转换:

1. 错误转异常,比如程序逻辑上尝试请求某个URL,最多尝试三次,尝试三次的过程中请求失败是错误,尝试完第三次还不成功的话,失败就被提升为异常了。

2. 异常转错误,比如panic触发的异常被recover恢复后,将返回值中error类型的变量进行赋值,以便上层函数继续走错误处理流程。

1、异常处理场景

1. 空指针引用
2. 下标越界
3. 除数为0
4. 不应该出现的分支,比如default
5. 输入不应该引起函数错误

2、错误处理的正确姿势

  • 失败的原因只有一个时,不使用error
  • 没有失败时,不使用error
  • error应放在返回值类型列表的最后
  • 错误值统一定义,而不是跟着感觉走
  • 错误逐层传递时,层层都加日志
  • 错误处理,使用defer释放资源
  • 当尝试几次可以避免失败时,不要立即返回错误
  • 当上层函数不关心错误时,建议不返回error
  • 当发生错误时,不忽略有用的返回值

3、异常处理的正确姿势

  • 在程序开发阶段,坚持速错
  • 在程序部署后,应恢复异常避免程序终止
  • 对于不应该出现的分支,使用异常处理
  • 针对入参不应该有问题的函数,使用panic设计

4、golang常用的debug工具

  • ContentsMemStats and GC
  • PProf
  • Trace
  • GDB and Delve

4.1. MemStat & GC
首先先获得一些基本的信息,内存使用和GC情况。
MemStats:

 // read mem stats
var m runtime.MemStats
runtime.ReadMemStats(&m)

GC:

// disable gc when start
GOGC=off go run main.go
// disable gc and manually trigger gc
debug.SetGCPercent(-1)
runtime.GC()
 
// read gc stats
var g debug.GCStats
debug.ReadGCStats(&g)

MemStats中我们关注的是Alloc/HeapAlloc,这个值代表了当前heap的大小,另外就是HeapObjects,代表了当前heap中有多少个对象,同时应该关注一下Frees,这个值代表了一共释放过多少的对象,可能当前内存使用已经降下来了但过去某个时间曾经升高过。同时可以结合GCStats查看一下GC的情况, LastGC代表了上次GC时间,NumGC代表了一共GC过多少次,PauseTotal总暂停时间以及Pause暂停历史。

4.2. pprof
MemStats和GCStats可以给我们一个大概的情况,但是更具体的信息,例如哪里耗内存最多,哪里耗CPU更多,我们需要更准确的情况,可以使用自带的pprof。

// CPU
pprof.StartCPUProfile(os.Stdout)
defer pprof.StopCPUProfile()
go run main.go > cpu.profile
go tool pprof cpu.profile
 
// Mem
pprof.WriteHeapProfile(os.Stdout)
go run main.go > cpu.profile
go tool pprof cpu.profile

同理,memory.profile给我们的也是当前的内存使用情况,有些时候并不适合debug,如果想知道全部的分配情况:

go tool pprof --alloc_space memory.profile

除了问题原因比较明确或者比较容易复现的情况,上面输出memory和cpu profile的情况有些时候并不那么实用,这个时候一方面我们可以结合上面的MemStats使用,如果达到某个值就输出一份profile,或者直接使用下面的通过web ui把profile信息实时输出:

// In time
import _ "net/http/pprof"
func main(){
  go func() {
    log.Println(http.ListenAndServe("localhost:6060", nil))
  }()
}
 
// http://localhost:6060/debug/pprof/
// - allocs: 这个跟上面的--alloc_space是一样的,同时还可以查看到MemStats
// - block: 没有在运行的时间,例如等待channel,等待mutex等。
// - cmdline: 当前程序的启动命令
// - goroutine: goroutine的信息
// - heap: 等同于上面的memory profle
// - mutex: Stack traces of holders of contended mutexes
// - profile: 等同于上面的CPU Profile
// - threadcreate: 线程
// - trace: 见下面trace部分

依旧可以使用工具来查看:

go tool pprof --alloc_space http://localhost:6060/debug/pprof/heap

除了实时分析外,还可以导出调用栈的图表:

go tool pprof -web/pdf/gif/dot/png... cpu.profile

或者直接在浏览器实时查看:

go tool pprof -http [host]:[port] [options] [binary] <source>

**4.3. traceprofile **
go程序可以给我们一些直观的输出,例如时间用在哪里,内存用在哪里,但是更加深层次的原因,例如一个web服务器为什么响应时间总是很久,pprof和trace主要的区别还是维度不太一样,一个更像是代码栈层面的,一个是更横向的。trace更关注与latency。

比如说一个请求在client观察从发送到完成经过了5s,做profile可能发现这个请求的CPU时间只有2s,那剩下的3s就不是很清楚了,profile更侧重的是我们代码执行了多久,至于其他的,例如网络io,系统调用,goroutine调度,GC时间等,很难反映出来。
生成trace可以通过下面的代码:

trace.Start(os.Stdout)
defer trace.Stop()
go run main.go > app.trace
go tool trace app.trace

或者上面的pprof的web ui也支持下载trace。
在trace上可以清楚的看到每个goroutine的起始,怎么生成的,每个cpu内核在做什么这些。

4.4. gdb & delve
对于百分之80的情况,上面的工具就已经足够了,有些极端的情况依旧无法避免,例如说我们的程序已经无响应了,那么即使开了实时的web ui,也是打不开的,这个时候就需要经典的gdb了。

gdb --pid=pid [executable]
gcore pid // 保存core dump

GDB是很大的一个topic,而且也有很多资源是介绍GDB的,关于GDB+go的使用可以直接查看官方文档:https://golang.org/doc/gdb, 而更多GDB的使用直接Google。

GDB比较通用,不是很能直接反映出go语言的特点,例如goroutine。所以也可以使用delve,delve可以理解为go版本的gdb:

dlv debug // 以dlv方式运行,跟go run一致
dlv test // 以dlv方式运行测试 跟go test一致
dlv exec [executable]// 以attach方式运行一个编译好的binary
dlv attach pid [executable] // attach到一个运行中的进程 貌似会暂停进程?

同时,delve也可以查看gdb保存的core文件:

dlv core [executable] [core file]

对比GDB来说,比较有特色的命令有两个(gdb也有这个):

(dlv) goroutines
(dlv) goroutine [goroutine-id]

可以用来查看具体的goroutine信息。
全部命令可以参考 https://github.com/go-delve/delve/blob/master/Documentation/cli/README.md

参考原文链接:
https://www.cnblogs.com/zhangboyu/p/7911190.html
https://www.zhihu.com/question/40980436/answer/767289819

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342