Dorado 5 : 3.1.发掘性能隐患 (RF4)

每一个开发人员都应该了解那些与开发相关的性能技巧。同时,开发人员也应该在日常开发的开发过程中持续的对已完成部分的性能表现做一些测试。以便于尽早的对可能存在的性能瓶颈进行优化。

通过观察现象来定位

当我们发现一个性能表现不佳的页面时,首先需要确认问题出在那个环节。此处总结了4种常见的有明显停滞感的场景。
注意,以下的分析的假设基于常见的开发环境,只有数据库通过局域网络访问,应用服务器与客户端均在本地计算机中,同时此处不考虑系统内存已趋于耗尽或完全耗尽的情况。另外,如果您是在系统启动后的第一次使用某功能时感觉较慢,也不必急着进行性能分析,应该以第二次开始的使用体验作为判断标准。
对于怎样的响应速度才算是慢,这没有什么统一的标准,有时是1秒以上,有时是5秒以上。或许项目负责人应该根据实际的情况为项目量身订制一个标准。通常来说,越是经常被用到的功能,我们就越应该保证他的响应速度快一些。如果你今天心情还不错,却在点完一个测试按钮后开始变得有些灰色,加之用户有可能每天点击它20次以上,那么这时,我们就应该找一找这里有没有什么可以优化的了。

  • 打开页面的阶段

如果感觉在页面打开的过程中有明显的停滞感,问题最有可能出在SQL不合理或界面设计不合理。
一个Dorado页面刷新的大致处理过程如下:

      1. 向服务器发起请求。
      2. 服务端处理,对于普通的页面来讲大部分的时间应当消耗在执行SQL的环节。
      3. 网络传输,向客户端输出响应内容。
      4. Dorado的客户端引擎开始构建并最终显示界面。

按照这面的方法可以基本定位出问题出在哪一端。重新刷新一下页面,注意观察IE浏览器的进度条。

图表 6状态1

图表 7状态2
如果在整个刷新的过程中(从开始点击刷新按钮到页面显示完成)。进度条停留在上图中"状态1"的时间比较长,则说明此时浏览器正在等待服务器的响应,那么服务端处理的时间相应较长,我们应该把优化的重点放在服务端。即步骤2。
如果进度条停留在上图中"状态2"的时间比较长,则说明客户端处理的时间相应较长,我们应该把优化的重点放在客户端。即步骤4。
注意,因为此处我们假设应用服务器根客户机是同一台机器,所以步骤3的时间基本可以忽略。
对于"打开页面的阶段"除了利用此处介绍的方法外,我们还可以利用Dorado提供的调式器来做类似的分析。在任何一个Dorado页面中只要按下CTRL+SHIFT+ALT+F12就可以调出一个调试窗口,切换到其中的"Log"一栏就可以看到下图中的信息:

其中可以很清晰的看到Dorado统计到的步骤3和步骤4的耗时。需要注意的是,由于Dorado的客户端引擎不可能统计的全部的网络传输时间,因此此处给出的"下载及HTML解析耗时"只具有一定的参考价值。实际上,网络传输的耗时可能会比这里显示的略长一些。

  • 执行提交或保存操作时

如果是在执行数据提交过程中有明显的停滞感,问题同样可能出在SQL不合理或客户端的代码不合理上。
一个Dorado提交的处理过程大致如下:

      1. 点击提交按钮或其它操作触发提交过程。
      2. Dorado的客户端引擎将要提交的数据组织成XML信息包
      3. 网络传输,有客户端向服务端上传提交信息
      4. 服务端处理。
      5. 网络传输,向客户端输出响应内容。对于提交操作而言向客户端输出的响应内容一般不大。
      6. 执行提交完成后的客户端动作,例如界面显示的刷新(并非页面刷新)、执行开发人员定义的客户端事件。

