对于一个具备使用价值的应用而言,其使用者有可能会在一段时间内疯狂的增 长。随着越来越多的关键性质的应用在Java EE上运行,很多的Java开发者也开始 关注可扩展性的问题了。但目前来说,大部分的web 2.0站点是基于script语言编 写的,对于Java应用可扩展能力,很多人都抱着质疑的态度。在这篇文章中, Wang Yu基于他本身在实验室项目的经验来展示如何构建可扩展的java应用,同时 ,基于一些在可扩展性上做的比较失败的项目给读者带来构建可扩展java应用的 实践、理论、算法、框架和经验。
我一直为一家互联网性质的实验室工作,这个实验室采用我们公司最新的大型 服务器环境为合作伙伴的产品和解决方案免费做性能测试,我工作的部分就是帮 助他们在强大的CMT和SMP服务器上进行性能调优。
这些年来,我已经为不同的解决方案测试了数十种java应用。许多的产品都是 为了解决同样的领域问题,因此这些产品的功能基本都是类似的,但在可扩展性 上表现的却非常不同,其中有些不能扩展到64 CPU的服务器上运行,但可以扩展 到20台服务器做集群运行,有些则只能运行在不超过2 CPU的机器上。
造成这些差别的原因在于设计产品时的架构愿景,所有的具备良好扩展性的 java应用从需求需求阶段、系统设计阶段以及实现阶段都为可扩展性做了考虑, 所以,你所编写的java应用的可扩展能力完全取决于你的愿景。
可扩展性作为系统的属性之一,是个很难定义的名词,经常会与性能混淆。当 然,可扩展性和性能是有关系的,它的目的是为了达到高性能。但是衡量可扩展 性和性能的方法是不一样的,在这篇文章中,我们采用wikipedia中的定义:
可扩展性是系统、网络或进程的可选属性之一,它表达的含义是可以以一种优 雅的方式来处理不断增长的工作,或者以一种很明白的方式进行扩充。例如:它 可以用来表示系统具备随着资源(典型的有硬件)的增加提升吞吐量的能力。
垂直扩展的意思是给系统中的单节点增加资源,典型的是给机器增加CPU或内 存,垂直扩展为操作系统和应用模块提供了更多可共用的资源,因此它使得虚拟 化的技术(应该是指在一台机器上运行多个虚拟机)能够运行的更加有效。
水平扩展的意思是指给系统增加更多的节点,例如为一个分布式的软件系统增 加新的机器,一个更清晰的例子是将一台web服务器增加为三台。随着计算机价格 的不断降低以及性能的不断提升,以往需要依靠超级计算机来进行的高性能计算 的应用(例如:地震分析、生物计算等)现在可以采用这种多个低成本的应用来 完成。由上百台普通机器构成的集群可以达到传统的基于RISC处理器的科学计算 机所具备的计算能力。
这篇文章的第一部分来讨论下垂直扩展Java应用。
如何让Java EE应用垂直扩展
很多的软件设计人员和开发人员都认为功能是产品中最重要的因素,而性能和 可扩展性是附加的特性和功能完成后才做的工作。他们中大部分人认为可以借助 昂贵的硬件来缩小性能问题。
但有时候他们是错的,上个月,我们实验室中有一个紧急的项目,合作伙伴提 供的产品在他们客户提供的CPU的机器上测试未达到性能的要求,因此合作伙伴希 望在更多CPU(8 CPU)的机器上测试他们的产品,但结果却是在8 CPU的机器上性 能反而比4 CPU的机器更差。
为什么会这样呢?首先,如果你的系统是多进程或多线程的,并且已经用尽了 CPU的资源,那么在这种情况下增加CPU通常能让应用很好的得到扩展。
基于java技术的应用可以很简单的使用线程,Java语言不仅可以用来支持编写 多线程的应用,同时JVM本身在对java应用的执行管理和内存管理上采用的也是多 线程的方式,因此通常来说Java应用在多CPU的机器上可以运行的更好,例如Bea weblogic、IBM Websphere、开源的Glassfish和Tomcat等应用服务器,运行在 Java EE应用服务器中的应用可以立刻从CMT和SMP技术中获取到好处。
但在我的实验室中,我发现很多的产品并不能充分的使用CPU,有些应用在8 CPU的服务器上只能使用到不到20%的CPU,像这类应用即使增加CPU也提升不了多 少的。
热锁(Hot Lock)是可扩展性的关键障碍
在Java程序中,用来协调线程的最重要的工具就是 synchronized这个关键字 了。由于java所采用的规则,包括缓存刷新和失效,Java语言中的synchronized 块通常都会其他平台提供的类似的机制更加的昂贵。即使程序只是一个运行在单 处理器上的单线程程序,一个synchronized的方法调用也会比非同步的方法调用 慢。
要检查问题是否为采用synchronized关键字造成的,只需要像JVM进程发送一 个QUIT指令(译者注:在linux上也可以用kill -3 PID的方式)来获取线程堆栈 信息。如果你看到类似下面线程堆栈的信息,那么就意味着你的系统出现了热锁 的问题:
... ... ...
"Thread-0" prio=10 tid=0x08222eb0 nid=0x9 waiting for monitor entry [0xf927b000..0xf927bdb8]
at testthread.WaitThread.run(WaitThread.java:39)
- waiting to lock <0xef63bf08> (a java.lang.Object)
- locked <0xef63beb8> (a java.util.ArrayList)
at java.lang.Thread.run(Thread.java:595)
... ... ...