This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
<audio id="audio" title="23 | 决定容量场景成败的关键因素有哪些?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8a/bf/8a5bb7ec187853e822b44ce5ec5bfbbf.mp3"></audio>
你好,我是高楼。
从这节课开始,我们就要进入到容量场景的分析了。
在当前的性能市场中,如果你让一个性能人员设计一个容量场景,他可能不知道应该怎么去做,你自己可能也会有一些茫然。因为设计容量场景需要的前提条件太多了,很多人都会觉得无从下手。
虽然我们前面在[第5讲](https://time.geekbang.org/column/article/357539)中已经描述了容量场景的大致内容,但仍然不够详细,并且可能不足以引起你的重视,而容量场景的重要性又让我们不能轻视它。所以,这节课我将带你来看一下容量场景中的各个关键点,以便让你在设计和执行容量场景时,不会那么盲目。
## 容量场景目标
首先是目标,你要记住,**容量场景一定要有目标**。如果做容量场景没有目标,那就是没有结束时间点的。因此,我在写[性能方案那一讲](https://time.geekbang.org/column/article/357539)时,特别提到了性能项目的目标。
关于容量场景,我写的目标是“达到系统的最佳运行状态”。而这样的目标很显然在一个项目中是不够具体的。如果你想更具体,就需要把一个系统的最大容量很具体地写出来。
请你注意在很多性能项目中由于给出的容量场景目标不够具体或者是存在“只给出最大TPS而不给业务模型”这样的流氓需求导致容量场景的执行犹如无根之水想往哪流往哪流这肯定是不行的。
那一个容量场景的目标怎样才算具体呢我给你举一个例子比如说“系统容量要求达到1000TPS”这个1000TPS是一个总值它还会被细分到各个业务上这就用到了对应的业务模型。那么这个“1000TPS”就是我们的总体指标也就是容量场景的目标。而它对应的业务模型就应该是这个样子的下表为示例
<img src="https://static001.geekbang.org/resource/image/34/42/34954525b86ce1a4936286d53b5cfa42.png" alt="">
但是,这里只有比例,没有指标,也是不行的,因为没有结束的标准。所以,我们还要对容量目标有一个精准界定(下表为示例):
<img src="https://static001.geekbang.org/resource/image/66/0a/66140502a53d5f3fb82a19fe3d4ca60a.png" alt="">
这样,我们不仅有了比例,还有了优化和项目结束的目标,这样的容量场景目标就非常具体了。
## 业务模型
刚才我们讲到,容量场景要在一个确定的业务模型下执行,同时我们也在上一段落给出了业务模型的示例。
关于业务模型,我要跟你明确一下:我们在执行容量场景时,要包括所有的并发业务(你可以用接口拼出业务),在一个业务系统中,只有包括了所有的业务接口,才是真正的容量场景。
并且,**业务模型的比例要符合生产中的真实业务场景**(具体请看[第5讲](https://time.geekbang.org/column/article/357539)中的业务模型和性能指标部分),也就是说,在容量场景中,业务模型一定要覆盖生产环境中的业务峰值,并且还要覆盖生产环境中的最大资源使用率峰值。
之前我看到有些人直接把每个接口都做了性能测试,然后一个性能项目就算是结束了。这是不合理的,因为一个系统里的接口都是并行的,不会都是串行。
既然业务接口是并行的,那必然要有前后执行的关系,也要有比例关系,这就涉及到了业务模型的来源。
我们在[第6讲](https://time.geekbang.org/column/article/358483)说过,要想抽取出符合真实业务场景的业务模型,正常的逻辑应该是:先把生产环境中的业务做统计,并给出相应的业务比例,然后把业务比例在压力工具中设置好,并保证结果中的比例和统计出的业务比例一致。
这是对应的流程图,希望你还记得:
<img src="https://static001.geekbang.org/resource/image/88/e9/88e999166d5fa98c2a677b9c62b274e9.png" alt="">
请你记住,**我们现在努力让所有的业务模型都符合生产模型,是为了在做容量场景时,可以对“生产峰值是否可以支持业务”这个问题给出一个明确的答案**。所以,业务模型的来源是至关重要的。
同时,也有人说了,我拿不到生产的数据来做统计怎么办?我觉得,这个真的不是技术问题。如果你的权限不够,你可以向上汇报;如果是公司的系统不支持,你可以协调公司的相关人想办法;如果你又没有数据又没权限又只知道报怨,那只能说你没努力过;如果你努力了,也得不到结果,那就是你公司的相关人,对性能没有足够的认识。
性能行业发展到现在,“业务模型要从生产环境中统计出来”这一点仍然没有在每个公司中深入人心,这也是行业发展的一个悲哀了。
有人说,我这是新系统,没有生产数据怎么办?这也不是完全没招。
首先,每个项目的出现肯定是有业务需求的。如果市场有同类型的业务系统,你可以去找同类型的业务系统数据来做借鉴。通常情况下,每个项目的业务人员,都有同类型业务的经验,让他们给出来就可以。记住,他们只是给你业务需求,技术需求还是需要性能项目的相关人员细化下去。
如果市场上没有同类型的业务系统,也没有可借鉴的数据,我想你可以尝试一下我的做法:在我经历过的项目中,都会有一个项目试运行阶段。因为不敢确定这个系统上线后会是什么样子,所以,我们会先试运行一段时间,以便作出相应的调整。而试运行的数据,就可以拿来借鉴。
如果你说,我们这系统就是没有同类型的系统数据借鉴,也没有试运行阶段,怎么办?那我只能说,你随便压吧,怎么压都对,怎么压也都不对。
现在有一些生产流量复制的工具,它们的目标之一就是为了解决业务模型和生产一致的问题,这是行业正在做的努力。不管我们用什么样的方式,流量复制也好,业务模型统计也好,都是合理的思路和手段。
除此之外,当业务模型具体要配置到容量场景中时,还有一个问题是经常被问到的,那就是怎么用压力工具实现具体的业务比例。关于这一点,你可以在[第5讲](https://time.geekbang.org/column/article/357539)中得到答案:如果你是用 JMeter 的话,可以使用 Throughput Controller 来控制业务比例。当然,如果你使用的其他工具也有同样的功能,也是可以的。
关于业务模型能不能真正落实到容量场景中,还有一个动作至关重要,那就是**在容量场景执行结束之后,你要把场景的结果和业务模型中的比例做环比**。如果一致,那就是有效的容量场景;如果不一致,那就从头再来。这个动作你一定不要忘记。
## 数据量级
关于容量场景中的数据量级,我希望你记住:容量场景中使用到的参数,尽量不要通过造数据的方式实现。因为我们在容量场景中会用到所有的接口,而这些接口是有上下的业务逻辑关系的,所以,我建议你**最好根据业务逻辑做参数传递,而不是通过造数据来做参数化**。如果实在没办法实现,再考虑造数据。
同时我们要考虑好容量场景中需要用到的数据量级。请参考第5讲中的数据准备部分和第7讲中的具体描述。在这里我只强调几个关键点
<li>
**容量场景的参数化数据,一定要和生产中实际用户的使用规则与数据量级保持一致**。这一点我强调过很多次,但是仍然有人问我:参数化数据是不是可以用少量的数据,来实现生产级的压力。在这里,我义正言辞地再说一次:不可以!
</li>
<li>
**铺底数据一定要通过计算做相应的缩减,最好能和生产一致**。这个缩减怎么来做?我建议你做一下基准场景来比对。怎么比对呢?在压力级别和数据量相同的情况下,统计测试环境和生产环境中的资源使用等各类计数器,看看有什么区别。
</li>
关于数据这一块,我主要是这两个忠告,希望你能记住。
## 监控设计
我们在[第9讲](https://time.geekbang.org/column/article/361138)中已经详细描述过全局和定向监控设计策略,这里我就不再重复了。不过,我还要再啰嗦几点,希望能引起你的重视:
首先,**全局监控的所有计数器都要和项目级的性能分析决策树对应**。关于这一点,理解起来很容易,但是不一定能轻易做到,因为每个监控工具都不够全面,或者说很多监控工具的设计理念都各不相同。
如果你所在的企业,系统是固定的,并且在做不断的演进,那你最好能有自己的监控平台设计理念。我们在[第9讲](https://time.geekbang.org/column/article/361138)中也说到**全局监控来自于架构分析**,有了这个逻辑,你才能明白为什么性能和架构的相关性会这么大。
其次,**一定要在有了性能瓶颈的方向判断之后再做定向监控**。因为分支实在太多,而一开始就蒙定向监控的方向,基本上是蒙不对的。
再次,关于监控工具的选择,请你不要纠结,只要能准确收集计数器的值就可以了,没有哪个工具是必须使用的。
最后一点,**在容量场景中,所有的涉及到的业务组件,都要有全局监控的“分段-分层”的覆盖**。
在性能分析的具体操作过程中你会发现我们对计数器的理解程度和方向的判断有着绝对的关系比如说当我们看到us cpu高就自然地想到要去看用户级的应用栈到底在执行什么代码当我们看到sy cpu高首先想到的是去查syscall到底是被谁调用的。
因此,**监控这部分的难点在于理解计数器,而要理解计数器就必须理解技术组件的原理。**
当你看到一个计数器的值,却不知道下一步要干啥的时候,说明你没有理解这个计数器,这时候你就得补相关的知识了。就像有些不会做饭的人会问,是水开了再打蛋,还是凉水时就打蛋呢?你想想,凉水打蛋的效果是不是惨不忍睹?
## 压力策略
我们再来看容量场景中的压力策略。同样是在[第5讲](https://time.geekbang.org/column/article/357539)中,我强调了在性能场景的压力策略中,有两个关键词:递增、连续。在容量场景中,必须要做到这两点。
可能有人会问,连续比较容量理解,那递增是怎样一个递增法呢?这一点,我们在[第10讲](https://time.geekbang.org/column/article/362010)的基准场景中就有过描述,我也做过相应的压力线程的计算,在这里我就不重复了。如果你也有类似的疑问,建议你再详细回顾一下。
我要提醒你的是,**容量场景中的压力线程可以没有明显的阶梯,但是一定要实现递增**。为什么?你想想在生产场景中,真实的用户量级有没有可能一下子就上到峰值用户量级?很显然,这是不现实的嘛!
那如果实现了递增和连续的话,我们在服务端查看的时候,会是什么样的请求趋势呢?我给你一张图感受一下:
<img src="https://static001.geekbang.org/resource/image/53/7d/537b9c0412b4b108868e5de86aeefc7d.png" alt="">
从这个图中,你就可以看到压力是在持续递增的,这才是生产的样子。
当然,如果你是为了找瓶颈,想尽快把压力发上去,也可以直接上大压力。当然,这样的场景不是做为结论场景使用的,我们只在性能分析过程中使用一下。
在容量场景的压力策略中,有一个概念经常被讨论到,那就是:集合点。很多人认为它非常有用,但在我看来这并不符合真实的生产场景。
那集合点到底要不要?
有人会说为了模拟有多个用户同时操作用集合点不是挺合理的吗那我就要问一个问题了压力工具的集合点是在哪里集合的是在服务端集合的吗显然不是吧集合点只能在压力工具这一端集合。而集合的请求经过了CPU的争抢、网络中断的切换、网络传输的快慢、协议的转换等等动作之后到服务端还集合吗
再退一步讲,即便在服务端能集合了,服务端能处理超过自身能力的请求数吗?这显然不能吧。
光打嘴仗没有用,我还是放一张图让你感受一下:
<img src="https://static001.geekbang.org/resource/image/4a/7f/4a77579365d20140336eyy9508fab27f.png" alt="">
这张图就是我在同样的场景中加了集合点之后统计了服务端的请求日志然后细化到以20毫秒为粒度看到的结果。你看请求的趋势其实是断断续续的对吧这已经充分说明了“集合点在服务端的表现到底是怎样的”这个问题。
知道了问题的答案,再结合你的具体项目,你可以再仔细考虑一下,你需要的是不是这样的集合点。
## 启动条件
在[第5讲](https://time.geekbang.org/column/article/357539)中,我们提到过启动准则。在很多企业中,这个启动准则只是文档中的条目放在那里,在实际的项目执行中,根本就是毫不理会的。
启动准则其实和项目管理有关,在我经历过的性能项目中,要是不理会启动准则,那么性能项目消耗的过多时间,基本上都是由性能团队来背锅。
你想想,如果满足不了启动条件(比如未定版、功能未开发完成、架构和生产不一致、环境差异性等),就会导致边测边改,这是非常耗时间的,并且要重复做很多次。这样不仅增加了性能项目的成本,还降低了性能人员的价值,同时也体现出项目管理非常混乱的问题。
我能给你的建议是,最好把启动准则仔细看一下,在项目执行过程中,该谁背的锅就由谁来背。如果你愿意背,那就不要在意那些条目。
## 协调组织工作
最后,我们再来看容量场景的协调组织工作。在一些大型的项目中,容量场景涉及到的项目组会比较多,因此,我们要提前确定好每个项目的接口人,以免出现场景跑起来,系统却没人管的情况。
对于一个好的性能项目经理来说,要尽量避免性能执行人员去协调资源、督促解决性能问题这类的事情,因为从权限以及实际情况来看,性能执行人员去协调其他项目组是比较困难的,甚至会得不到支持。
## 总结
看到这里,你是不是觉得容量场景还挺复杂的?确实是这样。
容量场景不仅技术上有需要关注的点,在管理上,也有需要关注的点。我之所以对容量场景做这么多的描述,是因为容量场景对性能项目来说太重要了。当前的市场上经常提到的全链路压测,其实就是容量场景的一个具体场景。
因此,在这节课中,我把容量场景中重要的地方再给你捊了一遍,希望你能对容量场景重视起来。另外,我想给你一个小提醒,即便是所有的基准场景都执行得很好,容量场景也不是必然会顺畅,所以你要做好心理准备。
## 课后作业
这就是今天的全部内容,最后给你留两个思考题吧:
1. 如果让你设计容量场景的监控策略,你会如何来做?请描述下你的设计逻辑。
1. 在你的项目中,容量场景的压力策略是如何设计的?请描述下你自己的压力策略设计逻辑。
记得在留言区和我讨论、交流你的想法,每一次思考都会让你更进一步。
如果这节课让你有所收获,也欢迎你分享给你的朋友,共同学习进步。我们下一讲再见!

View File

@@ -0,0 +1,298 @@
<audio id="audio" title="24 | 容量场景之一索引优化和Kubernetes资源分配不均衡怎么办" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b8/9c/b891a92ba5035385195dc2e09d8f4a9c.mp3"></audio>
你好,我是高楼。
我们知道,做容量场景的目的是要回答“线上容量最大能达到多少”的问题,这就要求我们在设计和执行容量场景的时候要非常严谨。当然,这也意味着容量场景将是一个艰辛的过程。通过这节课,你就能深切地体会到。
今天我们重点来解决索引优化和Kubernetes调度不均衡的问题。关于索引优化你可能会奇怪基准场景都捊过一遍了为啥还有要看索引的问题是呀确实让人疑惑。从这里就可以看出容量场景和基准场景真的不太一样因为这其中有业务相互影响的问题。
而Kubernetes调度不均衡的问题将导致多个Pod运行在了同一个worker上像这样的问题我们不在容量场景中是看不到的希望能对你处理类似问题有一个借鉴。
此外,我们还将一起看看在压力稳定的情况下,响应时间不断攀升该怎么办。这种问题很常见,但是每次出现问题点都不太相同,这次你将看到一个具体的案例。
好,我们开始吧!
## 场景运行数据
### 第一次运行
不得不承认,第一次来到容量场景,还真是心惊胆颤的。
首先,我们小心翼翼地设置起容量场景的比例,也就是我们在[第5讲](https://time.geekbang.org/column/article/357539)中提到的业务比例,再设置好相应的参数和关联。然后,我们把容量场景跑起来,得到了这样的信息:
<img src="https://static001.geekbang.org/resource/image/76/57/76cd8b3983fdcbee733dbd0a378da457.png" alt="">
顿时就有一种满头包的感觉,有没有?!不过,“见招拆招,遇魔降魔”不就是我们的宗旨吗?既然有错,那咱们就先解决错误吧。
我们先看下错误信息:
<img src="https://static001.geekbang.org/resource/image/ea/0a/ea28271a2613192224441f8f57229b0a.png" alt="">
看来是两个脚本有问题。我单独运行了这两个脚本之后,发现是参数化数据设置错了。因为之前每个接口都是单脚本运行的,而现在要连到一起跑,所以,参数化数据需要重新配置。这种简单的错误,我就不详细描述了,只要你细心一点,很快就能查到。
修改了这个简单的脚本错误之后,我们再把容量场景跑起来:
<img src="https://static001.geekbang.org/resource/image/a5/1e/a58caec0bb88f6bed22d317313cefc1e.png" alt="">
先不管性能怎么样,你看,现在至少没有错误信息了,对不对?很好,我们接着来看容量场景中的其他问题。
### 第二次运行
解决了第一个问题之后,我们把场景运行一段时间,又看到了下面这样的场景运行数据:
<img src="https://static001.geekbang.org/resource/image/46/eb/461f57e83af0ce7284096866e46078eb.png" alt="">
数据并没有太好看,还是有失败的地方。我们来看看是怎么一回事:
<img src="https://static001.geekbang.org/resource/image/44/c4/44ffd785686b824c99f3c26c743627c4.png" alt="">
从数据上来看,错误信息应该和脚本无关,是某个组件出现了问题,导致所有的脚本都不能正常运行了。
通过查看全局监控的Pod界面我们看到这样的现象
<img src="https://static001.geekbang.org/resource/image/5b/47/5bc02443c663670010e9ab7f1204d947.png" alt="">
考虑到我们在前面分析中对ES的资源限制得比较狠只有1C因为不想让ES影响其他业务这一点在[](https://time.geekbang.org/column/article/366020)[15](https://time.geekbang.org/column/article/366020)[](https://time.geekbang.org/column/article/366020)中已经有过分析在前文中虽然我们通过对ES POD扩容提升了性能但为了不影响后面的优化我又给调回去去了。在这里我们先把使用ES的业务也就是查询商品给禁掉再次运行起来容量场景。
### 第三次运行
我们来看第三次运行的结果:
<img src="https://static001.geekbang.org/resource/image/e0/07/e03906d43bfa3f782101fe75d0338007.png" alt="">
还是有少量的报错,我们先看错在哪里,再解决响应时间长的问题。
这里我说明一点,对于报错,我们可以先看错误信息是什么,脚本有没有问题。但是,由于我们已经来到了容量场景,脚本如果有问题,就会在基准场景中体现出来了。所以,这时候我们去查应用的日志更为理智。
于是通过一层层地查日志我们留意到了Cart信息
```
2021-01-26 00:20:43.585 ERROR 1 --- [o-8086-exec-669] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.RetryableException: timeout executing GET http://mall-member/sso/feign/info] with root cause
java.net.SocketException: Socket closed
```
很显然在Cart服务上我们看到已经报远程调用Feign调用超时了并且调用的是member服务。
由于是Cart服务调用Member服务出的错我们现在去看Member日志
```
2021-01-26 00:20:46.094 ERROR 1 --- [o-8083-exec-308] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.RetryableException: timeout executing POST http://mall-auth/oauth/token?password=123456&amp;grant_type=password&amp;client_secret=123456&amp;client_id=portal-app&amp;username=7dcmtest13657257045] with root cause
java.net.SocketTimeoutException: timeout
```
你看Member上显示的是读Auth出错。可是当我再去看Auth服务时发现Auth服务上空空如也啥错也没有。
根据我的经验,报错可能是由于一个瞬间问题导致的读超时,只有等它再次出现我们再去收拾了。
因为对于错误信息,我们暂时还没查到具体是什么问题。所以,我们接着持续运行场景,我们看一下结果:
<img src="https://static001.geekbang.org/resource/image/aa/f1/aac46a3247f7a1d672cbc13b175bcff1.png" alt="">
你瞧确实有少量报错。从整体上看这只是少量的读超时问题。在性能测试中有毛刺是很正常的情况。而且现在的部署结构中仍然有单点Java应用存在这种情况也不可避免。
不过从上图中我们能看到一个比较明显的问题那就是TPS在不断下降我们得先解决这个问题才行。
## 第一阶段分析
### 定向监控分析
虽然整体的TPS不高但是我们面对的第一个问题仍然是TPS会在压力持续之下不断下降。
由于我们现在执行的是容量场景包含了多个业务所以我们得先知道是什么样的业务导致了像上图中那样的TPS趋势。而容量场景中的脚本都是接口串行的因此当一个脚本出现这种情况时所有的业务都会慢下来即使是那些本身不慢的业务TPS也会受到影响。
但是,对于没有问题的业务接口,它们的响应时间是不会受到影响的。那些响应时间变长的业务接口,自然就成了我们重点关注的对象了。
通过一个一个的业务接口排查,我发现有三个业务的响应时间,随着场景的持续在不断增加:
<img src="https://static001.geekbang.org/resource/image/81/e9/819a49b69fee7a75c21ecfyy8803f5e9.png" alt=""><br>
<img src="https://static001.geekbang.org/resource/image/29/08/29d8988f01c9ce0ac603081f55646508.png" alt=""><br>
<img src="https://static001.geekbang.org/resource/image/22/26/229b3aa6b00a19788a164cb0ea003526.png" alt="">
这三个业务分别是“支付前查询订单列表”、“支付订单信息”和“支付后查询订单详情”,但是在前面的基准场景的分析中,它们并没有出现这种现象,也就是说它们是在容量场景中新出现了问题。
既然这几个业务的脚本都有这种现象那我们就接着来分析时间具体慢在了哪里。我先从最简单的业务链路paySuccess支付订单信息接口查起。
这个接口的架构图我们在[](https://time.geekbang.org/column/article/372274)[22](https://time.geekbang.org/column/article/372274)[](https://time.geekbang.org/column/article/372274)中已经讲过了就是JMeter - Gateway - Order - Mysql如果再加上其他的技术组件架构图会更复杂一些。不过我们现在还没有看到它们的影响所以我暂时不列在这里
通过性能分析决策树的全局监控计数器逐层查看我看到有一个SQL语句在不断变慢
```
SELECT id, member_id, coupon_id, order_sn, create_time, member_username, total_amount, pay_amount, freight_amount, promotion_amount, integration_amount, coupon_amount, discount_amount, pay_type, source_type, STATUS, order_type, delivery_company, delivery_sn, auto_confirm_day, integration, growth, promotion_info, bill_type, bill_header, bill_content, bill_receiver_phone, bill_receiver_email, receiver_name, receiver_phone, receiver_post_code, receiver_province, receiver_city, receiver_region, receiver_detail_address, note, confirm_status, delete_status, use_integration, payment_time, delivery_time, receive_time, comment_time, modify_timeFROM oms_orderWHERE ( delete_status = 0 AND member_id = 277673 )ORDER BY create_time DESCLIMIT 4, 1;
```
接着我们进一步看看这个Select语句的索引
<img src="https://static001.geekbang.org/resource/image/42/94/422e519d2f529fcef9aacaff17b47094.png" alt="">
咦,怎么有两个可能会用到的索引?具体查看一下,果然有两个索引,并且还是在同一个列上。
<img src="https://static001.geekbang.org/resource/image/bf/a8/bfa2f707102b38dyy71dc2263dc649a8.png" alt="">
我们先删一个再说。不过,删索引并不是为了解决性能问题,我们只是顺手改一下而已。
现在索引上显示的数据很明确随着容量场景的执行这个SQL查的数据行数越来越多。既然这个Select语句越来越慢那我们就去查一下根据它的条件产生的直方图是什么。我查到了这样的直方图
<img src="https://static001.geekbang.org/resource/image/86/46/8620c13430acd59d60c8505b0310d946.png" alt="">
你看,一个用户下出现了很多个订单,应该是出现了集中插入订单数据的情况。因此,我们查一下在使用参数化的文件中,用户信息是不是也重复了。
<img src="https://static001.geekbang.org/resource/image/84/3c/84e41dd30fe01c09f72bc81e5bfbcd3c.png" alt="">
通过查找,我们看到在参数化文本中,确实有大量的重复数据。我们梳理一下业务脚本的参数化逻辑:
<img src="https://static001.geekbang.org/resource/image/b5/f0/b5b343a1bbd5ecffd673050e4a1b1af0.jpg" alt="">
既然订单数据是重复的那我们就反向追溯回去肯定是购物车数据重复了再接着往前追溯应该是member_id在使用用户Token时出现了重复。在检查了所有的业务脚本之后我们看到是获取用户参数脚本生成的Token
<img src="https://static001.geekbang.org/resource/image/1f/a2/1f54aff8b2555c7c46ac41bc4d5731a2.png" alt="">
而这个脚本中的数据,使用的是这样的参数文件:
<img src="https://static001.geekbang.org/resource/image/05/80/0590d8010de6f18f29e8804d7eb43280.png" alt="">
这个参数文件中只有100条数据而这100条数据中就有大量的重复member_ID。这样一来问题就清楚了原来是在造数据阶段我们一开始使用的Member数据已经有了问题从而导致了订单数据大量重复。
问题的原因查清楚了我们现在只有重新造数据了。这时候我们就要注意必须使用不重复的member_id来造订单数据并且在每一步中检查一下生成的数据是否有重复的现象。
经过一番折腾之后TPS如下
<img src="https://static001.geekbang.org/resource/image/e7/y0/e74c6a4633f7567e1d8501d285f52yy0.png" alt="">
这个优化效果很好,我们通过解决参数化的重复问题,解决了同一个用户下产生大量订单的问题,进而解决了我们在前面看到的响应时间不断增加的问题。
对于这样的参数化问题,其实难点就在于参数化是登录时做的,而在订单脚本中使用的参数,会用到登录脚本中产生的数据,但由于前面做的参数化出现了重复,导致后续的参数也重复了。对于这样的问题,我们需要耐心梳理参数化的数据来源。
到这里我们解决了响应时间不断增加的问题不过我们的优化还没结束。你看上图中响应时间的窗口出现了有的接口响应时间较长、整体TPS并没有太高的现象因此我们还要进行第二阶段的分析。
## 第二阶段分析
根据我们的性能分析逻辑,下面我们要做的就是拆分响应时间和查看全局监控数据了,并且这两步都要做。
相信你已经发现了,我们在分析的时候,有时候要拆分响应时间,有时候就直接看全局监控数据了。这是因为**当我们能从全局监控数据中看到问题所在时,不用拆分响应时间就能往下分析**,我们只需要按照分析七步法,接着往下走就可以了。
**如果我们没有从全局监控数据中看到比较明显的资源消耗,同时,响应时间又在不断上升,那我们就必须拆分响应时间,来精准地判断。**
### 全局监控分析
在查看了全局监控数据之后,数据库的资源如下所示:
<img src="https://static001.geekbang.org/resource/image/6f/61/6fce4a5e8a9c9925aff7328d7ed21b61.png" alt="">
你看worker-1的CPU资源使用率比较高我们进到这个机器中看一下top
<img src="https://static001.geekbang.org/resource/image/28/b2/283181c9f1d4ec0824f17061aec900b2.png" alt="">
显然多个CPU使用率已经达到了100%从process table中我们也能看到CPU消耗最高的显然是MySQL进程。那下面的的分析逻辑就很清楚了在[第16讲](https://time.geekbang.org/column/article/367285)中已经详细描述过)。
### 定向监控分析
这是一个典型的数据库CPU使用率高的问题而且也相对比较简单。我们仍然是看MySQL的全局监控数据、查SQL、查执行计划和创建索引。有问题的SQL如下所示
<img src="https://static001.geekbang.org/resource/image/9c/ac/9cbbb127ba8187a869e2d741c0094aac.png" alt="">
同样type列的ALL告诉我们这个SQL是全表扫描。在相应的表上创建索引之后执行计划如下
<img src="https://static001.geekbang.org/resource/image/5b/9f/5b4309720924c494f22de82e1ec05e9f.png" alt="">
从type中的ref值来看我们现在已经创建好索引了。
接着我们把容量场景再执行起来,我们得到下面这张全局监控图:
<img src="https://static001.geekbang.org/resource/image/08/7e/08bfd174648083eb31a2598fb5f9397e.png" alt="">
可以看到worker-1的CPU已经下去了。
我们再来看一下场景的运行结果:
<img src="https://static001.geekbang.org/resource/image/26/f4/264e88d5c2032f92a3562408cd57c8f4.png" alt="">
效果还是不错的。索引能带来的作用就是这样TPS会在我们增加索引之后增加很多。这一点我在之前的课程中已经反复提到了这里就不再详细说了。
有人可能会问,不对呀,在之前的基准场景中为什么没有增加索引呢?之前的接口难道没有因为没增加索引出现瓶颈吗?
对此我其实也有这样的疑问。你想想这个SQL是在生成订单时使用的和优惠卷相关我删除了索引之后又回去重新执行了生成订单的接口发现TPS还是能达到之前基准场景的峰值。可见这个SQL的问题在之前的场景中确实没有表现出来在混合场景中才会出现。
在加了索引之后我以为TPS可以正常一会了。可是没想到第二天我在执行场景时还是看到了不想看到的结果具体问题我们在下一个阶段分析。
这个项目就是这样,步步为坑。不过,也正是这种步步为坑的项目,才让我们有了更多的分析机会。下面我们就来看看到底出了什么问题。
## 第三阶段分析
### 全局监控分析
这就是在我持续执行容量场景之后,全局监控的结果:
<img src="https://static001.geekbang.org/resource/image/3b/1c/3b4c3cd84da89deaeb1065ea91d2231c.png" alt="">
注意这里不是说不需要拆分响应时间而是我已经看过了响应时间的拆分数据发现是Order服务上消耗的时间多。鉴于我们在前面的课程里已经做过多次响应时间的拆分所以我在这里就不再具体列出数据了。
看到全局监控数据的第一个界面我们就已经可以发现资源消耗比较高的节点了。从数据来看worker-3的CPU使用率上来了达到了90%以上worker-4的CPU使用率也上来了达到了70%以上也就是说TPS会不断地掉下来。下面我带着你来分析一下。
### 定向监控分析
我们进入到worker-3中执行top看看它上面都跑了哪些服务
```
top - 23:21:36 up 11 days, 5:37, 5 users, load average: 40.53, 49.79, 53.30
Tasks: 335 total, 1 running, 332 sleeping, 2 stopped, 0 zombie
%Cpu(s): 82.5 us, 8.7 sy, 0.0 ni, 3.0 id, 0.0 wa, 0.0 hi, 3.2 si, 2.6 st
KiB Mem : 16265984 total, 2802952 free, 7089284 used, 6373748 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 8759980 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
26573 root 20 0 8930960 837008 15792 S 231.5 5.1 113:46.65 java -Dapp.id=svc-mall-order -javaagent:/opt/skywalking/agent/skywalking-agent.jar -Dskywalking.agent.s+
26973 root 20 0 8920512 810820 15776 S 173.7 5.0 112:24.54 java -Dapp.id=svc-mall-order -javaagent:/opt/skywalking/agent/skywalking-agent.jar -Dskywalking.agent.s+
24386 root 20 0 8864356 702676 15764 S 98.7 4.3 295:33.69 java -Dapp.id=svc-mall-portal -javaagent:/opt/skywalking/agent/skywalking-agent.jar -Dskywalking.agent.+
17778 root 20 0 8982272 803984 16888 S 97.4 4.9 375:15.37 java -Dapp.id=svc-mall-portal -javaagent:/opt/skywalking/agent/skywalking-agent.jar -Dskywalking.agent.+
1087 root 20 0 2574160 132160 31928 S 25.6 0.8 1637:21 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubern+
25589 root 20 0 8839392 585348 15772 S 20.8 3.6 160:58.44 java -Dapp.id=svc-mall-auth -javaagent:/opt/skywalking/agent/skywalking-agent.jar -Dskywalking.agent.se+
1095 root 20 0 998512 86168 13100 S 6.5 0.5 837:37.56 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
29226 root 20 0 8906120 881632 13700 S 5.8 5.4 760:36.91 java -Dapp.id=svc-mall-search -javaagent:/opt/skywalking/agent/skywalking-agent.jar -Dskywalking.agent.+
28206 root 20 0 7960552 341564 15700 S 4.9 2.1 66:28.23 java -Dapp.id=svc-mall-search -javaagent:/opt/skywalking/agent/skywalking-agent.jar -Dskywalking.agent.+
9844 root 20 0 1632416 47092 16676 S 2.9 0.3 559:35.51 calico-node -felix
9646 polkitd 20 0 4327012 97744 4752 S 2.6 0.6 25:26.93 /usr/local/lib/erlang/
```
你看,杂七杂八的应用都弄到这一台机器上了,非常不符合正常的架构。
我们在前面提到在拆分响应时间的过程中发现是Order服务消耗的时间多。而Order服务又是当前这个场景中最需要资源的应用那我们就先把Auth、Portal之类的服务移走。
移走了一些服务之后,我们先看看场景执行数据如何,再来决定是否需要再进行定向监控分析。
为了确保我们瓶颈判断的方向正确,我们先来看一下全局监控数据:
<img src="https://static001.geekbang.org/resource/image/b7/b7/b71c3d18878c1e8b22096205b3d6cfb7.png" alt="">
再看一下场景运行数据:
<img src="https://static001.geekbang.org/resource/image/79/36/799d894810b501da16c7474a3c8bac36.png" alt="">
你看TPS还挺正常的对不对看来我们不用再继续定向分析了。不过这样的结果是不是真的正常了呢我们的容量场景是不是可以结束了呢欲知后事如何且听下回分解。
## 总结
我们这节课的分析过程还是挺坎坷的,光是场景运行就执行了三次,可见即便基准容量已经测试通过了,容量场景也没那么容易运行起来。
在第一阶段的分析中我们主要定位了在压力线程不变的情况下TPS随时间增加而增加的问题。请你注意**压力不变时TPS平稳才是正常的**。
在第二阶段分析中,我们定位了索引不合理的问题。这样的问题算是比较常见的,定位起来比较简单,优化起来也比较容易出效果。
在第三阶段分析中,我们解决了资源使用过于集中的问题,其中,我做了资源的重新分配,并且也看起来挺正常了。
对于资源的分配我们要作出明确的判断。在Kubernetes中资源的均衡分配很多时候都需要依赖Kubernetes的调度能力。而Kubernetes的调度是需要在调度的那个时刻来判断的在压力持续的过程中资源消耗并不一定合理因此我们需要在压力持续的过程中对Pod做出调整。
## 课后作业
这就是今天的全部内容,最后给你留两个思考题吧:
1. 为什么在压力线程不变的情况下TPS曲线下降、响应时间上升是不合理的
1. 当资源使用过于集中的时候如何定位Pod相互之间的影响你有没有和这节课讲的不一样的招
记得在留言区和我讨论、交流你的想法,每一次思考都会让你更进一步。
如果这节课让你有所收获,也欢迎你分享给你的朋友,共同学习进步。我们下一讲再见!

View File

@@ -0,0 +1,435 @@
<audio id="audio" title="25 | 容量场景之二:缓存对性能会有什么样的影响?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d7/e7/d771ece5d7899146ee2c3a99f6c3c0e7.mp3"></audio>
你好,我是高楼。
上节课我们经历了三个阶段的分析优化分别解决了在压力线程不变的情况下TPS随时间增加而增加的问题还有数据库加索引的问题以及Kubernetes调度不均衡的问题。最后TPS曲线看起来挺正常了但是命运不会因为我努力了就会放过我。
为什么这么说呢因为在上节课中我们的场景只持续了十几分钟对于容量场景来说时间还是不够长。你知道压力持续十几分钟且TPS显示正常并不能说明系统没有问题。
因此,我又对系统进行持续的压力测试,就是在这个过程中,又遇到了新的问题……
## 第四阶段分析
### 场景压力数据
这是我在进行持续加压过程中,得到的场景数据:
<img src="https://static001.geekbang.org/resource/image/a0/ea/a0408ff35e086996e13f4218ca4c26ea.png" alt="">
看上面的曲线图就能知道这是在压力持续的过程中出现了TPS掉下来的问题这是不能接受的。
### 拆分响应时间
针对上述问题,我们先来看一下现在的时间消耗。这是已经运行了一段时间的响应时间图:
<img src="https://static001.geekbang.org/resource/image/18/c6/1858074580cc743b6259726e03de2ac6.png" alt="">
我们可以根据整体的平均响应时间,一个个分析这些接口的时间消耗在了哪里。其实,从这张图就能看出,所有的业务时间相比上一节课的响应时间图都增加了。由于所有业务的响应时间都增加了,说明不是某个业务本身的问题,所以,我们任意分析一个接口就可以。
这里我用生成确认订单这个接口做时间拆分。在之前部署系统的时候我们把SkyWalking采样率设置得非常低只有5%左右目的为了不让APM影响性能和网络。
下面这些数据是按分钟平均的。
<li>
<p>Gateway<br>
<img src="https://static001.geekbang.org/resource/image/57/58/5764d379eb0cd29cc19f506047430758.png" alt=""></p>
</li>
<li>
<p>Order<br>
<img src="https://static001.geekbang.org/resource/image/ac/f8/ac07769yyaa61dbcf4cddd92c5dff0f8.png" alt=""></p>
</li>
<li>
<p>Cart<br>
<img src="https://static001.geekbang.org/resource/image/55/3d/556ab449aa47a27fda1f52bb1195a73d.png" alt=""></p>
</li>
<li>
<p>Member<br>
<img src="https://static001.geekbang.org/resource/image/8d/e5/8d43f0322dae23b3bd6c20665a88b1e5.png" alt=""></p>
</li>
<li>
<p>Auth<br>
<img src="https://static001.geekbang.org/resource/image/67/1f/6735e1e63f7fca798bd17160c11ab51f.png" alt=""></p>
</li>
从数据上来看,似乎每个服务都和整体响应时间慢有关。悲催的场景总是这样。
不过,别慌张,我们仍然按照全局到定向的分析思路来走就可以了。我们先看全局监控数据。
### 全局监控分析
从全局监控的第一个界面来看worker-4上的CPU资源使用比较高其次是worker-6
<img src="https://static001.geekbang.org/resource/image/97/05/97a61841d5791f036592d36f997d8e05.png" alt="">
我们一个一个来收拾。
我们先进入到worker-4中执行top/vmstat命令截取到一些重要的数据下面这些是worker-4这个节点更为详细的全局监控数据
```
-- vmstat的数据
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa s
12 0 0 4484940 1100 4184984 0 0 0 0 45554 21961 58 25 14 0 3
9 0 0 4484460 1100 4185512 0 0 0 0 45505 20851 60 25 14 0 1
16 0 0 4483872 1100 4186016 0 0 0 0 44729 20750 62 24 12 0 2
15 0 0 4470944 1100 4186476 0 0 0 0 45309 25481 62 24 13 0 2
14 0 0 4431336 1100 4186972 0 0 0 0 48380 31344 60 25 14 0 1
16 0 0 4422728 1100 4187524 0 0 0 0 46735 27081 64 24 12 0 1
17 0 0 4412468 1100 4188004 0 0 0 0 45928 23809 60 25 13 0 2
22 0 0 4431204 1100 4188312 0 0 0 0 46013 24588 62 23 13 0 1
12 0 0 4411116 1100 4188784 0 0 0 0 49371 34817 59 24 15 0 2
16 1 0 4406048 1100 4189016 0 0 0 0 44410 21650 66 23 10 0 1
..................
--- top的数据
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
935 root 20 0 6817896 1.3g 16388 S 301.7 8.7 71:26.27 java -Dapp.id=svc-mall-gateway -javaagent:/opt/skywalking/agent/skywalking-agent.jar -Dskywalking.agent.service_name=svc+
1009 101 20 0 13500 2980 632 R 37.6 0.0 10:13.51 nginx: worker process
1007 101 20 0 13236 2764 632 R 20.8 0.0 3:17.14 nginx: worker process
7690 root 20 0 3272448 3.0g 1796 S 14.2 19.1 2:58.31 redis-server 0.0.0.0:6379
6545 101 20 0 737896 48804 12640 S 13.9 0.3 12:36.09 /nginx-ingress -nginx-configmaps=nginx-ingress/nginx-config -default-server-tls-secret=nginx-ingress/default-server-secr+
1108 root 20 0 1423104 106236 29252 S 12.2 0.7 16:28.02 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
1008 101 20 0 13236 2760 628 S 6.9 0.0 0:46.30 nginx: worker process
6526 root 20 0 109096 8412 2856 S 6.3 0.1 7:30.98 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/6eb72c56b028b0d5bd7f8df+
1082 root 20 0 3157420 116036 36328 S 5.3 0.7 11:15.65 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf+
6319 nfsnobo+ 20 0 759868 53880 18896 S 3.0 0.3 3:18.33 grafana-server --homepath=/usr/share/grafana --config=/etc/grafana/grafana.ini --packaging=docker cfg:default.log.mode=c+
6806 root 20 0 1632160 47276 17108 S 2.6 0.3 5:09.43 calico-node -felix
6 root 20 0 0 0 0 S 1.0 0.0 0:14.19 [ksoftirqd/0]
..................
```
从vmstat的in列可以看出系统的中断数比较高从vmstat中的cscontext switch来看CS也达到了3万左右。同时sy cpusyscall消耗的CPU也占到了25%左右。这就说明我们需要着重关注syscall系统调用层面。
### 定向监控分析
我们先查看中断数据,下面这张图就是全部的中断数据截图:
<img src="https://static001.geekbang.org/resource/image/0b/1b/0bfyy6b43928554d94eaf83dd44yyd1b.png" alt="">
虽然从这张图里,我们可以看到整体的中断数据,也可以从白底黑字的数据上看到数据的变化,但是我们还没有结论。
我们再看软中断的数据:
<img src="https://static001.geekbang.org/resource/image/a5/ac/a5e8055b5b893c376bb9379a7de69cac.png" alt="">
从白底黑字的数据可以看到NET_RX的变化较大TIMER是系统的时钟我们不用做分析而这个服务器worker-4上同时放了Gateway和Redis并且这两个服务都是用网络的大户显然这是我们要分析的地方。
由于网络的中断比较高我们进入到Pod中查看一下网络队列
<img src="https://static001.geekbang.org/resource/image/dc/96/dce7bf1af8e13b3273e79bc054f48f96.png" alt="">
你看这里面有recv_Q。我们知道recv_Q是网络数据的接收队列它持续有值说明了接收队列中确实有阻塞。
请你注意这个结论不是我只刷一次netstat得到的而是刷了很多次。因为每次都有这样的队列出现所以我才会判断网络接收队列recv_Q中确实有未处理完的数据。如果它只是偶尔出现一次那问题倒是不大。
现在,我们继续分析这个问题,待会再给出解决方案。
既然接收队列中有值,从数据传递的逻辑来看,这应该是上层的应用没有及时处理。因此,我们来跟踪一下方法的执行时间。
在这里我们先跟踪负责生成确认订单的generateConfirmOrder接口其实这里要跟踪哪个方法倒是无所谓因为前面说了所有业务都很慢
```
Command execution times exceed limit: 5, so command will exit. You can set it with -n option.
Condition express: 1==1 , result: true
`---ts=2021-02-18 19:20:15;thread_name=http-nio-8086-exec-113;id=3528;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@20a46227
`---[151.845221ms] com.dunshan.mall.order.service.impl.PortalOrderServiceImpl$$EnhancerBySpringCGLIB$$11e4c326:generateConfirmOrder(
`---[151.772564ms] org.springframework.cglib.proxy.MethodInterceptor:intercept() #5
`---[151.728833ms] com.dunshan.mall.order.service.impl.PortalOrderServiceImpl:generateConfirmOrder(
+---[0.015801ms] com.dunshan.mall.order.domain.ConfirmOrderResult:&lt;init&gt;() #8
+---[75.263121ms] com.dunshan.mall.order.feign.MemberService:getCurrentMember() #8
+---[0.006396ms] com.dunshan.mall.model.UmsMember:getId() #9
+---[0.004322ms] com.dunshan.mall.model.UmsMember:getId() #9
+---[0.008234ms] java.util.List:toArray() #5
+---[min=0.006794ms,max=0.012615ms,total=0.019409ms,count=2] org.slf4j.Logger:info() #5
+---[0.005043ms] com.dunshan.mall.model.UmsMember:getId() #9
+---[28.805315ms] com.dunshan.mall.order.feign.CartItemService:listPromotionnew() #5
+---[0.007123ms] com.dunshan.mall.order.domain.ConfirmOrderResult:setCartPromotionItemList() #9
+---[0.012758ms] com.dunshan.mall.model.UmsMember:getList() #10
+---[0.011984ms] com.dunshan.mall.order.domain.ConfirmOrderResult:setMemberReceiveAddressList() #5
+---[0.03736ms] com.alibaba.fastjson.JSON:toJSON() #11
+---[0.010188ms] com.dunshan.mall.order.domain.OmsCartItemVo:&lt;init&gt;() #12
+---[0.005661ms] com.dunshan.mall.order.domain.OmsCartItemVo:setCartItemList() #12
+---[19.225703ms] com.dunshan.mall.order.feign.MemberService:listCart() #12
+---[0.010474ms] com.dunshan.mall.order.domain.ConfirmOrderResult:setCouponHistoryDetailList() #5
+---[0.007807ms] com.dunshan.mall.model.UmsMember:getIntegration() #13
+---[0.009189ms] com.dunshan.mall.order.domain.ConfirmOrderResult:setMemberIntegration() #5
+---[27.471129ms] com.dunshan.mall.mapper.UmsIntegrationConsumeSettingMapper:selectByPrimaryKey() #13
+---[0.019764ms] com.dunshan.mall.order.domain.ConfirmOrderResult:setIntegrationConsumeSetting() #13
+---[0.154893ms] com.dunshan.mall.order.service.impl.PortalOrderServiceImpl:calcCartAmount() #13
`---[0.013139ms] com.dunshan.mall.order.domain.ConfirmOrderResult:setCalcAmount() #13
```
你看这个接口中有一个getCurrentMember方法它是Member上的一个服务是用来获取当前用户信息的而其他服务都会用到这个服务因为需要Token嘛。
从上面的栈信息看getCurrentMember用了75ms多这个时间明显是慢了我们跟踪一下这个方法看看是哪里慢了
```
Condition express: 1==1 , result: true
`---ts=2021-02-18 19:43:18;thread_name=http-nio-8083-exec-25;id=34bd;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@6cb759d5
`---[36.139809ms] com.dunshan.mall.member.service.imp.MemberServiceImpl:getCurrentMember(
+---[0.093398ms] javax.servlet.http.HttpServletRequest:getHeader() #18
+---[0.020236ms] cn.hutool.core.util.StrUtil:isEmpty() #18
+---[0.147621ms] cn.hutool.json.JSONUtil:toBean() #19
+---[0.02041ms] com.dunshan.mall.common.domain.UserDto:getId() #19
`---[35.686266ms] com.dunshan.mall.member.service.MemberCacheService:getMember() #5
```
这种需要瞬间抓的数据要反复抓很多遍才能确定。虽然我在这里只展示了一条但是我抓的时候可是抓了好多次才得到的。从上面的数据来看getCurrentMember中使用的getMember方法耗时比较长达到了35ms多。
我们看一下getMember的具体实现
```
@Override
public UmsMember getMember(Long memberId) {
String key = REDIS_DATABASE + &quot;:&quot; + REDIS_KEY_MEMBER + &quot;:&quot; + memberId;
return (UmsMember) redisService.get(key);
```
这个代码的逻辑很简单拼接Key信息然后从Redis里找到相应的Member信息。
既然getMember函数是从Redis里获取数据那我们就到Redis里检查一下slowlog
```
127.0.0.1:6379&gt; slowlog get
1) 1) (integer) 5
1) (integer) 1613647620
2) (integer) 30577
3) 1) &quot;GET&quot;
1) &quot;mall:ums:member:2070064&quot;
4) &quot;10.100.140.46:53152&quot;
5) &quot;&quot;
2) 1) (integer) 4
1) (integer) 1613647541
2) (integer) 32878
3) 1) &quot;GET&quot;
1) &quot;mall:ums:member:955622&quot;
4) &quot;10.100.140.46:53152&quot;
5) &quot;&quot;
........................
```
你看确实是get命令慢了看时间都超过了10msslowlog默认设置是10ms以上才记录。如果这个命令执行的次数不多倒也没啥。关键是这个命令是验证用户的时候用的这样的时间是没办法容忍的。
为什么这么说呢?
因为在业务上来看,除了打开首页和查询商品不用它之外,其他的脚本似乎都需要用它。所以,它慢了不是影响一个业务,而是影响一堆业务。
然而,正当我们分析到这里,还没来得及做优化的时候,又......出现了新问题。我在接着压的过程中,发现了这样的现象:
<img src="https://static001.geekbang.org/resource/image/50/09/50ce836643202d334b172c1b806eb109.png" alt="">
你瞧瞧TPS不稳定就算了后面居然还全报错了这也太不合适了吧
于是我开始对报错日志一通查最后发现了Redis的容器都飘红了下面是Redis在架构中的状态截图
<img src="https://static001.geekbang.org/resource/image/55/29/55acca876156a22d6cfb2f4ea6aae129.png" alt="">
这明显是Redis没了呀这时候我们再去看应用的状态
<img src="https://static001.geekbang.org/resource/image/5b/a1/5b3777e79f5880c7c34f31fa5f3b73a1.png" alt="">
满目疮痍呀!
接着我们登录到Redis服务所在的worker节点查看日志
```
[ 7490.807349] redis-server invoked oom-killer: gfp_mask=0xd0, order=0, oom_score_adj=807
[ 7490.821216] redis-server cpuset=docker-18cc9a81d8a58856ecf5fed45d7db431885b33236e5ad50919297cec453cebe1.scope mems_allowed=0
[ 7490.826286] CPU: 2 PID: 27225 Comm: redis-server Kdump: loaded Tainted: G ------------ T 3.10.0-1127.el7.x86_64 #1
[ 7490.832929] Hardware name: Red Hat KVM, BIOS 0.5.1 01/01/2011
[ 7490.836240] Call Trace:
[ 7490.838006] [&lt;ffffffff9af7ff85&gt;] dump_stack+0x19/0x1b
[ 7490.841975] [&lt;ffffffff9af7a8a3&gt;] dump_header+0x90/0x229
[ 7490.844690] [&lt;ffffffff9aa9c4a8&gt;] ? ep_poll_callback+0xf8/0x220
[ 7490.847625] [&lt;ffffffff9a9c246e&gt;] oom_kill_process+0x25e/0x3f0
[ 7490.850515] [&lt;ffffffff9a933a41&gt;] ? cpuset_mems_allowed_intersects+0x21/0x30
[ 7490.853893] [&lt;ffffffff9aa40ba6&gt;] mem_cgroup_oom_synchronize+0x546/0x570
[ 7490.857075] [&lt;ffffffff9aa40020&gt;] ? mem_cgroup_charge_common+0xc0/0xc0
[ 7490.860348] [&lt;ffffffff9a9c2d14&gt;] pagefault_out_of_memory+0x14/0x90
[ 7490.863651] [&lt;ffffffff9af78db3&gt;] mm_fault_error+0x6a/0x157
[ 7490.865928] [&lt;ffffffff9af8d8d1&gt;] __do_page_fault+0x491/0x500
[ 7490.868661] [&lt;ffffffff9af8da26&gt;] trace_do_page_fault+0x56/0x150
[ 7490.871811] [&lt;ffffffff9af8cfa2&gt;] do_async_page_fault+0x22/0xf0
[ 7490.874423] [&lt;ffffffff9af897a8&gt;] async_page_fault+0x28/0x30
[ 7490.877127] Task in /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod6e897c3a_8b9f_479b_9f53_33d2898977b0.slice/docker-18cc9a81d8a58856ecf5fed45d7db431885b33236e5ad50919297cec453cebe1.scope killed as a result of limit of /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod6e897c3a_8b9f_479b_9f53_33d2898977b0.slice/docker-18cc9a81d8a58856ecf5fed45d7db431885b33236e5ad50919297cec453cebe1.scope
[ 7490.893825] memory: usage 3145728kB, limit 3145728kB, failcnt 176035
[ 7490.896099] memory+swap: usage 3145728kB, limit 3145728kB, failcnt 0
[ 7490.899137] kmem: usage 0kB, limit 9007199254740988kB, failcnt 0
[ 7490.902012] Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod6e897c3a_8b9f_479b_9f53_33d2898977b0.slice/docker-18cc9a81d8a58856ecf5fed45d7db431885b33236e5ad50919297cec453cebe1.scope: cache:72KB rss:3145656KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:3145652KB inactive_file:0KB active_file:20KB unevictable:0KB
[ 7490.962494] [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
[ 7490.966577] [27197] 0 27197 596 166 5 0 807 sh
[ 7490.970286] [27225] 0 27225 818112 786623 1550 0 807 redis-server
[ 7490.974006] [28322] 0 28322 999 304 6 0 807 bash
[ 7490.978178] Memory cgroup out of memory: Kill process 27242 (redis-server) score 1808 or sacrifice child
[ 7490.983765] Killed process 27225 (redis-server), UID 0, total-vm:3272448kB, anon-rss:3144732kB, file-rss:1760kB, shmem-rss:0kB
```
原来是worker节点的内存不够用了而Redis在计算OOM评分时也达到了1808分。于是操作系统就义无反顾地把Redis给杀了。
我们再次把Redis启动之后观察它的内存消耗结果如下
```
[root@k8s-worker-4 ~]# pidstat -r -p 5356 1
Linux 3.10.0-1127.el7.x86_64 (k8s-worker-4) 2021年02月18日 _x86_64_ (6 CPU)
19时55分52秒 UID PID minflt/s majflt/s VSZ RSS %MEM Command
19时55分53秒 0 5356 32.00 0.00 3272448 1122152 6.90 redis-server
19时55分54秒 0 5356 27.00 0.00 3272448 1122416 6.90 redis-server
19时55分55秒 0 5356 28.00 0.00 3272448 1122416 6.90 redis-server
19时55分56秒 0 5356 28.00 0.00 3272448 1122680 6.90 redis-server
19时55分57秒 0 5356 21.78 0.00 3272448 1122680 6.90 redis-server
19时55分58秒 0 5356 38.00 0.00 3272448 1122880 6.90 redis-server
19时55分59秒 0 5356 21.00 0.00 3272448 1122880 6.90 redis-server
19时56分00秒 0 5356 25.00 0.00 3272448 1122880 6.90 redis-server
```
我只是截取了Redis没死之前的一小段数据然后通过RSS实际使用内存来不断观察这段数据发现内存确实会一直往上涨。我又查了一下Redis的配置文件发现没配置maxmemory。
没配置倒是没什么内存不够就不够了呗Pod不是还有内存限制吗但可惜的是worker上的内存不够了导致了Redis进程被操作系统杀掉了这就解释了TPS图中后半段会报错的问题。
但是响应时间慢我们还是得接着分析。我们在前面看到软中断和带宽有关为了减少服务中断之间的相互影响待会我把Redis和Gateway两个服务分开。
我们都知道Redis是靠内存来维护数据的如果只做内存的操作它倒是会很快。但是Redis还有一块跟内存比较有关的功能就是持久化。我们现在采用的是AOF持久化策略并且没有限制AOF的文件大小。
这个持久化文件是放到NFS文件服务器上面的既然是放到文件服务器上那就需要有足够的磁盘IO能力才可以。因此我们到nfs服务器上查看一下IO的能力截取部分数据如下
```
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 0.00 65.00 0.00 6516.00 0.00 200.49 1.85 28.43 28.43 0.00 3.95 25.70
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 0.00 24.00 0.00 384.00 0.00 32.00 0.15 6.46 6.46 0.00 6.46 15.50
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 0.00 8.00 0.00 1124.00 0.00 281.00 0.07 8.38 8.38 0.00 4.00 3.20
..........................
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 0.00 11.00 0.00 556.00 0.00 101.09 0.15 13.55 13.55 0.00 10.36 11.40
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 0.00 4.00 0.00 32.00 0.00 16.00 0.08 19.25 19.25 0.00 15.25
```
通过svctmIO响应时间计数器这个参数可以看到IO的响应时间也增加了。虽然在sysstat的新版本中已经不建议使用svctm了但是在我们当前使用的版本中仍然有这个参数。并且通过它我们可以看到IO的响应时间确实在增加。
为了证明IO的响应时间是和AOF有关我们先把AOF关掉设置appendonly no看看效果。如果有效果那优化方向就非常明确了我们要做的就是这几个优化动作
<li>
把Redis先移到一个网络需求没那么大的Worker上去观察一下TPS能不能好一点。如果这一步有效果我们就不用再折腾下一步了
</li>
<li>
如果上一步做完之后没有效果就再把AOF关掉再观察TPS。如果AOF关掉后有效果那我们就得分析下这个应用有没有必要做Redis的持久化了。如果有必要就得换个快一点的硬盘
</li>
<li>
不管上面两步有用没用对Redis来说我们都应该考虑限制内存的大小和AOF文件的大小。
</li>
我们看一下把Redis从worker-4移到worker-7上之后的TPS如下
<img src="https://static001.geekbang.org/resource/image/82/b0/8280f7c008b93b51b1a8cfd18e0041b0.png" alt="">
TPS还是在下降并且没有一开始的那么高了之前都能达到1000TPS。这个看似非常正确的优化动作却导致了TPS下降的现象显然不是我们期望的。
现在还不知道问题在哪里不过我们一直想达到的目标是降队列。所以我们先确认下网络队列有没有降下来再来考虑TPS怎么提升。
你看worker-4上的队列没有recv_Q的值了
<img src="https://static001.geekbang.org/resource/image/ee/29/eedebbd0338a8de3a68a9bffe3f99029.png" alt="">
现在我们就得来处理下AOF了因为我们虽然移开了Redis但是TPS并没有上升。所以我们还得看看AOF的影响。
关掉AOF之后TPS如下
<img src="https://static001.geekbang.org/resource/image/00/ab/009dc49121215309fd600454ae07d5ab.png" alt="">
总体资源如下:
<img src="https://static001.geekbang.org/resource/image/31/e0/31afcba048861d02981f5947c88819e0.png" alt="">
看到了没效果还是有的吧我们可是得到了1000以上的稳定的TPS曲线。
在这个容量场景中,我们完成了四个阶段的分析之后,优化效果还不错。不过,每个性能测试都应该有**结论**。所以,我们还需要做一个动作,就是接着增加压力,看一下系统的最大容量能达到多少。
于是,我们进入第五个阶段的分析。
## 第五阶段分析
请你注意**,容量****场景最重要的变化只有一个,就是增加线程**。而跟着线程一起变化的就是参数化的数据量。在这样的增加线程的场景中,我们还要关注的就是资源的均衡使用。因此,在第四阶段的优化之后,我们先来看一下这个场景的结果是个什么样子。
### 场景运行数据
场景压力数据如下:
<img src="https://static001.geekbang.org/resource/image/db/5d/dbcd3ae8b6df86ac8bf2c9a919270a5d.png" alt="">
从效果上来看不错哦TPS已经达到1700了。
### 全局监控分析
全局监控的数据如下:
<img src="https://static001.geekbang.org/resource/image/ey/c4/eyy599b05a63e03383107a67d56d5ec4.png" alt="">
从上面两张图中可以看到在我们这样的压力之下TPS最大能达到1700左右系统整体资源使用率也不算少了。
**经过了基准场景和容量场景之后,我们现在<strong><strong>就可以下**</strong>结论了</strong>******系统资源在这个最大容量的场景中已经达到了比较高的使用率。**
你有没有听过性能行业中一直流传的一句话:性能优化是无止境的。所以,**我们一定要选择好性能项目结束的关键点。**
就拿我们这个课程的案例来说,这个系统在技术上已经没有优化的空间了,或者说在技术上优化的成本比较高(比如说要做一些定制的开发和改造)。如果你在这种情况下还想要扩大容量,那么你能做的就是增加节点和硬件资源,把所有的硬件资源全都用完。
但是!请所有做性能项目的人注意!**我们做性能项目,不是说把系统优化到最好<strong><strong>后**</strong>,就可以在生产环境中按这样的容量来设计整体的生产资源****了</strong>!要知道,生产环境中出现问题的代价是非常高的,一般我们都会增加一定的冗余,但是冗余多少就不一而足了。
在很多企业中生产环境里使用的CPU都没有超过20%。为什么会有这么多的冗余呢?在我的经验中,大部分的项目都是根据业务的发展在不断迭代,然后形成了这样的局面。你可以想像一下,这样的生产环境里有多少资源浪费。
说到这里,我们不得不说一下怎么评估架构级的容量。因为对于一个固定客户端数的系统来说,很容易判断整体的容量。但是,对非固定客户端数的系统而言,要想抵挡得住突发的业务容量。那就要经过严格的设计了,像缓存、队列、限流、熔断、预热等等这些手段都要上了。
对于整体的架构容量设计,在所有的企业中都不是一蹴而就的,都要经过多次的、多年的版本迭代,跟着业务的发展不断演进得到。这就不是一个专栏可以尽述的了。
## 总结
从基准场景做完之后,我们来到了容量场景,这是一个非常大的变化。在这个场景中,我们解决了几个问题并最终给出了结论:
第一个阶段分析了压力工具参数化的问题解决了TPS不断降低、响应时间不断上升的问题。
第二个阶段分析了数据库索引解决了TPS低的问题。
第三个阶段:分析了资源争用,解决了多容器跑到一个节点上的问题。
第四个阶段分析了网络争用和Redis的AOF解决了TPS不稳定的问题。
第五个阶段:递增压力,给出最终系统整体容量的结论。
在做完这些动作之后我们终于可以给出比较明确的结论了TPS能达到1700
请你记住,对于一个性能项目来说,没有结论就是在耍流氓。所以,我一直强调,**在性能项目中****,我们****一定要给出最大容量的结论。**
## 课后作业
这就是今天的全部内容,最后给你留两个思考题吧:
1. 为什么性能项目一定要有结论?
1. 当多个性能问题同时出现时,我们怎么判断它们产生的相互影响?
1. 如何判断一个系统已经优化到了最优的状态?
记得在留言区和我讨论、交流你的想法,每一次思考都会让你更进一步。
如果这节课让你有所收获,也欢迎你分享给你的朋友,共同学习进步。我们下一讲再见!

View File

@@ -0,0 +1,379 @@
<audio id="audio" title="26 | 稳定性场景之一:怎样搞定业务积累量产生的瓶颈问题?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7b/61/7baa45d0529d0135cbb2db43f82b3b61.mp3"></audio>
你好,我是高楼。
根据我们的RESAR性能理论在执行完基准场景、容量场景之后接下来就是稳定性场景了。
做过性能项目的工程师应该都有一个感觉:在跑稳定性场景之前,内心是战战兢兢的,因为不知道在运行长时间之后,系统会是什么样的表现。
并且,还有一个复杂的地方就是,在稳定性场景中,由于运行的时间长,出现问题后,我们分析起来会比较困难,主要有三点原因:
1分析一定要有完整且持续的计数器监控。因为在稳定性场景中实时查看性能计数器是不现实的我们不可能一直盯着。而且问题出现的时间点也不确定。所以在分析问题时我们需要完整且持续的计数器监控。
2累积业务量产生的问题点在整个系统中也是不确定的。
3你知道稳定性场景回归比较耗时在分析优化的过程中但凡调个参数、改行代码啥的总是要回归场景的而把稳定性场景拉起来就需要几个小时。所以稳定性场景中的优化动作即便看似简单也会消耗比较长的时间。
基于这几点原因,**我们在稳定性运行之前,一定要想好监控哪些计数器**,避免在稳定性运行过程中遇到问题时,发现没有可用的计数器分析问题,那就悲催了。这是极有可能出现的情况,你要格外注意。
根据[第9讲](https://time.geekbang.org/column/article/361138)中提到的监控逻辑,在执行我们稳定性场景前,我们已经按“组件 - 模块 - 计数器”这样的逻辑罗列了所有需要监控的计数器,并且也用相应的工具去实现了。一切看起来已经万事具备。下面我们来看看在执行稳定性场景时,有哪些要点需要注意?
## 稳定性场景的要点
在稳定性场景中,有两点是需要你着重关注的:一个是运行时长,另一个是压力量级。
### 1. 运行时长
我们在前面提到,容量场景是为了看系统所能承受的最大容量,而**稳定性场景主要看的是系统提供长时间服务时的性能稳定性,观察系统在长时间运行过程中出现的累积效应**。因此,运行时长就是稳定性场景中非常重要的一个指标了。
在每个业务系统中,稳定性运行时长都不是固定的,这取决于业务系统的具体应用场景。
对于大部分长年不能宕机的系统来说,它们靠的不是系统中的所有节点都能长年运行,而是**架构设计可以在任一节点出现问题之后,将对应的业务承接到其他节点上**。而这些架构设计就涉及到了DNS分区、扩展能力、高可用能力等技术。
可是,对于我们性能项目来说,即便是长年不宕机的系统,稳定性场景也不可能长年运行。因为如果这样做,就相当于长年运行着另一个生产系统,成本高、维护难,这显然是非常不现实的。
这时候,另一个岗位的重要性就体现出来了,那就是:运维。
在运维的职责里,就有“处理生产环境中出现的各种问题”这一项,我们俗称背锅侠。运维要做的就是保障系统在各种场景下都要正常运行。不过我想多啰嗦几句,要保证这一点,就不能只靠运维岗的工程师,它需要一个企业中所有技术岗的通力合作。换句话说,运维的职责实际上应该由一个企业的所有技术人员来承担。
话说回来,我们知道,运维会制定各种工作内容来保障系统的正常运行,其中,非常重要的一项就是,搭建完善的监控系统,因为你让一个运维眼睛都不眨眼地盯着系统是不现实的。而我们这个课程中提到的全局监控和定向监控,就可以完全覆盖到这种监控系统的要求。
为什么要提到运维呢?
因为**稳定性场景的运行时长,不能覆盖长年运行的系统,这就需要运维人员来保障那线上的稳定性状态了**。总体来看,运维有两大类工作内容:一类是日常巡检(用手工或自动化的方式,查看系统的健康状态);另一类是运维动作(用手工或自动化的方式,完成归档、日志清理等动作)。
有些系统有固定的运维周期,周期按照天、周或者月来计算。而有些系统是没有固定的运维周期的,这就要靠监控系统提供的信息来判断什么时候做运维动作了。在自动化运维比较完善的情况下,有些运维动作就由自动化系统承接了;在自动化运维不完善的情况下,就只能靠人了。
不过,不管有没有自动化运维,每个系统都是有运维周期的,像下面这样:
<img src="https://static001.geekbang.org/resource/image/50/c9/50abb9b4044ca8205b4725a0936fd6c9.jpg" alt="">
下面我们具体来看看,对于上述两种系统,怎么计算稳定性场景的运行时长。
- **有固定运维周期的系统**
对于有固定运维周期的系统,稳定性场景的运行时长就比较容易定义了。我们先根据生产系统的数据统计,看一下系统在固定的运维周期内,最大的业务容量是多少。
假设你根据生产系统统计出在之前的运维周期中有1亿的业务容量而在容量场景中得到的最大TPS有1000。那么我们就可以通过下面这个公式来计算
$$ 稳定性运行时长 = 1亿(业务累积量) \div 1000(TPS) \div 3600(秒) \approx 28(小时) $$
用这种方式得出的稳定性运行时长,对于有固定运维周期的系统来说已经足够了。
- **没有固定运维周期的系统**
对于没有固定运维周期的系的系统,该怎么办呢?也许有人会说,运行时间只有尽可能长了。但是,“尽可能”也得有一个界限。根据我的经验,我们不能用“尽可能”来判断稳定性场景的运行时长。
根据上面的运算公式TPS来自于容量场景时间是最大的变量所以业务累积累是不确定的。现在我们要做的就是把业务累积量确定下来。
我们知道,**业务积累量需要根据历史业务的统计数据来做决定**。如果你的系统一个月有1000万的业务累积量同时稳定性运行的指标是稳定运行三个月也就是说即便没有固定的运维周期我们也得给出一个时间长度<br>
<img src="https://static001.geekbang.org/resource/image/a8/13/a8e36a90955af2aa75638b48f924d613.jpg" alt="">
那么总业务累积量就是3000万。
我们再根据上面的公式来计算就可以了:
$$ 稳定性运行时长 = 3000万(业务累积量) \div 1000(TPS) \div 3600(秒) \approx 8(小时) $$
总之,**不管是什么样的系统,要想运行稳定性场景,都得确定一个业务累积量**。
### 2. 压力量级
我们再来看压力量级,这是稳定性场景中必须要确定的另一个前提条件。
我们在网上经常能看到这样的说法稳定性的压力应该用最大TPS的80%来运行。可是,我们来看一下**稳定性场景的目标:保障系统的业务累积量**。也就是说我们只要保证这一目标就可以了至于TPS是多少并不重要。
因此,**我们不用考虑80%的问题直接用最大TPS来运行即可**。一个系统如果能在最大TPS的状态下正常运行才算是真正经受住了考验。
你可能会有这样的疑问当一个系统在最大TPS状态下运行如果有突增的压力需要更高的TPS怎么办请你注意稳定性场景不是为了解决突增的压力峰值而设计的。如果你要考虑突增的业务压力我建议你增加容量场景来验证。
另外,如果我们要对付突增的业务容量,不止要在性能场景中考虑增加容量场景,还要在架构设计时,把相应的限流、熔断、降级等异常保障机制加进来。
到这里,我们就把两个重要的稳定性条件讲完了。
下面我们具体操作一下,以我们这个课程的电商系统为例,看看稳定性场景该怎么确定。
## 场景运行数据
因为这是一个示例系统所以我们先定一个小目标稳定运行业务累积量为5000万。
对于这个系统我们在容量场景中得到的最大TPS在1700但是随着容量场景的不断增加数据库中的数据量越来越大TPS也会慢慢降低因为我并没有做数据库的容量限制和归档等动作。那我们就用容量场景中的相应的压力线程来运行稳定性场景让我们的理论能在落地时得到印证。根据前面的计算公式运行时长为
$$ 稳定性运行时长 = 5000万 \div 1700(TPS) \div 3600(秒) \approx 8.16(小时) $$
也就是说我们要运行稳定性场景8个小时多一点。
下面我们来看一下具体的运行数据:
<img src="https://static001.geekbang.org/resource/image/62/b6/62bbb6910332dcb7d9954377d704ddb6.png" alt="">
从数据上来看在稳定性场景运行4个多小时的时候TPS就没了响应时间又非常高这明显是出现问题了。
这时候的业务积累量为:
<img src="https://static001.geekbang.org/resource/image/80/42/804f51d7a985b39338e2eb67aa699f42.png" alt="">
总的业务累积量是2900多万这和我们的预期并不相符。
下面我们分析一下到底是怎么回事。
## 全局监控分析
按照我们一贯的性能分析逻辑,我们先来查看全局监控数据:
<img src="https://static001.geekbang.org/resource/image/39/a3/390d06dfa6c2d4aae450f64a1cbb25a3.png" alt="">
你看在运行期间好几个worker的CPU资源都在70%以上,这样的数据中规中矩,还不是我们关注的重点。因为对于稳定性场景来说,资源只要能撑得住就行了。
但是在场景运行数据中TPS直接就断掉了。在我查看每个主机的资源情况时在worker-1上看到了这样的数据
<img src="https://static001.geekbang.org/resource/image/ca/ed/ca3b821ee2cb9ff37dabdc67223339ed.png" alt=""><img src="https://static001.geekbang.org/resource/image/b5/b4/b51b0545310a1fe58222b3fd2256d7b4.png" alt="">
这是数据断掉了呀!那我们就要定向分析这个主机了。
## 定向监控分析
### 定向分析第一阶段
根据断掉的时间点,和我们前面使用的监控手段,一层层查(这个步骤就是把我们的项目级全局监控计数器看一遍,在[](https://time.geekbang.org/column/article/356789)[4](https://time.geekbang.org/column/article/356789)[](https://time.geekbang.org/column/article/356789)中已经有了明确的说明,我这里不再赘述了),结果看到了这样的日志信息:
```
Feb 20 04:20:41 hp-server kernel: Out of memory: Kill process 7569 (qemu-kvm) score 256 or sacrifice child
Feb 20 04:20:41 hp-server kernel: Killed process 7569 (qemu-kvm), UID 107, total-vm:18283204kB, anon-rss:16804564kB, file-rss:232kB, shmem-rss:16kB
Feb 20 04:20:44 hp-server kernel: br0: port 4(vnet2) entered disabled state
Feb 20 04:20:44 hp-server kernel: device vnet2 left promiscuous mode
Feb 20 04:20:44 hp-server kernel: br0: port 4(vnet2) entered disabled state
Feb 20 04:20:44 hp-server libvirtd: 2021-02-19 20:20:44.706+0000: 1397: error : qemuMonitorIO:718 : 内部错误End of file from qemu monitor
Feb 20 04:20:44 hp-server libvirtd: 2021-02-19 20:20:44.740+0000: 1397: error : qemuAgentIO:598 : 内部错误End of file from agent monitor
Feb 20 04:20:45 hp-server systemd-machined: Machine qemu-3-vm-k8s-worker-1 terminated.
```
显然因为宿主机内存不够worker-1被直接杀掉了。既然是内存不足我们肯定要确定一下这个宿主机是为什么内存不足了。
我检查了宿主机的overcommit参数。这个参数是确定操作系统是否允许超分内存的。对于Linux来说内存分配出去不一定会被用完。所以对宿主机来说超分可以支持更多的虚拟机。
```
[root@hp-server log]# cat /proc/sys/vm/overcommit_memory
1
```
我们看到overcommit的配置是1那就是允许超分。
我在这里简单说明一下,这个参数的几个选项:
- 0不允许超分。
- 1不管当前的内存状态都允许分配所有的物理内存。
- 2允许分配的内存超过物理内存+交换空间。
请你注意允许超分并不是说允许超用而我们现在的情况是宿主已经OOM内存溢出这就说明内存真的已经不够用了。
这个逻辑其实挺有意思Linux虽然允许超分内存但是当内存真正不够用的时候即便是收到了超分请求也得为了保证自己的正常运行而做OOM的判断。也就是说分给你你不见得能用得起来这种耍流氓的手段像不像领导画大饼
没办法,我们还是要理智地来分析,看看怎么解决。
因为虚拟机是worker-1被杀掉的我们来看一下worker-1的内存
<img src="https://static001.geekbang.org/resource/image/5f/27/5f6e7e3510d3833b2f1c9dd50c87af27.png" alt="">
从worker-1的资源上来看如果worker-1是因为内存用得多被杀掉那应该在12点20分到12点30分之间就被杀掉了因为上面的内存曲线在12点半左右之后就没有大的波动了。
可是为什么要等到凌晨4点20分呢这说明worker-1被杀掉并不是因为worker-1上的内存使用突然增加。而是宿主机上的内存使用变多进而内存不足然后在计算了OOM评分之后杀掉了worker-1。那我们就到宿主机上看看还有哪些虚拟机在运行
```
[root@hp-server log]# virsh list --all
Id 名称 状态
----------------------------------------------------
1 vm-k8s-master-1 running
2 vm-k8s-master-3 running
4 vm-k8s-worker-2 running
5 vm-k8s-worker-3 running
6 vm-k8s-worker-4 running
7 vm-k8s-worker-1 running
```
宿主机上总共运行了6个虚拟机它们在12点半之后的时间里对应的内存依次如下
vm-k8s-worker-2
<img src="https://static001.geekbang.org/resource/image/f0/yy/f03dc5cd91ac0756c1ee27dfb008fcyy.png" alt="">
vm-k8s-worker-3
<img src="https://static001.geekbang.org/resource/image/5d/57/5d108482d8db10d9daa2eebf90666657.png" alt="">
vm-k8s-worker-4
<img src="https://static001.geekbang.org/resource/image/7b/6e/7bcba1c104c2377225af8c91df60ee6e.png" alt="">
vm-k8s-master-1
<img src="https://static001.geekbang.org/resource/image/30/02/30d449c7243661d6a523ea2c92eab602.png" alt="">
vm-k8s-master-3
<img src="https://static001.geekbang.org/resource/image/c5/73/c548ec1aca5aac3520d4cfc8842df173.png" alt="">
看到了没有4点多的时候在worker-2上有一个内存较大的请求。
针对这种情况,如果我们要细细地分析下去,接下来应该分析这个内存请求是从哪来的。但是,在稳定性场景中,要做这样的分析是比较麻烦的。因为这个场景的运行时间长,并且业务众多,不容易拆分时间。因此,我建议你到基准场景中去做分析。
现在,我们不能断言这个内存请求不合理,我们要做的是让这个系统稳定运行下去。所以,我们先来解决问题。
你可能会有疑问既然是worker-2请求了内存为啥要把worker-1杀掉呢这就需要了解Linux的OOM killer机制了。
在OOM killer机制中不是说谁用的内存大就会杀谁当然如果谁用的内存大被杀的可能性也会比较大而是会经过计算评分哪个进程评分高就杀哪个。
在每个进程中都会有三个参数oom_adj、oom_score、oom_score_adj系统的评分结果就记录在oom_score中。其他两个是调节参数oom_adj是一个旧的调节参数为了系统的兼容性被保留了下来oom_score_adj是一个新的调节参数Linux会根据进程的运行参数来判断调节参数为多少。
这里提到的运行参数主要是这几个:
- 运行时长(存活时间越长的进程,越不容易被杀掉)
- CPU时间消耗CPU消耗越大的进程越容易被干掉
- 内存消耗(内存消耗越大的进程,越容易被干掉)
这些参数组合在一起,决定了哪个进程要被干掉。
而在我们这个场景中是worker-1被干掉了这就说明worker-1的评分是高的。
因为前面有worker-1上的内存消耗也比较大所以我们在worker-1、worker-2这两台机器上查一下有多少Pod
```
[root@k8s-master-1 ~]# kubectl get pods -o wide --all-namespaces| grep worker-2
default cloud-nacos-registry-76845b5cfb-bnj76 1/1 Running 0 9h 10.100.140.8 k8s-worker-2 &lt;none&gt; &lt;none&gt;
default sample-webapp-755fq 0/1 ImagePullBackOff 0 19h 10.100.140.7 k8s-worker-2 &lt;none&gt; &lt;none&gt;
default skywalking-es-init-4w44r 0/1 Completed 0 15h 10.100.140.11 k8s-worker-2 &lt;none&gt; &lt;none&gt;
default skywalking-ui-7d7754576b-nj7sf 1/1 Running 0 9h 10.100.140.14 k8s-worker-2 &lt;none&gt; &lt;none&gt;
default svc-mall-auth-6ccf9fd7c9-qh7j8 1/1 Running 0 151m 10.100.140.21 k8s-worker-2 &lt;none&gt; &lt;none&gt;
default svc-mall-auth-6ccf9fd7c9-sblzx 1/1 Running 0 151m 10.100.140.23 k8s-worker-2 &lt;none&gt; &lt;none&gt;
default svc-mall-member-df566595c-9zq9k 1/1 Running 0 151m 10.100.140.19 k8s-worker-2 &lt;none&gt; &lt;none&gt;
default svc-mall-member-df566595c-dmj67 1/1 Running 0 151m 10.100.140.22 k8s-worker-2 &lt;none&gt; &lt;none&gt;
kube-system calico-node-pwsqt 1/1 Running 8 37d 172.16.106.149 k8s-worker-2 &lt;none&gt; &lt;none&gt;
kube-system kube-proxy-l8xf9 1/1 Running 15 85d 172.16.106.149 k8s-worker-2 &lt;none&gt; &lt;none&gt;
monitoring node-exporter-wcsj7 2/2 Running 18 42d 172.16.106.149 k8s-worker-2 &lt;none&gt; &lt;none&gt;
nginx-ingress nginx-ingress-7jjv2 1/1 Running 0 18h 10.100.140.62 k8s-worker-2 &lt;none&gt; &lt;none&gt;
[root@k8s-master-1 ~]# kubectl get pods -o wide --all-namespaces| grep worker-1
default mysql-min-c4f8d4599-fxwf4 1/1 Running 0 9h 10.100.230.9 k8s-worker-1 &lt;none&gt; &lt;none&gt;
kube-system calico-node-tmpfl 1/1 Running 8 37d 172.16.106.130 k8s-worker-1 &lt;none&gt; &lt;none&gt;
kube-system kube-proxy-fr22f 1/1 Running 13 85d 172.16.106.130 k8s-worker-1 &lt;none&gt; &lt;none&gt;
monitoring alertmanager-main-0 2/2 Running 0 162m 10.100.230.12 k8s-worker-1 &lt;none&gt; &lt;none&gt;
monitoring node-exporter-222c5 2/2 Running 10 7d 172.16.106.130 k8s-worker-1 &lt;none&gt; &lt;none&gt;
nginx-ingress nginx-ingress-pjrkw 1/1 Running 1 18h 10.100.230.10 k8s-worker-1 &lt;none&gt; &lt;none&gt;
[root@k8s-master-1 ~]#
```
我们进一步查看那些应用经常使用的Pod看看它们的内存情况如何
```
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
7609 27 20 0 12.4g 7.0g 12896 S 118.9 45.0 167:38.02 /opt/rh/rh-mysql57/root/usr/libexec/mysqld --defaults-file=/etc/my.cnf
```
通过查看worker-1上的进程我们发现主要是MySQL使用的内存最多这是吃内存的大户。如果宿主机内存不够把worker-1杀掉确实是有可能的。
下面我们增加几个临时的监控把一些重要服务的内存记录一下比如Gateway、Member、MySQL、Redis等。然后再恢复所有的应用把场景跑起来看看是什么样的结果
<img src="https://static001.geekbang.org/resource/image/fe/99/fecdae9ec7e1375313ca3effeb5d7399.png" alt="">
运行时长已经快有七个小时了。你可能会奇怪为什么上一个场景只运行了4个多小时而现在却能运行7个小时了呢这是因为worker-1被杀了之后虚拟机重启了状态都重置了。
而在上次的场景运行之前,我们并没有重启过虚拟机,也就是说前面已经有了一段时间的内存消耗。对于稳定性场景来说,增删改查都是有的,数据量也在不断增加,所以内存会使用得越来越多。
这一次运行的累积业务量是3200多万
<img src="https://static001.geekbang.org/resource/image/de/97/de6e783a5f88ee5da6188419f2efbf97.png" alt="">
但是问题还是出现了通过查看宿主机的日志我看到worker-2又被杀掉了
```
Feb 20 19:42:44 hp-server kernel: Out of memory: Kill process 7603 (qemu-kvm) score 257 or sacrifice child
Feb 20 19:42:44 hp-server kernel: Killed process 7603 (qemu-kvm), UID 107, total-vm:17798976kB, anon-rss:16870472kB, file-rss:0kB, shmem-rss:16kB
Feb 20 19:42:46 hp-server kernel: br0: port 5(vnet3) entered disabled state
Feb 20 19:42:46 hp-server kernel: device vnet3 left promiscuous mode
Feb 20 19:42:46 hp-server kernel: br0: port 5(vnet3) entered disabled state
Feb 20 19:42:46 hp-server systemd-machined: Machine qemu-4-vm-k8s-worker-2 terminated.
Feb 20 19:42:46 hp-server avahi-daemon[953]: Withdrawing address record for fe80::fc54:ff:fe5e:dded on vnet3.
Feb 20 19:42:46 hp-server avahi-daemon[953]: Withdrawing workstation service for vnet3.
[root@hp-server log]#
```
也就是说在内存不够的情况下杀掉哪个worker并不是固定的。至少这可以说明宿主机真的是因为自己的内存不够用而杀掉虚拟机的。这可能就和具体的组件无关了因为组件的内存消耗是根据运行需求来的是合理的。
为什么做这样的判断呢因为如果是某个固定的worker被杀掉那我们可以去监控这个worker上运行的技术组件看看是哪个组件的内存增加得快然后进一步判断这个技术组件的内存不断增加的原因。
可是现在被杀掉的worker并不是固定的。根据OOM的逻辑宿主机操作系统在内存不够用的时候才会调用OOM killer。我们前面也提到overcommit的参数设置是1也就是说宿主机操作系统允许内存在请求时超分。
但是在宿主机真正使用内存的时候内存不够用了进而导致虚拟机被杀掉。这意味着在宿主机创建KVM虚拟机时产生了超分但并没有提供足够的可用内存而在压力持续的过程中虚拟机又确实需要这些内存。所以虚拟机不断向宿主机申请内存可宿主机没有足够的内存因而触发了OOM killer机制。
这样看来,我们就得算一下内存到底超分了多少,看看是不是因为我们配置的超分过大,导致了这个问题。我们把虚拟机的内存列出来看看:
<img src="https://static001.geekbang.org/resource/image/21/55/218237ca581de41ayyyy0b5c2e1a6755.png" alt="">
我们计算一下总分配内存:
$$ 总分配内存 = 8 \times 2 + 16 \times 4 = 80G $$
而宿主机的物理内存只有:
```
[root@hp-server log]# cat /proc/meminfo|grep Total
MemTotal: 65675952 kB
SwapTotal: 0 kB
VmallocTotal: 34359738367 kB
CmaTotal: 0 kB
HugePages_Total: 0
[root@hp-server log]#
```
也就是说宿主机的最大物理内存也只有65G左右。这也难怪物理内存在真实使用时会不够用。
现在我们把虚拟机的内存降下来,让它不会产生超分,配置如下:
<img src="https://static001.geekbang.org/resource/image/35/fc/35c3b6275047993698dde957372cf6fc.png" alt="">
总分配内存计算下来就是:
$$ 总分配内存 = 4 \times 2 + 13 \times 4 = 60G $$
这样就足够用了。
不过根据性能分析中时间和空间相互转换的原则这样可能会导致TPS降低。因为在虚拟机的操作系统内存减少时会更早地出现page faults也就是页错误换页时会产生。不过如果只是换页而不是出现OOM至少不会导致虚拟机被杀掉。
我们再把场景跑起来,看看结果:
<img src="https://static001.geekbang.org/resource/image/8d/75/8d4ebfa73a4120d7d024d6299ef96075.png" alt="">
这个结果看起来不错虽说TPS有掉下来的时候但是总体上是稳定的。运行时间也超过了12小时。
我们再来看累积业务量:
<img src="https://static001.geekbang.org/resource/image/92/59/92a154c43e56e4b43ba75ff44b3e9d59.png" alt="">
这次的累积业务量超过了7200万超过了我们定的5000万的小目标。现在是不是可以欢呼一下了
别高兴太早,在下节课中,你会感受到性能项目中的大起大落。
## 总结
今天我们讲了稳定性场景的两个要点,分别是运行时长和压力量级。要想把稳定性场景做得有意义,这两点是必备前提条件。
同时,你要记住一点,稳定性场景是为了找出业务积累的过程中出现的问题。所以,**如果业务积累量不能达到线上的要求,就不能说明稳定性场景做得有意义。**
此外在这节课中我们也分析了物理内存增加的问题。在内存的使用上特别是在这种Kubernetes+Docker的架构中资源分配是非常关键的。不要觉得Kubernetes给我们做了很多自动的分配工作我们就可以喝咖啡了。你会发现仍然有不少新坑在等着我们。
## 课后作业
这就是今天的全部内容,最后给你留两个思考题吧:
1. 在你的项目中,怎么将这节课的稳定性理念落地?
1. 在查找稳定性的问题时,如何设计监控策略,才能保证我们可以收集到足够的分析数据?在你的项目中是如何做的?
记得在留言区和我讨论、交流你的想法,每一次思考都会让你更进一步。
如果这节课让你有所收获,也欢迎你分享给你的朋友,共同学习进步。我们下一讲再见!

View File

@@ -0,0 +1,257 @@
<audio id="audio" title="27 | 稳定性场景之二:怎样搞定磁盘不足产生的瓶颈问题?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2d/5b/2ddeb8bcefd19db3fc4293899685665b.mp3"></audio>
你好,我是高楼。
上节课,我们讲解了稳定性场景的两个要点:运行时长和压力量级,并通过课程的示例系统,带你具体操作了稳定性场景。
在定向分析的第一个阶段中我们分析了虚拟机内存超分导致的操作系统OOM的问题发现是配置的超分过大导致的。在我们降低了虚拟机的内存之后稳定性场景的运行时间超过了12个小时累积业务量达到7200多万这样的结果已经达到了我们的目标。
可是,由于贪心,我并没有停止场景,就在它继续运行的时候,又出现了新问题……因此,我们今天就进入到定向分析的第二阶段,看看还有什么问题在等着我们。
## 定向监控分析
### 定向分析第二阶段
当场景继续运行的时候,我看到了这样的数据:
<img src="https://static001.geekbang.org/resource/image/eb/8f/eb84fbb4e1105ce58dd9e45f849b768f.png" alt="">
从图中我们可以很明显地看到在场景持续运行的过程中TPS掉下来了响应时间则是蹭蹭往上涨。
我们看一下这时候的总业务累积量:
<img src="https://static001.geekbang.org/resource/image/c9/92/c9855e4f7cdb3b0d18d222f44bf50692.png" alt="">
也就是说多了20多万的业务累积量。
见到问题,不分析总是觉得不那么舒服,那我们就来分析一下。
还是按照性能分析决策树我们把计数器一个一个查过去。在我查看MySQL的Pod日志时发现它一直在被删掉重建
<img src="https://static001.geekbang.org/resource/image/75/43/75aa8e663d3a3a8959d4443de60ba043.png" alt="">
请注意我们这是一个示例系统为了方便重建我把MySQL放到Pod中了。如果是在真实的环境中我建议你最好根据生产的实际配置来做数据库的配置。
讲到这里我稍微回应一下行业里的一种声音数据库不应该放到Kubernetes的Pod中去。我不清楚持这样观点的人只是在感觉上认为不安全还是真正遇到了问题。在我的经验中很多系统对数据库的性能要求其实并不高业务量也不大。而用容器来管理便于迁移和重建并且性能上也完全跟得上。所以在这种场景下用Pod也没有关系。
当然也有一些系统用数据库比较狠为了保障更高的性能会在物理机上直接部署。如果你面对的系统确实需要用到物理机来创建数据库那就选择物理机。如果Pod可以满足需求我觉得不用纠结直接用Pod就可以了。
因为MySQL的Pod被删掉重建而MySQL又位于worker-1中那我们就来看一下worker-1的操作系统日志
```
Feb 22 00:43:16 k8s-worker-1 kubelet: I0222 00:43:16.085214 1082 image_gc_manager.go:304] [imageGCManager]: Disk usage on image filesystem is at 95% which is over the high threshold (85%). Trying to free 7213867827 bytes down to the low threshold (80%).
```
原来是分配给worker-1的磁盘被用光了难怪MySQL的Pod一直在被删掉重建。
我们检查一下磁盘的配额:
```
[root@k8s-worker-1 ~]# df -h
文件系统 容量 已用 可用 已用% 挂载点
devtmpfs 6.3G 0 6.3G 0% /dev
tmpfs 6.3G 24M 6.3G 1% /dev/shm
tmpfs 6.3G 67M 6.3G 2% /run
tmpfs 6.3G 0 6.3G 0% /sys/fs/cgroup
/dev/mapper/centos-root 47G 45G 2.7G 95% /
/dev/vda1 1014M 304M 711M 30% /boot
tmpfs 1.3G 4.0K 1.3G 1% /run/user/42
tmpfs 6.3G 12K 6.3G 1% /var/lib/kubelet/pods/9962f8d2-f6bb-4981-a073-dd16bfa9a171/volumes/kubernetes.io~secret/kube-proxy-token-vnxh9
tmpfs 6.3G 12K 6.3G 1% /var/lib/kubelet/pods/f5872331-14b1-402b-99e0-063834d834fa/volumes/kubernetes.io~secret/calico-node-token-hvs7q
overlay 47G 45G 2.7G 95% /var/lib/docker/overlay2/e61e5b2232592ef9883861d8536f37153617d46735026b49b285c016a47179cf/merged
overlay 47G 45G 2.7G 95% /var/lib/docker/overlay2/4c057d86c1eabb84eddda86f991ca3852042da0647fd5b8c349568e2a0565591/merged
shm 64M 0 64M 0% /var/lib/docker/containers/f1e8c983be46895acc576c1d51b631bd2767aabe908035cff229af0cd6c47ffb/mounts/shm
shm 64M 0 64M 0% /var/lib/docker/containers/c7e44cdfc5faa7f8ad9a08f8b8ce44928a5116ccf912fbc2d8d8871ab00648a5/mounts/shm
overlay 47G 45G 2.7G 95% /var/lib/docker/overlay2/a685a1652586aca165f7f159347bf466dd63f497766762d8738b511c7eca1df3/merged
overlay 47G 45G 2.7G 95% /var/lib/docker/overlay2/b7da3fde04f716a7385c47fe558416b35e471797a97b28dddd79f500c62542f2/merged
tmpfs 1.3G 36K 1.3G 1% /run/user/0
tmpfs 6.3G 12K 6.3G 1% /var/lib/kubelet/pods/d01f8686-e066-4ebf-951e-e5fe9d39067d/volumes/kubernetes.io~secret/node-exporter-token-wnzc6
overlay 47G 45G 2.7G 95% /var/lib/docker/overlay2/5215bd987a62316b3ebb7d6b103e991f26fffea4fe3c05aac51feeb44ab099ab/merged
shm 64M 0 64M 0% /var/lib/docker/containers/d0cf9df15ac269475bb9f2aec20c048c8a61b98a993c16f5d6ef4aba2027326a/mounts/shm
overlay 47G 45G 2.7G 95% /var/lib/docker/overlay2/aa5125b01d60b19c75f3f5d018f7bb51e902264580a7f4033e5d2abaaf7cc3f6/merged
overlay 47G 45G 2.7G 95% /var/lib/docker/overlay2/3a7d3d4cddc51410103731e7e8f3fbcddae4d74a116c88582557a79252124c5d/merged
tmpfs 6.3G 12K 6.3G 1% /var/lib/kubelet/pods/34f60184-07e5-40da-b2cb-c0295d560d54/volumes/kubernetes.io~secret/default-token-7s6hb
overlay 47G 45G 2.7G 95% /var/lib/docker/overlay2/940009fca9f57e4b6f6d38bab584d69a2f3ff84153e3f0dfd3c9b9db91fa2b30/merged
shm 64M 0 64M 0% /var/lib/docker/containers/12c6a27bb53a4b0de5556a960d7c394272d11ceb46ac8172bd91f58f762cde14/mounts/shm
overlay 47G 45G 2.7G 95% /var/lib/docker/overlay2/366007a9f82dfb9bd5de4e4cadf184cba122ef2070c096f393b7b9e24ae06a98/merged
tmpfs 6.3G 12K 6.3G 1% /var/lib/kubelet/pods/251e9c86-4f25-42bd-82a0-282d057fe238/volumes/kubernetes.io~secret/nginx-ingress-token-cbpz9
overlay 47G 45G 2.7G 95% /var/lib/docker/overlay2/1defd5a0004201a0f116f48dd2a21cba16647a3c8fdfde802fb5ea1d3e5591ff/merged
shm 64M 0 64M 0% /var/lib/docker/containers/459bf58b1cafcc9ab673d30b92ae815a093e85593ab01921b9ba6e677e36fe45/mounts/shm
overlay 47G 45G 2.7G 95% /var/lib/docker/overlay2/49197bcd5b63e30abc94315b0083761a4fd25ebf4341d2574697b84e49350d53/merged
[root@k8s-worker-1 ~]#
```
可以看到磁盘的配置是使用到95%就会驱逐Pod而现在的磁盘使用量已经到了配置的限额。
既然如此那我们的解决方案也就非常明确了就是把磁盘再加大一些我们再扩大100G
```
[root@k8s-worker-1 ~]# df -h
文件系统 容量 已用 可用 已用% 挂载点
devtmpfs 6.3G 0 6.3G 0% /dev
tmpfs 6.3G 0 6.3G 0% /dev/shm
tmpfs 6.3G 19M 6.3G 1% /run
tmpfs 6.3G 0 6.3G 0% /sys/fs/cgroup
/dev/mapper/centos-root 147G 43G 105G 30% /
/dev/vda1 1014M 304M 711M 30% /boot
tmpfs 1.3G 4.0K 1.3G 1% /run/user/42
tmpfs 6.3G 12K 6.3G 1% /var/lib/kubelet/pods/d01f8686-e066-4ebf-951e-e5fe9d39067d/volumes/kubernetes.io~secret/node-exporter-token-wnzc6
tmpfs 6.3G 12K 6.3G 1% /var/lib/kubelet/pods/9962f8d2-f6bb-4981-a073-dd16bfa9a171/volumes/kubernetes.io~secret/kube-proxy-token-vnxh9
tmpfs 6.3G 12K 6.3G 1% /var/lib/kubelet/pods/251e9c86-4f25-42bd-82a0-282d057fe238/volumes/kubernetes.io~secret/nginx-ingress-token-cbpz9
tmpfs 6.3G 12K 6.3G 1% /var/lib/kubelet/pods/f5872331-14b1-402b-99e0-063834d834fa/volumes/kubernetes.io~secret/calico-node-token-hvs7q
overlay 147G 43G 105G 30% /var/lib/docker/overlay2/7380ac7d8f83ba37ddae785e5b4cd65ef7f9aa138bfb04f86e3c7f186f54211a/merged
shm 64M 0 64M 0% /var/lib/docker/containers/3c90444a51820f83954c4f32a5bc2d1630762cdf6d3be2c2f897a3f26ee54760/mounts/shm
overlay 147G 43G 105G 30% /var/lib/docker/overlay2/c841e85e88fdcfe9852dcde33849b3e9c5a229e63ee5daea374ddbc572432235/merged
overlay 147G 43G 105G 30% /var/lib/docker/overlay2/147a81e8a50401ec90d55d3d4df3607eb5409ffe10e2c4c876c826aa5d47caf0/merged
shm 64M 0 64M 0% /var/lib/docker/containers/9e2c04b858025523e7b586fe679a429ac49df3711881261cda40b158ad05aebf/mounts/shm
overlay 147G 43G 105G 30% /var/lib/docker/overlay2/83e01c8cda50233088dc70395a14c861ac09ce5e36621f1d8fdd8d3d3e0a7271/merged
shm 64M 0 64M 0% /var/lib/docker/containers/f23362117532f08ff89f937369c3e4d2039d55a9ba51f61e41e62d725b24e3a1/mounts/shm
overlay 147G 43G 105G 30% /var/lib/docker/overlay2/0cfe0dbd0c633e13a42bd3d69bd09ea51ab4354d77a0e6dcf93cabf4c76c3942/merged
overlay 147G 43G 105G 30% /var/lib/docker/overlay2/7b83010457d86cecf3c80ebc34d9db5d26400c624cba33a23f0e9983f7791aef/merged
overlay 147G 43G 105G 30% /var/lib/docker/overlay2/0f31c96b1961d5df194a3710fdc896063a864f4282d7a287b41da27e4d58a456/merged
overlay 147G 43G 105G 30% /var/lib/docker/overlay2/f67a6de6a1b18d4748581230ed7c34c8f16d8f0dd877a168eb12eacf6bf42f05/merged
shm 64M 0 64M 0% /var/lib/docker/containers/e3eb1ea1785e35045213518dd6814edcd361b501748b8e6bdede20c8961062d2/mounts/shm
overlay 147G 43G 105G 30% /var/lib/docker/overlay2/5cec0d1a7723dfcb0e5eaf139f4965a220575557795ad2959ce100aa888dc12b/merged
tmpfs 1.3G 32K 1.3G 1% /run/user/0
tmpfs 6.3G 12K 6.3G 1% /var/lib/kubelet/pods/704657eb-ea28-4fb0-8aee-c49870e692d3/volumes/kubernetes.io~secret/default-token-7s6hb
overlay 147G 43G 105G 30% /var/lib/docker/overlay2/da4073b560a2ce031fa234624c09757b65eb7b6cfc895186dbf8731e2d279fee/merged
shm 64M 0 64M 0% /var/lib/docker/containers/76a6814a838778049495e9f8b2b93e131d041c8f90e8dea867d3c99fa6ca918b/mounts/shm
tmpfs 6.3G 12K 6.3G 1% /var/lib/kubelet/pods/a73b0bc6-76fc-4e2a-9202-380397399b76/volumes/kubernetes.io~secret/default-token-7s6hb
overlay 147G 43G 105G 30% /var/lib/docker/overlay2/cea9c51e718964cc46824ba51ff631a898402318c19e9603c6d364ac3bed8a27/merged
shm 64M 0 64M 0% /var/lib/docker/containers/d936e646d12f7b8381a36e8a11094d76a0a72d95f84edf3f30c7e8b3981264e0/mounts/shm
overlay 147G 43G 105G 30% /var/lib/docker/overlay2/4c210c659428999d000676fde7f1c952f1f43d68b63b308fa766b0ce41568f06/merged
[root@k8s-worker-1 ~]#
```
这样就完美地解决了MySQL中的Pod一直在被删掉重建的问题对吧
不过,我们高兴得有些早了,正当我把这次的稳定性场景接着跑起来的时候,结果还没出来,又出现了新问题!于是,我们只得来到定向分析的第三阶段。
### 定向分析第三阶段
在上一阶段中我们发现了MySQL的Pod被删掉重建。于是当场景再次运行起来时我就直接查看了所有namespace中的Pod状态结果看到了下面的信息
<img src="https://static001.geekbang.org/resource/image/90/67/9056513807ceb171d0d4691cb817ca67.png" alt="">
不知道什么原因Order容器一直在被驱逐。
通过查看日志,我发现了这样的信息:
<img src="https://static001.geekbang.org/resource/image/54/bb/544055c6ef899aa5d76123382a7e87bb.png" alt="">
DiskPressure这就是说Order服务所在的worker上的存储被用完了呗。在Order应用的虚拟机中我通过查看Kubernetes日志发现了这样的错误信息
```
kubelet: W0222 14:31:35.507991 1084 eviction_manager.go:344] eviction manager: attempting to reclaim ephemeral-storage
```
这是Order应用的存储已经用完了并且尝试扩展存储。我们接着查Kubernetes日志看到了“The node was low on resource: ephemeral-storage”的提示。
对Order服务来说不就是写个日志吗其他的应用方法都是在用CPU到底什么Order服务会用这么多的磁盘呢我们查一下日志结果看到了这样的错误信息
```
[2021-02-25 20:48:22.022] [org.apache.juli.logging.DirectJDKLog] [http-nio-8086-exec-349] [175] [ERROR] Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.jdbc.UncategorizedSQLException:
### Error querying database. Cause: java.sql.SQLException: Index oms_order is corrupted
### The error may exist in URL [jar:file:/app.jar!/BOOT-INF/lib/mall-mbg-1.0-SNAPSHOT.jar!/com/dunshan/mall/mapper/OmsOrderMapper.xml]
### The error may involve com.dunshan.mall.mapper.OmsOrderMapper.selectByExample-Inline
### The error occurred while setting parameters
### SQL: SELECT count(0) FROM oms_order WHERE (delete_status = ? AND member_id = ?)
### Cause: java.sql.SQLException: Index oms_order is corrupted
; uncategorized SQLException; SQL state [HY000]; error code [1712]; Index oms_order is corrupted; nested exception is java.sql.SQLException: Index oms_order is corrupted] with root cause
java.sql.SQLException: Index oms_order is corrupted
```
这样的错误有很多并且还增加得非常快。难道是Order表坏了
在Order服务的日志中我们可以看到明显是SQL执行报错了但是我们在上面已经增加了MySQL机器的硬盘按理说不应该是MySQL的磁盘不够导致的这类报错。所以我们再来看看MySQL错误日志
```
156 2021-02-25T11:26:35.520327Z 0 [Note] InnoDB: Uncompressed page, stored checksum in field1 2147483648, calculated checksums for field1: crc32 1193184986/3495072576, innodb 846701958, none 3735928559, stored checksum in field2 2147483659, calculated checksums for field2: crc32 1193184986/3495072576, innodb 810726412, none 3735928559, page LSN 0 0, low 4 bytes of LSN at page end 3836608512, page number (if stored to page already) 327680, space id (if created with &gt;= MySQL-4.1.1 and stored already) 0
157 InnoDB: Page may be a freshly allocated page
158 2021-02-25T11:26:35.520373Z 0 [Note] InnoDB: It is also possible that your operating system has corrupted its own file cache and rebooting your computer removes the error. If the cor rupt page is an index page. You can also try to fix the corruption by dumping, dropping, and reimporting the corrupt table. You can use CHECK TABLE to scan your table for corruption. Please refer to http://dev.mysql.com/doc/refman/5.7/en/forcing-innodb-recovery.html for information about forcing recovery.
159 2021-02-25T11:26:35.520408Z 0 [ERROR] InnoDB: Space id and page no stored in the page, read in are [page id: space=779484, page number=3836674048], should be [page id: space=111, pag e number=150922]
160 2021-02-25T11:26:35.520415Z 0 [ERROR] InnoDB: Database page corruption on disk or a failed file read of page [page id: space=111, page number=150922]. You may have to recover from a backup.
161 2021-02-25T11:26:35.520420Z 0 [Note] InnoDB: Page dump in ascii and hex (16384 bytes):
.............
```
从上面的信息来看,应该是数据库的文件损坏了,从而导致了报错。
那我们就尝试一下直接在MySQL中对这个表进行操作会不会报错。结果得到如下信息
```
[Err] 1877 - Operation cannot be performed. The table 'mall.oms_order' is missing, corrupt or contains bad data.
```
这个错误日志提示说,操作系统的文件有可能出现了问题,建议重启操作系统。既然系统有建议,那咱就重启一下吧。
然而,世事无常,处处是坑,我们在重启操作系统时看到了这个界面:
<img src="https://static001.geekbang.org/resource/image/93/05/9356af3cdae223212b55e4e33b1e0305.png" alt="">
想哭对吧?现在居然连磁盘都找不着了……
回想刚才对磁盘的操作,我们在增加磁盘空间时,直接用的是这个命令:
```
qumu-img resize imagename +100G
```
我在查看了官方资料之后发现,这个命令虽然能扩容,但是要先关闭虚拟机,不然磁盘会出问题。而我们当时并没有关虚拟机。没办法,我们只有把虚拟机重建一下了。
经过一翻折腾,我们再次把数据库启动,等应用都正常连上之后,再次把稳定性场景运行起来,看到了这样的结果:
<img src="https://static001.geekbang.org/resource/image/b2/ff/b28a7b6c3e9948eaf03aefcdd2e4a7ff.png" alt="">
此时的业务累积量为:
<img src="https://static001.geekbang.org/resource/image/25/66/2580844435055e892f4c6e5db3ae4866.png" alt="">
从场景数据来看TPS有所下降这是因为我们在压力过程中所有的数据一直都是累加的。累加得越多数据库中的操作就会越慢我们看一下worker-1中数据库的资源使用量就可以知道
<img src="https://static001.geekbang.org/resource/image/b8/f7/b8b5d3331422990a63811c8b006204f7.png" alt="">
<img src="https://static001.geekbang.org/resource/image/25/97/255a568b72bb370fd48f2398b6ed2697.png" alt="">
你看这里面的系统负载和IO吞吐都因稳定性运行时间越来越长、数据量越来越多受到了影响。我们后续的优化思路也就非常明确了那就是保证当前表的数据量级像分库分表、定期归档这样的手段就可以上了这些操作都是常规操作。
在我们当前这个场景中,由于稳定性目标已经达到,就不再接着往下整了。毕竟,性能优化是没有止境的。
## 总结
正如我们前面所说,在稳定性场景中,我们要关注的就是“运行时长”和“业务累积量”这两个指标。只要达到了这两个指标,稳定性场景即可结束。
在这节课中,我们分析了长时间运行导致的两个问题:
- **磁盘不足的问题**
这样的问题在稳定性中也是经常出现的。因此,我们在稳定性场景运行之前,最好预估一下会用到多少磁盘。像日志之类的增长速度,一定要做好循环日志,不要保留太长时间。如果你想保留,那就移到其他地方去。
- **数据库文件损坏的问题**
这是扩展磁盘空间导致的问题。虽然在操作的过程中,磁盘看似成功扩展了,但由于操作的不当导致了数据库文件损坏。这类问题虽然是操作上的问题,但在操作的过程中我们却没有看到任何的错误信息和提醒。
另外,我们还需要充分考虑数据库的累积量。我建议你先通过计算来判断,当数据库的数据量增加后,会对应用产生什么样的影响。磁盘要能保证数据库当前表的数据量,以及数据增长过程中的查询性能。
在这里我无法穷举出稳定性中的所有问题只有通过实际案例给你一个分析的思路供你借鉴。在你的应用场景中你可以根据RESAR性能工程理论分析具体的问题。
总之,在性能工程中,稳定性场景对系统来说是一个严峻的考验。“合情合理地设计稳定性场景,并且做到符合生产业务场景”是我们必须要达到的目标。
## 课后作业
这就是今天的全部内容,最后给你留两个思考题吧:
1. 你在稳定性场景中遇到过什么由于业务不断累积导致的问题?
1. 在稳定性场景中,如何保证磁盘使用量不会随场景持续增加,而是保持在一个使用量级?
记得在留言区和我讨论、交流你的想法,每一次思考都会让你更进一步。
如果这节课让你有所收获,也欢迎你分享给你的朋友,共同学习进步。我们下一讲再见!

View File

@@ -0,0 +1,159 @@
<audio id="audio" title="28 | 如何确定异常场景的范围和设计逻辑?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/19/9e/194f940c3998bc366327e07c5b41199e.mp3"></audio>
你好,我是高楼。
在性能的领域中,异常场景一直都处在薄弱的环节,大家都觉得异常场景应该做,但是又不知道怎么做才能把异常问题覆盖全面。
异常范围之所以比较难确定,是因为有很多问题都被归纳到了“异常”中,比如说高可用、可靠性、灾备等等。当然,也有些企业把这部分归纳到非功能中来做,因此在性能的项目中就不会有异常场景了。
在我的RESAR性能工程理论中异常场景是必须要做的这主要是因为异常场景需要压力背景。
既然要做异常场景,我们具体该怎么做?测试哪些问题才能将异常场景覆盖完整?这就需要我们明确两个关键点:一是异常场景的范围,二是异常场景的设计逻辑。
因此,在这节课中,我们就来看看如何确定异常场景的范围和设计逻辑。
## 异常场景的范围
在以前的异常场景中,基本上采用的是宕主机、断网络、宕应用这几种测试手段。此外,从主机、网络、应用等视角出发,还会有一些细分操作,比如说:
- 主机断电、reboot、shutdown等
- 网络ifdown命令关网卡、模拟抖动丢包延时重传等
- 应用kill、stop等。
上述这些操作在当前新的技术架构中仍然有效,只不过现在会有更多的细分操作。因为现在微服务的应用多了之后,又多出了几层,比如虚拟机层、容器层、网关层等等。我在这里画一张图,大概罗列一下异常场景测试的不同角度:
<img src="https://static001.geekbang.org/resource/image/b4/e9/b4901e3b49f18e0401ed6ef0cd48d0e9.png" alt="" title="异常场景范围图">
关于做异常场景的范围和时机,有两个话题也一直在争论:
- 异常场景到底应不应该放到性能项目中完成?
- 异常场景到底包括什么样的内容?
对于第一个问题,我是这样考虑的:不管是代码逻辑验证、功能验证、还是性能验证,我们只要模拟出真实的异常场景,都会有异常场景的细分。在当前的测试市场中,有很多企业也确实这样做了,这是一个好现象。而这些异常场景需要在有压力背景的前提下进行,所以它应该放到性能项目中来完成。
因为如果把这一类场景放在其他阶段完成,像脚本、参数、监控等这些工作都要重复做。如果还需要不同的团队共同完成,那成本显然会增加。
对于第二个问题,你可能会感到奇怪,上面那张图不是已经把异常场景包含的内容全都列出来了吗?这里怎么还要提呢?这主要是因为在技术市场中,有很多不同的声音和视角,一些人觉得在异常场景中也还应该包含高可用、可靠性、可扩展、可伸缩、稳定性等内容。
其实,对于这些技术名词,我们很多时候都似懂非懂,感觉自己知道是怎么一回事,但是又抓不住重点。就拿可靠性为例,可靠性在实施的过程中,我们能想到的就是一个系统在一定的时间和条件下无故障地运行。可是,既然如此,那“稳定性”又是什么呢?我们知道,稳定性是指在规定的一定长的时间内系统无故障运行。
咦,怎么看起来意思差不多?“可靠性”和“稳定性”到底有啥区别呢?在我看来,稳定性包括在了可靠性之内。
我这么一说,你应该就明白了,在当前技术市场中,虽然有很多人提出了不同的视角,但是,如果我们把这些视角对应的落地步骤罗列一下,你就会发现,它们都能落在我刚才讲的这张图里。
<img src="https://static001.geekbang.org/resource/image/b4/e9/b4901e3b49f18e0401ed6ef0cd48d0e9.png" alt="" title="异常场景范围图">
因此,请你记住,在异常场景中,我们只要包含图中的这些内容就足够了。
## 异常场景的设计逻辑
从逻辑上来说,异常场景的设计主要分为两步:
<li>
分析架构:把技术架构中的组件全部列出来,并分析可能产生异常的点。
</li>
<li>
列出异常场景:根据分析的异常点设计对应的场景。
</li>
这样的逻辑看起来并不复杂,如果我们只从组件级来考虑,那就可以设计通用的异常场景了。但是,如果从业务逻辑异常的视角来看,就没有通用的异常场景了,我们需要针对不同业务设计不同的异常场景。
不过,在性能领域中,大部分人对异常场景没有什么设计套路,都是跟着感觉走的,而且即便是遵循上述这两个步骤设计异常场景,也必然会涉及到一个问题:异常场景覆盖得全不全?
对于这个问题,**我建议你在异常场景的设计逻辑中参考FMEA失效模型的逻辑因为FMEA至少是一套有逻辑的设计思路可以让我们有章可循**。
FMEA在性能行业中使用率并不高大家对它基本上处于不明就理的状态。在我深入了解了FMEA之后觉得它作为一套分析失效模型的方法策略可以被应用在性能项目的异常场景设计中。因此如何把FMEA借鉴到异常场景中是我们接下来要讨论的一个问题。
我在这里先给你简单介绍一下FMEA。
FMEA是一套做事的逻辑它最初被用于战斗机的操作系统设计分析中后来又被广泛应用于航天、汽车、医疗、微电子等领域。
FMEA是Failure Mode&amp;Eeffect Criticality Analysis的缩写中文含义是潜在失效模型或影响的严重等级分析它又分为DFMEA、PFMEA和FMEA-MSR
- DFMEA也就是Design FMEA是针对设计阶段的潜在失效模式分析
- PFMEA是指Process FMEA它针对的是过程
- FMEA-MSR是“FMEA for Monitoring and System Response”的缩写也就是“监控和系统响应”的意思它通过分析诊断监控和系统响应MSR来维护功能安全。
FMEA的这三个细分采用的逻辑是一致的只是针对的阶段和关键点不同而已。
如今在IT技术圈中也不乏有人尝试在软件系统中落地。在FMEA中最重要的就是下面这样的表格你在网上也经常能看到。
<img src="https://static001.geekbang.org/resource/image/f9/13/f9c9e4d909f5dd017c6d13a488b23e13.png" alt="">
我解释一下表格中的“RPN”它是Risk Priority Number的缩写意思是风险系数、风险优先级。RPN是“严重度 S”、“频度 O”、“探测度 D”三个的乘积。至于表格中其他的名词你看了字就能大概理解我在这里就不多啰嗦了。
看到这样的表格你是不是觉得它很难在IT架构中使用其实在异常场景具体落地的时候我们可以根据自己的理解把表格做一些变化
<img src="https://static001.geekbang.org/resource/image/b1/71/b10c6861bb4a180eaff260e5f70bd171.png" alt="">
我在表格中加了一个“系统”列,这是因为一些项目有多个系统。当然,你也可以不加这一列,把整个表格命名为某系统的表格。至于其他名词,我只是做了相应的调整,并没有改变原有的表结构。
在我们填写这张表格之前有一点我要说明一下。在FMEA中严重度、频率、探测度需要各自分级并且都分为110这10个等级。下面我大概列一下这三个方面不同级别的含义。请你注意我只是描述相对通用的内容尽量不和业务挂勾。
<li>
<p>严重度S<br>
<img src="https://static001.geekbang.org/resource/image/a3/9f/a3b259ceba5acc61f0185da54c5fbc9f.jpg" alt=""></p>
</li>
<li>
<p>频率O<br>
<img src="https://static001.geekbang.org/resource/image/3f/72/3fa7449eb8ccbf36147fa402fe809872.jpg" alt=""></p>
</li>
<li>
<p>测试度D<br>
<img src="https://static001.geekbang.org/resource/image/28/12/282b209915ec9926211f72b317a3da12.jpg" alt=""></p>
</li>
对于你自己的系统,不一定要完全照搬上面表格中对等级标准的划分,不过,逻辑还是可以借鉴的。
现在,我给你举一个异常用例,来看看严重度、频率和探测度这三个角度具体是怎么落地的:
<img src="https://static001.geekbang.org/resource/image/bc/41/bc49767a06a89d55a64f642b26b60241.png" alt="">
对应着这张表格,你应该知道怎么列出你自己的异常场景了。
请你注意即便你想用FMEA来设计异常场景我在这节课一开始画的那张图仍然是不可或缺的因为那是这张表格的输入条件之一。也就是说**在填写这张表格之前,我们一定要清楚在异常场景中测试哪些内容**,这一点非常重要。
不过有了这张表格以及严重度、频率和探测度的10个等级之后异常场景一下子就变得复杂了。因为PRN有太多的可能性具体算下来的话应该有10X10X10=1000种PRN值。
如果我们把系统中的全部异常场景都列出来那就得按PRN的值从上到下挨个执行了。假设PRN为1000的场景有10个PRN为9001000的场景有20个........这样一个一个数下去,都要吐了对不对?
记得之前我跟一个IT经验非常丰富的朋友聊天我们说到写异常用例这个话题。他说如果让他来设计异常用例针对一个系统设计出上万甚至更多的用例基本不在话下。然后我说“那你设计的这些异常用例在生产上出现的概率是多少呢如果系统运行到寿终正寝都没出现这些情况那要这些用例有什么用呢
通过这段对话,你可以思考一个问题:我们是不是非得把自己系统的异常场景弄这么复杂呢?
当然不是其实我们可以做简化比如说把等级减少。我们在前面讲到在FMEA中严重度、频率和探测度分别都定义了10级那对于系统的异常场景我们定义三四个等级就可以了。如果你要较真觉得三四个等级不合适的话那你可以根据自己系统的情况来用这个逻辑具体怎么用就要看你系统的重要性有多高了。
总体来说FMEA是一套非常完整的逻辑它的第四版白皮书就有130多页你要是有兴趣可以去看看。
其实在FMEA落地到异常场景测试的过程中套用FMEA并不复杂复杂的是如何制定S、O、D。因为在具体制定的时候并不像我在前面列出三个表格那么简单它需要拿系统的逻辑来进行详细分析。
接下来我就得摆摆观点了,请你记住,**对任何一套方法论逻辑的落地实施,都不要过度使用,而要注意合理使用。**从我接触过的老外的思路和逻辑来看他们很喜欢弄一些RESEARCH方面的功能并延伸出一套理念然后拿着这套理念就可以忽悠一辈子。
记得在我带过的团队中有一个老外是一个年轻小伙他一直在做缺陷管理员的工作也就是天天去追Bug的修复进度。有一天他找我说想离职我问他“那你想干什么呢”他说“我想做RESEARCH”。我继续问“那你想研究个啥呢”他回答说“我还没想好但是我想做RESEARCH。”他说的时候似乎觉得RESEARCH是一个挺高端的事业。我微微一笑说“行那你去吧。”
我讲这一段不是说FMEA也是没有经过深思熟虑的方法论而是想说**我们在看待外来的理念时,一定要保持冷静,至于哪里的月亮更圆更亮,取决于时间。**对于FMEA在性能中的应用我们同样也要理智地使用。同时我们也应该有自己完整的思维能力。
因此我建议你在异常场景设计时可以参考FMEA中的逻辑把不适用的部分给清理掉设计出符合自己系统的失效模型。而我们这节课的描述也只是给你一个思路因为授人以鱼不如授人以渔才是我的初衷。
上述内容就是我针对异常场景的设计所做的尽可能全面的描述了。请注意其关键点不是FMEA而是上文中的异常场景范围图。
可是,叭叭地讲了这么多,如果不落地,实在不符合我的风格。所以,我们还是要有具体的操作实例的。在下节课中,我们就按照这节课一开始画的视角,做几个实际的案例来看一看,异常场景具体该怎么执行。
## 总结
针对异常场景,在性能行业中各有各的看法,并且谁都说服不了谁,这就导致每个企业做的异常场景范围都不一样。同时,行业中又有很多关于混沌测试、非功能测试的不同说法。因此,异常场景一直都没有在性能项目中固定下来。
而**在RESAR性能工程理念中<strong><strong>对于**</strong>有压力背景的异常场景**<strong>来说**</strong>,我觉得**<strong>由**</strong>性能人员**<strong>来完成它,是**</strong>责无旁贷的。</strong>
通过这节课,我想告诉你的就是异常场景的范围应该有多大,以及设计的逻辑应该是怎样的。有了这些内容之后,异常场景的覆盖率就会足够全,并且也有章可循了。
## 课后作业
最后,我给你留两道题,请你思考一下:
1. 你做过哪些异常场景设计?说说你的设计思路。
1. 你遇到过什么样的异常问题,请举例说明一下。
记得在留言区和我讨论、交流你的想法,每一次思考都会让你更进一步。
如果你读完这篇文章有所收获,也欢迎你分享给你的朋友,共同学习进步。我们下一讲再见!