前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >线上问题排错经验总结

线上问题排错经验总结

作者头像
林淮川
发布2021-12-20 15:59:52
1.2K0
发布2021-12-20 15:59:52
举报
文章被收录于专栏:分布式架构分布式架构

- 概述 -

很多年以前,当我还是一个开发菜鸟的时候,觉得写代码是很牛逼并且很关键的事情,当听到有人说做一个项目或者开发一个系统,代码的编写工作只占其中30%的工作量时,当时的我对此说法嗤之以鼻,感觉开发工作受到了侮辱。后来,自己开始做技术leader、项目经理、做架构,慢慢认识到软件开发是一个系统工程,代码编写真的只是其中的一环,而且如果代码写不好测试不到位的话,那就是噩梦的开始。经历过多次噩梦洗礼之后,认清一个现实:CRUD,Ctrl c,Ctrl v,这不是高科技。开发与测试/运营/业务,不是对立关系,而应该相辅相成,如果开发人员对代码抱有敬畏之心,明白每行代码会带来什么样的系统行为,对测试/运营/业务抱有开放包容的心态,对他们的挑刺当成一种鞭策和挑战,写出更加“美丽”的代码,那这样的开发人员将是任何公司的财富。

但再牛逼的开发工程师都无法避免bug的存在,排查问题的能力是开发工程师对技术认知的一个整体反映。这里从简到难整理一些常见线上问题处理场景。

- 代码异常,排查难度低,解决难度低 -

这种问题,一般会在日志文件中发现明确的报错信息,只要代码中对异常处理设计好,日志打印按照规范来,会很容易定位到代码中快速解决,测试,然后发版。

解决:线上大部分的问题都是这样的情况,也是代码质量的一个评判指标,而提高代码质量的方法也有很多比如首先开发人员要明白业务逻辑,然后通过findbug,sonar等工具发现低级错误,通过单元测试再发现一些隐藏bug,然后通过结对review代码,小组review代码进一步优化代码,这样的流程下来基本可以有效提升代码质量。

- 环境异常,排查难度中,解决难度中 -

环境问题也是常见的线上问题,但绝大多数的环境问题都是代码问题导致的,计算机系统设计的复杂度远比代码复杂度要高,所以环境问题的表现也是多种多样。对环境问题的排查能力也体现了一个开发工程师的能力高低。耳熟能详的几个命令top,free,netstat等等,一般上来先敲上去看看机器的整体资源情况,但是接下来问题就来了,如何能通过这些指标来判断机器问题呢,cpu使用率高就代表系统慢么?iowait高就代表磁盘性能下去了?free的内存比较低就代表内存不够了?tcp连接的各种状态代表什么?每个参数都是有含义的,要能够熟练的分析则需要经验的积累和不断的学习。

1、内存不足或者泄露

这类问题表现为系统响应变慢甚至无法响应,内存使用率飙高,cpu使用率和负载可能也会飙高,代码有时会报出异常,比如OOM异常,有时又不会报出异常,内存不足的原因一般分几种

  1. 系统内存就是不足,运行一个程序总归是需要内存资源的,如果系统的内存就是很低,比如2C4G的机器,那就很难运行一些复杂的程序,那这时就考虑增加系统配置。
  2. JVM内存分配不足,或者内存泄露,通过jstat -gc pid [间隔时间] [重复次数] 这样的命令可以方便快速的看到系统的gc情况,如果发现gc频繁,那就说明jvm内存不足或者泄露,当然也可以借助jdk自带的jvisualvm或者jconsole工具来监控jvm的情况,也是非常好用的。

如果是内存不足,则可以调整JVM堆内存大小以及垃圾回收算法,目前一般是使用CMS或者G1这两种垃圾回收器,当然这两种垃圾回收器有各自的特点,比如CMS一般是大量online请求,请求数据量小,系统RT要求高的场景下使用;G1则适用于偏分析和处理数据的后台项目,适合大对象的回收,同时也兼具了响应时间的要求。

