程序员该如何定位问题

 

1. 把问题说清楚

2. 说“肯定”的人就是引发问题的第一怀疑对象

3. 做为问题的定位者,一定要有怀疑所有的精神

4. 用数据说话

    4.1 逻辑证据

    4.2 数据分析

    4.3 测试验证

1. 把问题说清楚

定位问题的第一步,也是最重要的一步,就是把问题说清楚。很多时候我们把问题说清楚了,问题也就解决了大部分了。相信很多人都有这样的经历,某个问题花了很长的时间都没有解决,于是向朋友诉苦,讲到一半,突然停住说:我好像知道怎么解决这个问题了。这是因为在讲述的过程中,我们注意到了之前被忽略的细节,把问题说的更清楚了。

工作中,有人反馈问题的时候,我总是会问问题什么时候发生的,调用了哪个地址?问题发生的时候你使用了哪些参数,调用的过程是怎样的?有没有错误码或者错误提示?如果是概率性的问题,还会问问题发生的频率怎样,是固定时间间隔,还是随机均匀分布,还是集中时间段高概率,发生的概率不变还是有逐渐增加或者逐渐减弱的趋势?问题发生的时候服务有什么异常,系统有没有异常,客户端有没有异常?这个过程就像中医的问诊一样,把问题的方方面面问个明白。提问者能回答出来最好,提问者回答不出来,自己就要把这些问题调查清楚,这些问题调查清楚了,问题也就解决一半了。

比如工作中,稳定性测试一般会在晚上进行,测试人员向我反馈问题,说晚上有超时的错误。于是,我开始问诊。

“测试的是什么环境?”

“环境 A”,我想了下,环境 A 的机器和设备较好,应该不会是机器性能的原因。

“请求的参数是什么?”

“是参数组合 P”,我想了下,参数组合 P 是比较轻量级的,一般处理的时间很短,稳定性较强,应该也不是参数引起。于是继续问。

“除了超时错误码,还有其他的错误码或者异常吗?”

“没有了,只有超时的错误码,也没有其他的异常”,没有其他的错误码,那就也不是异常请求的快速中止引起的负载异常导致的超时问题。

“问题发生有什么规律码?”

“有日志,你自己看!”

是的,很多时候,不会让你把所有的问题问完的,他们会烦的。这时候,就需要自己问自己了,自己找答案了。取到测试人员的日志,确实和他们的回答一致,环境 A,参数 P,只有超时,没有其他异常。问题发生的也很有规律,规律就是只集中一小段时间,连续报几秒钟,然后就不报了,其他的也是。调查了那段时间的服务的系统监控,内存和 CPU 没有升高,反而出现了下降,调查 jvm 虚拟机,也没有 full gc 的经典异常。从请求在几秒内总是异常,过后总是正常,再结合这几秒内内存和 CPU 不升反降这两点,网络问题的疑点非常大。后来抓包验证,确实是网络问题。

这个问题的定位,还没看代码,甚至连服务本身运行的日志都没看,只看了请求的日志和系统的资源情况,就可以定位到问题了,如果一得到问题,就开始分析服务的日志,分析服务的耗时,反而接近不了问题的真相。所以问清楚,把问题描述清楚,是定位问题的最关键的一环。

 

2. 说“肯定”的人就是引发问题的第一怀疑对象

问的过程中,有一种回答需要十分注意,就是问有没有执行某个操作时,如果回答包含“肯定”、“绝对”、“一定”这些表示强烈肯定的词汇时,一定不要掉以轻心,谁用了这个词,就调查谁,问题十有八九就是这家伙造成的。这点一定要坚持,无论多大的阻力,无论对方多么不情愿,多么不甘心,多么生气,多么讨厌你,让他查自己,除非给出绝对的证据证明他们没有执行操作,否则说“肯定”的人就是引发问题的第一怀疑对象。当我们问某个操作有没有被执行时,一般是因为这个操作是有问题的,而越是绝对的回答越是缺乏调查的基础。一般人的绝对是调查不充分时的对自己的盲目自信,充分调查过的人回答应该是因为ABC,所以这个操作应该是没有被执行的,反而不会出现强烈的肯定词。

我之前在一家语音公司上班时,有一个做语音识别就是把语音转成文字的产品,客户反馈说相同的语音输出有可能出现不同的识别结果。他们反馈问题的时候,我问他们你没有参数 B 的请求,因为公司内部发现过当参数 A 和 B 一同请求时,相同语音的识别结果会出现变化。下面是我们的对话过程:

“你们有类型 B 的请求吗?”

“没有,我们只有 A 类型请求的业务。”

“确定吗?”

