<audio id="audio" title="春节特别策划 | 高并发下如何发现和排查问题？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f4/85/f4e8f150dc20d51de43137da7d2f5a85.mp3"></audio>

你好，我是唐扬，新年快乐！

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

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

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

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

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

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

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

## 如何及时发现问题

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

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

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

从客户端监控来看，在带宽被打满的那段时间里，客户端请求服务接口会有大量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在做BGSAVE，Redis Server会有短暂时间的阻塞，这就解释了Redis的慢请求以及SLA的下降。

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

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

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

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

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

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

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

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

## 课程小结

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

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

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

## 一课一思

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

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