如果是内存泄露,仅仅调整JVM堆内存配置是无法解决的,它往往意味着代码问题或者资源被频繁申请又没有释放。

  • 首先如何定位发生OOM的点呢?通过jmap -histo:live PID | more命令可以列出存活对象的信息,并按照内存占用大小排序,帮助定位问题。
  • 但实际情况下,远远不够,比如,我们经常会发现占用内存最大的对象是char[],那到底是哪些对象使用的char[]占用内存过多呢?这时就可以通过jmap -dump:live,format=b,file=heap-dump.bin命令将堆里的内容打印到dump文件中,
  • 然后使用MAT工具来分析,找到出问题的对象,对于MAT工具的使用也是有一点门槛的,比如MAT提供的一些树状,饼状图该如何看;outgoing references和incoming referenecs分别代表什么意思?OQL这种MAT特有的对象查询语句该如何使用等等。

2、代码没有异常,系统没有响应,进入一种夯死状态。

这种问题的表现,经常是看系统资源负载时看不出问题,但是系统就是不提供服务了。

往往要关注系统的进程情况,比如,我遇到过进程不正常退出导致的僵尸进程过多,把整个系统给拖死的情况,当时是敲了ps -ef|grep defunt命令,发现了大量的僵尸进程存在,通知运维同事将系统回炉重造之后,问题解决。

还可能需要关注系统的线程情况,如果线程创建过多,线程的切换以及资源的占用,会给系统带来非常大的负荷,通过pstree命令或者/proc/PID/fd /proc/PID/task这些命令可以查看某个PID下打开了多少线程数,然后再做分析。更具体的一些方法,例如使用jstack -F -l PID命令来打印JVM线程堆栈信息,来详细分析线程问题。这就要求开发工程师对多线程开发以及对应的线程状态要有足够的了解,比如线程堆栈中显示“xxx-thread-1” id = 9826 BLOCKED on 对象A owned by “xxx-thread-2” id = 9080;然后再根据9080这个线程id,查看其线程情况发现,“xxx-thread-2”id = 9080 WAITING on 对象B,然后下面是这个对象的堆栈信息。这个例子比较典型,它说明id=9826的线程阻塞在对象A上了,而这个对象A当前被id=9080的线程持有,而这个9080的线程又在等待对象B的锁释放,这时对象B如果迟迟不释放锁的话,那9826这个线程就会一直阻塞在那里。这个例子是当时我定位druid数据库连接池泄露问题时,查看线程堆栈的一个情况,由于开发人员对与druid连接池的配置不合理,导致数据库连接无法释放,后续获取连接的线程阻塞,又没有抛出异常,这时程序就"夯死"了,无法提供服务。

- 系统间排查问题,排查难度中,解决难度中 -

现在的服务化盛行,同时系统中还会使用很多的中间件,系统间的问题排查也是常有的事情,由于各个系统的实现方式和逻辑内聚,这就要求要有完备的监控平台来支撑系统间的问题排查,比如,需要有统一的OS层监控系统,展示各系统服务器,网络等情况,同时还要有定制化的监控系统,比如服务治理平台的服务各项指标监控,服务调用的全链路监控,网络存储或者反向代理负载均衡设备的流量,连接,网络吞吐等监控,特定中间件比如MQ的消息挤压情况,zookeeper的节点数,watch数,长连接数等监控。

示例1,netstat命令查看,主动发起关闭连接的一方出现大量的TIME WAIT状态,这样的情况比较多的发生在大量短连接请求下,服务端处理完这些请求之后,主动发起大量的关闭连接请求,这时服务端会有非常多的连接处于TIME WAIT状态,TIME_WAIT它是有一个2MSL的等待时间的,而且连接是占用系统资源的,如果这时持续的高并发一直打到服务器上,必然会导致因资源不足而产生很多的连接失败,解决问题的思路可以从负载均衡策略上做文章,或者增加服务端的机器。

示例2,netstat命令查看,被动关闭连接的一方出现大量的CLOSE WAIT状态,同样也是关闭TCP连接的过程中,被动关闭的一方因为自身性能衰减严重,无法响应关闭的请求,从而导致了大量的连接处于CLOSE WAIT状态不能释放,直接影响服务端对外提供网络服务。这个问题一般是发生在被动关闭连接的一方负载特别高,或者资源特别紧张情况下,无法快速响应关闭连接的请求,这时解决问题的思路,一是看,被动的一方为什么负载高;二是看,主动发起连接的一方的并发数是否合理,可不可以降低并发。

示例3,有一次使用分布式存储处理文件的时候,发现处理的时间好像比之前变长了(运维和开发人员需要特别细心才能发现这样的异常),这时通过网络监控平台,发现与分布式存储集群之间的网路吞吐衰减的厉害,比如,前几天监控来看一直维持在30G左右的网络吞吐,但是发生问题的那天却只有10G的网络吞吐,这样的情况,必然会导致系统处理文件的时间变长。有了这个样的切入点,就很容易分析问题,网络存储组和应用开发组同时排查自己的问题,最终发现是由于业务方的服务器上最新挂载了NAS的软连接,去掉这个软连接再试,网络吞吐就回归正常水位,处理时间也回归正常。至于为什么挂载了NAS的软连接会拖慢整体的文件处理速度,这就是后话了,问题要快速解决,锅总归要先"甩出去"先。

- 日志无异常,监控正常,但系统运行出错,排查难度高,解决难度高 -

这样“诡异”的问题,是开发人员最不不愿意碰到的,就像侦探在处理案发现场的时候,没有给他提供任何表面上的线索,这时怎么办?后来我总结了一下,出现这种情况,一般有两种原因:

  • 一种是非常低级的错误,比如代码中将异常给“吞了”,这种问题如果定位到了,那就“杀”一个当事程序员祭旗。。。
  • 另一种就是框架或者中间件底层代码出现了问题,这就需要花费一些时间去深扒源码,看看能不能找到一些蛛丝马迹。

示例,我们现有项目中使用了spring batch这个批处理框架来开发我们的批处理程序,有一次发生了线上问题,表现是没有任何异常出现,但是批处理的作业就是无法正常调起,服务器重启之后问题就解决了。开发人员束手无策,不知从何处下手。问题转到我们这边之后:

首先根据一个现象就是“服务器重启之后问题就解决了”来入手,对代码一层层的往下扒,发现了一些蛛丝马迹,spring batch在系统加载的时候会初始化一些运行信息到内存中,然后根据这些初始化的信息来依次调用作业程序,

那这时候大胆猜测一下,如果这些加载到内存中的“元数据”缺失了,或者顺序错了,就会发生那个诡异的问题。但是如何佐证这个推测呢。

我的方法就是将线上出问题那台机器的dump信息打印出来,通过MAT工具来进行分析,这时就需要用到MAT强大的对象查询语言OQL了,这个东西可以像查数据库一样查出你怀疑的那个对象在对内存中的信息,然后扒开来一看,果然发现加载在内存中的“元数据”有问题,剩下的就是将原因讲清楚,然后交给开发人员去调整对框架的用法咯。

这里贴上,当时用的OQL语句:select name from 对象A全名 a where a.name.toString() = "程序中使用对象A的变量名"

- 总结 -

絮絮叨叨写了这一堆抽象的文字,又想起以前还是菜菜鸟的时候,那时的我无知又无畏,而现在的我,依然感觉无知同时又变得更“怂”了,这个“怂”是源于我对软件领域工作的敬畏之心与日俱增。工匠精神,即不放弃探究本质的努力,同时又保持一颗敬畏之心,与君共勉。

- 作者介绍 -

chris 架构师一枚,早期就职于知名通信公司,致力于通讯软件解决方案。之后就职于五百强咨询公司,致力于为大型车企提供数字化转型方案。现就职于平安银行信用卡中心,帮助平安银行落地核心系统的去IOE化改造。追求技术本质,目前主要方向是复杂系统的分布式架构设计。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 川聊架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档