mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-11-16 14:13:47 +08:00
fix img
This commit is contained in:
@@ -163,7 +163,7 @@ function hide_canvas() {
|
||||
<p>那是不是说,其他公司就没有性能测试需求了呢?并不是的。</p>
|
||||
<p>这两年,测试开发这个职位火了起来,许多公司招测试时都是在招测试开发。虽然招聘的不是专职的性能测试人员,但任职要求水涨船高,往往都需要你能够进行非功能测试,如性能测试、自动化测试。</p>
|
||||
<p>我们来看一则快手的招聘信息,其中就明确要求求职者有性能测试的相关经验。</p>
|
||||
<p><img src="assets/Ciqc1F_1HHeADFVaAAB_G69bz7U057.png" alt="Drawing 0.png" /></p>
|
||||
<p><img src="assets/Ciqc1F_1HHeADFVaAAB_G69bz7U057.png" alt="png" /></p>
|
||||
<p>招聘信息来源拉勾网</p>
|
||||
<p>我也看到很多测试同学会在简历上写:“熟悉 JMeter 的基本使用和性能测试。”</p>
|
||||
<p>但当我在面试时问:“性能测试的基本过程是什么?”很多人说“我就是用 JMeter 做了脚本”,<strong>至于“如何监控数据?”“需要监控哪些数据?”这样的问题,回答就更是模糊不清了</strong>。</p>
|
||||
|
||||
@@ -170,9 +170,9 @@ function hide_canvas() {
|
||||
<p>这是你在 JMeter 的线程组元件中的线程属性,线程组建立是你使用 JMeter 进行性能测试最基础的步骤,压力发起策略几乎都依赖于这个元件。</p>
|
||||
<h4>线程与循环</h4>
|
||||
<p>我们先来看两张图,看看它们之间有什么区别。</p>
|
||||
<p><img src="assets/Cip5yF_1Ha-ADn1PAABkFOXpDRg902.png" alt="Drawing 0.png" /></p>
|
||||
<p><img src="assets/Cip5yF_1Ha-ADn1PAABkFOXpDRg902.png" alt="png" /></p>
|
||||
<p>图 1:设置图 A</p>
|
||||
<p><img src="assets/CgpVE1_1HbmATgMeAACJsBol0Mc903.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/CgpVE1_1HbmATgMeAACJsBol0Mc903.png" alt="png" /></p>
|
||||
<p>图 2:设置图 B</p>
|
||||
<p>从两张图的对比中,我们可以看到图 1 和图 2 的区别在于线程数和循环次数,一个是 1 和 10,一个则是 10 和 1。从结果来看,图 1 和图 2 都是发送了 10 个请求,那它们的核心区别是什么呢? 我们不妨来看两段代码演示。</p>
|
||||
<p>先来看图 1 的代码演示:</p>
|
||||
@@ -206,10 +206,10 @@ Thread-9 //不同的线程
|
||||
<h4>Ramp-Up</h4>
|
||||
<p>Ramp-Up 其实是一个可选项,如果没有特殊要求,保持默认配置脚本即可。如果填 1,代表在 1 秒内所有设置线程数全部启动。不过这个是理论上的,实际启动时间也依赖于硬件的接受程度。如果硬件跟不上,启动时间自然也会增加。</p>
|
||||
<p>在有的性能测试场景中,如果你不想在性能测试一开始让服务器的压力过大,希望按照一定的速度增加线程到既定数值,你就可以使用这个选项。比如我想用 10 个线程进行测试,启动速度是每秒 2 个线程,就可以在这里填 5,如下所示:</p>
|
||||
<p><img src="assets/Cip5yF_1HcyABzDxAABletIULYA933.png" alt="Drawing 2.png" /></p>
|
||||
<p><img src="assets/Cip5yF_1HcyABzDxAABletIULYA933.png" alt="png" /></p>
|
||||
<p>图 3:设置图 C</p>
|
||||
<p>我们来通过运行展示一下。</p>
|
||||
<p><img src="assets/CgpVE1_1HdKAeBVLAAG5WhpVanI202.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/CgpVE1_1HdKAeBVLAAG5WhpVanI202.png" alt="png" /></p>
|
||||
<p>图 4:生成线程数</p>
|
||||
<p>我使用了监听器中的用表格查看结果插件。通过这组数据可以看到,每秒产生了 2 个新的线程,合计在 5 秒内完成。</p>
|
||||
<h3>组件和元件</h3>
|
||||
@@ -238,10 +238,10 @@ Thread-9 //不同的线程
|
||||
<p>我经常看到有的测试人员在需要在后置处理器中使用 BeanShell PostProcesor 的时候,错误地用了前置处理器中的 Beanshell PreProcessor,导致系统报错,无法实现预期的功能,甚至是测试无法进行下去。</p>
|
||||
<h4>元件作用域</h4>
|
||||
<p>以上说的都是组件相关的东西,这里就来看看元件作用域。我们先来看一张图:</p>
|
||||
<p><img src="assets/Cip5yF_1He-ALsdzAACx-Dpj8Qo799.png" alt="Drawing 5.png" /></p>
|
||||
<p><img src="assets/Cip5yF_1He-ALsdzAACx-Dpj8Qo799.png" alt="png" /></p>
|
||||
<p>图 7:结果树 1、2、3</p>
|
||||
<p>在图中可以看到,我在不同位置放了 3 个一样的元件“查看结果树”(为了方便区分,我分别标记了 1、2、3)。运行后发现,查看结果树 1(图 8)里面显示了 HTTP1 和 HTTP2,而插件结果树 2 里只有 HTTP1,查看结果树 3 里面只有 HTTP2。</p>
|
||||
<p><img src="assets/CgpVE1_1HfyAP0epAAA7IET5M00253.png" alt="Drawing 6.png" /></p>
|
||||
<p><img src="assets/CgpVE1_1HfyAP0epAAA7IET5M00253.png" alt="png" /></p>
|
||||
<p>图 8:查看结果树 1 的显示图</p>
|
||||
<p>这是为什么呢?这就要说到元件作用域了。</p>
|
||||
<p>通过截图可以发现 JMeter 元件除了从上到下的顺序外,有还具备一定的层次结构,比如图 5 中的响应断言和查看结果树,它相对于取样器存在父子组件的关系,说白了就是 HTTP 元件对取样器有效的区域,比如查看结果树 2 是 HTTP1 请求的子节点,那它就只对 HTTP1 生效;如果父节点是测试计划,那就会对测试计划下的 HTTP1 和 HTTP2 都生效。</p>
|
||||
|
||||
@@ -181,18 +181,18 @@ function hide_canvas() {
|
||||
<h4>CSV Data Set Config</h4>
|
||||
<p>CSV Data Set Config 的可配置选项较多,也是目前性能测试参数化时使用最多的插件,这里我就重点介绍一下 CSV Data Set Config。</p>
|
||||
<p>在配置组件中添加元件 CSV Data Set Config,如下图所示:</p>
|
||||
<p><img src="assets/Ciqc1F_7uM2AMFOoAAFcsKt5GDc012.png" alt="Drawing 0.png" /></p>
|
||||
<p><img src="assets/Ciqc1F_7uM2AMFOoAAFcsKt5GDc012.png" alt="png" /></p>
|
||||
<p>图 1:CSV Data Set Config</p>
|
||||
<p>我们来看一下 CSV Data Set Config 各项的含义。</p>
|
||||
<p><strong>文件名</strong>:顾名思义,这里填写文件的名字即可。</p>
|
||||
<p>保存参数化数据的文件目录,我这边是将 user.csv 和脚本放置在同一路径下。在这里我要推荐一个小技巧,就是“<strong>相对路径</strong>”。使用绝对路径,在做脚本迁移时大部分情况下都需要修改。如果你是先在 Windows 或 Mac 机器上修改脚本,再将脚本上传到 Linux 服务器上执行测试的,那你就可以用相对路径,这样就不用频繁修改该选项了。</p>
|
||||
<p><strong>文件编码</strong>:指定文件的编码格式,设置的格式一般需要和文件编码格式一致,大部分情况下保存编码格式为 UTF-8 即可。</p>
|
||||
<p><strong>变量名称</strong>:对应参数文件每列的变量名,类似 Excel 文件的文件头,主要是作为后续引用的标识符,一般使用英文。如下图所示:</p>
|
||||
<p><img src="assets/CgqCHl_7uN-ALfhLAADCt_4kBrI773.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/CgqCHl_7uN-ALfhLAADCt_4kBrI773.png" alt="png" /></p>
|
||||
<p>图 2:user.csv</p>
|
||||
<p>图中我标示了 name 和 password,相对应 user.csv 中的第一列和第二列。</p>
|
||||
<p>那如何引用需要的文件数据?通过“${变量名称}”就可以了,如下图所示:</p>
|
||||
<p><img src="assets/Ciqc1F_7uOaACU7pAACyoK2kugg837.png" alt="Drawing 2.png" /></p>
|
||||
<p><img src="assets/Ciqc1F_7uOaACU7pAACyoK2kugg837.png" alt="png" /></p>
|
||||
<p>图 3:引用演示图</p>
|
||||
<p><strong>忽略首行</strong>: 第一行不读取。比如图 2 的第一行我只是标示这一列是什么类型的数据,实际上并不是需要读取的业务数据,此时就可以采用忽略首行。</p>
|
||||
<p><strong>分隔符</strong>:用来标示参数文件中的分隔符号,与参数文件中的分隔符保持一致即可。</p>
|
||||
@@ -216,7 +216,7 @@ function hide_canvas() {
|
||||
<h4>JMeter 如何实现关联</h4>
|
||||
<p>JMeter实现关联有 3 种方式:边界提取器,通过左右边界的方式关联需要的数据;Json Extractor提取器,针对返回的 json 数据类型;正则表达式提取器,通过正则表达式去提取数据,实现关联。</p>
|
||||
<p>正则表达式提取器是最为常用,也是这里我要向你介绍的关联方式。我们来看下面的例子:</p>
|
||||
<p><img src="assets/Cip5yF_7uPGATVcpAABwbh3j2xc538.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/Cip5yF_7uPGATVcpAABwbh3j2xc538.png" alt="png" /></p>
|
||||
<p>图 4:正则表达式提取器</p>
|
||||
<p>我们来看一下正则表达式提取器中每一项的含义。</p>
|
||||
<ul>
|
||||
@@ -236,10 +236,10 @@ function hide_canvas() {
|
||||
<ul>
|
||||
<li><strong>缺省值</strong>:正则匹配失败时的取值。比如这里我设置的是 null(token 值取不到时就会用 null 代替)。上面我们已经匹配了 token 值,在被测接口传参处直接用“${token}”就可以了。</li>
|
||||
</ul>
|
||||
<p><img src="assets/CgpVE1_7uPyAFTS7AAAim7aU0H8431.png" alt="Drawing 4.png" /></p>
|
||||
<p><img src="assets/CgpVE1_7uPyAFTS7AAAim7aU0H8431.png" alt="png" /></p>
|
||||
<p>图 5:关联 Token</p>
|
||||
<p>关联后就可以看到,每次都能进行正常的业务返回了。</p>
|
||||
<p><img src="assets/CgpVE1_7uQWAYQQjAAIhTkpdTuc704.png" alt="Drawing 5.png" /></p>
|
||||
<p><img src="assets/CgpVE1_7uQWAYQQjAAIhTkpdTuc704.png" alt="png" /></p>
|
||||
<p>图 6:关联后正常业务返回</p>
|
||||
<h3>总结</h3>
|
||||
<p>这一讲我介绍了参数化策略以及使用场景。作为性能测试中最常用到的操作,你不仅要学会基本操作,还需要思考参数化策略适合的场景以及参数化数据对性能测试的影响。说到这里,我就要问一个问题了:不同的参数对性能结果会不会有影响?</p>
|
||||
|
||||
@@ -162,7 +162,7 @@ function hide_canvas() {
|
||||
<p><strong>脚本构建就是编写脚本</strong>,是你正式开始执行性能测试的第一步,对于常规的请求来说只需要通过界面的指引就可以完成,这个是非常容易的,但是上手容易不代表你使用方法科学,下面我带你看看常见的误区。</p>
|
||||
<h4>一个线程组、一条链路走到底</h4>
|
||||
<p>先来看下这样一张线程组的图:</p>
|
||||
<p><img src="assets/CgqCHl_-p9GABuguAABBUSnBL1I633.png" alt="Drawing 0.png" /></p>
|
||||
<p><img src="assets/CgqCHl_-p9GABuguAABBUSnBL1I633.png" alt="png" /></p>
|
||||
<p>图 1:一个线程组</p>
|
||||
<p>图中包含了注册、登录、浏览商品、查看订单等,它们在同一个线程组,基于同一线程依次进行业务。这样的做法其实和自动化非常相似。</p>
|
||||
<p>比如张三先注册一个网站,然后进行登录、添加购物车等操作。但仔细想一想,对于一个网站的性能而言,这么考虑是有些问题的。</p>
|
||||
@@ -183,7 +183,7 @@ function hide_canvas() {
|
||||
<p>在正确构建了脚本之后,我们就要来看如何执行脚本了。脚本执行就是你怎么去<strong>运行脚本</strong>,可能有的同学会一头雾水,我直接点击界面上的运行按钮不就行了吗?事实上真正的压测可不是这个样子的。</p>
|
||||
<h4>界面化执行性能测试</h4>
|
||||
<p>一些测试人员在 Windows 或 Mac 环境编写完脚本后,会直接用界面化的方式进行性能测试,这样的做法是非常不规范的。打开 JMeter 界面之后就会弹出提示,如图 2 所示:</p>
|
||||
<p><img src="assets/CgqCHl_-p-SAVYK0AAGf14hnG0w748.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/CgqCHl_-p-SAVYK0AAGf14hnG0w748.png" alt="png" /></p>
|
||||
<p>图 2:界面化性能测试提示</p>
|
||||
<p>很多人会选择直接忽略掉,但图中的第一段是这样的:</p>
|
||||
<blockquote>
|
||||
@@ -210,7 +210,7 @@ function hide_canvas() {
|
||||
<li>-o 表示指定的生成结果文件夹位置。</li>
|
||||
</ul>
|
||||
<p>我们来看一下执行了上面的脚本后的实际效果,如图 3 所示:</p>
|
||||
<p><img src="assets/CgqCHl_-p_GATU1yAACmJ7JsOJs941.png" alt="Drawing 2.png" /></p>
|
||||
<p><img src="assets/CgqCHl_-p_GATU1yAACmJ7JsOJs941.png" alt="png" /></p>
|
||||
<p>图 3:脚本执行后的效果</p>
|
||||
<p>从图中可以看到,命令行的方式直接产生了总的 TPS、报错和一些时间层级的指标。命令行的执行方式规避了一些图形化界面存在的问题,但这样的结果输出方式存在 2 个问题:</p>
|
||||
<ul>
|
||||
@@ -220,7 +220,7 @@ function hide_canvas() {
|
||||
<p>这 2 个问题都有个关键词是“实时”,是在压测过程中容易遗漏的点。虽然压测之后我们有很多方式可以回溯,但在性能测试过程中,发现、排查、诊断问题是必不可少的环节,它能够帮助我们以最快的速度发现问题的线索,让问题迅速得到解决。</p>
|
||||
<p>先来看第一点:<strong>看不到实时的接口返回报错的具体信息</strong>。</p>
|
||||
<p>jmeter.log 刚刚执行过程中记录了哪些内容呢?如图 4 所示:</p>
|
||||
<p><img src="assets/CgqCHl_-p_yAeoEUAACugfrmrq0387.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/CgqCHl_-p_yAeoEUAACugfrmrq0387.png" alt="png" /></p>
|
||||
<p>图 4:jmeter.log 执行时记录的内容</p>
|
||||
<p>你可以看到只能显示报错率,但看不到具体的报错内容,那如何去解决呢?一般我会使用 beanshell,把判定为报错的内容增加到 log 里。beanshell 的示意代码如下所示,你可以根据自己的需求改进。</p>
|
||||
<pre><code>String response = prev.getResponseDataAsString();
|
||||
@@ -236,12 +236,12 @@ if (code.equals("200")){//根据返回状态码判断
|
||||
}
|
||||
</code></pre>
|
||||
<p>这样就会自动在 jmeter.log 中打印出具体的报错信息,如图 5 所示:</p>
|
||||
<p><img src="assets/Cip5yF_-qAaAX8ChAADQYOI-pf0391.png" alt="Drawing 4.png" /></p>
|
||||
<p><img src="assets/Cip5yF_-qAaAX8ChAADQYOI-pf0391.png" alt="png" /></p>
|
||||
<p>图 5:报错信息</p>
|
||||
<p>客户端的日志只是我们需要关注的点之一,排查错误的根因还需要结合服务端的报错日志,一般来说服务端的报错日志都有相关的平台记录和查询,比较原始的方式也可以根据服务器的路径找相关日志。</p>
|
||||
<p>我们再来看第二个问题:<strong>看不到综合场景下的每个接口的实时处理能力</strong>。</p>
|
||||
<p>我个人认为原生的实时查看结果是有些鸡肋的,如果想实时且直观地看到每个接口的处理能力,我比较推荐 JMeter+InfluxDB+Grafana 的方式,基本流程如下图所示:</p>
|
||||
<p><img src="assets/CgqCHl_-zTuAMf2DAABpdYPtYUw826.png" alt="Lark20210113-183606.png" /></p>
|
||||
<p><img src="assets/CgqCHl_-zTuAMf2DAABpdYPtYUw826.png" alt="png" /></p>
|
||||
<p>图 6:JMeter+InfluxDB+Grafana</p>
|
||||
<p>JMeter 的二次开发可以满足很多定制化的需求,但也比较考验开发的能力(关于二次开发,我会在《04 | JMeter 二次开发其实并不难 》中介绍)。如果不想进行二次开发,JMeter+InfluxDB+Grafana 也是一种比较好的实现方式了。</p>
|
||||
<p>下面,我会对 InfluxDB 和 Grafana 做一个简单的介绍,包括特点、安装等。</p>
|
||||
@@ -270,13 +270,13 @@ show measurements;
|
||||
</code></pre>
|
||||
<p>InfluxDB 成功安装并且建库之后,我们就可以来配置 JMeter 脚本了。配置过程可以分为以下 3 步。</p>
|
||||
<p>(1)添加核心插件,在 listener 组件中选择 Backend Listener(如下图所示)。</p>
|
||||
<p><img src="assets/Cip5yF_-qB6AfHs5AAC0sfc1PXk340.png" alt="Drawing 6.png" /></p>
|
||||
<p><img src="assets/Cip5yF_-qB6AfHs5AAC0sfc1PXk340.png" alt="png" /></p>
|
||||
<p>图 7:添加 Backkend Listenner</p>
|
||||
<p>(2)Backend Listener implementation 中选择第二项(如下图所示)。</p>
|
||||
<p><img src="assets/Cip5yF_-qCeAbgnDAABvAV5R0ZE972.png" alt="Drawing 7.png" /></p>
|
||||
<p><img src="assets/Cip5yF_-qCeAbgnDAABvAV5R0ZE972.png" alt="png" /></p>
|
||||
<p>图 8:Backend Listener implementation</p>
|
||||
<p>(3)配置 InfluxDB URL,示例“http://127.0.0.1:8086/write?db=jmeter”;IP 为实际 InfluxDB 地址的 IP,DB 的值是 InfluxDB 中创建的库名字(如下图所示)。</p>
|
||||
<p><img src="assets/Cip5yF_-qC6AJwqYAADAf5IPUoI636.png" alt="Drawing 8.png" /></p>
|
||||
<p><img src="assets/Cip5yF_-qC6AJwqYAADAf5IPUoI636.png" alt="png" /></p>
|
||||
<p>图 9:配置连接 influxdb 库的具体信息</p>
|
||||
<p>配置完后运行 JMeter 脚本,再去 InfluxDB 的 JMeter 数据库中查看是否有数据,有数据则代表如上链路配置没有问题。</p>
|
||||
<p>我们再来了解一下 Grafana。</p>
|
||||
@@ -299,27 +299,27 @@ show measurements;
|
||||
默认账号/密码:admin/admin
|
||||
</code></pre>
|
||||
<p>输入密码后如果出现了如下界面,说明 Grafana 安装成功了。</p>
|
||||
<p><img src="assets/Cip5yF_-qD2AKS4tAAB2Nrw_SqU315.png" alt="Drawing 9.png" /></p>
|
||||
<p><img src="assets/Cip5yF_-qD2AKS4tAAB2Nrw_SqU315.png" alt="png" /></p>
|
||||
<p>图 10:Grafana 界面</p>
|
||||
<h4>数据源配置</h4>
|
||||
<p>为什么要配置数据源呢,简单来说就是 Grafana 需要获取数据去展示,数据源的配置就是告诉你去哪里找数据,配置安装的 InfluxDB 地址和端口号,如下图所示:</p>
|
||||
<p><img src="assets/Cip5yF_-qEWADsLAAAA3aGGImVo621.png" alt="Drawing 10.png" /></p>
|
||||
<p><img src="assets/Cip5yF_-qEWADsLAAAA3aGGImVo621.png" alt="png" /></p>
|
||||
<p>图 11:配置地址和端口号</p>
|
||||
<p>然后输入 InfluxDB 中写入的数据库名字,如下图所示:</p>
|
||||
<p><img src="assets/Cip5yF_-qFCABo-uAABwZqAxu-0952.png" alt="Drawing 11.png" /></p>
|
||||
<p><img src="assets/Cip5yF_-qFCABo-uAABwZqAxu-0952.png" alt="png" /></p>
|
||||
<p>图 12:数据库名字</p>
|
||||
<p>输入完成之后可以 Save & Test,如出现以下示意图即配置成功:</p>
|
||||
<p><img src="assets/CgpVE1_-qFeAUOatAAA-H-XTdm0808.png" alt="Drawing 12.png" /></p>
|
||||
<p><img src="assets/CgpVE1_-qFeAUOatAAA-H-XTdm0808.png" alt="png" /></p>
|
||||
<p>图 13:配置成功</p>
|
||||
<h4>导入 JMeter 模板</h4>
|
||||
<p>为了达到更好的展示效果,Grafana 官网提供了针对性的展示模版。先下载 <a href="https://grafana.com/Grafana/dashboards?search=InfluxDB">JMeter 模板</a>,然后再导入 Grafana。</p>
|
||||
<p><img src="assets/CgqCHl_-qGCAG68rAABPyrMBy0M066.png" alt="Drawing 13.png" /></p>
|
||||
<p><img src="assets/CgqCHl_-qGCAG68rAABPyrMBy0M066.png" alt="png" /></p>
|
||||
<p>图 14:导入 JMeter 模板</p>
|
||||
<p>配置完成后,运行 JMeter 脚本。如果在界面右上方下拉选择 5s,则每 5s 更新一次:</p>
|
||||
<p><img src="assets/Ciqc1F_-qGiAX-znAAD8972KlRs589.png" alt="Drawing 14.png" /></p>
|
||||
<p><img src="assets/Ciqc1F_-qGiAX-znAAD8972KlRs589.png" alt="png" /></p>
|
||||
<p>图 15:运行 JMeter 脚本</p>
|
||||
<p>如上图便是完成了实时压测情况下运行结果的实时展示图,你可以以此为基础,进行多接口的数据采集,相应增加脚本里的 Backend Listener 插件,区分不同的 application name 名称,你会看到不同的接口数据都进入 influxdb 数据库中。并且 Grafana 从 Edit 中进入, 你可以根据不同的 application name 修改 SQL 来区分展示。</p>
|
||||
<p><img src="assets/CgqCHl_-qHCABegiAAAtFFIYLCc365.png" alt="Drawing 15.png" /></p>
|
||||
<p><img src="assets/CgqCHl_-qHCABegiAAAtFFIYLCc365.png" alt="png" /></p>
|
||||
<p>图 16:编辑 Grafana</p>
|
||||
<h3>总结</h3>
|
||||
<p>这一讲我们主要介绍了构建和执行性能测试脚本时的一些注意事项,总结了目前业内使用 JMeter 常见的方法。你不仅需要知道这些常见的手段,也需要知道为什么要这么做,这么做有什么好处,同样随着实际采集数据指标的增高,这些做法可能还会存在哪些缺陷或者注意点,如果上述内容你都能考虑清楚了,相信你也就掌握工具了。</p>
|
||||
|
||||
@@ -296,7 +296,7 @@ public Arguments getDefaultParameters() {
|
||||
}
|
||||
</code></pre>
|
||||
<p>这一步实际的效果图可以看下方的初始界面图。</p>
|
||||
<p><img src="assets/Cip5yGAFPAKAEmBcAAB8OFXAQYo399.png" alt="Drawing 0.png" /></p>
|
||||
<p><img src="assets/Cip5yGAFPAKAEmBcAAB8OFXAQYo399.png" alt="png" /></p>
|
||||
<p>初始界面图</p>
|
||||
<p>(3)在上一步骤进行了参数的输入,接下来实现接收这些参数,并进行参数的输入、发送、返回判断等,如下代码所示:</p>
|
||||
<pre><code>public SampleResult runTest(JavaSamplerContext javaSamplerContext) {
|
||||
@@ -325,7 +325,7 @@ public Arguments getDefaultParameters() {
|
||||
)
|
||||
</code></pre>
|
||||
<p>(4)完成后打成 jar 包放到 /lib/ext 下重启 JMeter 即可,实际的效果图你可以参考上方的初始界面图和下方的运行图。</p>
|
||||
<p><img src="assets/Cip5yGAFPBOAecdTAAC22dYxB2Q653.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/Cip5yGAFPBOAecdTAAC22dYxB2Q653.png" alt="png" /></p>
|
||||
<p>运行图</p>
|
||||
<h3>自定义函数助手</h3>
|
||||
<p>通过 Java Sampler 插件开发的学习,我们知道 JMeter 相关插件的开发其实都是有一定的套路可循,那 JMeter 函数助手开发也不例外,接下来进行函数助手开发流程的了解。</p>
|
||||
@@ -340,7 +340,7 @@ public Arguments getDefaultParameters() {
|
||||
<pre><code> public String getReferenceKey() {}
|
||||
</code></pre>
|
||||
<p>这一方法表示函数助手对话框中的下拉框中显示的函数名称,如下图所示:</p>
|
||||
<p><img src="assets/Cip5yGAFPB6ADKzZAAB86ItSH7Q951.png" alt="Drawing 2.png" /></p>
|
||||
<p><img src="assets/Cip5yGAFPB6ADKzZAAB86ItSH7Q951.png" alt="png" /></p>
|
||||
<pre><code>public List<String> getArgumentDesc() {}
|
||||
</code></pre>
|
||||
<p>这一方法是设置入参的描述语,用于函数助手对话框中,显示函数名称提示。</p>
|
||||
|
||||
@@ -170,10 +170,10 @@ function hide_canvas() {
|
||||
<li>结果追溯时,我们需要找一些历史数据却发现并没有<strong>存档或共享</strong>。</li>
|
||||
</ul>
|
||||
<p>这些场景使我们的性能测试平台具有了更多现实意义,我们希望有一个<strong>可以协作共享</strong>,并能够<strong>追溯历史数据的性能测试平台</strong>。基于这点我梳理了性能测试平台的基础功能,如下图所示:</p>
|
||||
<p><img src="assets/Ciqc1GAFPJ-ASd02AAB5h9Xz8Ok173.png" alt="Drawing 0.png" /></p>
|
||||
<p><img src="assets/Ciqc1GAFPJ-ASd02AAB5h9Xz8Ok173.png" alt="png" /></p>
|
||||
<p>图 1:性能测试平台基础功能</p>
|
||||
<p>目前市面上的性能测试平台大多是基于 JMeter 提供的 API 开发的,核心流程如下图所示:</p>
|
||||
<p><img src="assets/CgqCHmAFPKaACL8lAACO4B5j9fY519.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/CgqCHmAFPKaACL8lAACO4B5j9fY519.png" alt="png" /></p>
|
||||
<p>图 2:性能测试平台开发核心流程</p>
|
||||
<p>接下来我们根据这 4 个阶段来学习如何使用 JMeter 的 API 实现性能测试。</p>
|
||||
<h3>环境初始化</h3>
|
||||
@@ -197,7 +197,7 @@ HashTree jmxTree = SaveService.loadTree(file);
|
||||
</code></pre>
|
||||
<p>由于本地脚本是 JMeter 客户端手动生成的,所以这里只需要做读取文件操作即可,loadTree 会把 jmx 文件转成内存对象,并返回内存对象中生成的 HashTree。</p>
|
||||
<p>那<strong>创建脚本文件</strong>是怎么做的呢?它是通过 API 构建测试计划,然后再保存为 JMeter 的 jmx 文件格式。核心步骤如下图所示:</p>
|
||||
<p><img src="assets/Cip5yGAFPLWAE3XRAAHi31Yd_oY766.png" alt="Drawing 2.png" /></p>
|
||||
<p><img src="assets/Cip5yGAFPLWAE3XRAAHi31Yd_oY766.png" alt="png" /></p>
|
||||
<p>图 3:脚本文件创建步骤</p>
|
||||
<p>该方式需要自己构建 HashTree,我们可以参考 JMeter 客户端生成的 jmx 文件。</p>
|
||||
<p>通过观察 jmx 文件我们可以知道需要构建的 jmx 结构,最外层是 TestPlan,TestPlan 是 HashTree 结构,包含 ThreadGroup(线程组)、HTTPSamplerProxy、LoopController(可选)、ResultCollector(结果收集)等节点。</p>
|
||||
@@ -282,7 +282,7 @@ engine.run();
|
||||
<h3>结果收集</h3>
|
||||
<p>性能实时数据采集可以更方便发现和分析出现的性能问题。我们在性能测试平台的脚本页面点击执行了性能测试脚本,当然希望能看到实时压测的性能测试数据,如果等测试完再生成测试报告,时效性就低了。</p>
|
||||
<p>性能测试平台结果收集的流程图如下:</p>
|
||||
<p><img src="assets/CgpVE2AFPMiAUfRUAAHZ0vk2YZg058.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/CgpVE2AFPMiAUfRUAAHZ0vk2YZg058.png" alt="png" /></p>
|
||||
<p>图 4:结果收集流程图</p>
|
||||
<p>上面流程图中与 JMeter 关联最密切的是第 1 步,获取 JMeter 结果数据。那我们如何获取这些数据呢?</p>
|
||||
<p>JMeter 性能测试用例执行完成之后会生成结果报告,既然生成了结果报告,那 JMeter 源码里一定有获取每次 loop 执行结果的地方。我们可以找到这个类,然后新建一个类去继承这个类,再重写每次结果获取的方法就能得到实时结果了。如果获取每次 loop 执行结果的是私有方法,我们也可以通过反射拿到它。</p>
|
||||
@@ -350,7 +350,7 @@ while (true) {
|
||||
}
|
||||
</code></pre>
|
||||
<p>实现后的效果图如下:</p>
|
||||
<p><img src="assets/Cip5yGAFPNWAIzKMAACkBrnfdmY418.png" alt="Drawing 4.png" /></p>
|
||||
<p><img src="assets/Cip5yGAFPNWAIzKMAACkBrnfdmY418.png" alt="png" /></p>
|
||||
<p>图 5 :效果图</p>
|
||||
<blockquote>
|
||||
<p>其中横坐标是时间,纵坐标是实时处理能力的展示,可以看到每秒请求次数在 400 ~ 600 之间波动。</p>
|
||||
|
||||
@@ -175,9 +175,9 @@ function hide_canvas() {
|
||||
<p>那我们的外网用户如何能够访问到内部的应用呢,Nginx 可以暴露端口给外网用户访问,当接收到请求之后分发给内部的服务器,此时的 Nginx 扮演的是<strong>反向代理的角色</strong>。这样一个过程,客户端是明确的,但对于访问到哪台具体的应用服务器是不明确的。就好像一个上海飞北京的班次,可能还有很多乘客到达北京之后会去沈阳、哈尔滨等,对于出发地上海而言,这个是不关心的。</p>
|
||||
<h4>负载均衡</h4>
|
||||
<p>负载均衡是 Nginx 最重要也是最常见的功能,为什么需要负载均衡呢?你可以想一想,比如你线上只有一台应用服务器,如下图所示。</p>
|
||||
<p><img src="assets/Ciqc1GAOtASAFkMnAAB8g0S7vEo985.png" alt="-1.png" /></p>
|
||||
<p><img src="assets/Ciqc1GAOtASAFkMnAAB8g0S7vEo985.png" alt="png" /></p>
|
||||
<p>但是随着用户体量的上升,一台服务器并不能支撑现有用户的访问,那你就会考虑使用两台或者多台服务器,如下图所示:</p>
|
||||
<p><img src="assets/CgqCHmAOtBuAdrykAADniCVZ-pg926.png" alt="-2.png" /></p>
|
||||
<p><img src="assets/CgqCHmAOtBuAdrykAADniCVZ-pg926.png" alt="png" /></p>
|
||||
<p>那用户如何能够相对均匀地访问到这些服务器呢,这就需要你去了解 Nginx 的负载均衡策略,简单来说,就是 Nginx 如何分发这些请求到后面的应用服务器集群,下面我介绍下 Nginx 的三种分配策略。</p>
|
||||
<p><strong>(1)轮询</strong></p>
|
||||
<p>也就是使用平均分配的方式,将每个请求依次分配到配置的后端服务器上。除非有服务宕机,才会停止分发。如下代码所示:</p>
|
||||
|
||||
@@ -169,7 +169,7 @@ function hide_canvas() {
|
||||
</ul>
|
||||
<h3>衡量指标 2:响应时间</h3>
|
||||
<p>除了 TPS,第二个比较重要的衡量指标是<strong>响应时间</strong>。响应时间和用户体验密切相关,我们往往把一次请求从客户端发出到返回客户端的时间作为响应时间。在实际工作中,我们会以 TPS 的量级来限制响应时间必须在多久之内。以下图 1 为例,从最左侧的客户端到最右侧的数据持久化再返回到客户端,这样一个来回的过程就是一次完整的请求响应时间。</p>
|
||||
<p><img src="assets/Cip5yGAR876AGx1TAAEaBWbe1-Q706.png" alt="shangchuan1.png" /></p>
|
||||
<p><img src="assets/Cip5yGAR876AGx1TAAEaBWbe1-Q706.png" alt="png" /></p>
|
||||
<p>图 1:请求响应的过程图</p>
|
||||
<p>上图描述的是在正常情况下的响应流程,但当你有了一定的性能测试实践之后,你会发现这样的过程并不是绝对的。比如有的业务第一次在数据库请求到数据后,应用层本地缓存会将数据存储在应用服务器上,也就是接下来在缓存有效时间内不会再去数据库取数据,而是在应用层得到数据后就会直接返回,所以响应时间会比第一次低不少,这也是随着性能测试的进行响应时间变低的原因之一。</p>
|
||||
<h3>衡量指标 3:报错率</h3>
|
||||
@@ -213,7 +213,7 @@ function hide_canvas() {
|
||||
<li><strong>服务维度</strong></li>
|
||||
</ul>
|
||||
<p>那什么是服务维度呢?以目前比较流行的电商微服务架构为例,我一般会做服务级别的拆分。先画一个示意图,方便你对微服务有个简单了解。</p>
|
||||
<p><img src="assets/CgpVE2AR9CGAI6D6AAEZb8DFzE8175.png" alt="shangchuan2.png" /></p>
|
||||
<p><img src="assets/CgpVE2AR9CGAI6D6AAEZb8DFzE8175.png" alt="png" /></p>
|
||||
<p>图 4:微服务架构图</p>
|
||||
<p>网关一般是请求进入应用层的第一个入口,也是统计网站入口访问量的方式之一。当我们的请求通过网关之后会下发到各个业务应用服务,如图 4 中所示的服务 A、B、C,我会按照确定的时间节点去统计各个服务的访问量数据。完成服务级的访问数据统计之后,我会继续按照时间维度统计服务下的接口访问数据。你可以看到,每个服务和每个接口的调用比例都不一样,如图 5 所示。</p>
|
||||
<p><img src="assets/Cip5yGAR9DOAGoHqAAKy0PYCcno557.png" alt="Gateway网关.png" /></p>
|
||||
|
||||
@@ -168,24 +168,24 @@ function hide_canvas() {
|
||||
</ul>
|
||||
<h3>单接口负载场景</h3>
|
||||
<p><strong>单接口负载场景就是通过模拟多线程对单接口进行负载测试</strong>。我的具体做法是选定线程数后持续循环运行一定时间,比如分别运行 100 线程、200 线程、300线程等,一般相同线程数运行 10~15 min,然后获取事务响应时间、TPS、报错率,监测测试系统的各服务器资源使用情况(CPU、内存、磁盘、网络等),把具体数据记录之后再开始跑下一个线程数。每一组线程数级别会有对应的 TPS,直到你找到 TPS 的拐点。如下图所示,横坐标是线程数,纵坐标是 TPS,线程数增加到 400 时出现了拐点。</p>
|
||||
<p><img src="assets/Cip5yGAXxtmALS-eAAJEi_Hw2MM930.png" alt="1.png" /></p>
|
||||
<p><img src="assets/Cip5yGAXxtmALS-eAAJEi_Hw2MM930.png" alt="png" /></p>
|
||||
<p>这里需要<strong>注意的点</strong>有两个。</p>
|
||||
<ul>
|
||||
<li>使用工具做性能测试时,动辄就是上千的线程数,所以如果你是一位初学者,我更加倾向于你从一个相对<strong>比较低的线程数梯度增加</strong>,这样才能够比较清晰地找到 TPS 的拐点。</li>
|
||||
<li>我还建议为<strong>每个虚拟用户级别做单独的场景</strong>,网上绝大部分的教程,在一个场景中做了很多梯度(如下图所示),这样只是看上去简单方便一些,其实很不利于分析和诊断,这个方式我并不推荐。因为并不是每一个量级的性能表现都是类似的,而且一个场景多梯度出来的报表也可能没你想象中的清晰明了。在 JMeter 的聚合报告中还会将结果数据平均化,<strong>这样的方式并不能准确地记录每个线程梯度对应的 TPS</strong>。而在一个场景里先固定虚拟用户可以将自己的精力聚焦在诊断上。</li>
|
||||
</ul>
|
||||
<p><img src="assets/CgpVE2AXxvSANDuYAAEQbLHr5vg312.png" alt="2.png" /></p>
|
||||
<p><img src="assets/CgpVE2AXxvSANDuYAAEQbLHr5vg312.png" alt="png" /></p>
|
||||
<h3>混合场景负载测试</h3>
|
||||
<p>混合场景是性能测试中最重要的场景之一,这个场景是为了最大程度模拟用户真实的操作。真实的线上操作不只有单接口的操作,一定是多种业务同时在进行,比如张三在浏览商品,李四在添加购物车等。</p>
|
||||
<p>所以混合场景测试会将多个接口按照实际大促时候的比例混合起来,然后增加线程数找出多个接口 TPS 的和对应的峰值。这个比例也是混合场景的关键,在[《07 | 你真的知道如何制定性能测试的目标吗?》]中已经较为详细地阐述了制定比例的方法,本讲就不再赘述。加用户运行的基本策略可以参考上文的<strong>单接口负载测试</strong>。混合场景执行除了要观察总的 TPS,还有一个非常关键的因素就是如何控制接口之间的调用比例,使其不能偏离预期。</p>
|
||||
<p><strong>如何使用 JMeter 去控制场景比例?</strong></p>
|
||||
<p>相信你已经知道线程数可以改变接口的 TPS,但是如果每次通过线程数调整这个过程会比较烦琐。JMeter 提供了一个能较好地解决这个问题的插件,叫作<strong>吞吐量控制器</strong>,它在逻辑控制器组件中,如下图所示:</p>
|
||||
<p><img src="assets/Cip5yGARB_2Ad2-zAACP3jPiNm4329.png" alt="Drawing 2.png" /></p>
|
||||
<p><img src="assets/Cip5yGARB_2Ad2-zAACP3jPiNm4329.png" alt="png" /></p>
|
||||
<p>我来简单介绍一下这个插件配置规则,默认的情况下使用的是<strong>百分比模式</strong>,也就是 Percent Excutions。吞吐量一栏对应的是 TPS 占比,我用 login 和 register 这两个接口来模拟下,</p>
|
||||
<p>login 接口配置比例是 80%,如下图所示,剩下的 20% 配置给 register。</p>
|
||||
<p><img src="assets/Cip5yGARCASAO_NdAABzzrCLU6s777.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/Cip5yGARCASAO_NdAABzzrCLU6s777.png" alt="png" /></p>
|
||||
<p>看下运行后的效果,我直接在 JMeter 中添加聚合报告元件,如下图所示:</p>
|
||||
<p><img src="assets/Cip5yGAXxxiAFWIcAAC-NsocAFk929.png" alt="5.png" /></p>
|
||||
<p><img src="assets/Cip5yGAXxxiAFWIcAAC-NsocAFk929.png" alt="png" /></p>
|
||||
<p>实际计算下来的值为 1778.2/2222.7≈0.8,这个数据是比较准确的。</p>
|
||||
<p>以上是我所说的基石场景,包括基准测试、负载测试、混合场景测试等,这三个场景是有<strong>依次执行的顺序关系</strong>的,按照顺序执行更容易发现问题且减少不必要的工作,比如你连基准测试都不通过,就没有必要进行负载测试了。所以我们在做每一次性能测试时,都不应该省去或者颠倒上述的场景步骤。</p>
|
||||
<p>接着我带你继续学习其他性能测试场景,为了达到相对应的性能测试目的,这些场景可以根据需求进行选择。</p>
|
||||
|
||||
@@ -174,7 +174,7 @@ function hide_canvas() {
|
||||
<h4>3.确定被测业务的部署架构</h4>
|
||||
<p>被测的业务部署架构是什么意思呢,简单来说就是被测服务涉及哪些<strong>组件</strong>,每个组件部署在哪些服务器上,服务器的配置是怎样的。你需要画一个部署架构示意图,有了这张图,才能知道如何做到全貌监控,以及遇到问题从哪些服务入手。</p>
|
||||
<p>我用一个自己画的架构示意图来说明这个问题,如下图所示,这是一个经典的链路:从客户端发起到服务端,服务端从代理层到应用层,最后到数据层。需要注意的是,你需要明确被测的环境里的各个服务有多少节点,比如客户层的压测机节点有几台,分别在哪个网段。同理我们可以去调研服务层和数据层的节点。</p>
|
||||
<p><img src="assets/Cgp9HWAaZMiALIK2AADJeLZ_6Lc496.png" alt="1-shangchuan.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAaZMiALIK2AADJeLZ_6Lc496.png" alt="png" /></p>
|
||||
<h4>4.对测试数据进行调研</h4>
|
||||
<p>关于测试数据调研,包含了非常多的内容,对于业务测试来说<strong>数据调研</strong>就是获取必要的参数来满足既定的场景可以跑通。那对于性能测试来说,需要做哪些方面的数据调研呢,我带你一一解读。</p>
|
||||
<p><strong>(1)数据库基础数据量分析</strong></p>
|
||||
@@ -202,12 +202,12 @@ function hide_canvas() {
|
||||
<li>涉及服务器基础监控,包括 CPU、磁盘、内存、网络等。</li>
|
||||
</ul>
|
||||
<p>硬件资源的监控只能算一个层面。那完成一次性能测试都需要监控什么呢,我用一个导图给你做一个概览。</p>
|
||||
<p><img src="assets/Ciqc1GARCG-AN3DWAACM6ml91Mg409.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/Ciqc1GARCG-AN3DWAACM6ml91Mg409.png" alt="png" /></p>
|
||||
<p>监控还有个很重要的点是<strong>设置阈值来报警</strong>,无论是线上和线下的性能测试,报警功能都是必需的。因为通过人工的观察,往往不能以最快的速度发现问题。一旦能够及时报警,涉及的人员就可以快速响应,尽可能降低风险。</p>
|
||||
<h4>7.性能测试排期涉及的人员</h4>
|
||||
<p>一般来说测试是上线前的最后一道关卡,也是发现问题的重要角色,所以项目上的风险会在测试阶段集中爆发。性能测试作为测试中的一部分,也会面临类似问题,这也考验你的项目管理能力。而且性能测试需要<strong>大量的数据和专门的环境</strong>,这部分的工作内容和资源需要更多支持,所以在你的性能测试方案中,首先要标明开展的阶段和日期,还要明确主负责人和协调人员。在此基础上还需要面对面 check 和落实。</p>
|
||||
<p>你可以参考如下的表格,具体的内容需要根据公司的情况来确定。这些任务并不是从上到下依次执行,可能存在并行的情况,比如某一些公司环境是由运维人员统一部署,这部分内容就可以和性能测试需求分析一起进行。</p>
|
||||
<p><img src="assets/CioPOWAaZIaAM1wjAACn_XcIGUo811.png" alt="3shangchuan.png" /></p>
|
||||
<p><img src="assets/CioPOWAaZIaAM1wjAACn_XcIGUo811.png" alt="png" /></p>
|
||||
<h3>总结</h3>
|
||||
<p>关于如何打造性能测试方案就讲到这里了,通过本讲的学习,你已经了解了做一份性能测试方案的基本要素和关键点。性能测试方案对于一些公司来说可能只是一份流程化的文档,但对于测试个人来说,这部分内容可以体现出你的思考和计划。尤其对于性能测试新手来说,一定要充分思考每项的意义,这样你才能快速提升。</p>
|
||||
</div>
|
||||
|
||||
@@ -158,7 +158,7 @@ function hide_canvas() {
|
||||
<p>那我提一个问题,为什么会有硬件瓶颈呢?或者我说得更直白一点,如果服务器上没有应用还会造成硬件瓶颈吗?显然是不会的,所以我想向你传递一个观点:<strong>呈现出来的硬件瓶颈绝大多数是表象问题</strong>,我们往往需要在系统应用上寻找问题的根因。而寻找系统问题的根因,对于系统链路监控也是必不可少的,所以这一讲我将带你学习如何进行基于系统链路的监控。</p>
|
||||
<h3>为什么要链路监控?</h3>
|
||||
<p>随着微服务的流行,链路监控越来越受重视。微服务架构是根据业务进行拆分,对外统一暴露API 接口,而内部可能是分布式服务、分布式对象存储等,如图 1 所示。</p>
|
||||
<p><img src="assets/CioPOWAkcOeAJyX6AAH4oq8oV4s515.png" alt="1shangchuan.png" /></p>
|
||||
<p><img src="assets/CioPOWAkcOeAJyX6AAH4oq8oV4s515.png" alt="png" /></p>
|
||||
<p>图 1:微服务架构</p>
|
||||
<p>这些组件共同构成了复杂的分布式网络。而分布式系统一旦出现问题,比如一个请求经过多个微服务之后出现了调用失败的问题,或者一个请求经过多个微服务之后 Response 时间过长,但具体是哪个微服务节点的问题我们并不知道。只能去服务器上查看调用经过的每个微服务的日志,当然这种方式的效率是比较低的,相当于人肉运维。</p>
|
||||
<p>随着业务体系越来越复杂,加上服务间的相互依赖关系,微服务其中一个节点出现了问题,很可能牵一发而动全身,导致严重的后果。在这样的情况下,分布式链路监控的价值就体现出来了,它可以让你清晰地知道跨服务调用的链路耗时信息、执行方法等,并从整体到局部将信息呈现出来,可以帮助你节约故障排查时间。</p>
|
||||
@@ -178,7 +178,7 @@ function hide_canvas() {
|
||||
<p>综上我将以 SkyWalking 为例给你介绍下链路监控,希望通过介绍,你可以掌握 SkyWalking 的具体使用步骤和链路监控工具可以给我们带来什么好处,通过本讲的学习你也可以自由选择链路监控工具去实践。</p>
|
||||
<h3>SkyWalking 的模块分析</h3>
|
||||
<p>首先来看下 SkyWalking 的组件示意图:</p>
|
||||
<p><img src="assets/CioPOWAeP5uAc_faAAPptpn_1oo892.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/CioPOWAeP5uAc_faAAPptpn_1oo892.png" alt="png" /></p>
|
||||
<p>图 2:SkyWalking 的组件示意图</p>
|
||||
<ul>
|
||||
<li><strong>Tracing 和 Metric :</strong> 在应用上采集 Tracing(调用链数据)和 Metric(指标)信息通过 HTTP 或者 gRPC 方式发送数据到 Analysis Platform。</li>
|
||||
@@ -192,7 +192,7 @@ function hide_canvas() {
|
||||
tar -zxvf v8.0.1.tar.gz
|
||||
</code></pre>
|
||||
<p>解压后可以看到如下文件夹:</p>
|
||||
<p><img src="assets/CioPOWAeP6iABnf7AACwQCQbs98587.png" alt="Drawing 2.png" /></p>
|
||||
<p><img src="assets/CioPOWAeP6iABnf7AACwQCQbs98587.png" alt="png" /></p>
|
||||
<p>我们讲解下这个主要文件的作用。</p>
|
||||
<p>(1)<strong>修改配置文件 config/application.yml</strong>。在这里先进行数据库的配置,我使用当前服务器上的 mysql 来进行存储:</p>
|
||||
<pre><code> mysql:
|
||||
@@ -216,7 +216,7 @@ SkyWalking Web Application started successfully!
|
||||
</code></pre>
|
||||
<p>这里我强烈建议,不管是第一步还是第二步中的 started successfully,都并不意味着真正的启动成功,一般在提示 started successfully 后,还需要去 logs 文件夹下查看相关日志来判断启动过程中是否存在异常。</p>
|
||||
<p>UI 界面启动成功后示意图如下:</p>
|
||||
<p><img src="assets/CioPOWAeP7aAEe_mAAIEY3gT-2w743.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/CioPOWAeP7aAEe_mAAIEY3gT-2w743.png" alt="png" /></p>
|
||||
<p>(3)<strong>本地启动微服务</strong>。我 demo 里包含 system、auth、user 等服务,通过配置 SkyWalking Agent 的方式启动服务,示意如下:</p>
|
||||
<pre><code>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 &
|
||||
</code></pre>
|
||||
@@ -224,7 +224,7 @@ SkyWalking Web Application started successfully!
|
||||
<p>启动本地的微服务成功后,就可以访问服务,同时通过 SkyWalking 监控你可以看到服务部署图以及链路监控等,如下图所示:</p>
|
||||
<p><img src="assets/Cgp9HWAeP8KAOwR0AADQLStdVOY719.png.png" alt="img" /></p>
|
||||
<p>图 3:服务部署图</p>
|
||||
<p><img src="assets/CioPOWAeP8qALzJFAAMUz2rcn3k246.png" alt="Drawing 5.png" /></p>
|
||||
<p><img src="assets/CioPOWAeP8qALzJFAAMUz2rcn3k246.png" alt="png" /></p>
|
||||
<p>图 4:链路追踪图</p>
|
||||
<p>在我们进行链路追踪后,可能会出现一些超时、访问错误等异常,那我们如何能够更快地收到这些异常信息呢?</p>
|
||||
<h4>2.常见的报警方式</h4>
|
||||
@@ -237,13 +237,13 @@ SkyWalking Web Application started successfully!
|
||||
<p>随着钉钉越来越普及,很多公司都已经使用钉钉。员工在公司需要使用钉钉管理自己的考勤以及进行工作上的沟通,如果将监控报警信息推送到钉钉上其实就很方便的。不过也存在有的企业用的是其他沟通工具,不过对于报警推送到沟通软件上的原理都是类似的,接下来我会以钉钉作为模版来讲解如何进行报警信息的推送。</p>
|
||||
<h4>3.如何配置钉钉机器人?</h4>
|
||||
<p>(1)<strong>打开机器人管理页面</strong>。以 PC 端为例,打开 PC 端钉钉,进入首页面点击头像,在弹出框里选择机器人管理,打开管理页面后可以选择自定义,如下图所示:</p>
|
||||
<p><img src="assets/Cgp9HWAeP9WAKr2zAALfMHmtwh8462.png" alt="Drawing 6.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAeP9WAKr2zAALfMHmtwh8462.png" alt="png" /></p>
|
||||
<p>(2)在打开的机器人详情页面点击添加按钮,如下图所示:</p>
|
||||
<p><img src="assets/CioPOWAeP9qALHMNAAF-M44iggo590.png" alt="Drawing 7.png" /></p>
|
||||
<p><img src="assets/CioPOWAeP9qALHMNAAF-M44iggo590.png" alt="png" /></p>
|
||||
<p>(3)在打开的添加机器人页面输入机器人名字,选择要接收报警的钉钉群 ,设置机器人头像。根据需要勾选安全设置等就可以,点击完成之后,在页面拷贝出 Webhook 地址保存好,向这个地址发送 HTTP POST 请求,设置的 SkyWalking 钉钉报警群便能收到钉钉报警消息,如下图所示:</p>
|
||||
<p><img src="assets/Cgp9HWAeP-GAcQSLAAFB0-TVf6w116.png" alt="Drawing 8.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAeP-GAcQSLAAFB0-TVf6w116.png" alt="png" /></p>
|
||||
<p>配置好之后我们可以看到设置报警的钉钉群“SkyWalking 钉钉报警”出现了报警机器人消息,如下图所示:</p>
|
||||
<p><img src="assets/Cgp9HWAeP-qAURZCAAENpzpscRo136.png" alt="Drawing 9.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAeP-qAURZCAAENpzpscRo136.png" alt="png" /></p>
|
||||
<p>我们可以用 Linux 命令行工具 curl 快速验证是否可以推送成功,curl 命令行示意如下:</p>
|
||||
<pre><code>[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f684999982b6bcb2">[email protected]</a> ~]# curl 'https://oapi.dingtalk.com/robot/send?access_token=xxxxxxx' -H 'CONTENT-TyPE: application/json' -d '{"msgtype": "text","text": {"content": "业务报警"}}'
|
||||
{"errcode":0,"errmsg":"ok"}
|
||||
@@ -271,9 +271,9 @@ public void alarm(@RequestBody List<AlarmDto> alarmList){
|
||||
<pre><code> webhooks:-http://ip:port/dingdingAlarm
|
||||
</code></pre>
|
||||
<p>接下来我们测试下,比如 auth 服务获取验证码的接口出现错误,我们是可以在 SkyWalking 追踪页面清楚地看到的。同时对于其他相关的业务同学,也都可以在钉钉群收到报警信息,这样的方式在实际工作中非常实用。业务报错图和钉钉报警图如下所示:</p>
|
||||
<p><img src="assets/CioPOWAeP_mAAJfMAAOa59AALCQ026.png" alt="Drawing 10.png" /></p>
|
||||
<p><img src="assets/CioPOWAeP_mAAJfMAAOa59AALCQ026.png" alt="png" /></p>
|
||||
<p>图 5:业务报错图</p>
|
||||
<p><img src="assets/Cgp9HWAeQACAcTuwAAF2CS5plp8418.png" alt="Drawing 11.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAeQACAcTuwAAF2CS5plp8418.png" alt="png" /></p>
|
||||
<p>图 6:钉钉报警图</p>
|
||||
<h3>总结</h3>
|
||||
<p>这一讲主要讲解了关于 SkyWalking 的使用背景以及价值,在实操层面讲解了 SkyWalking 是如何追踪监控中出现的错误,并且把出现的错误通过钉钉通知给相关人员,相信通过这一讲的学习,你也对微服务下的报警方案会有一个更深刻的认识。</p>
|
||||
|
||||
@@ -159,9 +159,9 @@ function hide_canvas() {
|
||||
<p>这一讲我们就来讲解可视化监控,你可以认为它是一节实操课,需要提前准备好你的服务器环境(推荐 CentOS 7.0),跟着我的步骤一步步进行就可以完成酷炫的监控报表。</p>
|
||||
<h3>命令行和图形化界面展示对比</h3>
|
||||
<p>对于初学者而言,你可能并不能从上述文字中感受到命令行和图形化界面展示的区别,那么我用两张图来对比下。</p>
|
||||
<p><img src="assets/Cgp9HWAeQEGAH4ObAAIZnEuEI9M121.png" alt="Drawing 0.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAeQEGAH4ObAAIZnEuEI9M121.png" alt="png" /></p>
|
||||
<p>图 1:命令行方式</p>
|
||||
<p><img src="assets/Cgp9HWAeQEiAEWf_AAXTN9yw9Y0063.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAeQEiAEWf_AAXTN9yw9Y0063.png" alt="png" /></p>
|
||||
<p>图 2:可视化监控大屏</p>
|
||||
<p>我想绝大部分还是更愿意看图 2 的可视化监控大屏,本讲的开头也说了命令行监控和可视化监控是一种互补的形式,这就代表两种方式各有千秋。可视化监控除了直观外,我认为还有如下两点优势。</p>
|
||||
<p><strong>(1)信息高度集中</strong></p>
|
||||
@@ -195,7 +195,7 @@ function hide_canvas() {
|
||||
<pre><code>nohup ./node_exporter &
|
||||
</code></pre>
|
||||
<p>当启动完成之后,可以用 ip:9100 的方式打开页面,如下所示,即认为 node_exporter 安装成功了。</p>
|
||||
<p><img src="assets/Cgp9HWAeQHCARf2SAAAPsbMo84s659.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAeQHCARf2SAAAPsbMo84s659.png" alt="png" /></p>
|
||||
<p>图 4:node_exporter 安装示意图</p>
|
||||
<p>我们点击 Metrics 可以查看具体的采集信息,部分展示内容如下所示:</p>
|
||||
<pre><code># HELP node_cpu_seconds_total Seconds the cpus spent in each mode
|
||||
@@ -231,14 +231,14 @@ tar zxvf prometheus-2.15.1.linux-amd64.tar.gz
|
||||
<pre><code>nohup ./prometheus &
|
||||
</code></pre>
|
||||
<p>访问 <a href="http://ip:9090/targets">http://ip:9090/targets</a>,根据自己的实际情况填写 ip,出现如下截图表示安装成功。</p>
|
||||
<p><img src="assets/CioPOWAeQH2AP6pBAABkNC-TloM735.png" alt="Drawing 4.png" /></p>
|
||||
<p><img src="assets/CioPOWAeQH2AP6pBAABkNC-TloM735.png" alt="png" /></p>
|
||||
<p>图 5:Promethues 成功安装示意图</p>
|
||||
<h4>第三步是安装 Grafana</h4>
|
||||
<p>这部分第 03 讲已经讲解过,我们就不再赘述,安装完成 Grafana 之后,添加 Prometheus 数据源,测试并保存即可。</p>
|
||||
<p><img src="assets/Cgp9HWAeQIiAJMERAACXSmGUo8w948.png" alt="Drawing 5.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAeQIiAJMERAACXSmGUo8w948.png" alt="png" /></p>
|
||||
<p>图 6:Grafana 添加 Promethues 数据源</p>
|
||||
<p>接着导入官方提供的<strong>展示模板</strong>就可以,<a href="https://grafana.com/dashboards/8919">点击链接</a>。你可以自行选择相应的版本进行下载,也可以直接填写模板 ID,导入完成之后,便可以看到大屏了,示意图如下:</p>
|
||||
<p><img src="assets/CioPOWAeQJOAU5eUAAOCvFyaK3E786.png" alt="Drawing 6.png" /></p>
|
||||
<p><img src="assets/CioPOWAeQJOAU5eUAAOCvFyaK3E786.png" alt="png" /></p>
|
||||
<p>图 7:可视化大屏示意</p>
|
||||
<p>到目前为止,一款基于 Linux 硬件监控的大屏就打造完成了。</p>
|
||||
<h3>如何可视化监控数据库?</h3>
|
||||
@@ -262,7 +262,7 @@ host=127.0.0.1
|
||||
<pre><code>nohup ./mysqld_exporter --config.my-cnf=my.cnf &
|
||||
</code></pre>
|
||||
<p>然后通过网页访问来验证是否部署成功,访问地址一般是 ip:9104,可以看到如下展示信息:</p>
|
||||
<p><img src="assets/CioPOWAeQKGAUMGKAAAem_X3hX0515.png" alt="Drawing 7.png" /></p>
|
||||
<p><img src="assets/CioPOWAeQKGAUMGKAAAem_X3hX0515.png" alt="png" /></p>
|
||||
<p>点击 Meteric 你也可以发现很多手机端 MySQL 监控信息的参数选项,部分信息如下:</p>
|
||||
<pre><code># HELP mysql_global_variables_max_connections Generic gauge metric from SHOW GLOBAL VARIABLES.
|
||||
# TYPE mysql_global_variables_max_connections gauge
|
||||
@@ -274,7 +274,7 @@ mysql_global_variables_max_connections 151
|
||||
- targets: ['127.0.0.1:9104']
|
||||
</code></pre>
|
||||
<p>关于<strong>Grafana 展示,</strong> 选择 Grafana 的 MySQL 监控相关模板导入即可,<a href="https://grafana.com/grafana/dashboards/11323">点击模板链接</a>。下载并导入后就可以了,MySQL 展示效果如下图所示。</p>
|
||||
<p><img src="assets/Cgp9HWAeQMKAP1owAAJD8a1CZnw412.png" alt="Drawing 8.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAeQMKAP1owAAJD8a1CZnw412.png" alt="png" /></p>
|
||||
<p>图 8:mysql 可视化监控示意图</p>
|
||||
<h3>总结</h3>
|
||||
<p>这一讲主要讲述了<strong>Promethues + Exporter + Grafana</strong>的监控方案:</p>
|
||||
|
||||
@@ -158,7 +158,7 @@ function hide_canvas() {
|
||||
<h3>为什么要使用 Docker?</h3>
|
||||
<p>你可以回忆下 Docker 的图标(如图 1 所示),是不是像一条船上装了很多集装箱,其实这和Docker 的设计思想有关系,集装箱能解决什么问题呢?就是货物的隔离,如果我们把食物和化学品分别放在两个集装箱中用一艘轮船运走则无妨,但是你不可以把它们放在同一个集装箱中,其实对于 Docker 设计也是如此。</p>
|
||||
<p><strong>操作系统就相当于这艘轮船</strong>,上面可以有很多集装箱,即 Docker,你可以把 Docker 看作是独立的子环境,有独立的系统和应用,比如经常因为一些历史原因开发的多个模块依赖于不同的 JDK 版本,将这两个模块部署在一台 Linux 服务器上可能很容易出问题,但是如果以 Docker 的方式便很容易解决版本冲突的问题。</p>
|
||||
<p><img src="assets/Cgp9HWAs40-AKWVUAAI_WngHwng787.png" alt="Drawing 0.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAs40-AKWVUAAI_WngHwng787.png" alt="png" /></p>
|
||||
<p>图 1:Docker 图标</p>
|
||||
<h3>Docker 的用法(基于 CentOS 7.0)</h3>
|
||||
<p>如何学习 Docker 呢?<strong>从应用技术维度来看它是一个容器</strong>,<strong>从学习角度来看它就是一种工具。</strong></p>
|
||||
@@ -235,7 +235,7 @@ CONTAINER ID IMAGE COMMAND
|
||||
<li>而 Docker 容器则是已经煮熟可以食用的水饺了。</li>
|
||||
</ul>
|
||||
<p>通过下面这个示意图可以看出从 Dockfile 到 Docker 容器的过程:</p>
|
||||
<p><img src="assets/CioPOWAs5b6AZbO1AAAk45YQf-w768.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/CioPOWAs5b6AZbO1AAAk45YQf-w768.png" alt="png" /></p>
|
||||
<p>图 2:Dockfile 到 Docker 容器的过程</p>
|
||||
<h3>应用实例:如何制作基于 JMeter 的 Docker 镜像?</h3>
|
||||
<p>首先来说为什么会有这样的需求,对于<strong>用户体量比较大</strong>的公司,他们需要的系统处理能力自然也越高。在压测过程中,并不是单台压力机就可以解决问题,我们可能会在压测过程中动态调度JMeter 节点,其中一个比较方便的方式就是使用 Docker 的方式动态进行。</p>
|
||||
|
||||
@@ -194,7 +194,7 @@ function hide_canvas() {
|
||||
<p>以上便是 Java 对象在 JVM 中运行的大体过程,了解了这些基本信息之后,再来了解下堆空间中 Java 运行的线程状态,当程序开始创建线程时,便开始有了生命周期,其实就和人一样,会有“生老病死”几个状态,而对于线程来说会经历六个状态,如下表所示:</p>
|
||||
<p><img src="assets/CioPOWA0ZMuAGHBZAAD2QjCFz1A629.png" alt="图片1.png" /></p>
|
||||
<p>我们用一张图来直观地概括下这几个状态的演变:</p>
|
||||
<p><img src="assets/Cgp9HWA0ZSCAUrpaAAEB4nKOw-Q013.png" alt="image" /></p>
|
||||
<p><img src="assets/Cgp9HWA0ZSCAUrpaAAEB4nKOw-Q013.png" alt="png" /></p>
|
||||
<p>从字面上来看,NEW、RUNNABLE、TERMINATED 这几个状态比较好理解,但对于 BLOCKED、WAITING、TIMED_WAITING 很多人却分不清楚,我想通过一些实际生活中的例子来帮助你理解。</p>
|
||||
<h4>BLOCKED</h4>
|
||||
<p>先来说下 BLOCKED,比如你去参加面试,可是接待室里面已经有张三正在面试,此时你是线程 T1,张三是线程 T2,而会议室是锁。这时 T1 就被 blocked,而 T2 获取了会议室的锁。</p>
|
||||
|
||||
@@ -174,7 +174,7 @@ function hide_canvas() {
|
||||
<h4>5.堆</h4>
|
||||
<p>堆区域是 JVM 调优最重要的区域,堆中存放的数据很多是对象实例,如 HelloTester 的对象存储。堆空间占据着 JVM 中最大的存储区域,存放了很多对象,所以大多数基于 JVM 的内存调优也是对堆空间的调优。</p>
|
||||
<p>堆空间并非取之不尽,如果一直存放总有用完的时候,所以对于有用的对象应当保存起来,无用的对象应当回收,为了更好地实现这一机制,JVM 将堆空间分成了新生代和老生代,如下图所示:</p>
|
||||
<p><img src="assets/CioPOWA2vFqAaIvdAAChz7EIEu0014.png" alt="4.png" /></p>
|
||||
<p><img src="assets/CioPOWA2vFqAaIvdAAChz7EIEu0014.png" alt="png" /></p>
|
||||
<p>图 3:GC 示意对比图</p>
|
||||
<p>通过图 3 可以看到新生代和老年代的对比,Minor GC 发生在新生代,而 Full GC 发生在老年代。新生代分为三个区,一个 Eden 区和两个 Survivor 区。</p>
|
||||
<p>先来看下 Eden 区的作用,大部分新生成的对象都是在 Eden 区,Eden 区满了之后便没有内存给新对象使用,Eden 区便会 Minor GC 回收无用内存,剩下的存活对象便会转移到 Survivor 区。</p>
|
||||
@@ -262,15 +262,15 @@ root 18658 1 0 Dec09 ?后续省略
|
||||
</code></pre>
|
||||
<p>通过这样的方式可以启动暴露 1099 端口,且连接时不需要认证。</p>
|
||||
<p>然后在本机电脑 jdk 路径 bin 目录下找到 jvisualvm,双击打开,如下图所示:</p>
|
||||
<p><img src="assets/Cgp9HWAze7uAY4i0AAIY8AO0bo0055.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/Cgp9HWAze7uAY4i0AAIY8AO0bo0055.png" alt="png" /></p>
|
||||
<p>我们再配置相应的 jmx 连接,如下图所示:</p>
|
||||
<p><img src="assets/CioPOWAze8GAEEsIAALCNqd4FCQ080.png" alt="Drawing 4.png" /></p>
|
||||
<p><img src="assets/CioPOWAze8GAEEsIAALCNqd4FCQ080.png" alt="png" /></p>
|
||||
<p>如果出现如下图所示的界面,就证明连接成功了。</p>
|
||||
<p><img src="assets/Cgp9HWA2vMaASqv3AAJ2rG-zf3U45.jpeg" alt="image" /></p>
|
||||
<p><img src="assets/Cgp9HWA2vMaASqv3AAJ2rG-zf3U45.jpeg" alt="png" /></p>
|
||||
<p>这样我们就能够概览 JVM 的 CPU 和内存的使用情况,如下图所示,通过点击抽样器,你可以分别获得对象在 CPU 和内存的占用。值得注意的是很多初学者把这部分 CPU 监控或者内存监控认为是服务器硬件级别的,这是不对的,这些都是基于 JVM 的监控。</p>
|
||||
<p><img src="assets/CioPOWAze9CAShx_AAG7jwD3hwI714.png" alt="Drawing 6.png" /></p>
|
||||
<p><img src="assets/CioPOWAze9CAShx_AAG7jwD3hwI714.png" alt="png" /></p>
|
||||
<p>按照内存占用进行排序是非常清晰的,你可以看到随着性能测试的进行,User 类字节占用比例越来越高,如下图所示:</p>
|
||||
<p><img src="assets/CioPOWAze9aAZifIAANlYUMJTPQ367.png" alt="Drawing 7.png" /></p>
|
||||
<p><img src="assets/CioPOWAze9aAZifIAANlYUMJTPQ367.png" alt="png" /></p>
|
||||
<h3>总结</h3>
|
||||
<p>通过本讲的学习,你了解了 JVM 的内存结构,知道了 Java 内存对象经常活动的区域,同时列举了常见的排查手段诊断内存问题。</p>
|
||||
</div>
|
||||
|
||||
@@ -221,10 +221,10 @@ pid 10694
|
||||
}
|
||||
</code></pre>
|
||||
<p>把以上代码放到服务器中执行,然后我们可以使用 Arthas 的 jvm 命令查看到线程死锁数目为 2,说明程序发生了死锁,如下图所示:</p>
|
||||
<p><img src="assets/Cgp9HWA9DKuAMzKWAABTDaKlSO0241.png" alt="Drawing 0.png" /></p>
|
||||
<p><img src="assets/Cgp9HWA9DKuAMzKWAABTDaKlSO0241.png" alt="png" /></p>
|
||||
<p>图 1:死锁示意图</p>
|
||||
<p>接下来我们输入 thread -b 命令查看当前阻塞其他线程的线程,然后我们可以看到 Lock 1 被阻塞了,访问的资源被 Lock 2 占用,如图 2 所示,根据提示的行数我们便能找到死锁位置,对代码进行优化。</p>
|
||||
<p><img src="assets/CioPOWA9fFWAHUHaAAApcGDj53c885.png" alt="image" /></p>
|
||||
<p><img src="assets/CioPOWA9fFWAHUHaAAApcGDj53c885.png" alt="png" /></p>
|
||||
<p>图 2:thread 演示死锁详情图</p>
|
||||
<h4>2.使用 trace 命令查看耗时</h4>
|
||||
<p>我们写几个不同循环次数的方法,分别代表方法耗时不同,代码如下:</p>
|
||||
@@ -250,7 +250,7 @@ private void cost1() {
|
||||
com.cctest.arthas_demo.controller.StressSceneController timeCost
|
||||
</code></pre>
|
||||
<p>输完命令后回车,然后 arthas 程序就进入了等待访问状态。这时候访问接口 /timeCost,我们就可以看到被测程序台在疯狂打印日志,等结束之后,arthas 命令窗口便输出了各方法耗时时间,如图 3 所示:</p>
|
||||
<p><img src="assets/CioPOWA9fHeACowAAABtN4Cdlac932.png" alt="image" /></p>
|
||||
<p><img src="assets/CioPOWA9fHeACowAAABtN4Cdlac932.png" alt="png" /></p>
|
||||
<p>图 3:方法耗时详情</p>
|
||||
<p>我们可以看到 timeCost 方法总耗时 258391ms:</p>
|
||||
<ul>
|
||||
@@ -276,7 +276,7 @@ public String login(@RequestParam(value="userName") String userName, @
|
||||
<pre><code>watch com.cctest.arthas_demo.controller.StressSceneController login "{params[0],params[1],returnObj}"
|
||||
</code></pre>
|
||||
<p>你可以看到输入上述命令后的返回信息如下:</p>
|
||||
<p><img src="assets/CioPOWA9DNqARGyPAAA2hv8N8DA381.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/CioPOWA9DNqARGyPAAA2hv8N8DA381.png" alt="png" /></p>
|
||||
<p>图 4:watch 返回信息</p>
|
||||
<h4>4.使用 tt 命令定位异常调用</h4>
|
||||
<p>tt 与上面的 watch 的命令有些类似,都可以排查函数的调用情况。但是对于函数调用 n 次中有几次是异常情况,用 watch 就没有那么方便,使用 tt 命令就可以很方便地查看异常的调用及其信息。</p>
|
||||
@@ -284,7 +284,7 @@ public String login(@RequestParam(value="userName") String userName, @
|
||||
<pre><code>tt -t com.cctest.arthas_demo.controller.StressSceneController login
|
||||
</code></pre>
|
||||
<p>然后我们访问接口,每次访问的状态和结果显示如图 5 所示:</p>
|
||||
<p><img src="assets/Cgp9HWA9fMiAXBhHAABdFH1m-Fk917.png" alt="image" /></p>
|
||||
<p><img src="assets/Cgp9HWA9fMiAXBhHAABdFH1m-Fk917.png" alt="png" /></p>
|
||||
<p>图 5:tt 的返回信息</p>
|
||||
<p>从图中可以看出,tt 显示的信息比 watch 全面。其中 IS-RET 项如果为 false,即为错误的调用。</p>
|
||||
<p>以上部分介绍了 Arthas 命令在实际例子中的使用,我是通过命令行的方式来演示的,所以你需要登上服务器。之前有提到过,并不是所有的同学都有权限直接进行服务器的操作,那面对这样的情况如何使用 Arthas 呢?其实也是有解决方法的,接下来我们将介绍通过 Web 的方式操作 Arthas。</p>
|
||||
@@ -301,7 +301,7 @@ public String login(@RequestParam(value="userName") String userName, @
|
||||
<li>目标服务器即 Arthas Tunnel Server 启动的服务器,端口号默认是 8080。</li>
|
||||
</ul>
|
||||
<p>(4)在浏览器中输入 <a href="http://xn--ip-fr5c86lx7z3kb880d:目标服务器port就可以访问到WebConsole/">http://目标服务器ip:目标服务器port</a>,就可以访问 WebConsole,如图 6 所示:</p>
|
||||
<p><img src="assets/Cgp9HWA9DPOARvDFAAHh5zegLvs919.png" alt="Drawing 5.png" /></p>
|
||||
<p><img src="assets/Cgp9HWA9DPOARvDFAAHh5zegLvs919.png" alt="png" /></p>
|
||||
<p>图 6:Web 方式 Arthas 启动</p>
|
||||
<p>然后我们输入 ip、port 和 agentid 就可以连上被测程序,并且可以开始对被测程序输入 Arthas 命令。接下来的 Arthas 的使用和命令行方式是一样的,不再赘述。</p>
|
||||
<h4>2.Arthas Tunnel Server 的操作原理</h4>
|
||||
|
||||
@@ -157,12 +157,12 @@ function hide_canvas() {
|
||||
<p>上一讲我带你学习了如何应用 Arthas 定位代码以及链路问题。这一讲我将带你来学习一个关键的内存数据库中间件 Redis,希望你可以了解它的作用,以及在使用过程中的常见问题以及解决方案。</p>
|
||||
<h3>为什么使用内存数据库?</h3>
|
||||
<p>首先我们来看看最早期的 Web 架构是什么样的,如图 1 所示:</p>
|
||||
<p><img src="assets/Cgp9HWA_jkyAW6_xAABZGKmSgp0617.png" alt="1.png" /></p>
|
||||
<p><img src="assets/Cgp9HWA_jkyAW6_xAABZGKmSgp0617.png" alt="png" /></p>
|
||||
<p>图 1:早期架构</p>
|
||||
<p>这是互联网早期的常用架构,不过这样的架构一般只满足于基本的业务运转,一旦业务量迅速增高,就会出现各种<strong>请求延迟</strong>,甚至<strong>超时响应</strong>或者<strong>直接请求拒绝</strong>的情况 ,也就是在高访问量下会发生性能问题,而且这样的框架性能问题又集中在<strong>数据库层面</strong>。</p>
|
||||
<p>那么问题来了,为什么会产生这种情况呢?由于数据库的数据是存在硬盘上,硬盘的 I/O 读写瓶颈会直接影响<strong>并发量</strong>。既然磁盘 I/O 读写时瓶颈,我们是不是可以采用速度更快的内存来<strong>存储常用但数据量不算大的数据</strong>呢?答案是肯定的。</p>
|
||||
<p>为了解决上面的问题,目前通用的做法是<strong>引入基于内存的数据库</strong>,这样的数据库一般是把数据先放到内存里,引入缓存中间件之后的项目 Web 服务架构图如下所示:</p>
|
||||
<p><img src="assets/Cgp9HWA_jl-AZ0_0AAC_NrxVK8s022.png" alt="2.png" /></p>
|
||||
<p><img src="assets/Cgp9HWA_jl-AZ0_0AAC_NrxVK8s022.png" alt="png" /></p>
|
||||
<p>图 2:演变架构</p>
|
||||
<p>这样便可以较大程度缓解传统数据库带来的磁盘 I/O 读写瓶颈,而我们最常使用的基于内存的数据库就是 <strong>Redis</strong> 和 <strong>MemCached</strong>。</p>
|
||||
<h3>Redis 和 Memcached 对比</h3>
|
||||
@@ -216,10 +216,10 @@ public Result getRedisTestData(){
|
||||
<p>通过如上三步就可以完成 Java 使用 Redis 的 demo,我大概总结下代码流程,第一次先判断 Redis 中是否存在查询的数据,如果没有就需要从数据库中读取数据了,读取成功之后把数据回写到 Redis 中,后面的请求就能直接从 Redis 中直接读取了,较大地减少了对数据库的查询压力。我们可以通过运行上面写好的代码来看下实际效果。</p>
|
||||
<p>首先我们向数据表 redis_test 插入 10w 条数据,然后分两次访问该接口,对比下两次访问的响应时间。</p>
|
||||
<p>第一次直接从 MySQL 数据库读取,一共花了 39.43s,如下图所示:</p>
|
||||
<p><img src="assets/Cgp9HWA9DVKAYW32AAFDyYw_tDs126.png" alt="Drawing 2.png" /></p>
|
||||
<p><img src="assets/Cgp9HWA9DVKAYW32AAFDyYw_tDs126.png" alt="png" /></p>
|
||||
<p>图 3:MySQL 取数据耗时</p>
|
||||
<p>而第二次数据已经进入 Redis,请求只需要 2.62s,节省了很长时间。值得注意的是为了演示效果,取出的数据条数达到 10w+,所以响应时间也达到了秒级别。在正常的互联网业务当中,Redis 读写操作均在毫秒级别。</p>
|
||||
<p><img src="assets/CioPOWA9DVmACUdmAAFWomKWRnk641.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/CioPOWA9DVmACUdmAAFWomKWRnk641.png" alt="png" /></p>
|
||||
<p>图 4:Redis 取数据耗时</p>
|
||||
<p>从上面实例可以看出使用 Redis 和不使用 Redis <strong>性能差距明显</strong>,所以从目前的互联网项目来讲,使用 Redis 是一个非常普遍的情况,接下来我们来了解下 Redis 其他特性和优缺点。</p>
|
||||
<h3>Redis 其他特性以及优缺点</h3>
|
||||
|
||||
@@ -168,11 +168,11 @@ function hide_canvas() {
|
||||
<li>将结果返回给客户端。</li>
|
||||
</ol>
|
||||
<p>然后我们将上述步骤使用流程图展示,如下所示:</p>
|
||||
<p><img src="assets/CioPOWBHMuSAIJw8AAFhFjqfQio469.png" alt="1.png" /></p>
|
||||
<p><img src="assets/CioPOWBHMuSAIJw8AAFhFjqfQio469.png" alt="png" /></p>
|
||||
<p>图 1:MySQL 查询过程</p>
|
||||
<h3>对于 MySQL 来说,影响性能的关键点有哪些?</h3>
|
||||
<p>关于这个问题,我想大家都应该可以回答一些,比如硬件层面、系统层面等等。但在性能领域中,一个不能忽略的问题是你需要考虑<strong>影响的面有多少</strong>,以及如何优化才是最具有性价比的。以我的经验来看,如何做优化更具性价比也存在漏斗模型,如图 2 所示。</p>
|
||||
<p><img src="assets/CioPOWBHMvWAWbmbAAH3N1TmlKA314.png" alt="2.png" /></p>
|
||||
<p><img src="assets/CioPOWBHMvWAWbmbAAH3N1TmlKA314.png" alt="png" /></p>
|
||||
<p>图 2:漏斗模型</p>
|
||||
<p>从上往下看:</p>
|
||||
<ul>
|
||||
|
||||
@@ -195,12 +195,12 @@ function hide_canvas() {
|
||||
+-------------+--------------+------+-----+---------+-------+
|
||||
</code></pre>
|
||||
<p>通过查看表信息,你可以发现我并没有添加索引,接着我使用 10 个线程测试一条SQL,其中SQL内容是通过 ID 号来查看数据,性能结果表现如下:</p>
|
||||
<p><img src="assets/CioPOWBJizKAXev8AACKgyKRQ_I971.png" alt="1.png" /></p>
|
||||
<p><img src="assets/CioPOWBJizKAXev8AACKgyKRQ_I971.png" alt="png" /></p>
|
||||
<p>在 ID 列添加索引后继续基于同一条 SQL ,进行 10 线程压测,结果数据如下:</p>
|
||||
<p><img src="assets/Cgp9HWBJi0qAOoq4AACHu1HrBXk996.png" alt="2.png" /></p>
|
||||
<p><img src="assets/Cgp9HWBJi0qAOoq4AACHu1HrBXk996.png" alt="png" /></p>
|
||||
<p>从测试结果来看,在未添加索引的情况下,TPS 是 109.4,而添加逐渐索引后,同等压力下 TPS 达到了 240.2。</p>
|
||||
<p>CPU 资源占用如下所示:</p>
|
||||
<p><img src="assets/Cgp9HWBJi16ADQXTAADIxOO_FtM589.png" alt="3.png" /></p>
|
||||
<p><img src="assets/Cgp9HWBJi16ADQXTAADIxOO_FtM589.png" alt="png" /></p>
|
||||
<p>CPU 使用率图</p>
|
||||
<blockquote>
|
||||
<p>红线:Total
|
||||
|
||||
@@ -178,7 +178,7 @@ function hide_canvas() {
|
||||
<p><strong>这两种方式各有千秋,可以根据公司的业务特性做具体的方案落地</strong>。</p>
|
||||
<h4>2.对业务场景梳理</h4>
|
||||
<p>理清核心链路流程,线上有很多的接口,所谓线上的全链路性能测试也不会把所有的接口纳入测试范围当中。我的做法是先理清楚核心的业务模块,你必须要对你的业务模块有最基本的感知,哪些是核心模块,哪些是非核心,下面我罗列一个电商基本的业务模块图。</p>
|
||||
<p><img src="assets/Cgp9HWBPF7aATC1ZAAX7u34dGzQ71.jpeg" alt="image.jpeg" /></p>
|
||||
<p><img src="assets/Cgp9HWBPF7aATC1ZAAX7u34dGzQ71.jpeg" alt="png" /></p>
|
||||
<p>你可以看到仅文字描述的功能就已经非常多了,如果再去细分接口,可能一张图表都放不下。你首先要考虑的就是在这些模块中,哪些是不用进行全链路性能测试的,比如类似于商家管理系统,这本身是后台系统,并不直接面向用户。同样在你确定需要测试的性能模块中进一步确定哪些接口需要进行性能测试,关于细分到接口的选择我一般将大促时的访问数据进行参考,具体的方法[《07 | 你真的知道如何制定性能测试的目标吗?》]也有所提及。</p>
|
||||
<h4>3.理清数据传输链路</h4>
|
||||
<p>什么是数据传输链路呢?可以想象一下你在线下压测的场景,是不是内网环境从你的压测机就直接到服务暴露出的访问接口。而线上的链路可能远比这个复杂得多,比如数据是不是首先经过防火墙、硬件层的负载均衡等,你需要把这些链路也理清楚,说白了就是要<strong>理清线上的部署架构</strong>。这也是性能测试需求分析的步骤,防火墙之类的底层结构是不会轻易变动的,但对于线上实际的应用部署节点可能变动就很多了,我们会针对不同的应用节点部署结构和数量经常调整,所以在压测之前需要前置好这些条件并记录清楚。</p>
|
||||
@@ -204,7 +204,7 @@ function hide_canvas() {
|
||||
<h4>2.线上全链路测试的“项目经理”</h4>
|
||||
<p>我刚刚也说了线上的性能测试需要一名“项目经理”,很多人思维定式认为这位项目经理需要一定级别的 leader 来担任,其实并不完全是这样。以我的思路来看,性能测试一线经验丰富的同学来承担更适合,当然这位同学不仅仅需要性能经验丰富,也需要对架构以及业务有基本盘的认知,至少做到心中有底。</p>
|
||||
<p>如果作为一个线上全链路压测实施的“项目经理”,你需要组织什么事情呢?我用一个思维导图罗列下。</p>
|
||||
<p><img src="assets/CioPOWBPF9iAKO_EAAWsXVsEDbA52.jpeg" alt="image" /></p>
|
||||
<p><img src="assets/CioPOWBPF9iAKO_EAAWsXVsEDbA52.jpeg" alt="png" /></p>
|
||||
<h3>线上全链路测试的风险</h3>
|
||||
<p>对于很多公司还是很忌惮在线上直接开始性能测试,因为觉得这样做风险太大了。这个担心是有道理的,阿里巴巴在刚刚提出这个议题时,内部也有很多不同的声音,一旦性能测试导致线上系统出现故障,轻则影响用户体验,重则系统不可用、真金白银的损失,任何人都不想为这个测试风险而买单。</p>
|
||||
<p>虽然文章中列举了很多事项和注意点,但真正实施起来还是可能会遇到问题,所以线上性能测试的手段也是需要被测试的,比如先基于单条数据的打标、隔离、清理等,而且更需要的是发散性思维去思考,能不能把相关问题都考虑全面了,比如我之前的举例,对数据库进行数据隔离,那你有没有考虑到如 ES 数据、日志数据是否也需要做到有效的隔离呢?</p>
|
||||
|
||||
Reference in New Issue
Block a user