一、前言
Dubbo RPC服務框架支持豐富的傳輸協議、序列化方式等通訊相關的配置和擴展。dubbo執行一次RPC請求的過程大致如下:消費者(Consumer)向注冊中心(Registry)執行RPC請求,注冊中心分配服務URL并路由到具體服務提供方(Provider),消費者和服務提供方建立網絡連接,服務提供方在本地創建連接池對象并提供遠程服務,對于長連接類型協議(如dubbo協議)將保持連接,減少握手認證,調用過程中可以避免頻繁建立和斷開連接導致的性能開銷,保持長連接需要有心跳包的發送,所以對于非頻繁調用的服務保持連接同樣會有消耗。更多關于dubbo詳細介紹請參照官方文檔( http://alibaba.github.io/dubbo-doc-static/Home-zh.htm )。
1、支持常見的傳輸協議:RMI、Dubbo、Hessain、WebService、Http等,其中Dubbo和RMI協議基于TCP實現,Hessian和WebService基于HTTP實現。
2、傳輸框架:Netty、Mina、以及基于servlet等方式。
3、序列化方式:Hessian2、dubbo、JSON( fastjson 實現)、JAVA、SOAP 等。
本文主要基于dubbo框架下的通訊協議進行性能測試對比。
?
二、測試方案
基于dubbo 2.5.3框架,使用zookeeper作為dubbo服務注冊中心,分別以單線程和多線程的方式測試以下方案:
? | Protocol | ? ? ? Transporter ? | ? ? Serialization ? ? | Remark |
A | ?dubbo 協議 | ?netty | ?hessian2 | ? |
B | ?dubbo 協議 | ?netty | ?dubbo | ? |
C | ?dubbo 協議 | ?netty | ?java | ? |
D | ?RMI?協議 | ?netty | ?java | ? |
E | ?RMI?協議 | ?netty | ?hessian2 | ? |
F | ?Hessian 協議 | ?servlet | ?hessian2 | ?Hessian,基于tomcat容器 ? ?? |
G | ?WebService 協議 ? | ?servlet | ?SOAP | ?CXF,基于tomcat容器 ? |
?
三、傳輸測試數據
1、單POJO對象,嵌套復雜集合類型
2、POJO集合,包含100個單POJO對象
3、1K字符串
4、100K字符串
5、1M字符串?
?
四、服務接口和實現
1、服務接口相關代碼:?
1 package ibusiness; 2 3 import java.util.List; 4 5 import model.* ; 6 7 public interface IBusinessOrder { 8 public String SendStr(String str); 9 10 public List<OrderInfo> LoadOrders(List<OrderInfo> orders); 11 12 public OrderInfo LoadOrder(OrderInfo order); 13 }
2、服務實現相關代碼,測試數據在服務器端不做任何處理原樣返回:

1 package business; 2 3 import ibusiness.IBusinessOrder; 4 5 import java.util.List; 6 7 import model.* ; 8 9 public class BusinessOrder implements IBusinessOrder { 10 public String SendStr(String str) { 11 return str; 12 } 13 14 public List<OrderInfo> LoadOrders(List<OrderInfo> orders) { 15 return orders; 16 } 17 18 public OrderInfo LoadOrder(OrderInfo order) { 19 return order; 20 } 21 }
?
五、單線程測試
1、測試僅記錄rpc調用時間,測試數據的讀取組裝以及首次建立連接等相關耗時時間不作統計,循環執行100次取平均值。
2、服務消費方測試代碼

1 import java.util.List; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.FileSystemXmlApplicationContext; 5 6 import com.alibaba.dubbo.rpc.service.EchoService; 7 import common.Common; 8 9 import ibusiness.* ; 10 import model.* ; 11 12 public class Program { 13 public static void main(String[] args) throws Exception { 14 15 ApplicationContext ctx = new FileSystemXmlApplicationContext("src//applicationContext.xml" ); 16 IBusinessOrder orderBusiness = (IBusinessOrder) ctx.getBean("orderBusiness" ); 17 18 // EchoService echoService = (EchoService) orderBusiness; 19 // String status = echoService.$echo("OK").toString(); 20 // if (!status.equals("OK")) { 21 // System.out.println("orderBusiness out of service!"); 22 // return; 23 // } else { 24 // System.out.println("orderBusiness in service !"); 25 // } 26 27 long startMili, endMili; 28 int loop = 100 ; 29 30 // 單個pojo 31 try { 32 OrderInfo order = Common.BuildOrder(); 33 orderBusiness.LoadOrder(order); // 防止首次連接的開銷 34 35 startMili = System.currentTimeMillis(); 36 OrderInfo returnOrder = null ; 37 for ( int i = 0; i < loop; i++ ) { 38 returnOrder = orderBusiness.LoadOrder(order); 39 } 40 endMili = System.currentTimeMillis(); 41 System.out.println("單個pojo 平均傳輸耗時為:" + ((endMili - startMili) / ( float ) loop) + "毫秒 ,返回對象BillNumber:" + returnOrder.getBillNumber()); 42 } catch (Exception ex) { 43 System.out.println("單個pojo 測試失敗!" ); 44 // ex.printStackTrace(); 45 } 46 47 // pojo集合 (100) 48 try { 49 List<OrderInfo> orderList = Common.BuildOrderList(); 50 startMili = System.currentTimeMillis(); 51 List<OrderInfo> returnOrderList = null ; 52 for ( int i = 0; i < loop; i++ ) { 53 returnOrderList = orderBusiness.LoadOrders(orderList); 54 } 55 endMili = System.currentTimeMillis(); 56 System.out.println("pojo集合 (100) 平均傳輸耗時為:" + ((endMili - startMili) / ( float ) loop) + "毫秒 ,返回記錄數:" + returnOrderList.size()); 57 } catch (Exception ex) { 58 System.out.println("pojo集合 (100) 測試失敗!" ); 59 } 60 61 // 1K String 62 try { 63 String str1k = Common.Build1KString(); 64 startMili = System.currentTimeMillis(); 65 String returnStr1k = null ; 66 for ( int i = 0; i < loop; i++ ) { 67 returnStr1k = orderBusiness.SendStr(str1k); 68 } 69 endMili = System.currentTimeMillis(); 70 System.out.println("1K String 平均傳輸耗時為:" + ((endMili - startMili) / ( float ) loop) + "毫秒,返回字符長度:" + returnStr1k.length()); 71 } catch (Exception ex) { 72 System.out.println("1K String 測試失敗!" ); 73 } 74 75 // 100K String 76 try { 77 String str100K = Common.Build100KString(); 78 startMili = System.currentTimeMillis(); 79 String returnStr100k = null ; 80 for ( int i = 0; i < loop; i++ ) { 81 returnStr100k = orderBusiness.SendStr(str100K); 82 } 83 endMili = System.currentTimeMillis(); 84 System.out.println("100K String 平均傳輸耗時為:" + ((endMili - startMili) / ( float ) loop) + "毫秒,返回字符長度:" + returnStr100k.length()); 85 } catch (Exception ex) { 86 System.out.println("100K String 測試失敗!" ); 87 } 88 89 // 1M String 90 try { 91 String str1M = Common.Build1MString(); 92 startMili = System.currentTimeMillis(); 93 String returnStr1M = null ; 94 for ( int i = 0; i < loop; i++ ) { 95 returnStr1M = orderBusiness.SendStr(str1M); 96 } 97 endMili = System.currentTimeMillis(); 98 System.out.println("1M String 平均傳輸耗時為:" + ((endMili - startMili) / ( float ) loop) + "毫秒,返回字符長度:" + returnStr1M.length()); 99 } catch (Exception ex) { 100 System.out.println("1M String 測試失敗!" ); 101 } 102 103 System.out.println("all test done!" ); 104 } 105 }
3、測試數據耗時記錄
A、dubbo 協議、netty 傳輸、hessian2 序列化
<dubbo:protocol name="dubbo" server="netty" port="30001" serialization="hessian2" ?/>
單個POJO | 0.958毫秒 |
POJO集合?(100) | 1.438毫秒 |
1K String | 0.68毫秒 |
100K String | 4.262毫秒 |
1M String | 32.473毫秒? |
B、dubbo 協議、netty 傳輸、dubbo 序列化
單個POJO | 1.45毫秒 |
POJO集合?(100) | 3.42毫秒 |
1K String | 0.94毫秒 |
100K String | 4.35毫秒 |
1M String | 27.92毫秒 |
C、dubbo 協議、netty 傳輸、java 序列化
<dubbo:protocol name="dubbo" server="netty" port="30001" serialization="java" />?
單個POJO | 1.91毫秒 |
POJO集合?(100) | 4.48毫秒 |
1K String | 1.0毫秒 |
100K String | 3.3毫秒 |
1M String | 18.09毫秒 |
D、RMI?協議、netty 傳輸、java 序列化?
<dubbo:protocol name="rmi" server="netty" port="1099" serialization="java" /> ??
單個POJO | 1.63毫秒 |
POJO集合?(100) | 5.15毫秒 |
1K String | 0.77毫秒 |
100K String | 2.15毫秒 |
1M String | 15.21毫秒 |
E、RMI?協議、netty 傳輸、hessian2?序列化?
單個POJO | 1.63毫秒 |
POJO集合?(100) | 5.12毫秒 |
1K String | 0.76毫秒 |
100K String | 2.13毫秒 |
1M String | 15.11毫秒 |
F、Hessian協議、servlet(tomcat容器)、hessian2?序列化?
單個POJO | 1.6毫秒 |
POJO集合?(100) | 5.98毫秒 |
1K String | 1.88毫秒 |
100K String | 5.52毫秒 |
1M String | 39.87毫秒 |
G、WebService協議、servlet(tomcat容器)、SOAP序列化
<dubbo:protocol name="webservice" port="8080" server="servlet" />?
單個POJO | 7.4毫秒 |
POJO集合?(100) | 34.39毫秒 |
1K String | 6.0毫秒 |
100K String | 7.43毫秒 |
1M String | 34.61毫秒 |
4、性能對比
?
六、多線程測試
1、由于測試機器配置較低,為了避免達到CPU瓶頸,測試設定服務消費方Consumer并發10個線程,每個線程連續對遠程方法執行5次調用,服務提供方設置允許最大連接數100個,同時5個連接并行執行,超時時間設置為5000ms,要求所有事務都能正確返回沒有異常,統計包含首次建立連接的消耗時間。
2、服務消費方測試代碼
?
3、測試數據耗時記錄
A、dubbo 協議、netty 傳輸、hessian2 序列化
單個POJO | 1165毫秒 |
POJO集合?(100) | 1311毫秒 |
1K String | 1149毫秒 |
100K String | 1273毫秒 |
1M String | 2141毫秒 |
B、dubbo 協議、netty 傳輸、dubbo 序列化
<dubbo:protocol name="dubbo" server="netty" port="30001" serialization="dubbo" />?
單個POJO | 1220毫秒 |
POJO集合?(100) | 1437毫秒 |
1K String | 1145毫秒 |
100K String | 1253毫秒 |
1M String | 2065毫秒 |
C、dubbo 協議、netty 傳輸、java 序列化
<dubbo:protocol name="dubbo" server="netty" port="30001" serialization="java" />?
單個POJO | 1188毫秒 |
POJO集合?(100) | 1401毫秒 |
1K String | 1123毫秒 |
100K String | 1227毫秒 |
1M String | 1884毫秒 |
D、RMI?協議、netty 傳輸、java 序列化 ?
<dubbo:protocol name="rmi" server="netty" port="1099" serialization="java" /> ??
單個POJO | 1751毫秒 |
POJO集合?(100) | 1569毫秒 |
1K String | 1766毫秒 |
100K String | 1356毫秒 |
1M String | 1741毫秒 |
E、RMI?協議、netty 傳輸、hessian2?序列化?
<dubbo:protocol name="rmi" server="netty" port="1099" serialization="hessian2" ?/>?
單個POJO | 1759毫秒 |
POJO集合?(100) | 1968毫秒 |
1K String | 1239毫秒 |
100K String | 1339毫秒 |
1M String | 1736毫秒 |
F、Hessian協議、servlet、hessian2?序列化?
<dubbo:protocol name="hessian" port="8080" server="servlet" serialization="hessian2" />?
單個POJO | 1341毫秒 |
POJO集合?(100) | 2223毫秒 |
1K String | 1800毫秒 |
100K String | 1916毫秒 |
1M String | 2445毫秒 |
G、WebService協議、servlet、SOAP序列化
<dubbo:protocol name="webservice" port="8080" server="servlet" />?
單個POJO | 1975毫秒 |
POJO集合?(100) | 2768毫秒 |
1K String | 1894毫秒 |
100K String | 2098毫秒 |
1M String | 2887毫秒 |
4、性能對比
七、性能分析
測試過程中盡管考慮了非常多的影響因素,但仍然有很多局限性,包括連接數限制、并發量、線程池策略、Cache、IO、硬件性能瓶頸等等因素,而且各自的適用場景不同,測試結果 僅供參考 。
從單線程測試結果可以看出,dubbo協議采用NIO復用單一長連接更適合滿足高并發小數據量的rpc調用,而在大數據量下的傳輸性能并不好,建議使用rmi協議,多線程測試中dubbo協議對小數據量的rpc調用同樣保持優勢,在大數據量的傳輸中由于長連接的原因對比rmi協議傳輸耗時差距并不明顯,這點同樣驗證了上述觀點。關于數據的序列化方式選擇需要考慮序列化和反序列化的效率問題,傳輸內容的大小,以及格式的兼容性約束,其中hessian2作為duobb協議下的默認序列化方式,推薦使用。
如果有描述錯誤或者不當的地方歡迎指正。
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
