您的位置:首页 > 理论基础 > 计算机网络

RMI、Hessian、Burlap、Httpinvoker、WebService的比较

2015-02-02 14:32 477 查看
Java远程调用方法性能比较

 【IT168技术】现在,Java远程调用方法很多,各种方法的优缺点网络上也有很多的参考文章,这次我对几个典型的Java远程调用方法做了一个简单的性能分析比较,可供大家参考。

  测试环境

  CPU:奔腾双核 T4500,内存:DDR3-10672G,Web容器:Tomcat6.0.33,操作系统:WinXP-sp3

  测试项目

  ①RMI:用Spring3集成发布。

  ②hessian:用Spring3集成发布到Tomcat容器。

  ③Java6WebService:使用Java6原生WebService和注解,直接用Endpoint.publish发布服务。

  ④CXF:用Spring3集成发布到Tomcat容器。

  测试结果

  说明:以上测试虽不是非常的精确,但基本能说明一定的问题。每项案例的服务端方法,都是简单方法,接收客户端传递的一个String类型参数,并打印到console。每轮测试取三次时间的平均值。所有单线程访问测试全部完成并正常处理请求,没有请求拒绝情况发生。而并发访问测试,除hessian中途抛异常无法完成,其余均正常完成测试。

  结论:

  RMI的性能最高,这已是公认,RMI底层基于Java远程方法协议(JRMP)和对象序列化技术,而JRMP是直接基于TCP/IP协议的封装,在网络上传输2 byte的有效数据,对于TCP而言,总共有478 byte被额外传输,而对于RMI,1645byte被额外传输。可见RMI的效率还是相当不错的。JavaEE标准的EJB就是基于RMI的调用。

  hessian是一个轻量级的remoting onhttp框架。Hessian没有采用WebService标准的SOAP协议,而是自己实现了一种二进制RPC(RemoteProcedure 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. <propertyname="serviceInterface">

3. <value>com. tonysoft.demo.service.UserServicevalue>

4. property>

5. <propertyname="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 )概念的基础上进行扩展,我们得出了如下的系统模型:

在核心位置上是一个服务中心资源库( 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.HelloHttpInvokervalue >

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(HelloHttpInvokerhelloHttpInvokerService) {

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(), newServiceProxyInterceptor());

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 implementsMethodInterceptor {

47.

50. public Object invoke(MethodInvocation invocation) throwsThrowable {

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) throwsServiceException {

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 implementsServiceProxyGenerator {

2.

3.

4. private HttpInvokerProxyFactoryBean httpInvokerFactory = newHttpInvokerProxyFactoryBean();

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.HelloHttpInvokervalue > property >

7. < property name = "serviceId">< value > defaultvalue > property >

8. < property name = "serviceType">< value > httpinvokervalue > property >

9. < property name = "serviceUrl">< value>http://localhost:8080/serviceRepositoryApplication...voker/helloHttpInvoker.servicevalue > 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 > defaultvalue > property >

17. < property name = "serviceType">< value > xfire value> property >

18. < property name = "serviceUrl">< value>http://localhost:8080/serviceRepositoryApplication.../xfire/helloXFire.service?WSDLvalue > property >

19. < property name = "props" >

20. < props > props>

21. property >

22. bean >

23. list >

24. property >

25. bean >

具体内容可以参看附件中的资源:

一、 ServiceRepository 的源代码( Eclipse 工程)

二、 一个示例应用

三、 打包部署的 ANT 脚本

把项目导入 Eclipse 中,直接运行 Ant 脚本,在 target 目录下会生成服务中心的 jar 包,同时生成示例应用的war 包,把 war 包放到任意服务器( Server )上并启动服务器并确保应用正常启动。 运行ServiceRepositoryTest .java 执行完整的单元测试,观测结果。其他的自己看源码吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: