原文地址:http://www.theserverside.com/tt/articles/article.tss?l=ScalingYourJavaEEApplicationsPart2
当并发用户数明显的开始增长,你可能会不满意一台机器所能提供的性能,或者由于单个JVM实例gc的限制,你没法扩展你的java应用,在这样的情况下你可以做的另外的选择是在多个JVM实例或多台服务器上运行你的系统,我们把这种方法称为水平扩展。
请注意,我们相信能够在一台机器的多个JVM上运行系统的扩展方式是水平扩展方式,而非垂直扩展方式。JVM实例之间的IPC机制是有限的,两个JVM实例之间无法通过管道、共享内存、信号量或指令来进行通讯,不同的JVM进程之间最有效的通讯方式是socket。简而言之,如果Java EE应用如果扩展到多个JVM实例中运行,那么大多数情况下它也可以扩展到多台服务器上运行。
随着计算机越来越便宜,性能越来越高,通过将低成本的机器群组装为集群可以获得超过那些昂贵的超级计算机所具备的计算能力。不过,大量的计算机也意味着增加了管理的复杂性以及更为复杂的编程模型,就像服务器节点之间的吞吐量和延时等问题。
Java EE集群是一种成熟的技术,我在TSS上写了一篇名为“Uncover the Hood of J2EE Clustering”的文章来描述它的内部机制。
从失败的项目中吸取的教训
采用无共享的集群架构
Figure 3: share nothing cluster
最具备扩展性的架构当属无共享的集群架构。在这样的集群中,每个节点具备完全相同的功能,并且不需要知道其他节点存在与否。负载均衡器(Load Balancer)来完成如何将请求分发给这些后台的服务器实例。由于负载均衡器只是做一些简单的工作,例如分派请求、健康检查和保持session,因此负载均衡器很少会成为瓶颈。如果后端的数据库系统或其他的信息系统足够的强大,那么通过增加更多的节点,集群的计算能力可以得到线性的增长。
几乎所有的Java EE提供商在他们的集群产品中都实现了HttpSession的failover功能,这样即使在某些服务器节点不可用的情况下也仍然能够保证客户端的请求中的session信息不丢失,但这点其实是打破了无共享原则的。为了实现failover,同样的session数据将会被两个或多个节点共享,在我之前的文章中,我曾经推荐除非是万不得已,不要使用session failover。就像我文章中提到的,当失败发生时,session failover功能并不能完全避免错误,而且同时还会对性能和可扩展性带来损失。
使用可扩展的session复制机制
为了让用户获得更友好的体验,有些时候可能必须使用session failover功能,这里最重要的在于选择可扩展的复制型产品或机制。不同的厂商会提供不同的复制方案 - 有些采用数据库持久,有些采用中央集中的状态服务器,而有些则采用节点间内存复制的方式。最具可扩展性的是成对节点的复制(paired node replication),这也是现在大部分厂商采用的方案,包括BEA Weblogic、JBoss和IBM Websphere,Sun在Glassfish V2以及以上版本也实现了成对节点的复制。最不可取的方案是数据库持久session的方式。在我们实验室中曾经测试过一个采用数据库持久来实现 session复制的项目,测试结果表明如果session对象频繁更新的话,节点在三到四个时就会导致数据库崩溃。
采用collocated部署方式来取代分布式
Java EE技术,尤其是EJB,天生就是用来做分布式计算的。解耦业务功能和重用远程的组件使得多层的应用模型得以流行。但对于可扩展性而言,减少分布式的层次可能是一个好的选择。
在我们实验室曾经以一个政府的项目测试过这两种方式在同样的服务器数量上的部署 - 一种是分布式的,一种是collocated方式的,如下图所示:
Figure 4: distributed structure
|
Figure 5: collocated structure
|
结果表明collocated式的部署方式比分布式的方式更具备可扩展性。假设你应用中的一个方法调用了一堆的EJB,如果每个EJB的调用都需要load balance,那么有可能会因为需要分散到不同的服务器上进行调用导致你的应用崩溃,这样的结果就是,你可能做了很多次无谓的跨服务器的调用。来看更糟糕的情况,如果你的方法是需要事务的,那么这个事务就必须跨越多个服务器,而这对于性能是会产生很大的损害的。
共享资源和服务
对于用于支撑并发请求的Java EE集群系统而言,其扩展后的性能取决于对于那些不支持线性扩展的共享资源的操作。数据库服务器、JNDI树、LDAP服务器以及外部的文件系统都有可能被集群中的节点共享。
尽管Java EE规范中并不推荐,但为了实现各种目标,通常都会采用外部的I/O操作。例如,在我们实验室测试的应用中有用文件系统来保存用户上传的文件的应用,或动态的创建xml配置文件的应用。在集群内,应用服务器节点必须想办法来复制这些文件到其他的节点,但这样做是不利于扩展的。随着越来越多节点的加入,节点间的文件复制会占用所有的网络带宽和消耗大量的CPU资源。在集群中要达到这样的目标,可以采用数据库来替代外部文件,或采用SAN作为文件的集中存储,另外一个可选的方案是采用高效的分布式文件系统,例如Hadoop DFS(http://wiki.apache.org/hadoop/)。
在集群环境中共享服务很常见,这些服务不会部署到集群的每个节点,而是部署在专门的服务器节点上,例如分布式的日志服务或时间服务。分布式锁管理器 (DLM)来管理集群中的应用对这些共享服务的同步访问,即使在网络延时和系统处理失败的情况下,锁管理器也必须正常操作。举例来说,在我们的实验室中测试的一个ERP系统就碰到了这样的问题,他们写了自己的DLM系统,最终发现当集群中持有锁的节点失败时,他们的lock system将会永远的持有锁。
分布式缓存
我所碰到过的几乎所有的Java EE项目都采用了对象缓存来提升性能,同样所有流行的应用服务器也都提供了不同级别的缓存来加速应用。但有些缓存是为单一运行的环境而设计的,并且只能在单JVM实例中正常的运行。由于有些对象的创建需要耗费大量的资源,我们需要缓存,因此我们维护对象池来缓存对象的实例。如果获取维护缓存较之创建对象而言更划算,那么我们就提升了系统的性能。在集群环境中,每个jvm实例维护着自己的缓存,为了保持集群中所有服务器状态的一致,这些缓存对象需要进行同步。有些时候这样的同步机制有可能会比不采用缓存的性能还差,对于整个集群的扩展能力而言,一个可扩展的分布式缓存系统是非常重要的。
如今很多分布式缓存相关的开源java产品已经非常流行,在我们实验室中有如下的一些测试:
- 1个基于JBoss Cache的项目的测试;
- 3个基于Terracotta的项目的测试;
- 9个基于memcached的项目的测试;
测试结果表明Terracotta可以很好的扩展到10个节点,并且在不超过5个节点时拥有很高的性能,但memcached则在超过20个服务器节点时会扩展的非常好。
Memcached
Memcached是一个高性能的分布式对象缓存系统,经常被用于降低数据库load,同时提升动态web应用的速度。Memcached的奇妙之处在于它的两阶段hash的方法,它通过一个巨大的hash表来查找key = value对,给它一个key,就可以set或get数据了。当进行一次memcached查询时,首先客户端将会根据整个服务器的列表来对key进行 hash,在找到一台服务器后,客户端就发送请求,服务器端在接收到请求后通过对key再做一次内部的hash,从而查找到实际的数据项。当处理巨大的系统时,最大的好处就是memcached所具备的良好的水平扩展能力。由于客户端做了一层hashing,这使得增加N多的节点到集群变得非常的容易,并不会因为节点的互连造成负载的增高,也不会因为多播协议而造成网络的洪水效应。
实际上Memcached并不是一款java产品,但它提供了Java client API,这也就意味着如果你需要在Java EE应用中使用memcached的话,并不需要做多大的改动就可以从cache中通过get获取值,或通过put将值放入cache中。使用 memcached是非常简单的,不过同时也得注意一些事情避免对扩展性和性能造成损失:
- 不要缓存写频繁的对象。Memcached是用来减少对数据库的读操作的,而非写操作,在使用Memcached前,应先关注对象的读/写比率,如果这个比率比较高,那么采用缓存才有意义。
- 尽量避免让运行的memcached的节点互相调用,对于memcached而言这是灾难性的。
- 尽量避免行方式的缓存,在这样的情况下可采用复杂的对象来进行缓存,这对于memcached来说会更为有效。
- 选 择合适的hashing算法。在默认的算法下,增加或减少服务器会导致所有的cache全部失效。由于服务器的列表hash值被改变,可能会造成大部分的 key都要hash到和之前不同的服务器上去,这种情况下,可以考虑采用持续的hashing算法(http://weblogs.java.net /blog/tomwhite/archive/2007/11/consistent_hash.html) 来增加和减少服务器,这样做可以保证你大部分缓存的对象仍然是有效的。
Terracotta
Terracotta(http://www.terracottatech.com/)是一个企业级的、开源的、JVM级别的集群解决方案。JVM级的集群方案意味着可以支撑将企业级的Java应用部署部署到多JVM上,而且就像是运行在同一个JVM中。 Terracotta扩展了JVM的内存模型,各虚拟机上的线程通过集群来与其他虚拟机上的线程进行交互(Terracotta extends the Java Memory Model of a single JVM to include a cluster of virtual machines such that threads on one virtual machine can interact with threads on another virtual machine as if they were all on the same virtual machine with an unlimited amount of heap.)。
Figure 6: Terracotta JVM clustering
采用Terracotta来实现集群应用的编程方式和编写单机应用基本没有什么差别,Terrocotta并没有特别的提供开发者的API,Terracotta采用字节码织入的方式(很多AOP软件开发框架中采用的技术,例如AspectJ和AspectWerkz)来将集群方式的代码插入到已有的java语言中。
我猜想Terrocotta是通过某种互连的方式或多播协议的方式来实现服务器和客户端JVM实例的通讯的,可能是这个原因导致了在我们实验室测试时的效果:当超过20个节点时Terracotta扩展的并不是很好。(注:这个测试结果仅为在我们实验室的测试结果,你的结果可能会不同。)
并行处理
我之前说过,单线程的任务会成为系统可扩展性的瓶颈。但有些单线程的工作(例如处理或生成巨大的数据集)不仅需要多线程或多进程的运行,还会有扩展到多节点运行的需求。例如,在我们实验室测试的一个Java EE项目有一个场景是这样的:根据他们站点的日志文件分析URL的访问规则,每周产生的这些日志文件通常会超过120GB,当采用单线程的Java应用去分析时需要耗费四个小时,客户改为采用Hadoop Map-Reduce使其能够水平扩展从而解决了这个问题,如今这个分析URL访问规则的程序不仅运行在多进程模式下,同时还并行的在超过10个节点上运行,而完成所有的工作也只需要7分钟了。
有很多的框架和工具可以帮助Java EE开发人员来让应用支持水平扩展。除了Hadoop,很多MPI的Java实现也可以用来将单线程的任务水平的扩展到多个节点上并行运行。
MapReduce
MapReduce由Google的Jeffrey Dean和Sanjay Ghemawat提出,是一种用于在大型集群环境下处理巨量数据的分布式编程模型。MapReduce由两个步骤来实现 - Map:对集合中所有的对象进行操作并基于处理返回一系列的结果,Reduce:通过多线程、进程或独立系统并行的从两个或多个Map中整理和获取结果。Map()和Reduce()都是可以并行运行的,不过通常来说没必要在同样的系统同样的时间这么来做。
Hadoop是一个开源的、点对点的、纯Java实现的MapReduce。它是一个用于将分布式应用部署到大型廉价集群上运行的Lucene-derived框架,得到了全世界范围开源人士的支持以及广泛的应用,Yahoo的Search Webmap、Amazon EC2/S3服务以及Sun的网格引擎都可运行在Hadoop上。
简单来说,通过使用“Hadoop Map-Reduce”,"URL访问规则分析"程序可以首先将日志文件分解为多个128M的小文件,然后由Hadoop将这些小文件分配到不同的Map()上去执行。Map()会分析分配给它的小文件并产生临时的结果,Map()产生的所有的临时结果会被排序并分配给不同的Reduce(),Reduce()合并所有的临时结果产生最终的结果,这些Map和Reduce操作都可以由Hadoop框架控制来并行的运行在集群中所有的节点上。
MapReduce对于很多应用而言都是非常有用的,包括分布式检索、分布式排序、web link-graph reversal、term-vector per host、web访问日志分析、索引重建、文档集群、机器智能学习、statistical machine translation和其他领域。
MPI
MPI是一种语言无关、用于实现并行运行计算机间交互的通讯协议,目前已经有很多Java版本的MPI标准的实现,mpiJava和MPJ是其中的典型。mpiJava 基于JNI绑定native的MPI库来实现,MPJ是100%纯java的MPI标准的实现。mpiJava和MPJ和MPI Fortran和C版本提供的API都基本一致,例如它们都对外提供了具备同样方法名和参数的Comm class来实现MPI的信息传递。
CCJ是一个类似MPI通讯操作的java库。CCJ提供了barrier、broadcast、scatter、 gather、all-gather、reduce和all-reduce操作的支持(但不提供点对点的操作,例如send、receive和send- receive)。在底层的通讯协议方面,CCJ并没有自己实现,而是采用了Java RMI,这也就使得CCJ可以用来传递复杂的序列化对象,而不仅仅是MPI中的原始数据类型。进一步看,CCJ还可以从一组并行的processes中获取到复杂的集合对象,例如实现了CCJ的DividableDataObject接口的集合。
采用不同的方法来获取高扩展能力
有很多的书会教我们如何以OO的方式来设计灵活架构的系统,如何来使服务透明的被客户端使用以便维护,如何采用正常的模式来设计数据库schema以便集成。但有些时候为了获取高扩展性,需要采用一些不同的方法。
Google设计了自己的高可扩展的分布式文件系统(GFS),它并不是基于POSIX API来实现的,不过GFS对于用户来说并不完全透明。为了使用GFS,你必须采用GFS的API包。Google也设计了自己的高可扩展的分布式数据库系统(Bigtable),但它并不遵循ANSI SQL标准,而且其中的概念和结构和传统的关系数据库几乎完全不同,但最重要的是GFS和Bigtable能够满足Google的存储要求、良好的扩展性要求,并且已经被Google的广泛的作为其存储平台而使用。
传统方式下,我们通过使用更大型的、更快和更贵的机器或企业级的集群数据库(例如RAC)来将数据库扩展到多节点运行,但我有一个我们实验室中测试的social networking的网站采用了不同的方式,这个应用允许用户在网站上创建profiles、blogs,和朋友共享照片和音乐,此应用基于Java EE编写,运行在Tomcat和Mysql上,但不同于我们实验室中测试的其他应用,它只是希望在20多台便宜的PC Server上进行测试,其数据模型结构如下:
Figure 7: Users data partitions
这里比较特殊的地方子碍于不同的用户数据(例如profile、blog)可能会存储在不同的数据库实例上,例如,用户 00001存储在服务器A上,而用户20001存储在服务器C上,分库的规则以一张元信息的表的方式存储在专门的数据库上。当部署在Tomcat的 Java EE应用希望获取或更新用户信息时,首先它会从这张元信息的表中获取到需要去哪台服务器上获取这个用户,然后再连到实际的服务器上去执行查询或更新操作。
用户数据分区和这种两步时的动作方式可以带来如下的一些好处:
- 扩展了写的带宽:对于这类应用而言,blogging、ranking和BBS将会使得写带宽成为网站的主要瓶颈。分 布式的缓存对于数据库的写操作只能带来很小的提升。采用数据分区的方式,可以并行的进行写,同样也就意味着提升了写的吞吐量。要支持更多的注册用户,只需 要通过增加更多的数据库节点,然后修改元信息表来匹配到新的服务器上。
- 高可用性:如果一台数据库服务器down了,那么只会有部分用户被影响,而其他大部分的用户可以仍然正常使用;
同时也会带来一些缺点:
- 由于数据库节点可以动态的增加,这对于在Tomcat中的Java EE应用而言要使用数据库连接池就比较难了;
- 由于操作用户的数据是两步式的,这也就意味着很难使用ORMapping的工具去实现;
- 当要执行一个复杂的搜索或合并数据时,需要从多台数据库服务器上获取很多不同的数据。
这个系统的架构师这么说:“我们已经知道这些缺点,并且准备好了应对它,我们甚至准备好了应对当元信息表的服务器成为瓶颈的状况,如果出现那样的状况我们将会把元信息表再次划分,并创建出一个更高级别的元信息表来指向众多的二级元信息表服务器实例。“
参考
- Scalability definition in wikipedia: http://en.wikipedia.org/wiki/Scalability
- Javadoc of atomic APIs: http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/atomic/package-summary.html
- Alan Kaminsky. Parallel Java: A unified API for shared memory and cluster parallel programming in 100% Java: http://www.cs.rit.edu/~ark/20070326/pj.pdf
- OMP-an OpenMP-like interface for Java: http://portal.acm.org/citation.cfm?id=337466
- Google MapReduce white paper: http://labs.google.com/papers/mapreduce-osdi04.pdf
- Google Bigtable white paper: http://labs.google.com/papers/bigtable-osdi06.pdf
- Hadoop MapReduce tutorial: http://hadoop.apache.org/core/docs/r0.17.0/mapred_tutorial.html
- Memcached FAQ: http://www.socialtext.net/memcached/index.cgi?faq
- Terracotta: http://www.terracotta.org/
关于作者
Wang Yu目前在Sun的ISVE Group小组工作,担任的职位为Java工程师和架构咨询师,他承担的职责包括支持本地的ISVs,为一些重要的Java技术例如Java EE、EJB、JSP/Servlet、JMS和web services技术提供咨询,可以通过wang.yu@sun.com联系他。
分享到:
相关推荐
本书将帮助您了解启动绿地开发与将现有的棕地应用程序分解为服务所面临的挑战,并检查您的业务领域,以了解微服务是否合适。
JBoss EAP (Enterprise Application ...构建高可用性和可扩展性的应用程序:JBoss EAP具有集群和负载均衡功能,可以实现应用程序的高可用性和可扩展性。 提供企业级支持和安全性:作为商业产品,JBoss EAP提供了
可扩展的,由事件驱动的咖啡店 如何在事件驱动的架构(Apache Kafka和Java EE)的几种服务中使用事件源。 跑步 启动Apache Kafka代理,例如使用Docker compose: 。 将KAFKA_ADVERTISED_HOST_NAME配置为您相应的IP...
本资源将深入探讨软件开发中两个关键的设计和编程概念:三层架构设计模式(MVC)和...AOP的实际应用: 我们将提供实际的示例和案例,展示如何使用AOP来改善代码的可维护性和可扩展性。您将了解如何在Spring框架等流行
Eclipse:Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。它最初是由 IBM 公司开发的,用于通过插件组件构建开发环境。Eclipse 是一个集成开发环境(IDE),用于 Java 语言开发,但也支持其他编程语言,...
通过本书,您将获得在Java EE 8中构建健壮且可扩展的应用程序所需的所有工具和技术。本书涵盖了所有主要的Java EE 8 API,包括JSF 2.3,Enterprise JavaBeans(EJB)3.2,上下文和依赖关系。注入(CDI)2.0,...
本次设计使用JAVA EE平台构建思想来实现自行车租赁管理系统,他的优越性在于更多的实现实用性、可用性,另外可维护性和可扩展性相结合,使得更加安全和规范。另外,在本次系统的开发中还采取了MVC分层结构,CSS布局...
Java EE平台构建于Java SE平台之上,Java EE平台提供一组API和运行环境来开发和运行大规模的,多层的,可扩展的,可靠的和安全的网络应用程序。 做过JAVA EE开发的朋友应该知道,JAVA EE的相关的概念很多,...
使用架构设计模式构建企业就绪的可扩展应用程序 这本书是关于什么的? 模式是Java开发人员必不可少的设计工具。 Java EE设计模式和最佳实践通过检查每种可用模式的用途并通过各种代码示例演示其实现,来帮助开发...
Java EE帮助开发和部署可移植、健壮、可伸缩且安全的服务器 端Java应用程序,它是在Java SE的基础上构建的,它提供Web服务、组件模型、管理和 通信API,可以用来实现企业级的面向服务体系结构(SOA)和Web 2。...
使用架构设计模式构建企业就绪的可扩展应用程序 这本书是关于什么的? 模式是Java开发人员必不可少的设计工具。 Java EE设计模式和最佳实践通过检查每种可用模式的用途并通过各种代码示例演示其实现,来帮助开发...
Grizzly的目标是帮助开发人员使用NIO构建可扩展且强大的服务器,并提供扩展的框架组件:Web框架(HTTP / S),WebSocket,Comet等!入门灰熊目前在以下分支机构中有几条发展线: 2.3.x:这是2.
Java Web开发源码弹簧框架 Spring是最流行的企业Java应用程序开发框架。...EE平台上有一些扩展用于构建Web应用程序。 Spring框架旨在通过启用基于POJO的编程模型来使J2EE开发更易于使用并促进良好的编程实践。
Google AppEngine 是一种可扩展的平台即服务,可在 Google 的基础架构内运行您的应用程序。 AppEngine Managed VM 允许您使用自定义运行时运行您的应用程序,例如 NodeJS、Ruby,或者在本例中使用带有 Wildfly 的 ...
Java毕业设计-Java进销存管理系统(JSP+MSSQL)是一个基于Java技术栈和MSSQL数据库构建的高效、稳定的进销存管理系统。该系统旨在为中小企业提供一个集商品管理、采购、销售、库存、报表等功能于一体的综合性解决...
Spearal 是一种紧凑的二进制格式,用于在 Java EE、JavaScript/HTML、Android 和 iOS 应用程序等各种端点之间交换任意复杂的数据。 Spearal-Java 是所有 Spearal/Java 特定扩展中使用的通用代码库。 如何使用图书...
计算机毕业设计中的旅游管理系统是一个综合性的应用项目,旨在通过现代信息技术提升旅游服务...这样的技术栈选择,确保了系统的可扩展性和良好的用户体验,同时也为计算机专业学生提供了一个展示其软件开发能力的平台。
JavaServer Faces 带有PrimeFaces,Hibernate和Maven的JavaServer Faces... 并提供所有这些功能的可扩展性。 标记库,用于将组件添加到网页以及将组件连接到服务器端对象。 JavaServer Faces技术提供了定义明确的编
其次,我们的安卓项目源码具有良好的可扩展性和可定制性。我们将项目源码设计为模块化的结构,开发人员可以根据自己的需求选择和定制所需的功能模块。同时,我们还提供了丰富的文档和示例代码,以帮助开发人员理解和...
《JUnit实战(第2版)》从认识JUnit、不同的测试策略、JUnit与构建过程、JUnit扩展4个方面,由浅入深、由易到难地对JUnit展开了系统的讲解,包括探索JUnit的核心、软件测试原则、测试覆盖率与开发、使用stub进行粗粒度...