赞
踩
上一讲我们学习了性能测试的场景,并且明确了每个场景的核心意义,这一讲我将带你学习如何做好一份性能测试方案,相信你对测试方案这个概念并不陌生,那如何做好一份性能测试方案呢?这个方案能解决什么问题呢?这一讲我们来一起探索。
性能测试方案,通俗一点说就是指导你进行性能测试的文档,包含测试目的、测试方法、测试场景、环境配置、测试排期、测试资源、风险分析等内容。一份详细的性能测试方案可以帮助项目成员明确测试计划和手段,更好地控制测试流程。
为测试活动做计划,每项测试活动的对象、范围、方法、进度和预期结果会更透明化。
制定出有效的性能测试模型,能够排查出性能问题,从而更符合真实场景。
确定测试所需要的人力、时间和资源,以保证其可获得性、有效性。
预估和消除性能测试活动存在的风险,降低由不可能消除的风险所带来的损失。
性能测试方案是在你正式进行性能测试之前的工作,通过前几讲的学习你已经知道了性能方案中的必备内容。
性能测试目的是你做一次测试首先要考虑的内容。是要完成既定的指标,还是验证超卖问题,抑或是验证并发下的稳定性问题。如果是验证指标,你的指标如何制定,拿到业务访问数据如何转化成为性能测试模型,在《07 | 你真的知道如何制定性能测试的目标吗?》中已经说得比较详细了。
对于性能测试有哪些场景,每种场景的目的是什么,《08 | 性能测试场景的分类和意义》已经给了你答案,你需要根据性能测试的目的进行场景的设计。
那除了这些,性能测试方案还需要包含哪些内容呢?
被测的业务部署架构是什么意思呢,简单来说就是被测服务涉及哪些组件,每个组件部署在哪些服务器上,服务器的配置是怎样的。你需要画一个部署架构示意图,有了这张图,才能知道如何做到全貌监控,以及遇到问题从哪些服务入手。
我用一个自己画的架构示意图来说明这个问题,如下图所示,这是一个经典的链路:从客户端发起到服务端,服务端从代理层到应用层,最后到数据层。需要注意的是,你需要明确被测的环境里的各个服务有多少节点,比如客户层的压测机节点有几台,分别在哪个网段。同理我们可以去调研服务层和数据层的节点。
关于测试数据调研,包含了非常多的内容,对于业务测试来说数据调研就是获取必要的参数来满足既定的场景可以跑通。那对于性能测试来说,需要做哪些方面的数据调研呢,我带你一一解读。
(1)数据库基础数据量分析
数据库的基础数据量就是目前线上数据库实际的数据量,为什么要统计基础数据量呢?很多公司往往有独立的性能测试环境,但是数据库的数据量与线上相比差距较大,可能出现一条 SQL 在性能测试环境执行很快,但上了生产却会很慢的问题。这就导致测试觉得该测的都测了,但上了生产还是会有问题出现。
这种问题可能会因为索引缺失以及性能环境数据量较少而不能将问题暴露出来,所以在性能测试环境下的数据量一定要和生产上一致。为了达到这个目的,有的公司可以将生产数据脱敏后备份,有的则需要你自己写脚本来根据业务规则批量造数据。
(2)压测增量数据分析
除了数据库的基础数据量,我们也需要考虑一轮性能测试下来会增加多少数据量。往往增加的数据量最终落到数据库,可能会经过各种中间件如 Redis、Mq 等,所以涉及的链路可能存在数据量的激增,所以这方面需要根据增加情况制定相应的兜底方案。
(3)参数化的数据分析
关于参数化,我相信你已经通过《02 | JMeter 参数化策略》有了深入的了解。在这里,我还想抛出一道思考题,如何参数化订单号,你可以分别从读写接口两个层面写出你的思考或者实践。
(4)冷热数据的分析
以我的从业经历来讲,能够在方案阶段考虑到冷热数据分布的公司并不多,往往都是从性能测试结果的一些异常点或者实际产线出现的问题去追溯。接下来我就带你了解下什么是冷热数据,以及如果不对其进行分析可能会带来什么影响。
冷数据是指没有经常被访问的数据,通常情况下将其存放到数据库中,读写效率相对较低。
热数据是经常被用户访问的数据,一般会放在缓存中。
在性能测试的过程中,被频繁访问的冷数据会转变为热数据。如果参数化数据量比较少,持续压测会让 TPS 越来越高。而在实际大促情况下,往往有千万级的用户直接访问,但大多都是冷数据,会存在处理能力还没达到压测结果的指标,系统就出现问题的情况。所以在需求调研时,你也需要考虑数据会不会被缓存,缓存时间多久的问题。
对于性能测试而言,业务规则的了解也是不可或缺的。一些公司的性能测试组在进行压测时,业务线的测试也需要协助支持压测的进行,由此可以体现业务的重要性。
对业务的充分了解不仅可以帮助你提高写脚本的效率,也可以帮助你构造更为真实的性能测试场景。举个简单的例子,你模拟下单的时候是否考虑商品属性,比如是单一商品还是套餐商品,下单的时候购物车里有几件商品,这些都会影响性能测试的结果。
监控是你做性能测试的重点内容之一,一旦出现问题,第一反应就是查监控,关于监控管理建设我在《01 | JMeter 的核心概念》中也有所陈述。对于性能测试方案,不仅需要罗列清楚你所需要的监控工具和访问方式,同时也需要层次分明地传递你监控的内容。对我来说做监控最基本的一个关键词:全。
怎么去理解“全”呢?先举一个典型的例子,有时候做一个新的项目,询问支持的同学有没有部署监控,他们说已经部署了,但等你真正使用的时候发现只监控了一台应用服务器的 CPU。这个例子我相信大多数人都似曾相识,所以我说的全,至少包含两个方面:
涉及所有服务器;
涉及服务器基础监控,包括 CPU、磁盘、内存、网络等。
硬件资源的监控只能算一个层面。那完成一次性能测试都需要监控什么呢,我用一个导图给你做一个概览。
监控还有个很重要的点是设置阈值来报警,无论是线上和线下的性能测试,报警功能都是必需的。因为通过人工的观察,往往不能以最快的速度发现问题。一旦能够及时报警,涉及的人员就可以快速响应,尽可能降低风险。
一般来说测试是上线前的最后一道关卡,也是发现问题的重要角色,所以项目上的风险会在测试阶段集中爆发。性能测试作为测试中的一部分,也会面临类似问题,这也考验你的项目管理能力。而且性能测试需要大量的数据和专门的环境,这部分的工作内容和资源需要更多支持,所以在你的性能测试方案中,首先要标明开展的阶段和日期,还要明确主负责人和协调人员。在此基础上还需要面对面 check 和落实。
你可以参考如下的表格,具体的内容需要根据公司的情况来确定。这些任务并不是从上到下依次执行,可能存在并行的情况,比如某一些公司环境是由运维人员统一部署,这部分内容就可以和性能测试需求分析一起进行。
关于如何打造性能测试方案就讲到这里了,通过本讲的学习,你已经了解了做一份性能测试方案的基本要素和关键点。性能测试方案对于一些公司来说可能只是一份流程化的文档,但对于测试个人来说,这部分内容可以体现出你的思考和计划。尤其对于性能测试新手来说,一定要充分思考每项的意义,这样你才能快速提升。
在参数化的数据分析部分,我抛出的一个思考题,你可以着重考虑下,有任何问题都可以给我留言。
接下来我将带你进入专栏的下半阶段,下一讲我们一起来进行监控部署的实操。
前面两个模块带你学习了如何使用 JMeter 工具,如何做好一份性能测试方案,第三模块我将带你进行监控的学习。在你执行性能测试的过程中,监控服务端的资源消耗等也是必备内容,监控的结果是帮助你发现问题的眼睛。然而在实操过程中发现很多同学喜欢用JMeter 工具提供的插件进行监控,但是我并不推荐你使用这种方式,原因如下:
指标相对简单且固定,结果数据粗糙且界面显示并不是很友好;
较大地增加了客户端压测机的资源开销,影响性能测试结果;
特定环境下,在服务器上安装插件是不被允许的,会很不方便。
所以这一讲我想带你了解下监控的内容有哪些?既然不推荐使用 JMeter 自带的监控方式,那我是如何做监控的呢?
本讲作为监控模块的第一篇,我想先聊一聊如何能够把监控这件事情做好,正所谓“磨刀不误砍柴工”,监控绝不是简单地敲几个命令,做几个图表就可以的,你需要从多角度来理解这件事情。首先我认为把监控做好需要有以下三个关键词:层次清晰、全面覆盖、定向深入。我来解释下这三个关键词代表的含义。
从执行一次性能测试来看,你需要监控的内容有很多,重点是要能理清楚不同的监控类型,以及分别能够解决什么问题?我从下面几个层次做下介绍,从而让你对各层面的监控做一个初步了解,也为后面的章节做一些铺垫。
硬件层是最容易想到的一个层面,一般包含了 CPU 的使用率、内存使用率、磁盘和网络读写速度等,通过这些指标能够反馈出系统运行的基本情况,以及不同的 TPS 量级会消耗多少硬件资源。
系统层监控包括连接请求数、拒绝数、丢包率、请求超时等,相对于基础的硬件监控而言,这些指标更能够反映出目前系统存在的瓶颈,从而为根因问题的定位提供有力的线索。
在我看来,链路层是直接面向架构和代码的,它的监控能够帮助你更加准确地看到代码执行了哪些函数,涉及哪些服务,并且能够较为清晰地看到函数之间的调用耗时,还可以帮助你定位代码存在的问题。
业务层监控本意是帮助你判断用户输入是否合规,代码逻辑是否健壮。对于性能测试而言,业务层的监控可以帮助你发现脚本参数问题以及高并发下业务逻辑运行是否正常等,比如随着测试的进行,可能会存在商品库存不足的情况。如果有业务层面的监控,当库存低于某阈值时,可以进行一定的提示以规避此类问题。
如果你能够完整地画出应用的部署架构图(参考第 09 讲的部署架构图),并且能够按照我说的几个层次将其完整地部署落地,我想监控这件事情至少可以给你打到 85 分,剩下来的 15 分在哪里呢?我认为除了应用层的监控,你还需要考虑底层链路的监控,比如防火墙、F5 负载均衡等,这些往往是一下子考虑不到的事情。
在我的实际工作中,尤其是新项目监控部署经常存在“缺斤少两”的情况。虽然在测试之前做了系统监控,但出现问题后仔细分析时,经常发现某一些机器并没有被监控到,或者监控了 CPU 又发现磁盘没有被监控上。这些问题主要是考验你的组织能力,也反映了团队是否能在性能测试上更细致更深入,毕竟性能的分析是不能放过任何“蛛丝马迹”的。
首先通过基本的监控可以获得一些异常点,比如 CPU 高了、磁盘在等待,这些说白了是表象问题。就比如说某位同学今天发烧了,通过发烧这个现象并不能直接下定论说他感冒了,医生也需要做进一步的化验分析才可以下结论。对于监控也是这样,是否有定位根因问题的手段,CPU 高了,需不需要进行线程分析,需要哪些权限和定位工具,这些在监控部署时都需要考虑到。
下面我从监控硬件资源开始,通过使用 Linux 命令行对服务器进行监控,为什么我要讲解 Linux 命令的监控呢?我认为它具有灵活迅速的特点,通过命令可以最快地输出对应结果。接下来我会分别从 CPU、内存、磁盘、网络维度既快又能直击要害地帮你分析硬件瓶颈。
top 是我们查看各个进程的资源占用状况最常用的命令,如下代码所示,这个命令简单却包含很大的信息量,接下来我选一些常用的内容给你重点解释。
top - 18:17:47 up 158 days, 9:32, 2 users,
load average: 0.07, 0.15, 0.21
Tasks: 154 total, 1 running, 152 sleeping, 0 stopped, 1 zombie
%Cpu(s): 3.9 us, 1.3 sy, 0.0 ni, 94.6 id, 0.2 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8010676 total, 337308 free, 6036100 used, 1637268 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 1223072 avail Mem
以下省略
- 1
- 2
- 3
- 4
- 5
- 6
- 7
关于这一内容的代码如下所示:
load average: 0.07, 0.15, 0.21
- 1
三个数字都是代表进程队列的长度,从左到右分别表示一分钟、 五分钟和十五分钟的数据,数字越小压力值就越低,数字越大则压力越高,然而这个数值多小算小呢?多大算大呢?
以单核处理器为例,打个比方就像收费站的一个 ETC 通道一样:
0 表示没有任何车辆需要通过;
从 0 到 1 可以认为很流畅,车辆不需要任何等待就可以通过;
1 表示正好在这个通道可接受范围之内;
超过 1 就已经有车辆在后面排队了。
所以理想情况下,希望平均负载值在 1 以下。如果是 1 就代表目前没有可用资源了。在实际情况中,很多运维同学会把理想负载设置在 0.7 以下,这也是业内的一个“经验值”。
刚刚说的是一个单核处理器的情况,多核 CPU 的话,负载数值 / CPU 核数在 0.00~1.00 之间表示正常,理想值也是在 0.7 以内。
从 top 中你也可以看到每种类型进程消耗的 CPU 时间百分比,如下所示:
%Cpu(s): 3.9 us, 1.3 sy, 0.0 ni, 94.6 id, 0.2 wa, 0.0 hi, 0.0 si, 0.0 st
- 1
首先来看代码中的一些重要信息。
us 列显示了用户进程所花费 CPU 时间的百分比。这个数值越高,说明用户进程消耗的 CPU 时间越多,可以用来分析代码中的 CPU 消耗热点。
sy 列表示系统进程消耗的 CPU 时间百分比。
ni 列表示改变优先级的进程占用 CPU 的百分比。
id 列表示 CPU 处于空闲状态的时间百分比。
wa 列显示了 I/O 等待所占用的 CPU 时间的百分比,这里 wa 的参考值为 0.5,如果长期高于这个参考值,需要注意是否存在磁盘瓶颈。
hi 列表示硬件中断占用 CPU 时间百分比。
si 列表示软件中断占用 CPU 时间百分比。
st 列表示当系统运行在虚拟机中时,当前虚拟机在等待 CPU 为它服务的时间。
在已经输入 top 的情况下再输入数字 1,可以查看 CPU 的核数和每个核的运行状态。
如下图是两核 CPU 的运行状态。
%Cpu0 : 3.0 us, 1.7 sy, 0.0 ni, 95.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 2.4 us, 1.0 sy, 0.0 ni, 96.6 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
- 1
- 2
值得注意的是,很多同学看 CPU 的使用率时,只看 us 这个数值,通过上面的讲解,可以看出这是不准确的。除了用户进程,还有其他系统进程会占用 CPU,所以实际 CPU 的使用率可以用 100 减去空闲值(id)去计算。
最常见的是通过 free 来查看 Linux 内存使用情况。
[root@JD ~]# free -m
total used free shared buff/cache available
Mem: 7822 5917 302 373 1602 1195
Swap: 0 0 0
- 1
- 2
- 3
- 4
相信通过单词的意思我们也能大概看出来 total、used、free 表示什么,它们分别是总的物理内存大小、已经被使用的物理内存和空闲的物理内存值是多少。
曾经有同学问我,为什么 free 值很低却未必代表内存达到瓶颈呢?
这和 Linux 内核机制有关系,简单来说,内存空间会开辟 buffer 和 cache 缓冲区,对于物理内存来说,这都属于被使用过的内存。而应用需要内存时,如果没有可用的 free 内存,内核就会从缓冲区回收内存以满足要求,当 free 值很低的时候,如上代码中的 available 就能体现出缓冲区可用内存的大小,这个指标可以比较真实地反映出内存是否达到使用上限。
这一部分我们来讲两个重要的命令。
[root@JD ~]# iostat -x
Linux 3.10.0-514.el7.x86_64 (JD) 01/18/2021 _x86_64_ (2 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
5.24 0.00 1.57 0.07 0.00 93.12
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.29 0.57 5.30 20.50 630.14 221.82 0.07 11.53 59.83 6.36 1.18 0.69
- 1
- 2
- 3
- 4
- 5
- 6
通过这个命令你能看到磁盘实时运行的情况,一般可以优先看 idle、util 和 svctm 这几列的数值:
idle 代表磁盘空闲百分比;
util 接近 100%,表示磁盘产生的 I/O 请求太多,I/O 系统已经满负荷在工作,该磁盘可能存在瓶颈;
svctm 代表平均每次设备 I/O 操作的服务时间 (毫秒)。
在我的经验中,会组合看这些指标,如果 idle 长期在 50% 以下,util 值在 50% 以上以及 svctm 高于 10ms,说明磁盘可能存在一定的问题。接着我会定位到具体是哪个进程造成的磁盘瓶颈,下面我就为你介绍一个关于定位的命令。
iotop 这个命令并不是 linux 原生的,需要安装,以 CentOS 7.0 为例:
[root@JD ~]# yum -y install iotop
- 1
安装完成之后,直接输入 iotop,示意如下,你就能清楚地看到哪些进程在消耗磁盘资源。
6448 be/4 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % ifrit-agent
14647 be/4 root 0.00 B/s 7.70 K/s 0.00 % 0.00 % java -Dserver.port=9080
- 1
- 2
netstat 能提供 TCP 和 UDP 的连接状态等统计信息,可以简单判断网络是否存在堵塞。
[root@JD ~]# netstat
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 1 JD:49190 169.254.169.250:http FIN_WAIT1
tcp 0 0 JD:39444 169.254.169.254:http TIME_WAIT
tcp 0 0 JD:us-srv worker-18.:sentinel-ent ESTABLISHED
- 1
- 2
- 3
- 4
- 5
- 6
**Proto:**协议名(可以 TCP 协议或者 UDP 协议)。
recv-Q:网络接收队列还有多少请求在排队。
send-Q:网络发送队列有多少请求在排队。
recv-Q 和 send-Q 如果长期不为 0,很可能存在网络拥堵,这个是判断网络瓶颈的重要依据。
Foreign Address:与本机端口通信的外部 socket。
State:TCP 的连接状态。
通过本讲的学习,你已经知道了如何通过命令行监控 Linux 资源,包括 CPU、磁盘、内存、网络,也知道了判断硬件瓶颈的一些策略。
这里抛出一个思考题,在工作过程中如果发现硬件资源异常,你会怎么做呢?欢迎在评论区给出你的思考或者实践。
下一讲将带你学习如何进行链路监控以及常见的报警机制。
上一讲我们主要讲解了硬件的命令行资源监控,相信你已经学会了通过命令行的方式查看硬件瓶颈。
那我提一个问题,为什么会有硬件瓶颈呢?或者我说得更直白一点,如果服务器上没有应用还会造成硬件瓶颈吗?显然是不会的,所以我想向你传递一个观点:呈现出来的硬件瓶颈绝大多数是表象问题,我们往往需要在系统应用上寻找问题的根因。而寻找系统问题的根因,对于系统链路监控也是必不可少的,所以这一讲我将带你学习如何进行基于系统链路的监控。
随着微服务的流行,链路监控越来越受重视。微服务架构是根据业务进行拆分,对外统一暴露API 接口,而内部可能是分布式服务、分布式对象存储等,如图 1 所示。
图 1:微服务架构
这些组件共同构成了复杂的分布式网络。而分布式系统一旦出现问题,比如一个请求经过多个微服务之后出现了调用失败的问题,或者一个请求经过多个微服务之后 Response 时间过长,但具体是哪个微服务节点的问题我们并不知道。只能去服务器上查看调用经过的每个微服务的日志,当然这种方式的效率是比较低的,相当于人肉运维。
随着业务体系越来越复杂,加上服务间的相互依赖关系,微服务其中一个节点出现了问题,很可能牵一发而动全身,导致严重的后果。在这样的情况下,分布式链路监控的价值就体现出来了,它可以让你清晰地知道跨服务调用的链路耗时信息、执行方法等,并从整体到局部将信息呈现出来,可以帮助你节约故障排查时间。
全链路监控系统有很多,可以从这几方面选择:
探针的性能消耗,探针是搜集信息的“情报员”,尤其是在多节点情况下,搜集数据的成本会越来越高,监控组件服务的影响应该做到足够小、数据分析快、性能占用小;
对代码的非侵入性,减少开发的维护成本;
监控、分析的维度尽可能多。
目前市面上的全链路监控工具很多,比如 CAT、SkyWalking、Pinpoint 等,对于工具的选型来说最重要的是采样数据对系统的性能消耗足够小、数据分析和展示快、监控的维度尽可能丰富,简单比较下这几个工具。
CAT:是由美团和携程的同学开发的,通过代码埋点的侵入式方式,对应用日志分析、监控、展示等,不过侵入式的方式会带来开发以及维护成本的增加。
SkyWalking:也是由国人开发,目前项目已经提交到 Apache 孵化组织,无侵入性、UI 展示简洁清晰。
Pinpoint:由韩国人开发,相对于 SkyWalkingg 提供了更为详尽的链路监控信息,不过数据采集带来的性能损耗相对于 SkyWalking 来说比较大。
综上我将以 SkyWalking 为例给你介绍下链路监控,希望通过介绍,你可以掌握 SkyWalking 的具体使用步骤和链路监控工具可以给我们带来什么好处,通过本讲的学习你也可以自由选择链路监控工具去实践。
首先来看下 SkyWalking 的组件示意图:
图 2:SkyWalking 的组件示意图
Tracing 和 Metric : 在应用上采集 Tracing(调用链数据)和 Metric(指标)信息通过 HTTP 或者 gRPC 方式发送数据到 Analysis Platform。
Analysis Platform:数据的采集和计算,将传输的 Tracing 和 Metric 数据进行整合分析,通过 Analysis Core 模块把数据写入相关的数据库中。
Storage:SkyWalking 的存储,支持以 ElasticSearch、MySQL、TiDB 等数据库进行数据存储,其中 ElasticSearch、MySQL 用的居多。
SkyWalking UI:Web 可视化平台,用来展示落地的数据以及图表,比如链路调用、服务结构等。
首先下载 SkyWalking 安装包并进行解压:
wget https://github.com/apache/SkyWalking/archive/v8.0.1.tar.gz
tar -zxvf v8.0.1.tar.gz
- 1
- 2
解压后可以看到如下文件夹:
我们讲解下这个主要文件的作用。
(1)修改配置文件 config/application.yml。在这里先进行数据库的配置,我使用当前服务器上的 mysql 来进行存储:
mysql:
properties:
jdbcUrl: ${SW_JDBC_URL:"jdbc:mysql://127.0.0.1:3306/swtest"}
dataSource.user: ${SW_DATA_SOURCE_USER:root}
dataSource.password: ${SW_DATA_SOURCE_PASSWORD:123456}
- 1
- 2
- 3
- 4
- 5
将上述的配置文件根据自己的数据库实际地址修改,修改完成后进行启动:
$ bin/oapService.sh
SkyWalking OAP started successfully!
- 1
- 2
(2)接着来看 SkyWalking UI 的相关配置,由于 SkyWalking UI 的默认端口是 8080,这个端口是很多应用的默认端口,容易产生冲突,你可以修改一下,如下所示:
# 修改webapp/webapp.yml
server:
port: 18080
- 1
- 2
- 3
然后启动 SkyWalking UI 服务,启动完成后你会看到如下信息:
$ bin/webappService.sh
SkyWalking Web Application started successfully!
- 1
- 2
这里我强烈建议,不管是第一步还是第二步中的 started successfully,都并不意味着真正的启动成功,一般在提示 started successfully 后,还需要去 logs 文件夹下查看相关日志来判断启动过程中是否存在异常。
UI 界面启动成功后示意图如下:
(3)本地启动微服务。我 demo 里包含 system、auth、user 等服务,通过配置 SkyWalking Agent 的方式启动服务,示意如下:
nohup java -server -Xms256m -Xmx256m -Dspring.profiles.active=dev -Dspring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 -javaagent:/root/apm/apache-SkyWalking-apm-bin/agent/SkyWalking-agent.jar=agent.service_name=cctuser -Dspring.cloud.nacos.config.server-addr=127.0.0.1:8848 -jar blade-user.jar > log.file 2>&1 &
- 1
-javaagent 后的启动参数是 SkyWalking 的 agent 配置路径。
启动本地的微服务成功后,就可以访问服务,同时通过 SkyWalking 监控你可以看到服务部署图以及链路监控等,如下图所示:
图 3:服务部署图
图 4:链路追踪图
在我们进行链路追踪后,可能会出现一些超时、访问错误等异常,那我们如何能够更快地收到这些异常信息呢?
首先很多人想到了报警机制,那我带你了解下常见的几种报警方式。
(1)短信或者电话报警
这样的报警方式更适合高级别的报警提醒,用于处理紧急情况。出现级别不高而又频繁地发送短信会让人产生排斥感,而且电话或者短信的报警方式也存在一定的成本。
(2)邮件报警
邮件报警更适用于工作时的提醒,但是系统往往是不能区分你是不是在工作,有时候夜间的报警邮件你很难及时关注到,所以说邮件报警也存在一定的局限性。
(3)钉钉报警
随着钉钉越来越普及,很多公司都已经使用钉钉。员工在公司需要使用钉钉管理自己的考勤以及进行工作上的沟通,如果将监控报警信息推送到钉钉上其实就很方便的。不过也存在有的企业用的是其他沟通工具,不过对于报警推送到沟通软件上的原理都是类似的,接下来我会以钉钉作为模版来讲解如何进行报警信息的推送。
(1)打开机器人管理页面。以 PC 端为例,打开 PC 端钉钉,进入首页面点击头像,在弹出框里选择机器人管理,打开管理页面后可以选择自定义,如下图所示:
(2)在打开的机器人详情页面点击添加按钮,如下图所示:
(3)在打开的添加机器人页面输入机器人名字,选择要接收报警的钉钉群 ,设置机器人头像。根据需要勾选安全设置等就可以,点击完成之后,在页面拷贝出 Webhook 地址保存好,向这个地址发送 HTTP POST 请求,设置的 SkyWalking 钉钉报警群便能收到钉钉报警消息,如下图所示:
配置好之后我们可以看到设置报警的钉钉群“SkyWalking 钉钉报警”出现了报警机器人消息,如下图所示:
我们可以用 Linux 命令行工具 curl 快速验证是否可以推送成功,curl 命令行示意如下:
[root@JD ~]# curl 'https://oapi.dingtalk.com/robot/send?access_token=xxxxxxx' -H 'CONTENT-TyPE: application/json' -d '{"msgtype": "text","text": {"content": "业务报警"}}'
{"errcode":0,"errmsg":"ok"}
- 1
- 2
你可以看到通过 curl 后可以得到基本响应 {"errcode":0,"errmsg":"ok"}。
上述已经配置完成了钉钉机器人,那如何将 SkyWalking 的报警信息自动推送到钉钉机器人呢?我们可以实现一个接口作为它们沟通的“桥梁”。
首先在 pom 里面引入相关的 jar 包,如下所示:
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
</dependency>
- 1
- 2
- 3
- 4
然后自定义 DingTalkUtils 工具类,暴露接口访问路径 /dingdingAlarm。
@RequestMapping(value = "/dingdingAlarm", method = RequestMethod.POST)
public void alarm(@RequestBody List<AlarmDto> alarmList){
//示意代码
alarmList.forEach(alarm-> {
DingTalkUtils.sendMsg(alarm.getAlarmMessage());
});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
SkyWalking 告警推送到钉钉
SkyWalking 提供了告警的配置,我们可以很方便地配置上面暴露的接口。在 SkyWalking 程序包里的 config 文件夹下有个 alarm-settings.yml 文件,该文件专门用来配置相关的报警。在该配置文件中我们可以搜索到 webhooks,把上面暴露的接口写上去就好了。
webhooks:-http://ip:port/dingdingAlarm
- 1
接下来我们测试下,比如 auth 服务获取验证码的接口出现错误,我们是可以在 SkyWalking 追踪页面清楚地看到的。同时对于其他相关的业务同学,也都可以在钉钉群收到报警信息,这样的方式在实际工作中非常实用。业务报错图和钉钉报警图如下所示:
图 5:业务报错图
图 6:钉钉报警图
这一讲主要讲解了关于 SkyWalking 的使用背景以及价值,在实操层面讲解了 SkyWalking 是如何追踪监控中出现的错误,并且把出现的错误通过钉钉通知给相关人员,相信通过这一讲的学习,你也对微服务下的报警方案会有一个更深刻的认识。
这里你需要思考下,你所在的公司是如何进行链路监控的?你觉得有什么优点和缺点?欢迎在交流区留下你的答案。
下一讲我将带你学习如何玩转可视化监控,如何把监控大屏做得酷炫!
前面两讲分别讲解了硬件监控、链路监控以及相关的报警机制。一些同学在学习硬件监控的过程中可以发现,命令行操作反馈迅速及时,指哪儿打哪儿,非常灵活便捷,但并不是所有同学都需要或者都有权限直接在服务器上进行操作。而且很多中大型互联网公司是大型的服务集群,通过命令行去发现每台服务器的问题并不现实,所以很多企业都会采用大屏的图形化监控。在页面上根据自己的需要进行条件筛选,这样不仅简单、清晰、直观,而且能够很方便地向团队成员传递监控的实时信息。
所以作为一位优秀的性能测试工程师,你不仅仅需要掌握命令行监控是如何操作的,也需要了解监控大屏是如何制作的,二者可以说是相互补充的。
这一讲我们就来讲解可视化监控,你可以认为它是一节实操课,需要提前准备好你的服务器环境(推荐 CentOS 7.0),跟着我的步骤一步步进行就可以完成酷炫的监控报表。
对于初学者而言,你可能并不能从上述文字中感受到命令行和图形化界面展示的区别,那么我用两张图来对比下。
图 1:命令行方式
图 2:可视化监控大屏
我想绝大部分还是更愿意看图 2 的可视化监控大屏,本讲的开头也说了命令行监控和可视化监控是一种互补的形式,这就代表两种方式各有千秋。可视化监控除了直观外,我认为还有如下两点优势。
(1)信息高度集中
可视化监控大屏一般会根据不同的机器提供不同的维度,比如图 2 就是其中一台机器的各类监控信息汇总,可以说信息多元且海量,我们并不能在同一时间将所有机器的具体信息都看到。而可视化方式可以通过时间维度去追溯历史数据,这相对于命令行基于碎片时间收集的信息要全面很多,很方便后续的复盘或者追踪。
(2)加速信息传递效率
大屏的方式也是共享的方式,可以更快速地把信息传递给项目其他成员,每位成员可以通过大屏的链接去访问,自由选择自己所需要的信息,而且可以通过展现出来的趋势预判会触发的阈值以达到提前发现风险的效果。
大屏的监控并不算新概念,可以说各类方案层出不穷,老牌的监控工具如 Zabbix、Nagios 等,但随着互联网的发展,越来越多高性能且展示酷炫的方案应运而生,其中以 Promethues + Exporter + Grafana 为主的通用方案受到普遍欢迎。
首先来解释下 Promethues + Exporter + Grafana 这套组件的基本作用。
Prometheus:既然 Exporter 作为 agent,那必然有一套中心化的数据采集存储组件,这个组件就是 Promethues,它通过接收 Exporter 采集的数据,并按照一定的规则进行计算整合,通过 AlertManager 设置报警规则,达到触发条件后就发送报警信息。
Exporter:用于采集服务器中的监控数据,比如对服务器基础性能进行监控的 node_exporter 插件,也可以理解为 agent。
Grafana:用于数据的渲染展现,可以展示得非常酷炫,如果仔细阅读过《03 | 构建并执行 JMeter 脚本的正确姿势》,相信你对 Grafana 已经有了一定的了解。
我们再用下面这张图来总结下这个过程。
图 3:组件流程图
那可能有同学提问了,这套组件除了针对硬件进行监控,对于一些中间件或者数据库的监控也可以吗?
答案是肯定的,根据 Exporter 的不同,你可以监控不同的组件,这也是这套监控最灵活的部分,不同的 Exprter 可以达到不同的监控目的和效果。
接下来我就分别以监控服务器硬件和数据库这两个例子来讲述这套体系的搭建以及使用技巧。
通过对上文的学习,你应该知道关键部分是如何选择 Exporter,其中 node_exporter 就实现了对 Linux 操作系统中 CPU 使用、负载、磁盘空间、磁盘等待、网络传输等详尽数据的采集。
接着我就带你来看如何安装部署,比如你需要在 A、B、C 三台机器上同时监控,那必须都安装node_exporter 插件。我先以一台机器为例,带你安装下 node_exporter,使用 wget 直接下载就可以,如下所示:
wget -c https://github.com/prometheus/node_exporter/releases/download/v0.18.1/node_exporter-0.18.1.linux-amd64.tar.gz
- 1
然后解压如下命令:
tar zxvf node_exporter-0.18.1.linux-amd64.tar.gz
- 1
再进入相应的文件夹,使用后台启动方式开启服务:
nohup ./node_exporter &
- 1
当启动完成之后,可以用 ip:9100 的方式打开页面,如下所示,即认为 node_exporter 安装成功了。
图 4:node_exporter 安装示意图
我们点击 Metrics 可以查看具体的采集信息,部分展示内容如下所示:
# HELP node_cpu_seconds_total Seconds the cpus spent in each mode
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 995721.03
- 1
- 2
- 3
HELP是解释下面指标的含义,相当于协助文档;
TYPE用于解释指标的数据类型;
下面的信息是具体的统计信息,比如 node_cpu_seconds_total{cpu="0",mode="idle"} 就是指从开机到现在的 cpu0 的空闲时间。
你可以自行安装下 node_exporter,就能看到 Metrics 中的海量数据了。
Prometheus 作为时间序列数据库,提供本地存储和分布式存储,又支持多种数据大盘,而且性能优异,受到市场的欢迎。阿里云也全面接入了 Promethues 的生态,提供了更多开箱即用的组件。
首先我们使用如下命令进行下载:
wget -c https://github.com/prometheus/prometheus/releases/download/v2.15.1/prometheus-2.15.1.linux-amd64.tar.gz
tar zxvf prometheus-2.15.1.linux-amd64.tar.gz
- 1
- 2
然后进入解压文件夹 prometheus-2.15.1.linux-amd64,查看主要的配置文件 prometheus.yml。
该文件主要有四个核心节点,分别是 global、alerting、rule_files 和 scrape_configs。
global:全局配置,比如每次数据收集的间隔、规则地扫描数据的间隔。
alerting:设置告警的插件,在这里会设定 alertmanager 这个插件。
rule_files:具体的报警规则设置,比如基于什么指标进行报警,类似于触发器。
scrape_configs:采集数据的对象,job_name、target 以及 job_name 是配置主机的名称,target 是你安装的 Exporter 地址。
然后我们需要增加本地的监控配置,如下所示:
- job_name: 'cctester'
static_configs:
- targets: ['127.0.0.1:9100']
- 1
- 2
- 3
再启动 Prometheus:
nohup ./prometheus &
- 1
访问 http://ip:9090/targets,根据自己的实际情况填写 ip,出现如下截图表示安装成功。
图 5:Promethues 成功安装示意图
这部分第 03 讲已经讲解过,我们就不再赘述,安装完成 Grafana 之后,添加 Prometheus 数据源,测试并保存即可。
图 6:Grafana 添加 Promethues 数据源
接着导入官方提供的展示模板就可以,点击链接。你可以自行选择相应的版本进行下载,也可以直接填写模板 ID,导入完成之后,便可以看到大屏了,示意图如下:
图 7:可视化大屏示意
到目前为止,一款基于 Linux 硬件监控的大屏就打造完成了。
通过以上的讲解,你可以思考下如果做 MySQL 的监控,哪些组件是可以通用的,需要改变的组件又有哪些。
能够思考清楚这些问题,我想你就可以基于这套组件打造出属于你自己的监控系统了。
对于监控来说,关键是面对不同的监控对象是怎么采集和怎么展示的,所以需要改变的是你的采集的 Export 和展示的模板,而Promethues + Exporter + Grafana这套组件的社区又非常丰富,所以我们可以快速实现这个需求。
下面我们下载基于 MySQL 监控的 Exporter,如下所示:
wget https://github.com/prometheus/mysqld_exporter/releases/download/v0.12.1/mysqld_exporter-0.12.1.linux-amd64.tar.gz
- 1
下载完成之后对如下命令进行解压:
tar zxvf mysqld_exporter-0.12.1.linux-amd64.tar.gz
- 1
对于 MySQL 的监控,还需要创建一个配置文件,比如我在解压后的文件夹下创建 my.cnf,来看看 my.cnf 有哪些内容:
[client]
user=root
password=123456
port=3306
host=127.0.0.1
- 1
- 2
- 3
- 4
- 5
可以看出 my.cnf 里的配置信息就是数据库的连接信息,你可以根据自己的实际部署情况进行配置,配置完成之后就可以启动了,启动命令如下:
nohup ./mysqld_exporter --config.my-cnf=my.cnf &
- 1
然后通过网页访问来验证是否部署成功,访问地址一般是 ip:9104,可以看到如下展示信息:
点击 Meteric 你也可以发现很多手机端 MySQL 监控信息的参数选项,部分信息如下:
# HELP mysql_global_variables_max_connections Generic gauge metric from SHOW GLOBAL VARIABLES.
# TYPE mysql_global_variables_max_connections gauge
mysql_global_variables_max_connections 151
- 1
- 2
- 3
这个配置表示了最大连接数的配置信息,如果能看到这一步信息也说明 mysqld_exporter 安装成功了,接着增加 promethues.yml 里的 MySQL 配置节点,示意如下:
- job_name: 'mysql'
static_configs:
- targets: ['127.0.0.1:9104']
- 1
- 2
- 3
关于Grafana 展示, 选择 Grafana 的 MySQL 监控相关模板导入即可,点击模板链接。下载并导入后就可以了,MySQL 展示效果如下图所示。
图 8:mysql 可视化监控示意图
这一讲主要讲述了Promethues + Exporter + Grafana的监控方案:
首先需要掌握的是每种组件核心的意义以及使用方法,而不能满足于机械地执行完成上述步骤;
然后我是以监控硬件服务器资源和 MySQL 监控来举例,分别代表了硬件层和服务层两个维度,通过这两个例子让你更直观地明白哪些组件是可以复用的,不同的监控目标是否有配套的社区可以给你提供帮助;
再者我更想传递的信息是这套监控体系不仅仅是适用于我举的示例,它更是一揽子解决方案,比如说监控 Redis、JVM 等,它同样也是适用的。通过这套方法完全可以解决可视化监控层面的大部分需求,希望你能够多多实践,扫除你们公司可能存在的监控“死角”。
最后给你留一个思考题,你所在的公司监控是怎么做的,有什么优点和缺点?欢迎在评论区给出你的留言。
下一讲我将带你学习 Docker 的制作、运行以及监控,通过下一讲的学习你可以更多地了解容器相关的技术。
模块三主要讲解了不同层级的监控以及监控的方式,作为模块三的最后一讲,我将带你来学习 Docker 的制作、运行以及监控。对于很多测试来说,经常听到 Docker 容器,但自己好像又不是很熟悉,只是用相关命令去查询日志等,而对于为什么要使用 Docker 还不是特别清楚。其实 Docker 并不难学,有时候你只是差一个学习的切入点,这一讲我会从测试的使用层面带你学习下 Docker 的要点知识,希望作为一名测试的你,对 Docker 也不会再陌生。
你可以回忆下 Docker 的图标(如图 1 所示),是不是像一条船上装了很多集装箱,其实这和Docker 的设计思想有关系,集装箱能解决什么问题呢?就是货物的隔离,如果我们把食物和化学品分别放在两个集装箱中用一艘轮船运走则无妨,但是你不可以把它们放在同一个集装箱中,其实对于 Docker 设计也是如此。
操作系统就相当于这艘轮船,上面可以有很多集装箱,即 Docker,你可以把 Docker 看作是独立的子环境,有独立的系统和应用,比如经常因为一些历史原因开发的多个模块依赖于不同的 JDK 版本,将这两个模块部署在一台 Linux 服务器上可能很容易出问题,但是如果以 Docker 的方式便很容易解决版本冲突的问题。
图 1:Docker 图标
如何学习 Docker 呢?从应用技术维度来看它是一个容器,从学习角度来看它就是一种工具。
对于工具的学习我认为从实际的例子切入是最有代入感的,接下来我就在 CentOS 环境下安装一个基于 Ubuntu 的 Docker 环境,带你从使用层面了解下 Docker,知道 Docker 最基本的安装方式,如下所示:
yum install -y docker //安装Docker
service docker status //查看Docker运行状态
- 1
- 2
接下来运行一个 Docker 容器,我目前用的是 CentOS 系统,可现在还需要一个 Ubuntu 环境,我就需要通过如下命令基于 Ubuntu 镜像启动一个容器:
docker run -i -t ubuntu /bin/bash
- 1
通过这个命令,就直接创建了基于 Ubuntu 的 Docker 环境,并直接进入了交互 shell,这样你就可以认为是在 Ubuntu 系统下工作了,通过如下命令可以查看版本号:
root@ac3874a96890:/# cat /etc/issue
Ubuntu 20.04.1 LTS
- 1
- 2
同样的道理,如果你的 Java 服务有的依赖 JDK1.7,有的依赖 JDK1.8,则可以通过 Docker 来做不一样的服务。
上面就是一个简单的实例,在 CentOS 系统里创建一个基于 Docker 的 Ubuntu 系统以实现你特定的需求。
我们再来看看 Docker 常用的命令有哪些,这些可能是你和 Docker 打交道的过程中最常见的命令。
对于 Docker 的命令,都是在 Linux 终端直接输出就可以,比如查看 Docker 镜像,就是直接输出 docker images,展示信息如下所示:
[root@JD ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/ubuntu latest f643c72bc252
3 weeks ago 72.9 MB
docker.io/gitlab/gitlab-ce latest 6e2336419031
8 months ago 1.92 GB
- 1
- 2
- 3
- 4
- 5
- 6
REPOSITORY 是指仓库名字;
TAG 一般指版本号;
IMAGE ID 是指镜像 ID;
CREATED 指镜像创建时间;
SIZE 指镜像大小;
如果我们要查看正在运行的 Docker 进程,可以使用命令 docker ps,如下所示:
[root@JD ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3e6ab93074c7 rancher/scheduler:v0.8.6 "/.r/r /rancher-en..." 25 hours ago Up About an hour r-scheduler-scheduler-1-056ab74a 2a6a718fa85d gitlab/gitlab-ce:latest "/assets/wrapper" 8 months ago Restarting (127) 11 hours ago gitlab
- 1
- 2
- 3
其中第一列是容器的 ID 号,它是一个重要的标识,通过 ID 号我们可以查看指定容器的日志以及启停容器等。读到这里你会发现,你已经知道了两个 ID:
一个是 IMAGE ID;
另外一个是 CONTAINER ID。
当你要删除镜像时,就需要使用到 IMAGE ID 了,也就是使用命令 docker rmi image id。那 IMAGE 和 CONTAINER 是什么关系呢?按照我的理解打个比方:
IMAGE 相当于类;
CONTAINER 相当于实例化后的对象,是在使用层面表现出来的形态。
不过你要注意的是 docker ps 只会展示运行的容器:
如果你想展示所有的容器,需要使用 docker ps -a,这个命令会展示运行的容器和已经停止的容器;
如果你机器上运行的容器很多,想看最近创建的 10 个容器,可以使用 docker ps -n 10。
如果你要停止运行某个容器,可以使用 docker stop container id 来终止,并且可以结合上文说的 docker ps -a 来看终止状态的容器;
如果要使用 docker rmi删除容器镜像,你也需要先关闭对应运行的容器才能执行删除。
值得注意的是一些初学者会误用 systemctl stop docker 这个命令,它是停止整个 Docker 服务,相当于你机器上的 Docker 全部关闭,这是初学者一定要注意到的。
作为测试或者开发,通过日志去排查问题是必不可少的,如下所示就是查看指定 Docker 容器日志的方法:
docker logs -f 3e6asb93074c7 #最后一列为容器id号
- 1
你可以将 Docker 看作是一个子系统,自然可以进入这个系统进行一定的操作。在我的使用过程中,经常会使用如下命令进入 Docker 容器找应用的 dump 信息:
docker exec -it 3e6ab93074c7 /bin/bash
- 1
以上是测试同学在使用层面最常见的命令,如果你对 Docker 还不是很了解,可以将这些作为切入点,先掌握使用,在此基础上再去了解 Docker 的架构设计以及一些进阶思想。
上文带你熟悉了 Docker 的用法,相当于小试牛刀,可能你总听公司的人说 Dockerfile、Docker 容器、Docker 镜像,但又分不清楚,下面我就来解释下它们之间的具体区别是什么:
Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明,相当于你做镜像的材料清单和执行步骤;
Docker 镜像是根据这些原材料做出来的成品;
而 Docker 容器,你可以认为是基于镜像运行的软件。
我以包饺子为例:
Dockerfile 相当于猪肉、葱姜蒜、饺子皮这些原料的描述以及包饺子的步骤;
Docker 镜像是你包完的生水饺;
而 Docker 容器则是已经煮熟可以食用的水饺了。
通过下面这个示意图可以看出从 Dockfile 到 Docker 容器的过程:
图 2:Dockfile 到 Docker 容器的过程
首先来说为什么会有这样的需求,对于用户体量比较大的公司,他们需要的系统处理能力自然也越高。在压测过程中,并不是单台压力机就可以解决问题,我们可能会在压测过程中动态调度JMeter 节点,其中一个比较方便的方式就是使用 Docker 的方式动态进行。
接下来我主要讲解如何制作基于 JMeter 的 Docker 镜像,这也是基于 Docker 扩容的关键部分。
首先我新建了一个文件夹 jmeter_docker,里面存放制作 JMeter 的 Docker 的原材料,如下所示:
[root@JD jmeter_docker]# ls
apache-jmeter-5.2.1.tgz Dockerfile jdk-8u101-linux-x64.tar.gz
- 1
- 2
接着我打开 Dockerfile,看看我的“原料表”里面有哪些内容,从下面的文件描述中可以看出我需要的“原料”和执行步骤:
FROM java:8 # 基础java版本 MAINTAINER cctester # 作者 ENV http_proxy "" ENV https_proxy "" RUN mkdir /test && \ chmod -R 777 /test # 创建/test目录,用于存放jmx脚本、jtl结果文件、html测试报告文件 ENV JMETER_VERSION=5.2.1 ENV JMETER_HOME=/usr/local/apache-jmeter-${JMETER_VERSION} ENV JMETER_PATH=${JMETER_HOME}/bin:${PATH} ENV PATH=${JMETER_HOME}/bin:${PATH} # 设置JMeter环境变量 ADD apache-jmeter-${JMETER_VERSION}.tgz /usr/local # 添加JMeter RUN ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
在制作 JMeter 镜像时,请不要忽略后面的一个点(.),具体如下所示:
[root@JD jmeter_docker]# docker build -t jmeter .
.....省略
Successfully built 267c5b4303a6
# 你还可以通过docker images查看完成的镜像
[root@JD jmeter_docker]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
jmeter latest 267c5b4303a6 6 minutes ago 762 MB
- 1
- 2
- 3
- 4
- 5
- 6
- 7
为了方便替换压测脚本或者参数化文件,我在 jmeter_docker 文件下创建一个 test 文件夹来存放这些文件。
mkdir test
# 在当前路径创建test目录,用户存放jmeter文件
docker run -d -it --name jmeter5.2.1 -v $PWD/test:/test jmeter
31f465a1ae646c65e855084d46313754e74a2f377776d9692c0119d32949a130 //启动成功,生成运行id
- 1
- 2
- 3
- 4
然后进入容器,看下 JMeter 是否可用:
root@31f465a1ae64:/test# jmeter -v
Dec 19, 2020 6:11:34 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
- 1
- 2
- 3
到这里我们就可以运行 JMeter 进行测试了,上传一个 cctester.jmx 脚本到 test 文件夹,使用方式以及结果反馈如下所示:
root@31f465a1ae64:/test# jmeter -n -t /test/cctester.jmx
Creating summariser <summary>
Created the tree successfully using /test/cctester.jmx
Starting standalone test @ Sat Dec 19 18:22:11 CST 2020 (1608373331470)
Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445
summary + 3344 in 00:00:18 = 185.0/s Avg: 52 Min: 14 Max: 1312 Err: 0 (0.00%) Active: 10 St
- 1
- 2
- 3
- 4
- 5
- 6
到此就完成了一个基于 Docker 的 JMeter,上面演示了从制作到运行的全过程,同样对于其他Docker 的制作流程也是类似的,你可以基于一种先练习。
通过前面章节的学习,我想对于监控你已经并不陌生,并且可以提炼出一套搭建监控体系的方法,对于 Docker 监控本质上也是换汤不换药,我主要进行思路上的一些讲解。
Docker 本身也是可以通过命令行来监控的,看下 docker stats 的输出,如下所示:
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
b667f6b988b4 0.07% 381.3 MiB / 7.64 GiB 4.87% 119 MB / 105 MB 275 MB / 0 B 61
f650d561b729 0.04% 233.1 MiB / 7.64 GiB 2.98% 94.9 MB / 118 MB 139 MB / 397 MB 49
c7575bf9a7d7 0.00% 4.711 MiB / 7.64 GiB 0.06% 0 B / 0 B 954 kB / 0 B 6
2a72f849baaa 0.10% 4.008 MiB / 7.64 GiB 0.05% 18.8 MB / 14.5 MB 68.5 MB / 3.04 MB 6
760e653d4324 0.00% 4.887 MiB / 7.64 GiB 0.06% 0 B / 0 B 92.5 MB / 4.1 kB 27
- 1
- 2
- 3
- 4
- 5
- 6
你可以看到不同的实例都有对应包括 CPU、内存、磁盘、网络的监控,这样的数据比较详细直观。所以这一讲我给你留一个作业,自行搭建 Docker 的可视化监控,可以结合之前讲过的 Grafana、Promethues 等,欢迎在评论区留下你搭建过程中的心得体会以及问题。
本讲作为第三模块的收尾,带你学习了 Docker 的基础知识,包括镜像制作、运行,以及监控的常见方式。通过对第三模块的系统学习,你也应该掌握常见的监控方法以及监控部署开展的思路。
接下来的第四模块我将带你学习常见的性能问题定位以及优化思路,到时见。
上一模块我带你学习了如何进行系统监控,相信你已经掌握了监控部署的常见手段,通过监控这双“眼睛”,会帮助你及时发现系统资源异常,那当你发现资源异常时候,是不是觉得已经找到问题了呢?事实上并非如此,绝大多数资源异常只是你看到的表象问题,就好比你发现一个地方着火了,你可以先灭火,但是着火的原因是必须找到的,并制定相关的措施,这样才能有效避免下一次的火情。
对于系统也是这样的,当你发现了资源异常,你需要继续寻找发生问题的根因,所以作为一名专业的性能测试工程师,你也应当具备顺着表象去找问题根因的能力。这一讲我就以最流行的 Java 语言为例,带你学习如何透过现象看本质。
对于排查问题,不要只满足于掌握一些排查工具或者命令,你应当对被测语言以及运行原理有所了解,这样得出来的结论才可能更全面。
这一讲我先带你理解 Java 运行过程中的核心概念。首先要明白 Java 代码在哪里运行,一些初学者说是在 idea 或者 eclipse 里面,因为它们是写代码的软件,不过细心的同学会发现,所有的 idea 或者 eclipse 要运行 Java 代码都需要配置 Java 环境,其实 idea 是我们开发的编辑器,而真正运行代码的是 JVM。
什么是 JVM 呢?JVM 是 Java Virtual Machine 的缩写,它是一个独立出来的运行环境,通过这样的环境去进行 Java 代码中各种逻辑运行。
读到这里可能同学有疑问了:“我现在接触了很多环境,比如 JVM 运行环境、Docker 运行环境,还有云服务器之类,它们到底是什么关系?”这对于不少人来说,确实是有一定疑惑的,我先用一张图来示意下:
从图中你可以看到,一般在底层物理机上会部署多个云服务器,而云服务器上又可以部署多个基于 Docker 的 JVM 节点,这样的部署结构也是比较常用的,既能做到环境的隔离也能节约机器成本。
JVM 本身是一个较为庞大的知识体系,对于测试来说,不一定要理解 JVM 特别晦涩的概念,但至少需要了解 JVM 的结构以及运行的机制,你可以认为 JVM 是运行在 Win 或者 Linux 系统上专门运行 Java 的虚拟机,Java 虚拟机直接和操作系统交互。
比如我们现在写了一个 HelloTester.java,这个 HelloTester.java 就类似一个文本文件,不过这个文件里面包含了符合 Java 语法规范的文本。比如我在 idea 里写一个简单的方法,如下代码所示:
public class HelloTester {
public void sayName(String name){
System.out.println("my name is "+name);
}
public static void main(String[] args){
HelloTester helloTester=new HelloTester();
helloTester.sayName("cctester");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
那我们的JVM 是不认识文本文件的,所以它需要编译,让其成为一个会读二进制文件的 HelloTester.class,一般这个文件会产生在工程文件夹下的 Target 当中。
如果 JVM 想要执行这个 .class 文件,我们需要将其装进一个类加载器中,它就像一个搬运工一样,会把所有的 .class 文件全部搬进 JVM 里面来。如下图所示:
对于如上的过程我们再总结概括一下:
Java 文件经过编译后变成 .class 字节码文件;
字节码文件通过类加载器被搬运到 JVM 中,生成的对象一般会在 JVM 中堆空间运行。
同样还是根据以上代码示意,我带你看下 Java 对象如何进入堆空间以及在堆空间中运行的。
通过上文可知,编译 HelloTester.java 便会得到 HelloTester.class,执行 class 文件后系统会启动一个 JVM 进程,找到 HelloTester.class 后将类信息加载到 JVM 中。
JVM 找到 mian 方法后就可以执行 main 中的 HelloTester helloTester=new HelloTester(),也就是在 JVM 里创建一个 helloTester 对象,不过此时方法区里面还没有 HelloTester 类的信息,所以 JVM 就会去加载该类:
加载 HelloTester 类后,JVM 在堆内就会为新的 HelloTester 实例进行内存的分配使用;
然后执行 helloTester.sayName(),JVM 根据 HelloTester 对象引用定位到方法区中 HelloTester 类的类型信息的方法表,获得 sayName() 的字节码地址;
最后执行 sayName("cctester")。
以上便是 Java 对象在 JVM 中运行的大体过程,了解了这些基本信息之后,再来了解下堆空间中 Java 运行的线程状态,当程序开始创建线程时,便开始有了生命周期,其实就和人一样,会有“生老病死”几个状态,而对于线程来说会经历六个状态,如下表所示:
我们用一张图来直观地概括下这几个状态的演变:
从字面上来看,NEW、RUNNABLE、TERMINATED 这几个状态比较好理解,但对于 BLOCKED、WAITING、TIMED_WAITING 很多人却分不清楚,我想通过一些实际生活中的例子来帮助你理解。
先来说下 BLOCKED,比如你去参加面试,可是接待室里面已经有张三正在面试,此时你是线程 T1,张三是线程 T2,而会议室是锁。这时 T1 就被 blocked,而 T2 获取了会议室的锁。
接着我们来说 WAITING,你已经进入面试环节,面试官对你的第一轮面试比较满意,让你在会议室等第二轮面试,此时就进入了 WAITING 状态,直到第二轮面试开始你才能结束 WAITING 状态。
当你结束了所有面试环节,HR 对你说我们一般会在三天内给回复,如果三天内没有回复就不要再等了,此时你就进入 TIMED_WAITING 状态,如果三天内没答复,你可能会看其他机会或者直接入职备选公司了。
这几个例子我想可以帮助你理解 TIMED_WAITING、WATING、BLOCKED 状态。
处于 TIMED_WAITING、WATING、BLOCKED 状态的线程是不消耗 CPU 的,而处于RUNNABLE 状态的线程要结合当前线程代码的性质判断是否消耗 CPU:
纯 Java 运算代码,并且未被挂起,是消耗 CPU 的;
网络 IO 操作,在等待数据时是不消耗 CPU 的。
通过如上的学习,你了解了线程的状态,可以知道这个线程是在“休息”还是在“奔跑”。如果很多线程处于“奔跑”状态,必定会消耗相关的硬件资源,反过来理解,如果在性能测试过程中发现资源消耗是不是也能定位到相关的线程,从而发现代码问题呢?当你定位到具体的代码行,是不是可以和研发人员讨论下有没有优化的空间,而不是简单地将机器升级配置去解决问题,所以我将继续沿着如何定位代码问题这条思路为你讲解。
举一个实际例子,我以一个问题为切入点,首先看下面示意代码,可以看出 CPU 占用比较高的线程。
top - 17:41:39 up 168 days, 8:55, 2 users, load average: 0.71, 0.81, 0.57
Tasks: 155 total, 1 running, 153 sleeping, 0 stopped, 1 zombie
%Cpu(s): 68.4 us, 6.4 sy, 0.0 ni, 23.5 id, 0.0 wa, 0.0 hi, 1.7 si, 0.0 st
KiB Mem : 8010676 total, 326472 free, 6196656 used, 1487548 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 1120940 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
6937 root 20 0 4778684 518804 6
140 S 141.9 6.5 17:46.36 java
14643 root 20 0 4639440 821244 2472 S 11.6 10.3 1789:33 java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
通过如上示例的第 3 行你可以发现服务器上 CPU 占用蛮高的,空闲值为 23.5%,也就是说占用了 76.5%;再看第 8 行,你可以看到 PID 为 6937 的进程消耗 CPU 为 141.9%。可能你有疑问了,为什么使用率可以超过 100%。这和你的服务器核数有关系,因为这个数值是每个核上该进程消耗的 CPU 之和,会有叠加关系。那你已经知道了消耗 CPU 最高的进程,然后执行如下命令:
[root@JD jmeter_test]# top -Hp 6937
top - 23:20:53 up 168 days, 14:35, 3 users, load average: 1.33, 0.71, 0.88
Threads: 788 total, 1 running, 787 sleeping, 0 stopped, 0 zombie
%Cpu(s): 75.0 us, 6.2 sy, 0.0 ni, 18.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8010676 total, 576860 free, 5697612 used, 1736204 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 1616168 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
25695 root 20 0 5409224 1.0g 4892 S 6.2 13.2 0:00.09 java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
我们可以看到每个线程的使用状态,你可以选择 25695 这个线程号,将 25695 转化为 16 进制,如下所示:
printf "%x\n" 25695
645f
- 1
- 2
然后通过 jstack 命令定位可能存在问题的方法:
jstack 6937|grep 645f -A 30
- 1
通过运行上面的命令可以查看到的内容如下图所示:
标红部分就是定位的业务代码,能够比较清晰地知道哪个方法在消耗 CPU 资源。
总结下来,要确定哪些线程状态占用 CPU 至少需要如下步骤:
使用 top 命令找出有问题 Java 进程的 ID;
开启线程显示模式(top -Hp);
按照 CPU 使用率将线程排序(打开 top 后按 P 可以按 CPU 使用降序展示);
记下 Java 进程 ID 及其 CPU 高的线程 ID;
用进程 ID 作为参数,手动转换线程 ID 成十六进制,通过 jstack 去剖析对应的线程栈,以分析问题。
你可以看到,实际过程略显烦琐,而有能力的同学可以做成 shell 脚本,这样会比较方便,当然社区也已经有这样的开源脚本供大家使用,点击访问地址。
下载完成之后进入 useful-scripts,执行 ./show-busy-java-threads.sh,执行完成后的示意图如下所示:
这样的方式是可以看到这台服务上所有导致 CPU 飙升的 Java 方法的,当然直接一键也可以查看指定进程里的 java 方法,非常简单方便,方法如下所示:
show-busy-java-threads -p <指定的Java进程Id>
- 1
根据本讲的学习,相信你已经能够掌握 Java 在 JVM 中的运行过程,以及 Java 线程在 JVM 中的运行状态,并且能够从 CPU 飙升定位到代码问题。
那对于你来说,当你发现 CPU 占用过高怎么去处理呢?我相信不同的公司、不同的开发语言有不同的方案,欢迎在评论区给出你的实践。
下一讲我将带你学习基于 JVM 层的内存使用分析,到时见。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。