可控的环?/b>
?
控的环境最也需要两台独立的机器和第三台控制的机器。其中一台用来生成负载,另一C为控制机与前一台徏立测试应用ƈ接受反馈Q第三台机器q行应用。此
外,负蝲和应用机器间的网l应该与局域网分开。控制机接受q行应用机器的反馈如操作pȝ、硬件用率、应用(特别是VMQ的状态?br>
负蝲模拟
最_的模拟通常用实际的用户数据和WEB服务器端的访问日志。如果你q没有实际布|或者缺实际的用户数据Q你可以通过构造类似的场景或询问销售和产品理团队或做一些有依据的猜惟뀂协调负载测试和实际用户体验是一个持l的q程?br>
?
模拟中一些用户场景是必须的。如在一个通用地址薄应用中Q你应该区分更新和查询操作。在我的试应用中GrinderServletcd有一个场景。单?
戯?0ơ访问这个servletQ在每一ơ访问间有一D|停)。虽然这个应用很,我认可以重复一些常见的东西。用户通常不会q接l服务器h?
没有间断。如果没有间断,我们可能不能得到更精的实际用户上限?br>
串行10个请求的另一个原因是实际应用中不会只有一个HTTPh。单一而又分离的请求可以媄响环境中的许多因素。对Tomcat来说Q会为每一个请求创Z个会话,q且HTTP协议允许不同的请求重用连接。我会修改一下负载测试来避免h?br>
GrinderServletcM会执行Q何排序操作,但这个需求在大部分应用中都很普通。在q些应用中,你需要创建模拟的数据集ƈ且用他们来构造相关用例的负蝲试?br>
例如Q如果用例涉及到用户d一个WEB应用Q从可能的用户列表中选取随机的用户会只用一个用h_。否则,你可能不l意C用了pȝ~存或其他的优化或一些微妙的东西Q而这会得结果不正确?br>
负蝲试软g
?
载测试Y件可以构造测试场景ƈ且对服务q行负蝲试。我会在下面的示例中使用OpenSTA试软g。这软g单易学,l果也很Ҏ导出Qƈ且支持参数化
脚本Q还可以监视信息的变化,他的主要~点是基于WindowsQ但在这儿不是个问题。当然还有很多可选项如Apache的JMeter和Mercury
的LoadRunner?br>
The GrinderServlet
列表1中显CZGrinderServletc,列表2中显CZGrinderc?br>Listing 1
package pub.capart;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class GrindServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
Grinderv1 grinder = Grinderv1.getGrinder();
long t1 = System.currentTimeMillis();
grinder.grindCPU(13);
long t2 = System.currentTimeMillis();
PrintWriter pw = res.getWriter();
pw.print("<html>\n< body> \n");
pw.print("Grind Time = "+(t2-t1));
pw.print("< body> \n< /html> \n");
}
}
Listing 2
package pub.capart;
/**
* This is a simple class designed to simulate an application consuming
* CPU, memory, and contending for a synchronization lock.
*/
public class Grinderv1 {
private static Grinderv1 singleton = new Grinderv1();
private static final String randstr =
"this is just a random string that I'm going to add up many many times";
public static Grinderv1 getGrinder() {
return singleton;
}
public synchronized void grindCPU(int level) {
StringBuffer sb = new StringBuffer();
String s = randstr;
for (int i=0;i<level;++i) {
sb.append(s);
s = getReverse(sb.toString());
}
}
public String getReverse(String s) {
StringBuffer sb = new StringBuffer(s);
sb = sb.reverse();
return sb.toString();
}
}
c?
很简单,但他们会产生两个很常见的问题。咋一看瓶颈可能由grindCPU()Ҏ的同步修饰符引vQ但实际上内存消耗才是真正的问题所在。如?Q我?
W一个负载测试显CZ常见的负载变化。在q里负蝲变化很重要因Z正在模拟一个高的负载。这U热w的方式也更_因ؓ避免了JSP~译引v的问题。我通常
习惯于在q行负蝲试前先q行单用h拟?br>

