如何定位程序问题所在
如何定位bug所在,这是一个很好的问题,我的经验也不丰富,今天综合学习了一下,结合我的经验,总结成文。
在出问题之前,我们需要做的事情是(生产环境,测试环境一般容忍性更强,下面的步骤就看各团队要求了):
- 保留现场,将机器隔离出当前服务,如果是web服务的话,踢出Load Balancer的名单,同时用新的机器顶上去, 这样就不会影响用户,还有一些保留手段比如dump之类的,不过俺没用过
- 这个时候我们就要开始定位问题,常见的手段是看看机器本身啥问题,看应用的日志,根据一些表现来追查,基本上就是一个作出假设,验证假设的过程。
- 复现问题,定位问题的下一步,就是复现,这也是验证问题的一个步骤。
- 如果找到了,那么接下来就是修复问题,验证修复,然后发上去,恢复机器等步骤了。
我们把常见的错误总结分类,然后列举出常见情况及其解决方案。
语法错误
这大概是最常见的问题了,在Go里,编译阶段就可以检测出来,Rust就更了,稍微有一丁点错误,编译器就教你重新做人。 Python大概率是运行或者单测的时候抛异常,这个我们可以通过加lint来检查。
此外我们还需要加单元测试来保证业务逻辑是正确的(当然我们不会加测试来保证单元测试也是正确的。。。)。
这一类错误最容易发现,通常在开发和测试的阶段就已经发现了。
网络错误
网络错误应该是第二常见的,其实这个一般来说,我们都可以从日志里找到答案,毕竟,只要你养成了打日志防锅的习惯,这种问题 基本一翻一个准。
耗CPU类错误
这类问题通常表现为以下几种症状:
- CPU/负载非常高,直接把机器卡住了
- CPU倒是不高,但是程序自己卡死了
怎么区分第一类和第二类问题呢?很简单,看机器监控,如果机器负载直接高到无法响应,或者top发现有一个程序的cpu直接100%, 那么大概率是这个程序代码有问题,非常耗CPU,比如死循环,互相调用的死循环就是这种表现。
如果发现CPU不高,但是程序直接没有响应,这种很有可能是卡死锁了,卡channel了,卡在某个系统调用了。
经过搜索和向大佬请教,通常我们可以这样做:
strace -p [pid]
来看看程序都在调用哪些系统调用,说不定这里可以指明方向。gdb -p [pid]
直接attach上去,进行debug,比如打印调用栈。- 对于Go来说,pprof也可以用来定位CPU消耗在哪里
保留现场以后,还可以在本地尝试用火焰图等来判断是哪里出了问题,不过讲真,火焰图,俺从来没用过。
耗内存类错误
耗内存类的错误,长期运行的话,一定会发现运行的主机,内存越来越高。出现这个现象,很有可能就是出现了内存泄漏,内存泄漏 的问题不好找,一般来说,我会尝试在本地复现,内存泄漏的问题相对来说好复现,然后尝试使用工具去定位,比如go语言,一般用 自带的pprof,直接可以把内存消耗图画出来,但是也有时候定位不到,这个时候只能靠经验去翻一下代码,比如:
- 是否打开文件忘记关了?
- net/http 的 resp.Body 是否忘记关了?
- goroutine卡在某个channel了?或者卡住在别的地方了?
不过一般来说,pprof基本上可以找到问题所在。
耗硬盘类错误
高I/O的问题,通常也会体现为高负载。不过我们有 iftop 和 strace ,就可以定位到是哪个程序大量写入文件,然后就可以比较快的 定位到问题。
总结
常见的错误类型也就是这些了,我们要充分利用日志和监控,这两个东西其实基本上就可以找到绝大部分的问题所在,其余的就得靠 一些表象,和strace、gdb和pprof这类工具去排查,但是基本的思路都是 保留现场,提出假设,验证假设,复现问题,修复问题这个 思路来走的。
更多文章
- socks5 协议详解
- zerotier简明教程
- 搞定面试中的系统设计题
- frp 源码阅读与分析(一):流程和概念
- 用peewee代替SQLAlchemy
- Golang(Go语言)中实现典型的fork调用
- DNSCrypt简明教程
- 一个Gunicorn worker数量引发的血案
- Golang validator使用教程
- Docker组件介绍(一):runc和containerd
- Docker组件介绍(二):shim, docker-init和docker-proxy
- 使用Go语言实现一个异步任务框架
- 协程(coroutine)简介 - 什么是协程?
- SQLAlchemy简明教程
- Go Module 简明教程