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

RMI、Hessian、Burlap、Httpinvoker、WebService的比較 2(轉)

   

【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 執行完整的單元測試,觀測結果。其餘的本身看源碼吧。

相關文章
相關標籤/搜索