“确定,我们的业务请求只有 A 场景,没有 B 场景。”

“日志当中也没有 B 场景的请求?”

“没有,肯定没有,不用看日志了。”

到这里,我就很警惕了,日志都没有看,就这么肯定,我相信他们期望的请求只有 A 场景,但是谁知道有没有写错参数,有没有人偷偷发送了 B 类型的请求。

“你们还是看下日志,先确认下。”

“我们没有 B 场景的请求,不用看日志,麻烦你们看下怎么回事!”

“不行,至少你们要把日志给我们,要不然定位不了。”

曾经太单纯,别人说肯定没有执行某个操作,就不思考这个可能性了,然后花了几个小时,焦头烂额,百思不得其解,怀着尝试的心情调查下这个操作有没有被执行,结果可想而知,我简直要炸了——“你丫说没有,还那么肯定,然后浪费我几个小时”。所以这次,我当然坚持到底,他们不看日志,不给日志,我就不定位。后来他们看了日志,说日志里面找到了 B 请求,我们看下是谁发送的。于是问题解决了,当然 A 场景和 B 场景同时请求出现问题是另一个问题,客户只需要 A 场景的请求,对他们来说只需要解决 B 请求的出现就可以了。

 

3. 做为问题的定位者,一定要有怀疑所有的精神

在问题被真正定位之前,不仅负责反馈问题的人不能说“一定”,定位问题的人也不能用“一定”这样绝对肯定的词。同时思考问题的时候,一定要精确,比如客户端要获取数据 A,服务端给了 B,我们不能一上来就思考为什么服务端返回了数据 B 给客户端,而是要思考为什么客户端收到了数据 B。为什么要精确的思考呢?避免把自己的思路限定死了,在问题被真正定位之前,任意一个环节都是可疑的。甚至问题本身也是可疑的,我们展开定位问题的工作,也是在这个问题一定是个问题的前提下进行的,随着定位的深入,这个问题也可能被证明为不是一个问题,比如这就是程序运行的正常的结果,之所以被认为是异常,是因为之前我们不知道程序运行的过程。

因此,定位问题的人一定不要认为真的是自己出问题,很可能只是反馈问题的人不知道问题出现在哪儿,或者就是单纯的不想定位问题没有能力定位问题,然后把问题反馈给你了而已。比如上面,客户端要获取数据 A,服务端给了 B,也可能是客户端期望自己获取的是 A,但实际发送了请求 B 的参数,所以服务端是没有问题的,有问题的是客户端,如果一开始思路就是为什么客户端请求 A,服务端返回 B,那可能就要绕很大的弯子才能最终定位问题。

做为问题的定位者,一定要有怀疑所有的精神,不能相信任何承诺,也不能相信任何权威。按照疑点的顺序从高到低排列,从高到低开始排除。在问题的定位过程中,在问题被定位之前,没有任何一个环节,任何一个点是可以 100% 没有排除的,包括操作系统。我定位过的一个很经典的问题,就是虚拟机引发的服务超时问题。

那时服务刚部署到线上,双节点,还没有人访问,服务监控已经启动了,其中一台总是时不时的报一个超时错误,每天有几次吧。观察了一段时间,没有明显的规律,随机性很强,抓包也没有发现网络问题。服务端刚部署,还没有流量流入,只有监控程序在请求着,另一台一直完全正常。我和运维说可能是机器问题,运维说请给出证据证明不是服务的问题,而是机器的问题。“好吧,还有这操作”,我是一个遵守规矩的人,上 nmon,看看超时的时候系统在干嘛。nmon 是非常好用的系统监控的工具,大家可以上网学习。nmon 一看,内存正常,CPU 正常,磁盘 IO 正常,网络带宽,线程数和线程 switch 正常,disk busy 饱和了。disk busy 饱和表示磁盘十分繁忙,可是这个系统还没有请求,服务本身也不会有什么磁盘的读写,怎么会这么忙呢?那么只有一个可能了,服务部署在一台虚拟机上,虚拟机在一台物理机上,如果这台物理机上的其他虚拟机在大规模的读写,那么这台物理机上的所有虚拟机都会受影响的。这是个推测,不是 100% 成立的定位结果,后续需要运维通过查看该物理机上的虚拟机和各个虚拟机的 IO 情况来验证这个推测。运维没做,但是换了机器,问题解决了。

 

4. 用数据说话

