gitbook/高并发系统设计40问/docs/192195.md
2022-09-03 22:05:03 +08:00

11 KiB
Raw Blame History

春节特别策划 | 高并发下如何发现和排查问题?

你好,我是唐扬,新年快乐!

过年嘛,都要吃好玩好,给自己一年的辛苦付出“加餐”,那咱们的课程也不例外,在新的一年里,我为你策划了两期加餐,今天先来聊聊在高并发下,我们如何发现和排查问题。

为什么要讲这个问题呢?是因为我在课程结束之后,发现有同学反馈说:

虽然课程里几乎涵盖了高并发系统设计的全部方面(比如数据库、缓存和队列的使用、分布式系统主要组件的原理,以及系统运维方面需要关注的重点),但自己按照课程中提供的方式正确使用了组件,在实际工作中仍然会发现系统中各种各样的问题,比如服务性能衰减、依赖资源的抖动甚至是服务整体故障。

尤其在高并发环境下,由于并发请求更多,对于资源和服务的压力更大,所以原本隐藏在冰山下的问题又都会在某一时间突然浮出水面。

这其实就像墨菲定律说的那样: 如果事情有变坏的可能,不管这种可能性有多小,它总会发生。这不是一个数据概率问题,也不是一个心理学效应,而是一种必然的法则。

在高并发场景下一些细微的问题可能会迅速恶化并且对系统中多个模块的SLA带来巨大的影响。比如业务仅仅缓存的平均响应时间增加1ms或者缓存命中率下降1个百分点都会带来灾难性的影响。这不仅增加了问题排查的难度也对问题排查的及时性提出了更高的要求。

那么作为团队核心开发成员的你,在系统存在隐患的时候,如何快速发现问题?在出现问题的时候又要如何排查呢?接下来,我就结合课程中讲到的一些知识,通过一些实际案例,再带你深入了解一下。

如何及时发现问题

这一点我们在课程中已经有过介绍了,在我看来,主要有两个手段:监控和压测。在这期加餐中,我再用几个实际的案例强调一些你容易忽视的点。

首先,你需要格外重视客户端的监控(也就是我在31讲中提到的监控),因为这一级的监控是最靠近用户的,也最能真实反映用户的使用体验,有时候你发现后端的监控一切正常,但其实在用户这一侧已经存在比较严重的问题了。我分享一下这几天在项目中发生的事情。

我的项目最近在做上云的迁移,在迁移到云上之后,我们会使用某公有云的外网负载均衡服务。在这个负载均衡服务上,我们购买了一定量的外网带宽包,这样就可以让内网应用和外网通信了。但是,当流量超过了这个带宽包中提供的带宽总量,就会产生丢包的现象。而在元旦节日的高峰期时,这个带宽包就达到了瓶颈,但是从服务端监控来看,所有的性能指标都显示正常,但是在客户端这边已经有用户感觉到接口响应时间缓慢了。

从客户端监控来看在带宽被打满的那段时间里客户端请求服务接口会有大量504的响应码如果我们可以针对客户端监控做一些及时的报警就会很容易发现这个问题了。

另一方面,压测也是一种常规的发现系统问题和隐患的手段(在课程中我也介绍了应该如何实现全链路压测系统,以及在实现中需要注意的点)。而在最近的迁移上云项目中,我也着重对云上部署的服务做了一次完善的全链路压测,在压测的过程中确实发现了很多云上服务和组件隐藏的问题,下面我就分享一个真实的案例。

在我现在维护的项目中会重度依赖Redis缓存作为提升数据读取速率的手段而在我们做全链路压测过程中当我们的压测流量到达一定的量级会出现访问某一个或者几个Redis组件时平均响应时间有比较大波动的情况有比较多的慢请求影响了请求的响应时间。

发现这个问题之后我们首先看了一下Redis的监控发现在波动期间Redis的CPU使用率会有大幅度的上升接近100%同时观察到Redis会逐出大量的Key所以推断逐出Key时会消耗大量的CPU时间从而导致CPU负载升高。进一步通过观察监控发现在逐出大量的Key之前Redis的连接数会有比较大的上涨。

我们和公有云维护同学讨论后确认了原因由于我们的Redis内存使用率接近100%那么当连接数大量上涨的时候Redis需要逐出Key释放出内存资源从而保存连接信息那么为什么Redis的连接数会大涨呢 进一步观察业务错误日志同时排查Redis客户端代码之后我们发现在连接数上涨之前业务服务在访问Redis的时候会有一些慢请求这些慢请求会导致业务认为与Redis的连接出现问题会重新建立新的连接并且异步关闭现有连接从而导致连接会在短时间之内有大幅度的上升。

而我们通过使用tcpdump抓取网络包发现在这一段时间Redis的响应时间确实有比较大幅度的升高。通过进一步排查Redis的实现逻辑我们发现在Redis3.0版本中使用的jemalloc在释放内存时会存在偶发的卡顿情况会导致短时间内访问Redis的所有请求全部阻塞从而导致响应时间升高这样我们就找到了这个问题的根本原因。

