如何定位程序问题所在

如何定位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这类工具去排查,但是基本的思路都是 保留现场,提出假设,验证假设,复现问题,修复问题这个 思路来走的。


微信公众号
关注公众号,获得及时更新

更多文章
  • 我们真的需要这么复杂的技术栈吗?
  • Go设计模式:装饰器模式
  • 程序员的MySQL手册(一): 安装,基本配置
  • ElasticSearch学习笔记
  • Go设计模式:composite模式
  • 拯救删除ZFS之后的分区表
  • Linux使用redshift自动调整屏幕色温
  • Go设计模式:桥接模式和策略模式
  • Go设计模式:单例模式、原型模式和Builder模式
  • 操作系统也是CRUD
  • Go设计模式:简单工厂模式
  • 把USB设备穿透给虚拟机里的系统
  • debug故事之:事务让生活更美好
  • Go设计模式:模板模式
  • Go设计模式:适配器模式