Dorado的提交在默认情况下都是有一个弹出式的信息提示框的,其中显示类似"正在处理..."这样的信息。在不同的设置下该提示框的外观可能看起来不太一样,甚至有时不会显示。为了定义问题我们需要在进行下一步之前让这个信息框能够重新显示出来,方法是设置Command对象的showLoadingTip为true或空。
我们可以通过观察这个提示框来定位问题可能处在那个环节。
如果从点击提交按钮到看到信息提示框这个环节花去较长时间,这说明在进行上面的步骤2执行较慢。这时,我们应该检查是不是提交的信息量过于庞大,导致Dorado在组装这些信息时花去大量事件。
如果信息提示框本省停留的时间很长,这说明在进行上面的步骤3、4、5时花去了较多时间。因此此处可忽略网络传输,因此我们应该关注服务端的处理逻辑是否存在问题。
如果在信息提示框关闭之后仍有一段时间无法进行页面操作,这说明在进行上面的步骤6时花去了较多时间。这时,我们应该检查提交完成后的处理逻辑是否存在问题。

  • 执行动态数据刷新时

Dorado页面中执行数据查询或数据翻页操作就是一种动态数据刷新。执行动态数据刷新的过程大致如下:

      1. 客户端的Dataset发起数据刷新的AJAX请求。
      2. 服务端处理。
      3. 网络传输,向客户端输出新的数据。
      4. 客户端的Dataset解析响应内容得到数据,然后重新显示相关的部分控件。

如果是在执行动态数据刷新时有明显的停滞感,那么原因多半出自后台的SQL上。如果测试发现SQL的执行速度没有问题,还可以观察一下是否一次性的向客户端返回了过多的数据。

  • 其它

按照上面的方法,有时可能我们明明已经排除了各个环节嫌疑,却仍然发现响应速度很慢。这是就应该来考虑一下下面的原因了。
由于Dorado的客户端支持事件编程,开发人员可以在上述的各种操作环节中添加很多自定义的逻辑代码。而这些代码常常会干扰我们对性能瓶颈定位的准确性。例如我们可能在页面的onLoad事件中,增加了一段代码执行某个"重量级"Dataset的flushData()方法,而这个方法占用了很长时间。这时,如果按照上面"打开页面的阶段"里的方法进行测试,我们可能无法定位性能瓶颈。因为这真正的罪魁祸首是onLoad事件里其他代码。
有时,事件代码会隐藏的更深。例如Dataset上的afterChange、afterScroll;DataTable里的onRefresh等事件。因此,但你找不到页面变慢的原因时,最好静下心来读一读页面中的JavaScript代码,或者干脆把值得怀疑的JavaScript事件先注释掉,然后重新测试。

利用Dorado的Debugger功能

有时我们需要一些方法来测试一段JavaScript执行的耗时,以便与进一步定位问题的根本。例如:我们可能已经通过上面的方法找到了某一段存在问题的JavaScript,那么接下来就应该在这段代码定位到真正存在问题的命令。不同于Java代码,我们可以很方便在代码中植入一些调试信息或利用Debug功能找出最耗时的部分,在JavaScript中这往往有点困难。
Dorado提供了一个简单调式功能可以帮上我们。前面曾经提到过,在任何一个Dorado页面中按下CTRL+SHIFT+ALT+F12就可以调用一个调试窗口。这是一个利用HTML中的DIV对象实现的功能,其中提供了:评估JavaScript并计时、显示调式日志、显示部分系统信息这3项功能。不过有事这个功能在较小浏览器框架中不能很到的运行(那些狭小的框架可能显示不开这个"巨大"的调试器),因此,偶尔我们不得不使用CTRL+SHIFT+ALT+F11来调出一个"简陋"版,如图:

例如我们发现下段代码执行的异乎寻常的慢:

dataset1.flushData();
dataset2.copyRecord(dataset1.getCurrent());
dataset2.refreshControls();

要找出那句命令执行的最慢,我们可以把这3句话分别复制到Dorado调试器中执行,然后观察调试器显示的耗时。如下图:

不过有些代码可能不是那么容易分解的,特别是当代码中有循环体或者牵扯到大量的局部变量时。在这种情况下,我们也可以选择在代码中植入一些日志的方式:

Debugger.log("A:" + new Date().getTime());
dataset1.flushData();
Debugger.log("B:" + new Date().getTime());
dataset2.copyRecord(dataset1.getCurrent());
Debugger.log("C:" + new Date().getTime());
dataset2.refreshControls();;
Debugger.log("D:" + new Date().getTime());

这样在按照正常方式执行完成之后,我们就可以在调式器中看到类似下图的输出信息,现在我们应该很容易判断到底那个操作比较耗时了。

检查页面大小

