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