Figure 1
?
在这文章中会用相同的定w结图。在执行负蝲试时还有更多的可用信息Q但q里只用了有用的部分。最上面的面板包含每U完成的h数和h旉信息?
W二个面板包含活动用h和失败率Q我超时、不正确的服务器应答和长?U的h认ؓ是失败的。第三个面板包含JVM内存l计和CPU使用率。CPU?
是所有处理器的用h间的q_|q里所有的试机器都是双CPU的。内存统计图包含垃圾回收表和每秒垃圾回收数?br>
?中两个最明显的数据是50%的CPU使用率和大量内存使用和释放。从列表2中可以看个原因。同步修饰符D所有进E串行处理,好像只用了一个CPUQ而算法导致大量内存消耗在局部变量上?br>
通过CPU是个受限的资源,如果在这个测试中我可以完全利用到两个CPU的话可以提高一倍的性能。垃圑֛收器q行得如此频J以致于不能忽略。在试中每U释攄内存辑ֈ100MQ很昄q是个限制因素。失败数q么大明显这个应用是不可用的?br>
监视
在生成合理的用户负蝲后,监视工具需要收集进E的q行状况。在我的试环境中可以收集到各种有用的信息:
1?nbsp; 所有计机、网l设?br>2?nbsp; {等的用率
3?nbsp; JVM的统计数据?br>4?nbsp; 个别JAVAҎ所p的时间?br>5?nbsp; 数据库性能信息Q??nbsp; 包括SQL查询的统计?br>7?nbsp; 其他应用相关的信?br>
?
然这些监视也会媄响负载测试,但如果媄响比较小也可以忽略。基本上如果我们惌取所有上面的信息Q肯定会影响试的性能。但如果不是一ơ获取所有信息还?
有可能保证负载测试的有效性。仅对特定的Ҏ讄定时器,仅获取低负蝲的硬件信息和低频率地获取样例数据。当然不加蝲监视器来做测试是最好的Q然后和加蝲
监视器的试来做比较。虽然有时候R入式监视是个好主意,但就不可能有监视l果了?br>
获取所有监视数据到一个中央控制器来做分析?
最好的Q但使用动态运行时工具也可以提供有用的信息。例如,命o行工具如PS、TOP、VMSTAT可以提供UNIX机器的信息;性能监视器工具可以提?
WINDOWS机器的信息;而TeamQuest, BMC Patrol, SGI's Performance Co-Pilot, and
ISM's
PerfManq样的工具会在所有的试环境中的机器安装代理q且需要的信息传回中央控制机,q样可以提供文本或可视化的信息。在本文中,我用开?
的Performance Co-Pilot作ؓ试l计的工兗我发现他对试环境的媄响最,q且以相对直接的方式来提供数据?br>
JAVA
分析器提供很多信息,但通常对负载测试来说媄响太大而没有太多的用处。工L臛_以让你在负蝲服务器上做一些分析,但这也很Ҏ便测试无效。在q些试
中,我激zM详细的垃圾收集器来收集内存信息。我也用jconsole 和jstack工具Q包含在J2SE
1.5中)来检查高负蝲下的VM。我没有保留q些试用例中负载测试的l果因ؓ我认些数据不是很正确?br>
同步瓉
?
诊断服务器问题时U程的信息是非常有用的,特别是对同步之类的问题。jstack工具可以q接到运行的q程q且保存每一个线E的堆栈信息。在UNIXpȝ
可以用信号量3来保存线E的堆栈信息Q在WINDOWSpȝ的控制台中可以用Ctrl-Break。在W一Ҏ试中Qjstack指出许多U程?
grindCPU()Ҏ中被d?br>
你可以已l注意到列表2中grindCPU()Ҏ的同步修饰符实际上ƈ不必R我在后一Ҏ试中删除了他Q如?昄

Figure 2
在图2中,你会注意到性能下降了。虽然我使用了更多的CPUQ但吞吐量和p|数都更差了。虽然垃圑֛收周期变了,但每U依焉要回?00M。显然我们还没有扑ֈ主要的瓶颈?br>?
竟争的同步相对于单的函数调用q是很费时的。竟争性的同步更Ҏ了,因ؓ除了内存需要同步外QVMq需要维护等待的U程。在q种状况下,q些代h实际
上要于内存瓉。实际上Q通过消除了同步瓶颈,VM内存pȝ承担了更多的压力最后导致更差的吞吐量,即我用了更多的CPU。显然最好的方式是从最?
的瓶颈开始,但有时这也不是很Ҏ定的。当Ӟ保VM的内存处理够正怹是一个好的开始方向?br>
内存瓉
现在我会首先也定位内存问题。列?是GrinderServlet的重构版本,使用了StringBuffer实例。图3昄了测试结果?br>
Listing 3
package pub.capart;
/**
* This is a simple class designed to simulate an application consuming
* CPU, memory, and contending for a synchronization lock.
*/
public class Grinderv2 {
private static Grinderv2 singleton = new Grinderv2();
private static final String randstr =
"this is just a random string that I'm going to add up many many times";
private StringBuffer sbuf = new StringBuffer();
private StringBuffer sbufrev = new StringBuffer();
public static Grinderv2 getGrinder() {
return singleton;
}
public synchronized void grindCPU(int level) {
sbufrev.setLength(0);
sbufrev.append(randstr);
sbuf.setLength(0);
for (int i=0;i<level;++i) {
sbuf.append(sbufrev);
reverse();
}
return sbuf.toString();
}
public String getReverse(String s) {
StringBuffer sb = new StringBuffer(s);
sb = sb.reverse();
return sb.toString();
}
}