除了进行上面的测试之外,我们还应该关于一下页面的大小。因为开发环境中应用服务器和客户机之间往往不存在网络传输的问题,所有那些跟网络瓶颈相关的隐患是无法通过上面的测试发掘出来的。为了基本确保这里不出现大的问题,最好在已经打开的JSP上点击鼠标右键,然后选择其中的"查看源文件",将弹出的源文件内容保存到一个临时文件,看一看这个文件的大小,如果他的"体积"超过了200K,那可就要注意了。这里的尺寸代表了一个页面在打开过程会造成的最小网络通信量)在现实环境中,网页中的图片、脚本库和某些逻辑代码还会进一步的增加通信量)。
到底一个页面的大小不超过多少才合适,是需要根据实际情况来判断的。基本上需要结合目标网络的带宽、在线用户数量已经用户普遍可接受的响应时间这三种因素来确定。但不管怎样,页面的体积还是最好不要超过200K。因为当页面的体积太大时,不仅仅是网络,浏览器也会承受相当的解析压力。
对于HTML的数据通信量我们可以很容易的利用"查看源文件"得知,那么在系统运行过程中的那些数据上传和AJAX方式的数据下载的通信量又该如何统计呢?要做到这一点确实有些困难,我们不得不利用一些第三方的工具才能达到这个目的。
可以考虑使用一些HTTP的监视工具来辅助我们,例如前面曾经提到过BWMeter或者Microsoft Fiddler (http://www.fiddlertool.com/fiddler/),Fiddler是一个免费的工具,而且非常容易使用。通过它我们可以拦截到所有由浏览器发往服务器的信息,以及所有来自服务器的反馈信息,并且它可以直接帮我统计提交和返回的数据量的大小。如图:

在使用Fiddler时,我们可以重点观察像/dorado/smartweb2.RPC.d这样的请求,这是Dorado5中默认的远程过程的总入口,这里提到的"远程过程调用"包括数据刷新/查询、数据提交、等操作。我们可以通过阅读该请求的POST信息来确定它究竟是一个怎样的操作,以及提交和接受了多少数据量。
从图中最上面的框中可以看出这是一个刷新数据的操作。__type总共可能有下列3中取值:

  • loadData - 刷新数据的操作。
  • updateData - 提交数据的操作。
  • RPC - 普通的远程过程调用。

从图中中间的框中可以看出该请求提交了671个字节的数据。
从图中最下方的框中可以看出该请求的响应数据量是688个字节。

消灭垃圾数据和对象

在开发过程中尽可能的避免不良的设计和开发习惯有助于提高系统的整体健康度,这会大大的降低后期在系统部署和维护过程的工作量。但不管我们怎样努力都不太可能避免一个庞大的应用系统中的所有的性能障碍,因为有很多性能问题总是要随着数据量和并发访问量的逐渐增大才可能的暴露出来。比如说垃圾对象的隐患。
如果我们在页面中遗留了某个垃圾Dataset,在进行开发测试时可能只会包含一、两条记录,它对页面的影响几乎可以忽略不计。可是随着数据库中数据的变化,它包含的垃圾数据可能变成千、万的数量级,这时它的宿主页面就会被它拖累的几近崩溃。
所以,如果我们能够保持良好的编程习惯,就可以在很大程度上减少这种潜藏在系统中的性能隐患。无论何时当你发现一个对象是垃圾对象时,你最好把它删除,而不是抱着"或许他还用的着"的念头。当然,如果方便的话,最好先备份再删除。根据我们的经验,如果你没有立刻删掉它的习惯的话,那么,有50%的几率他永远不会被删除,除非,它的存在最终真的给你带来了大麻烦。
还有一种情况是Dataset虽然是真正有用的,但里面可能包含了多余的垃圾数据。这在表单型的页面上比较容易出现,如下图。

从界面上看我们可能会直觉的认为这个界面上只有一条记录,但事实是表单页面只能显示一条记录,在Dataset中究竟包含了多少记录是无法断言的。最常见的导致此问题的原因是开发人员编写了错误的SQL命令,结果集在被取出时进行了笛卡尔运算,出现了很多基本重复的记录。更可怕的是,这种错误往往并不影响系统的正常运行,直到有一天细心人发现了数据的不正常,或者因为重复的记录过多而导致运行速度极具下降。
有一种主动检查这种失误的方法是利用Dorado的客户端调试器。在调试器测试下面的命令可以看到某个Dataset中到底有多少条记录。

alert(datasetXXX.getVisibleCount())