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

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 執行完整的單元測試,觀測結果。其他的自己看源碼吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章