而在云厂商解决了这个问题之后,我们再次压测发现问题不再复现。你看,在这个案例中,我们正是通过全链路的压力测试发现了问题,并且压测也能够帮助我们验证优化方案是否可行。

排查问题的方法是怎样的

那么,发现了问题之后,有哪些排查问题的方法呢?

其实问题尤其是性能问题比较难排查的原因在于我们通常看到的是问题的外在表象比如接口响应时间长了、系统的SLA下降了、消息队列堆积了等等而我们想要从表象推理出根本原因就需要分析能力、归纳总结能力以及一些经验的积累了。这就好比你可以从表情和语气推断出女朋友生气了但要花费很多的精力再加上之前的一些经验总结才能够推断出女朋友为什么生气。

当然监控和日志依然是我们排查问题的主要手段大部分的问题我们都可以通过监控和日志来找到根本原因。比如我在刚刚维护现在的项目时发现每天凌晨2点的时候系统的SLA会有一个抖动于是我追查系统的错误日志发现那段时间访问Redis会有少量的慢请求进一步与DBA确认那段时间Redis在做BGSAVERedis Server会有短暂时间的阻塞这就解释了Redis的慢请求以及SLA的下降。

而有些问题需要我们做一些归纳总结,针对性地分析问题发生的一些共性特点。比如,是不是只有某几台服务器存在这个问题,或者出现问题的间隔时间是不是固定的等等。

我在之前维护一套注册中心的时候,遇到过这么一个问题: 注册中心总是在每天晚上的时候出现大量节点被标记为不可用并且很快又被标记可用的情况直到过了凌晨0点才会恢复。

拿到这个问题之后,我首先考虑的就是,如何找到问题每次发生的共性特点,于是我查看了注册中心服务标记节点的时间,发现只有一台服务器是在标记节点不可用,并且节点被标记之后,其他的服务器又很快地将它们恢复。我们在24讲,讲注册中心时曾经提到,注册中心是通过心跳机制来检测节点是否可用的,注册中心服务会比较上次心跳的时间,以及服务器本地时间,如果两者相差超过一定阈值,就标记服务节点不可用。

于是,我在确认了心跳时间正确的前提下,判断是服务器本地时间的问题。经过进一步排查,我们发现,标记节点不可用的注册中心服务器的系统时间是错误的,而它的系统时钟对时间隔是一天,而其它服务器是一个小时,这也解释了为什么过了凌晨之后就恢复了(时钟重新对时后系统时间就正确了)。于是我们修改了时钟对时的间隔,问题果然就不再出现了。

除了监控和日志以外一些常见的工具也是问题排查的重要手段当我们通过监控找不到思路的时候我们不妨看一看系统的CPU、内存、磁盘和网络等等是否存在错误饱和度如何也许可以给我们的问题排查提供一些线索。这就需要你在实际工作中不断地积累熟悉常见工具的使用方法和场景了。

比如我们想要查看CPU的负载情况我们都知道可以使用top命令而如果你是Java应用你还可以结合jstack命令来查看CPU使用率比较高的线程正在执行什么操作。但这些并不够你还可以使用pidstat、vmstat、mpstat来查看CPU的运行队列、阻塞进程数、上下文切换的数量这些都会给你的问题排查提供线索。同时Perf也是一个常见的工具可以帮助你排查哪些系统调用或者操作消耗了更多的CPU时间这样你就可以有针对性地做调整和优化了。

再比如我在面试的时候经常会问面试者如何来排查内存泄漏的问题大部分的Java面试者可以回答使用jmap命令dump出内存信息然后使用类似MAT的工具来分析。

这种分析方法只对java堆有效如果是堆外内存的泄漏我们要如何排查呢也许你可以使用pmap和GDB来查看堆外内存都有哪些数据这样也可以给我们的排查提供思路。

课程小结

以上就是本节课的全部内容了。本节课我带你了解了发现和排查问题的方式和手段,这里你需要了解的几个重点是:

  • 监控和压测是发现系统性能问题的两个最重要的手段,尤其我们不能忽略客户端监控,否则我们可能会错过一些问题;
  • 利用监控和日志,总结出问题的共性特点,是我们排查问题的主要手段;
  • 熟悉常见的分析工具会让我们的问题排查过程事半功倍。

问题的排查过程虽然痛苦,但是你每一次的排查经历都是在为你的下一次排查积累经验,同时也能让你更加熟悉工具的使用,慢慢地你就会发现,问题的排查关键在于你是否熟练,“无他,唯手熟尔”。

一课一思

在你开发和维护项目的过程中,你都遇到过哪些诡异的问题呢?你又是通过什么样的方法来发现和排查的呢?欢迎在留言区和我一起讨论,或者将你的实战经验分享给更多的人。

最后,感谢你的阅读,我们下期见。