【2】Java远程调用方法性能比较
【IT168技术】如今,Java远程调用方法不少,各类方法的优缺点网络上也有不少的参考文章,此次我对几个典型的Java远程调用方法作了一个简单的性能分析比较,可供你们参考。
测试环境
CPU:奔腾双核 T4500,内存:DDR3-1067 2G,Web容器:Tomcat6.0.33,操做系统:WinXP-sp3
测试项目
①RMI:用Spring3集成发布。
②hessian:用Spring3集成发布到Tomcat容器。
③Java6 WebService:使用Java6原生WebService和注解,直接用Endpoint.publish发布服务。
④CXF:用Spring3集成发布到Tomcat容器。
测试结果java
说明:以上测试虽不是很是的精确,但基本能说明必定的问题。每项案例的服务端方法,都是简单方法,接收客户端传递的一个String类型参数,并打印 到console。每轮测试取三次时间的平均值。全部单线程访问测试所有完成并正常处理请求,没有请求拒绝状况发生。而并发访问测试,除hessian中 途抛异常没法完成,其他均正常完成测试。
结论:
RMI的性能最高,这已经是公认,RMI底层基于Java远程方法协议(JRMP)和对象序列化技术,而JRMP是直接基于TCP/IP协议的封装,在 网络上传输2 byte的有效数据,对于TCP而言,总共有478 byte被额外传输,而对于RMI, 1645byte被额外传输。可见RMI的效率仍是至关不错的。JavaEE标准的EJB就是基于RMI的调用。
hessian是一个轻量级的remoting on http框架。Hessian没有采用WebService标准的SOAP协议,而是本身实现了一种二进制RPC(Remote Procedure Call Protocol,远程过程调用协议)协议。Hessian的设计理念就是简单高效,使用 Hessian 传输数据量比Soap协议要小不少。这也是为何Hessian的性能要高于WebService。可是,尽管它再简单高效,它始终是基于Http协议上 封装的,因此仍是比Java的RMI效率要差一些。我看过其余的一些研究测试报告称Hessian在传输少许对象时,比RMI还要快速高效,但传输数据结 构复杂的对象或大量数据对象时,较RMI要慢20%左右。这个结论在个人测试中还真没有发现,也许与测试环境或方法有关系吧。
Java6WebService 和 CXF的性能应该说是基本同级别,前者略高于后者。众所周知WebService是基于Soap协议实现的,而Soap协议是在Http协议基础上的 XML定义和封装。全部的请求和响应都要被转化成符合SOAP标准的XML格式。显然这直接会致使效率的下降。
XML格式的协议是一种易读易理解的协议,但并不高效。解析和组装XML协议数据流都须要耗费系统的处理时间,因此,WebService的性能不如 Hessian。这里要说一下的是Java6原生的WebService,开发起来很是方便,并且无需额外引入一大堆的Jar包。性能又强于CXF,至于 Axis2和Axis1就更不用说,已经有不少测试代表CXF的性能是Axis2的2倍以上,是Axis1的2-6倍。
那么既然RMI性能那么好,为何咱们须要那么多其余的远程调用方式呢?这个问题又引起到了一个原始的真理。越原始越底层的技术效率就越高,但局限性 也就越大。RMI是Java的特性,那么它必须基于JVM运行,也就是说RMI没法跨语言运行。而WebService就不一样了,Soap是基于标准 Http协议的,是一种与语言无关的字符级协议,因此它能够更好的实现异构系统的通讯。这在咱们现实环境中是很是有用的,相信你们仍是 WebService用的比较多点吧。
不足:此次的测试,仍是存在不少不足之处。
【3】远程调用服务池或者服务工厂
在现代 J2EE 企业应用系统中,存在着 Hessian 、 HttpInvoker 、 XFire 、 Axis 等多种形式的远程调用技术。尽管有 Spring 等框架对这些技术进行了封装,下降了使用的复杂度,但对普通程序员而言还是复杂的——至少须要要掌握这些技术的基础知识。
不管使用那种技术,其基本原理都是同样的:服务端生成骨架,对外暴露服务;客户端生成服务代理,访问调用服务。一般状况下,生成服务代理的代价比较高昂, 这也是咱们第一次访问远程服务速度比较慢的缘由,为每一个请求生成新的服务代理恐怕不是咱们所指望的。更况且,若是采用这种方式,就要在代码里针对各类不一样 的技术(如 XFire 、 HttpInvoker )编写不一样的服务生成和调用的处理代码。不只麻烦,并且容易出错。我想,没有人愿意去直接操做各类框架技术的底层代码,这并非一个好注意!程序员
做为一种替代方案,咱们设计了一个“服务池”的功能,或者说“服务工厂”更贴切一点。先看下面这张类图:服务器
如上图所示,针对 HttpInvoker 、 XFire 、 Hessian 等各类远程调用技术,抽象出一个“远程服务池”(服务工厂)既 RemoteServicePool 接口。该接口提供了获取服务及一些其余的辅助功能,并针对 HttpInvoker 、 XFire 、 Hessian 等不一样技术提供了相应的具体实现。采用这种方式,开发人员只需在代码中“注入” RemoteServicePool ,并以统一的方式(如 getService() )获取实际的服务,只是针对不一样技术在配置上有些须差别而已。该技术的原理很是简单,在应用启动以前把全部存在的服务提供者提供的服务都配置好,并为它们 分配一个惟一的 ID 。应用启动以后,框架会自动生成和这些地址相对应的服务代理( ServiceProxy ),这些代理已是可用的服务,服务获取的细节被彻底屏蔽掉,开发者只要知道如何从 RemoteServicePool 中获取服务就能够了。看一下服务池的接口定义:
java 代码
1.
9.
10. public interface RemoteServicePool {
11.
12.
26.
27. Object getService(String serviceId);
28.
29. }网络
xml 代码
1. <bean id="userServicePool" class="com. tonysoft .common.XFireRemoteServicePool">
2. <property name="serviceInterface">
3. <value>com. tonysoft .demo.service.UserServicevalue>
4. property>
5. <property name="serviceUrls">
6. <map>
7. <entry key=" server 1 ">
8. <value>http://localhost:8080/server1/service/userService?WSDLvalue>
9. entry>
10. <entry key="server2">
11. <value>http://localhost:8080/server2/service/userService?WSDLvalue>
12. entry>
13. map> J2EE 企业应用系统中,存在着 Hessian 、 HttpInvoker 、 XFire 、 Axis 等多种形式的远程调用技术。尽管有 Spring 等框架对这些技术进行了封装,下降了使用的复杂度,但对普通程序员而言还是复杂的——至少须要要掌握这些技术的基础知识。
14. property> 接下来看看如何配置服务:
15. bean>
最后再来看一下访问服务的代码:
java 代码
1.
2. public RemoteServicePool userServicePool ;
3.
6.
7. public void testAddUser() {
8.
9. UserService userService = null ;
10. try {
11. userService =(UserService) userServicePool .getService("server2");
12. } catch (Exception e){
13. throw new RuntimeException( " 获取服务失败,失败缘由:" + e);
14. }
15.
16. OperateResult result = userService .addUser( new User( "daodao" , " 技术部" ));
17.
18. assertEquals(result.isSuccess(), true );
19.
20. }
该方案还为“双向关联”的系统服务提供了一个很好解决办法。看下面一张图:并发
如图,系统 B 和系统 C 都调用系统 A 进行付款操做;同时系统 A 要用远程服务向系统 B 或系统 C 进行认证操做,认证操做的接口(契约)都是同样的,业务逻辑可能有所差别。在这种状况下,配置在系统 A 中的认证服务就比较麻烦,由于要根据不一样的系统调用认证服务,既从 B 过来的请求要访问 B 的认证服务,从 C 过来的请求要访问 C 的认证服务。用服务池能够很好的解决这个问题,把两个系统( B 、 C )提供的认证服务地址都配置在同一个服务池中,根据不一样的 ID (如 B 、 C )来决定使用那个系统的服务。
ServiceResportApplication.rar
描述:
下载
文件名: ServiceResportApplication.rar
文件大小: 49 KB
下载过的: 文件被下载或查看 104 次
尽管服务池解决了一些问题,在某种程度上下降了复杂度,但仍存在以下一些问题:
服务的运行期动态注册
服务的自动注入( IoC )
透明化服务 ID 的传递框架
在服务池( ServicePool )概念的基础上进行扩展,咱们得出了以下的系统模型:ide
在核心位置上是一个服务中心资源库( ServiceRepository ),存储了系统中用到的全部的远程服务。服务采起动态注册的机制,由对外提供的服务注册器( ServiceRegister )提供服务注册功能。外部系统能够实现该接口向资源中心注册服务。提供了一个启动时运行的注册器,能够把静态配置在系统中的服务都注册进来。性能
服务的生成、管理等均由服务中心本身维护,委托服务代理生成器( ServiceProxyGenerator )完成服务的建立。能够针对现有的远程调用方式,如 XFire,HttpInvoker,Hessian 等建立服务代理,也能够针对本身定义的远程调用方式建立服务代理,由 CustomServiceProxyGenerator 完成该功能。
一个服务模型包括 5 个因素:
服务接口 serviceClass
服务 ID serviceId
服务类型 serviceType
服务地址 serviceUrl
附加属性 props
查找一个服务须要两个因素,一个是服务接口,另外一个是服务 ID 。这两个因素共同决定了一个服务,既服务中心内部的“服务 ID ”。经过这种方式,能够容许存在多个 ID 相同但接口不一样的服务,也能够存在多个接口相同但 ID 不一样的服务。单元测试
服务 ID 的获取是系统中一个关键的功能,这部分对程序员来讲应该是透明的,由系统本身维护。相应的提供了一个服务 ID 提供者 (ServiceIdProvider) 接口,由实现该接口的子类完成服务 ID 获取功能(这是比较关键的地方,须要特殊考虑)。测试
对于程序员来讲,使用服务中内心的服务不再能比这样再简单了!看看配置:
xml 代码
1. < bean id = "helloHttpInvokerService" parent = "abstractServiceProxyFactory" >
2. < property name = "serviceInterface" >
3. < value > com.tonysoft.common.service.repository.example.HelloHttpInvoker value >
4. property >
5. bean >
再看如何使用这个 bean :
1. private HelloHttpInvoker helloHttpInvokerService ;
2. public void testHttpInvoker() {
3. assertNotNull( "helloHttpInvokerService can't be null !" , helloHttpInvokerService );
4. assertEquals ( "Hello , HttpInvoker !" , helloHttpInvokerService .sayHello());
5. }
6.
10.
11. public void setHelloHttpInvokerService(HelloHttpInvoker helloHttpInvokerService) {
12. this . helloHttpInvokerService = helloHttpInvokerService;
13. }
就是这样的简单! Spring 会把这个 bean 自动注入到程序中,能够象使用其余任何 bean 同样使用它!程序员彻底不用关心该服务由谁提供、采用什么技术,他只要知道系统中存在这样一个服务就 OK 了。该技术完全向程序员屏蔽了底层技术的实现细节,以统一的方式访问任何形式的远程服务。至于服务是如何生成、如何配置的将在后面叙述。
服务( Service Bean )是如何实现自动注入( IoC )的呢?
注意到上面配置的 bean 都继承了“ abstractServiceProxyFactory ”,它是一个工厂 bean ,负责根据给定的接口类型,到服务中心( ServiceRepository )查找服务,并生成服务代理。咱们来看一下它的核心代码:
java 代码
1.
15. public class ServiceProxyFactory implements FactoryBean {
16.
17.
18. private ServiceRepository serviceRepository ;
19.
20.
21. private ServiceIdProvider serviceIdProvider ;
22.
23. private Class serviceInterface ;
24.
27. public Object getObject() throws Exception {
28. return ProxyFactory.getProxy(getObjectType(), new ServiceProxyInterceptor());
29. // return serviceRepository.getService(serviceInterface, serviceIdProvider.getCurrentServiceId());
30. }
31.
34. public Class getObjectType() {
35. return serviceInterface ;
36. }
37.
40. public boolean isSingleton() {
41. return true ;
42. }
43.
46. private class ServiceProxyInterceptor implements MethodInterceptor {
47.
50. public Object invoke(MethodInvocation invocation) throws Throwable {
51. Method method = invocation.getMethod();
52. Object[] args = invocation.getArguments();
53. Object client = getClient();
54. return method.invoke(client, args);
55. }
56. private Object getClient() {
57. try {
58. return serviceRepository .getService( serviceInterface , serviceIdProvider .getCurrentServiceId());
59. } catch (ServiceException e) {
60. // TODO
61. e.printStackTrace();
62. return null ;
63. }
64. }
65. }
66. // ---- 容器自动注入 ----
67. ••••••
真正的魅力就在这个地方。根据服务接口类型和服务 ID ,从服务中心获取特定的服务。服务接口是配置好的, 而服务 ID 则在运行时才能肯定,根据不一样的应用、不一样的策略提供不一样的 ServiceIdProvider 。其中用到了 Spring 的 FactoryBean 和拦截器,至于为何要在这里使用拦截器,能够参考 Spring 框架的源码。
服务代理生成器( ServiceProxyGenerator )也是一个值得一提的地方,咱们先看一下它的接口:
1.
6. public interface ServiceProxyGenerator {
7.
8.
20. Object getService(Class serviceClass, String serviceUrl, Properties props) throws Exception;
21. }
22.
23.
24. public void registService(ServiceModel serviceModel) throws ServiceException {
25. ••••••
26. String key = serviceModel.getServiceId() + KEY_SPAR
27. + serviceModel.getServiceClass().getName();
28. if ( serviceNames .contains(key)) {
29. throw new ServiceRegistException( "service is exist!" );
30. }
31. Object proxy = null ;
32. try {
33. ServiceProxyGenerator proxyGenerator = (ServiceProxyGenerator) beanFactory
34. .getBean(serviceModel.getServiceType() + PROXY_GENERATOR_END );
35. proxy = proxyGenerator.getService(serviceModel.getServiceClass(), serviceModel
36. .getServiceUrl(), serviceModel.getProps());
37. } catch (Exception e) {
38. throw new ServiceRegistException( "can't regist service !" , e);
39. }
40. if (proxy != null ) {
41. serviceNames .add(key);
42. serviceContainer .put(key, proxy);
43. } else {
44. throw new ServiceRegistException( "fail to regist service !" );
45. }
46. }
上面作特殊标记的代码就是应用服务代理生成器的地方,这里咱们用到了 Spring 的 bean 工厂,根据注册服务的类型( xfire,httpinvoker,hessian 等)到 Spring 容器里查找相应的生成器,并生成指定类型的服务。看下面配置的几个服务代理生成器:
xml 代码
1. <!-- XFire 类型服务代理生成器 -->
2. < bean id = "xfire_generator" class = "com.tonysoft.common.service.repository.generator.XFireServiceProxyGenerator" lazy-init = "true" >
3. < property name = "serviceFactory" >
4. < ref bean = "xfire.serviceFactory" />
5. property >
6. bean >
7.
8. <!-- Hessian 类型服务代理生成器 -->
9. < bean id = "hessian_generator" class = "com.tonysoft.common.service.repository.generator.HessianServiceProxyGenerator" lazy-init = "true" >
10. bean >
11.
12. <!-- HttpInvoker 类型服务代理生成器 -->
13. < bean id = "httpinvoker_generator" class = "com.tonysoft.common.service.repository.generator.HttpInvokeServiceProxyGenerator" lazy-init = "true" >
14. bean >
15.
16. <!-- 自定义 类型服务代理生成器 -->
17. < bean id = "custom_generator" class = "com.tonysoft.common.service.repository.generator.CustomServiceProxyGenerator" lazy-init = "true" >
18. bean >
19.
20. <!-- 服务中心(资源库) -->
21. < bean id = "serviceRepository" class = "com.tonysoft.common.service.repository.DefaultServiceRepository" >
22. bean >
23.
24. <!-- 服务 ID 提供者 -->
25. < bean id = "serviceIdProvider" class = "com.tonysoft.common.service.repository.provider.DefaultServiceIdProvider" >
26. bean >
27. <!-- 全部远程服务的基础类 -->
28. < bean id = "abstractServiceProxyFactory" class = "com.tonysoft.common.service.repository.ServiceProxyFactory" abstract = "true" >
29. bean >
简单看一下 HttpInvoker 类型服务代理生成器的代码:
java 代码
1. public class HttpInvokeServiceProxyGenerator implements ServiceProxyGenerator {
2.
3.
4. private HttpInvokerProxyFactoryBean httpInvokerFactory = new HttpInvokerProxyFactoryBean();
5.
9. public Object getService(Class serviceClass, String serviceUrl, Properties props) {
10. // Todo initial httpInvokerFactory with props
11. httpInvokerFactory .setServiceInterface(serviceClass);
12.
13. httpInvokerFactory .setServiceUrl(serviceUrl);
14.
15. // must invoke this method
16. httpInvokerFactory .afterPropertiesSet();
17.
18. return httpInvokerFactory .getObject();
19.
20. }
21.
22. }
是的,正如你所看到的同样,咱们这里把真正生成服务代理的任务交给了 Spring 的 HttpInvokerProxyFactoryBean 来完成。
提供在初始化时注册的静态服务功能,配制以下:
1. <!-- 初始化时注册的静态服务 -->
2. < bean id = "bootupServiceRegister" class = "com.tonysoft.common.service.repository.register.BootupServiceRegister" lazy-init = "false" >
3. < property name = "services" >
4. < list >
5. < bean class = "com.tonysoft.common.service.repository.ServiceModel" >
6. < property name = "serviceClass" >< value > com.tonysoft.common.service.repository.example.HelloHttpInvoker value > property >
7. < property name = "serviceId" >< value > default value > property >
8. < property name = "serviceType" >< value > httpinvoker value > property >
9. < property name = "serviceUrl" >< value >http://localhost:8080/serviceRepositoryApplication...voker/helloHttpInvoker.service value > property >
10. < property name = "props" >
11. < props > props >
12. property >
13. bean >
14. < bean class = "com.tonysoft.common.service.repository.ServiceModel" >
15. < property name = "serviceClass" >< value > com.tonysoft.common.service.repository.example.HelloXFire value > property >
16. < property name = "serviceId" >< value > default value > property >
17. < property name = "serviceType" >< value > xfire value > property >
18. < property name = "serviceUrl" >< value >http://localhost:8080/serviceRepositoryApplication.../xfire/helloXFire.service?WSDL value > property >
19. < property name = "props" >
20. < props > props >
21. property >
22. bean >
23. list >
24. property >
25. bean >
具体内容能够参看附件中的资源:
1、 ServiceRepository 的源代码( Eclipse 工程)
2、 一个示例应用
3、 打包部署的 ANT 脚本
把项目导入 Eclipse 中,直接运行 Ant 脚本,在 target 目录下会生成服务中心的 jar 包,同时生成示例应用的 war 包,把 war 包放到任意服务器( Server )上并启动服务器并确保应用正常启动。 运行 ServiceRepositoryTest .java 执行完整的单元测试,观测结果。其余的本身看源码吧。