如何制定一份有效的性能测试方案?
上一讲我们学习了性能测试的场景,并且明确了每个场景的核心意义,这一讲我将带你学习如何做好一份性能测试方案,相信你对测试方案这个概念并不陌生,那如何做好一份性能测试方案呢?这个方案能解决什么问题呢?这一讲我们来一起探索。
性能测试方案,通俗一点说就是指导你进行性能测试的文档,包含测试目的、测试方法、测试场景、环境配置、测试排期、测试资源、风险分析等内容。一份详细的性能测试方案可以帮助项目成员明确测试计划和手段,更好地控制测试流程。
性能测试方案的要点以及解决的问题
-
为测试活动做计划,每项测试活动的对象、范围、方法、进度和预期结果会更透明化。
-
制定出有效的性能测试模型,能够排查出性能问题,从而更符合真实场景。
-
确定测试所需要的人力、时间和资源,以保证其可获得性、有效性。
-
预估和消除性能测试活动存在的风险,降低由不可能消除的风险所带来的损失。
测试方案需要包含哪些内容?
性能测试方案是在你正式进行性能测试之前的工作,通过前几讲的学习你已经知道了性能方案中的必备内容。
f,来看看 f 里的配置信息就是数据库的连接信息,你可以根据自己的实际部署情况进行配置,配置完成之后就可以启动了,启动命令如下:
然后通过网页访问来验证是否部署成功,访问地址一般是 ip:9104,可以看到如下展示信息:
点击 Meteric 你也可以发现很多手机端 MySQL 监控信息的参数选项,部分信息如下:
这个配置表示了最大连接数的配置信息,如果能看到这一步信息也说明 mysqld_exporter 安装成功了,接着增加 promethues.yml 里的 MySQL 配置节点,示意如下:
关于Grafana 展示, 选择 Grafana 的 MySQL 监控相关模板导入即可,点击模板链接。下载并导入后就可以了,MySQL 展示效果如下图所示。
图 8:mysql 可视化监控示意图
-
首先需要掌握的是每种组件核心的意义以及使用方法,而不能满足于机械地执行完成上述步骤;
-
然后我是以监控硬件服务器资源和 MySQL 监控来举例,分别代表了硬件层和服务层两个维度,通过这两个例子让你更直观地明白哪些组件是可以复用的,不同的监控目标是否有配套的社区可以给你提供帮助;
-
再者我更想传递的信息是这套监控体系不仅仅是适用于我举的示例,它更是一揽子解决方案,比如说监控 Redis、JVM 等,它同样也是适用的。通过这套方法完全可以解决可视化监控层面的大部分需求,希望你能够多多实践,扫除你们公司可能存在的监控“死角”。
最后给你留一个思考题,你所在的公司监控是怎么做的,有什么优点和缺点?欢迎在评论区给出你的留言。
下一讲我将带你学习 Docker 的制作、运行以及监控,通过下一讲的学习你可以更多地了解容器相关的技术。
Docker 的制作、运行以及监控
模块三主要讲解了不同层级的监控以及监控的方式,作为模块三的最后一讲,我将带你来学习 Docker 的制作、运行以及监控。对于很多测试来说,经常听到 Docker 容器,但自己好像又不是很熟悉,只是用相关命令去查询日志等,而对于为什么要使用 Docker 还不是特别清楚。其实 Docker 并不难学,有时候你只是差一个学习的切入点,这一讲我会从测试的使用层面带你学习下 Docker 的要点知识,希望作为一名测试的你,对 Docker 也不会再陌生。
你可以回忆下 Docker 的图标(如图 1 所示),是不是像一条船上装了很多集装箱,其实这和Docker 的设计思想有关系,集装箱能解决什么问题呢?就是货物的隔离,如果我们把食物和化学品分别放在两个集装箱中用一艘轮船运走则无妨,但是你不可以把它们放在同一个集装箱中,其实对于 Docker 设计也是如此。
操作系统就相当于这艘轮船,上面可以有很多集装箱,即 Docker,你可以把 Docker 看作是独立的子环境,有独立的系统和应用,比如经常因为一些历史原因开发的多个模块依赖于不同的 JDK 版本,将这两个模块部署在一台 Linux 服务器上可能很容易出问题,但是如果以 Docker 的方式便很容易解决版本冲突的问题。
如何学习 Docker 呢?从应用技术维度来看它是一个容器,从学习角度来看它就是一种工具。
对于工具的学习我认为从实际的例子切入是最有代入感的,接下来我就在 CentOS 环境下安装一个基于 Ubuntu 的 Docker 环境,带你从使用层面了解下 Docker,知道 Docker 最基本的安装方式,如下所示:
接下来运行一个 Docker 容器,我目前用的是 CentOS 系统,可现在还需要一个 Ubuntu 环境,我就需要通过如下命令基于 Ubuntu 镜像启动一个容器:
通过这个命令,就直接创建了基于 Ubuntu 的 Docker 环境,并直接进入了交互 shell,这样你就可以认为是在 Ubuntu 系统下工作了,通过如下命令可以查看版本号:
同样的道理,如果你的 Java 服务有的依赖 JDK1.7,有的依赖 JDK1.8,则可以通过 Docker 来做不一样的服务。
上面就是一个简单的实例,在 CentOS 系统里创建一个基于 Docker 的 Ubuntu 系统以实现你特定的需求。
我们再来看看 Docker 常用的命令有哪些,这些可能是你和 Docker 打交道的过程中最常见的命令。
对于 Docker 的命令,都是在 Linux 终端直接输出就可以,比如查看 Docker 镜像,就是直接输出 docker images,展示信息如下所示:
-
TAG 一般指版本号;
-
SIZE 指镜像大小;
如果我们要查看正在运行的 Docker 进程,可以使用命令 docker ps,如下所示:
其中第一列是容器的 ID 号,它是一个重要的标识,通过 ID 号我们可以查看指定容器的日志以及启停容器等。读到这里你会发现,你已经知道了两个 ID:
-
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 看作是一个子系统,自然可以进入这个系统进行一定的操作。在我的使用过程中,经常会使用如下命令进入 Docker 容器找应用的 dump 信息:
以上是测试同学在使用层面最常见的命令,如果你对 Docker 还不是很了解,可以将这些作为切入点,先掌握使用,在此基础上再去了解 Docker 的架构设计以及一些进阶思想。
上文带你熟悉了 Docker 的用法,相当于小试牛刀,可能你总听公司的人说 Dockerfile、Docker 容器、Docker 镜像,但又分不清楚,下面我就来解释下它们之间的具体区别是什么:
-
Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明,相当于你做镜像的材料清单和执行步骤;
-
Docker 镜像是根据这些原材料做出来的成品;
-
而 Docker 容器,你可以认为是基于镜像运行的软件。
-
Dockerfile 相当于猪肉、葱姜蒜、饺子皮这些原料的描述以及包饺子的步骤;
-
Docker 镜像是你包完的生水饺;
-
而 Docker 容器则是已经煮熟可以食用的水饺了。
通过下面这个示意图可以看出从 Dockfile 到 Docker 容器的过程:
首先来说为什么会有这样的需求,对于用户体量比较大的公司,他们需要的系统处理能力自然也越高。在压测过程中,并不是单台压力机就可以解决问题,我们可能会在压测过程中动态调度JMeter 节点,其中一个比较方便的方式就是使用 Docker 的方式动态进行。
接下来我主要讲解如何制作基于 JMeter 的 Docker 镜像,这也是基于 Docker 扩容的关键部分。
接着我打开 Dockerfile,看看我的“原料表”里面有哪些内容,从下面的文件描述中可以看出我需要的“原料”和执行步骤:
# 创建/test目录,用于存放jmx脚本、jtl结果文件、html测试报告文件在制作 JMeter 镜像时,请不要忽略后面的一个点(.),具体如下所示:
为了方便替换压测脚本或者参数化文件,我在 jmeter_docker 文件下创建一个 test 文件夹来存放这些文件。
# 在当前路径创建test目录,用户存放jmeter文件然后进入容器,看下 JMeter 是否可用:
到这里我们就可以运行 JMeter 进行测试了,上传一个 cctester.jmx 脚本到 test 文件夹,使用方式以及结果反馈如下所示:
到此就完成了一个基于 Docker 的 JMeter,上面演示了从制作到运行的全过程,同样对于其他Docker 的制作流程也是类似的,你可以基于一种先练习。
通过前面章节的学习,我想对于监控你已经并不陌生,并且可以提炼出一套搭建监控体系的方法,对于 Docker 监控本质上也是换汤不换药,我主要进行思路上的一些讲解。
Docker 本身也是可以通过命令行来监控的,看下 docker stats 的输出,如下所示:
你可以看到不同的实例都有对应包括 CPU、内存、磁盘、网络的监控,这样的数据比较详细直观。所以这一讲我给你留一个作业,自行搭建 Docker 的可视化监控,可以结合之前讲过的 Grafana、Promethues 等,欢迎在评论区留下你搭建过程中的心得体会以及问题。
本讲作为第三模块的收尾,带你学习了 Docker 的基础知识,包括镜像制作、运行,以及监控的常见方式。通过对第三模块的系统学习,你也应该掌握常见的监控方法以及监控部署开展的思路。
接下来的第四模块我将带你学习常见的性能问题定位以及优化思路,到时见。
如何从 CPU 飙升定位到热点方法?
上一模块我带你学习了如何进行系统监控,相信你已经掌握了监控部署的常见手段,通过监控这双“眼睛”,会帮助你及时发现系统资源异常,那当你发现资源异常时候,是不是觉得已经找到问题了呢?事实上并非如此,绝大多数资源异常只是你看到的表象问题,就好比你发现一个地方着火了,你可以先灭火,但是着火的原因是必须找到的,并制定相关的措施,这样才能有效避免下一次的火情。
对于系统也是这样的,当你发现了资源异常,你需要继续寻找发生问题的根因,所以作为一名专业的性能测试工程师,你也应当具备顺着表象去找问题根因的能力。这一讲我就以最流行的 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 虚拟机直接和操作系统交互。
Java 文件是如何被运行的
比如我们现在写了一个 HelloTester.java,这个 HelloTester.java 就类似一个文本文件,不过这个文件里面包含了符合 Java 语法规范的文本。比如我在 idea 里写一个简单的方法,如下代码所示:
那我们的JVM 是不认识文本文件的,所以它需要编译,让其成为一个会读二进制文件的 HelloTester.class,一般这个文件会产生在工程文件夹下的 Target 当中。
如果 JVM 想要执行这个 .class 文件,我们需要将其装进一个类加载器中,它就像一个搬运工一样,会把所有的 .class 文件全部搬进 JVM 里面来。如下图所示:
对于如上的过程我们再总结概括一下:
-
Java 文件经过编译后变成 .class 字节码文件;
-
字节码文件通过类加载器被搬运到 JVM 中,生成的对象一般会在 JVM 中堆空间运行。
Java 对象又是如何在堆空间运行的?
同样还是根据以上代码示意,我带你看下 Java 对象如何进入堆空间以及在堆空间中运行的。
以上便是 Java 对象在 JVM 中运行的大体过程,了解了这些基本信息之后,再来了解下堆空间中 Java 运行的线程状态,当程序开始创建线程时,便开始有了生命周期,其实就和人一样,会有“生老病死”几个状态,而对于线程来说会经历六个状态,如下表所示:
我们用一张图来直观地概括下这几个状态的演变:
从字面上来看,NEW、RUNNABLE、TERMINATED 这几个状态比较好理解,但对于 BLOCKED、WAITING、TIMED_WAITING 很多人却分不清楚,我想通过一些实际生活中的例子来帮助你理解。
先来说下 BLOCKED,比如你去参加面试,可是接待室里面已经有张三正在面试,此时你是线程 T1,张三是线程 T2,而会议室是锁。这时 T1 就被 blocked,而 T2 获取了会议室的锁。
接着我们来说 WAITING,你已经进入面试环节,面试官对你的第一轮面试比较满意,让你在会议室等第二轮面试,此时就进入了 WAITING 状态,直到第二轮面试开始你才能结束 WAITING 状态。
当你结束了所有面试环节,HR 对你说我们一般会在三天内给回复,如果三天内没有回复就不要再等了,此时你就进入 TIMED_WAITING 状态,如果三天内没答复,你可能会看其他机会或者直接入职备选公司了。
一般哪些线程状态占用 CPU 呢?
-
纯 Java 运算代码,并且未被挂起,是消耗 CPU 的;
-
网络 IO 操作,在等待数据时是不消耗 CPU 的。
通过如上的学习,你了解了线程的状态,可以知道这个线程是在“休息”还是在“奔跑”。如果很多线程处于“奔跑”状态,必定会消耗相关的硬件资源,反过来理解,如果在性能测试过程中发现资源消耗是不是也能定位到相关的线程,从而发现代码问题呢?当你定位到具体的代码行,是不是可以和研发人员讨论下有没有优化的空间,而不是简单地将机器升级配置去解决问题,所以我将继续沿着如何定位代码问题这条思路为你讲解。
举一个实际例子,我以一个问题为切入点,首先看下面示意代码,可以看出 CPU 占用比较高的线程。
通过如上示例的第 3 行你可以发现服务器上 CPU 占用蛮高的,空闲值为 23.5%,也就是说占用了 76.5%;再看第 8 行,你可以看到 PID 为 6937 的进程消耗 CPU 为 141.9%。可能你有疑问了,为什么使用率可以超过 100%。这和你的服务器核数有关系,因为这个数值是每个核上该进程消耗的 CPU 之和,会有叠加关系。那你已经知道了消耗 CPU 最高的进程,然后执行如下命令:
我们可以看到每个线程的使用状态,你可以选择 25695 这个线程号,将 25695 转化为 16 进制,如下所示:
然后通过 jstack 命令定位可能存在问题的方法:
通过运行上面的命令可以查看到的内容如下图所示:
标红部分就是定位的业务代码,能够比较清晰地知道哪个方法在消耗 CPU 资源。
总结下来,要确定哪些线程状态占用 CPU 至少需要如下步骤:
-
开启线程显示模式(top -Hp);
-
按照 CPU 使用率将线程排序(打开 top 后按 P 可以按 CPU 使用降序展示);
-
用进程 ID 作为参数,手动转换线程 ID 成十六进制,通过 jstack 去剖析对应的线程栈,以分析问题。
你可以看到,实际过程略显烦琐,而有能力的同学可以做成 shell 脚本,这样会比较方便,当然社区也已经有这样的开源脚本供大家使用,点击访问地址。
这样的方式是可以看到这台服务上所有导致 CPU 飙升的 Java 方法的,当然直接一键也可以查看指定进程里的 java 方法,非常简单方便,方法如下所示:
根据本讲的学习,相信你已经能够掌握 Java 在 JVM 中的运行过程,以及 Java 线程在 JVM 中的运行状态,并且能够从 CPU 飙升定位到代码问题。
那对于你来说,当你发现 CPU 占用过高怎么去处理呢?我相信不同的公司、不同的开发语言有不同的方案,欢迎在评论区给出你的实践。
下一讲我将带你学习基于 JVM 层的内存使用分析,到时见。