Figure 3
?
帔R用StringBufferq不是一个好LQ但q里我只是ؓ了重C些常见的问题Q而不量提供解x案。内存数据已l从图上消失了因为测试中没有?
圑֛收器q行。吞吐量戏剧性的增加而CPU使用率又回到?0%。列?不只是优化了内存Q但我认Z要了改善了过度的内存消耗?br>
视同步瓶?/span>
列表4另一个GrinderServletcȝ重构版本Q实C一个小的资源池。图4昄了测试结果?br>Listing 4
package pub.capart;
/**
* This is just a dummy class designed to simulate a process consuming
* CPU, memory, and contending for a synchronization lock.
*/
public class Grinderv3 {
private static Grinderv3 grinders[];
private static int grinderRoundRobin = 0;
private static final String randstr =
"this is just a random string that I'm going to add up many many times";
private StringBuffer sbuf = new StringBuffer();
private StringBuffer sbufrev = new StringBuffer();
static {
grinders = new Grinderv3[10];
for (int i=0;i<grinders.length;++i) {
grinders[i] = new Grinderv3();
}
}
public synchronized static Grinderv3 getGrinder() {
Grinderv3 g = grinders[grinderRoundRobin];
grinderRoundRobin = (grinderRoundRobin +1) % grinders.length;
return g;
}
public synchronized void grindCPU(int level) {
sbufrev.setLength(0);
sbufrev.append(randstr);
sbuf.setLength(0);
for (int i=0;i<level;++i) {
sbuf.append(sbufrev);
reverse();
}
return sbuf.toString();
}
public String getReverse(String s) {
StringBuffer sb = new StringBuffer(s);
sb = sb.reverse();
return sb.toString();
}
}

Figure 4
吞吐量有一定的增加Q而且使用更少的CPU资源。竟争和非竟争性同步都是费时的Q但通常最大的同步消耗是减少了系l的可~性。我的负载测试不再满系l的需求了Q因此我增加了虚拟的用户敎ͼ如图5 所C?br>

Figure 5
在图5 中吞吐量在负载达到饱和时下降了一些然后在负蝲减少时又提高了。此外注意到试使得CPU使用率达?00%Q这意味着试过了系l的最佛_吐量。负载测试的一个出是性能计划Q当应用的负载超q他的容量时会生更低的吞吐量?br>
水^可~?/span>
水^伸羃允许更大的性能Q但q不一定是费用相关的。运行在多个服务器上的应用通常比较q行在单个VM上的应用复杂。但水^伸羃支持在性能上的最大增加?br>
?是我的最后一Ҏ试的l果。我已经在三台基本一致的机器上用了负蝲qQ只是在内存和CPU速度上稍有不同。ȝ吞吐量要高于三倍的单机l果Q而且CPU从来没有完全利用。在?中我只显CZ一台机器上的CPUl果Q其他的是一L?br>

Figure 6
结
我曾l花?个月来布|一个复杂的JAVA应用Q但却没有时间来做性能计划。但差劲的性能使得用户合约几乎中止。开发h员用分析器׃很长旉扑ֈ几个问题但没有解决Ҏ的瓶颈,而且被后l的问题完全qh了。最后通过负蝲试扑ֈ解决ҎQ但你可以想到其中的处境?br>
又一ơ我得更难的问题,应用只能辑ֈ所预期性能?/100。但通过前期到的问题和认识到负载测试的必要性,q个问题很快被解决了。负载测试相对于整个软g开发的pq不多,但其所归避的风险就高多了?br>
关于作?/b>
Ivan
Small拥有14q的软g开发经验。他在LBNL从开发Supernovae Cosmology
Project开始他的职业生涯。这个项目是D反重力和无限扩展宇宙理论被发现的两个目之一。他从此工作于数据挖掘和企业UJAVA应用。现在他?
nnovative Interfaces公司的首席Y件工E师?br>
资源
·javaworld.com:javaworld.com
·Matrix-Java开发者社?http://www.matrix.org.cn/
·JAVA性能调优W二?http://www.amazon.com/exec/obidos/ASIN/0596003773/javaworld
·q发~程技术:JAVAq发~程W二?http://www.amazon.com/exec/obidos/ASIN/0201310090/javaworld
·JAVA|站分析QJAVA|站的性能分析:http://www.amazon.com/exec/obidos/ASIN/0201844540/javaworld
·JAVA性能Q高性能JAVAq_计算:http://www.amazon.com/exec/obidos/ASIN/0130161640/javaworld
·JAVA2性能和术语指?http://www.amazon.com/exec/obidos/ASIN/0130142603/javaworld
·BEA WebLogic服务器性能调优Q包含有用的一般信息:BEA WebLogic服务器上J2EE应用性能试:http://www.amazon.com/exec/obidos/ASIN/1904284000/javaworld
·JAVA性能调优:http://www.javaperformancetuning.com
·q度的JAVA同步:“轻量U程?http://www-106.ibm.com/developerworks/java/library/j-threads1.html
·负蝲和性能试工具Qhttp://www.softwareqatest.com/qatweb1.html#LOAD 
]]>