问题反馈和定位过程中,任何“觉得”、“好像”都应该进行量化。人的感觉是不可靠的,很容易受到主观偏见的影响,没有耐心的测试人员更容易发现性能问题,太有耐心的测试员更容易忽略性能问题。所对问题的描述进行量化,用数据说话,排除人的主观感受,对问题的排查有事半功倍的效果。比如有客户反馈一款语音交互的产品性能很差,我对它说句话,要很久才有反馈。作为研发人员,在收到这个反馈后的第一件事应该是对这个问题进行量化描述,比如统计产品的平均响应时间,统计该用户的平均响应时间,以明确问题是普遍的,所有用户都存在的,还是个例的,只有这个用户才存在,这决定了我们定位问题是从产品层面入手,还是从这个用户入手。同时,量化的时间也有利于进一步的耗时分析,并确定优化目标,比如原来是 1s,需要优化到 0.5s 。

相信没有一个程序员会愿意接受“你的产品好像有问题”这样的问题反馈。同样,也没有人会愿意接受问题的定位结果是“这个问题可能是这样发生的”。反馈问题、定位问题的过程中如果出现“感觉”、“好像”、“可能”这样的描述或者思考时,相关人应该对这些模棱两可的描述进行验证,要么肯定,要么否定。肯定与否定可以通过逻辑证据、数据分析,测试验证等方法进行。

4.1 逻辑证据

逻辑证据是可以确证问题,没有歧义的证据。比如同一个服务,在环境 A 正常,在环境 B 却出现了内存过高的现象。这是一个 java 的服务,通过 jmap 可以看到其中某个类 P 的对象数量过高。我们可以得到内存过高是由于类  P 的对象数量过高引起的。于是内存过高的问题转换成 P 对象数量过多的问题。P 对象为什么过多呢?它是调某个接口 X 时才会创建的。是不是这个接口只有 B 环境调用,A 环境不调用?我们不能直接抛出定位结果“B 环境内存高可能是因为 B 环境存在 X 调用,而 X 调用会导致类 P 的对象过多”,我们需要进行肯定或者否定。肯定或者否定的直接证据就是访问日志,或者客户端请求日志,如果没有日志,也可以通过 tcp 抓包确认。

4.2 数据分析

数据分析的一个经典例子是在公司初期,有客户端从它的日志中发现服务端有大量的超时错误,并提供了各类错误码的日统计量。服务端觉得自己没有问题,却也没有直接的证据可以证明。这时,我们可以通过分析错误码的变化对这个“觉得”进行肯定或者否定。服务端返回的某个错误码发生的概率一定,那么这个错误码的增加可以反应服务端请求量的增加,如果超时错误的增加趋势与这个错误码增加的趋势一致,那么超时错误很大概率是服务端引起,否则就是客户端引起的。通过分析错误码的变化曲线,与服务端关联的错误码的增加趋势一致,唯独两个超时错误码的增长率异常高,即超时错误的大量增加与请求量的增加是不同步的,这种现象的原因可能是服务端的超时错误增多,也可能是客户端存在异常,服务端的日志没有相应规模的错误日志,因此问题优先由客户端排查。最终问题是因为客户端某类产品的SDK 的 DNS 配置错误造成的,这类产品的访问没有发送到服务端,因此随着这类产品出货没有带来访问量的增加,只带来超时错误的增加,也就是之前看到的错误码的变化曲线。

4.3 测试验证

测试验证是消除不确定性的最佳手段之一。比如某个请求的参数很多,怀疑是其中某个参数引起问题的时候,对比正常请求的参数,取出不同参数,逐渐将异常请求的参数改为正常请求的参数,或者逐渐将正常请求的参数改为异常请求的参数,都可以找到引发问题的参数。

测试验证也是性能问题定位的常用办法。比如有用户反馈过语义请求的问题,A 文本的反应速度比 B 文本快。设计测试方案为发起 1000 次请求,统计请求的处理时长。得到的数据表明问题确实存在,但是服务端的处理时长基本一致,因为服务是多地部署,而问题客户端存在跨机房访问,因此对比两地机房的处理时间。跨机房的访问的时间确实长于本地机房的访问。为什么如此呢?语义的输入文本较短,返回文本随着输入文本内容的不同,有非常大的变化。因此怀疑是返回的文本长度导致网络传输时间的不同,跨机房的传输速率慢,因此变化幅度大。以返回文本长度为自变量,处理时长为因变量,控制其他因素不变进行测试,得到处理时长随文本长度线性增加的结论。因此这是语义返回文本过长,而跨机房带宽不足引起的传输时延过长的问题。

 

5. 总结

程序员工作这么多年,经常和各种各样的问题打交道,以上是在定位问题的过程中的一些比较直接的经验总结。这些总结还未经过提炼抽象与系统化,显得有些零碎,没有清晰的核心与条理性,因而也容易有许多缺漏。后续应该会有更加系统的思考和总结吧。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值