Dubbo RPC服務框架支持豐富的傳輸協議、序列化方式等通信相關的配置和擴展。dubbo執行一次RPC請求的過程大體以下:消費者(Consumer)向註冊中心(Registry)執行RPC請求,註冊中心分配服務URL並路由到具體服務提供方(Provider),消費者和服務提供方創建網絡鏈接,服務提供方在本地建立鏈接池對象並提供遠程服務,對於長鏈接類型協議(如dubbo協議)將保持鏈接,減小握手認證,調用過程當中能夠避免頻繁創建和斷開鏈接致使的性能開銷,保持長鏈接須要有心跳包的發送,因此對於非頻繁調用的服務保持鏈接一樣會有消耗。java
dubbo共支持以下幾種通訊協議:web
dubbo://redis
rmi://spring
hessian://瀏覽器
http://tomcat
webservice://服務器
thrift://網絡
memcached://多線程
redis://併發
一、dubbo協議
Dubbo缺省協議採用單一長鏈接和NIO異步通信,適合於小數據量大併發的服務調用,以及服務消費者機器數遠大於服務提供者機器數的狀況。
缺省協議,使用基於mina1.1.7+hessian3.2.1的tbremoting交互。
鏈接個數:單鏈接
鏈接方式:長鏈接
傳輸協議:TCP
傳輸方式:NIO異步傳輸
序列化:Hessian二進制序列化
適用範圍:傳入傳出參數數據包較小(建議小於100K),消費者比提供者個數多,單一消費者沒法壓滿提供者,儘可能不要用dubbo協議傳輸大文件或超大字符串。
適用場景:常規遠程服務方法調用
爲何要消費者比提供者個數多:
因dubbo協議採用單一長鏈接,
假設網絡爲千兆網卡(1024Mbit=128MByte),
根據測試經驗數據每條鏈接最多隻能壓滿7MByte(不一樣的環境可能不同,供參考),
理論上1個服務提供者須要20個服務消費者才能壓滿網卡。
爲何不能傳大包:
因dubbo協議採用單一長鏈接,
若是每次請求的數據包大小爲500KByte,假設網絡爲千兆網卡(1024Mbit=128MByte),每條鏈接最大7MByte(不一樣的環境可能不同,供參考),
單個服務提供者的TPS(每秒處理事務數)最大爲:128MByte / 500KByte = 262。
單個消費者調用單個服務提供者的TPS(每秒處理事務數)最大爲:7MByte / 500KByte = 14。
若是能接受,能夠考慮使用,不然網絡將成爲瓶頸。
爲何採用異步單一長鏈接:
由於服務的現狀大都是服務提供者少,一般只有幾臺機器,
而服務的消費者多,可能整個網站都在訪問該服務,
好比Morgan的提供者只有6臺提供者,卻有上百臺消費者,天天有1.5億次調用,
若是採用常規的hessian服務,服務提供者很容易就被壓跨,
經過單一鏈接,保證單一消費者不會壓死提供者,
長鏈接,減小鏈接握手驗證等,
並使用異步IO,複用線程池,防止C10K問題。
二、RMI
RMI協議採用JDK標準的java.rmi.*實現,採用阻塞式短鏈接和JDK標準序列化方式
Java標準的遠程調用協議。
鏈接個數:多鏈接
鏈接方式:短鏈接
傳輸協議:TCP
傳輸方式:同步傳輸
序列化:Java標準二進制序列化
適用範圍:傳入傳出參數數據包大小混合,消費者與提供者個數差很少,可傳文件。
適用場景:常規遠程服務方法調用,與原生RMI服務互操做
三、hessian
Hessian協議用於集成Hessian的服務,Hessian底層採用Http通信,採用Servlet暴露服務,Dubbo缺省內嵌Jetty做爲服務器實現
基於Hessian的遠程調用協議。
鏈接個數:多鏈接
鏈接方式:短鏈接
傳輸協議:HTTP
傳輸方式:同步傳輸
序列化:Hessian二進制序列化
適用範圍:傳入傳出參數數據包較大,提供者比消費者個數多,提供者壓力較大,可傳文件。
適用場景:頁面傳輸,文件傳輸,或與原生hessian服務互操做
四、http
採用Spring的HttpInvoker實現
基於http表單的遠程調用協議。
鏈接個數:多鏈接
鏈接方式:短鏈接
傳輸協議:HTTP
傳輸方式:同步傳輸
序列化:表單序列化(JSON)
適用範圍:傳入傳出參數數據包大小混合,提供者比消費者個數多,可用瀏覽器查看,可用表單或URL傳入參數,暫不支持傳文件。
適用場景:需同時給應用程序和瀏覽器JS使用的服務。
五、webservice
基於CXF的frontend-simple和transports-http實現
基於WebService的遠程調用協議。
鏈接個數:多鏈接
鏈接方式:短鏈接
傳輸協議:HTTP
傳輸方式:同步傳輸
序列化:SOAP文本序列化
適用場景:系統集成,跨語言調用。
六、thrif
Thrift 是 Facebook 捐給 Apache 的一個 RPC 框架,當前 dubbo 支持的 thrift 協議是對 thrift 原生協議的擴展,在原生協議的基礎上添加了一些額外的頭信息,好比service name,magic number等。
基於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容器 |
一、單POJO對象,嵌套複雜集合類型
二、POJO集合,包含100個單POJO對象
三、1K字符串
四、100K字符串
五、1M字符串
一、服務接口相關代碼:
package ibusiness; import java.util.List; import model.*; public interface IBusinessOrder { public String SendStr(String str); public List<OrderInfo> LoadOrders(List<OrderInfo> orders); public OrderInfo LoadOrder(OrderInfo order); }
二、服務實現相關代碼,測試數據在服務器端不作任何處理原樣返回:
package business; import ibusiness.IBusinessOrder; import java.util.List; import model.*; public class BusinessOrder implements IBusinessOrder { public String SendStr(String str) { return str; } public List<OrderInfo> LoadOrders(List<OrderInfo> orders) { return orders; } public OrderInfo LoadOrder(OrderInfo order) { return order; } }
一、測試僅記錄rpc調用時間,測試數據的讀取組裝以及首次創建鏈接等相關耗時時間不做統計,循環執行100次取平均值。
二、服務消費方測試代碼
import java.util.List; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import com.alibaba.dubbo.rpc.service.EchoService; import common.Common; import ibusiness.*; import model.*; public class Program { public static void main(String[] args) throws Exception { ApplicationContext ctx = new FileSystemXmlApplicationContext("src//applicationContext.xml"); IBusinessOrder orderBusiness = (IBusinessOrder) ctx.getBean("orderBusiness"); // EchoService echoService = (EchoService) orderBusiness; // String status = echoService.$echo("OK").toString(); // if (!status.equals("OK")) { // System.out.println("orderBusiness out of service!"); // return; // } else { // System.out.println("orderBusiness in service !"); // } long startMili, endMili; int loop = 100; // 單個pojo try { OrderInfo order = Common.BuildOrder(); orderBusiness.LoadOrder(order); // 防止首次鏈接的開銷 startMili = System.currentTimeMillis(); OrderInfo returnOrder = null; for (int i = 0; i < loop; i++) { returnOrder = orderBusiness.LoadOrder(order); } endMili = System.currentTimeMillis(); System.out.println("單個pojo 平均傳輸耗時爲:" + ((endMili - startMili) / (float) loop) + "毫秒 ,返回對象BillNumber:" + returnOrder.getBillNumber()); } catch (Exception ex) { System.out.println("單個pojo 測試失敗!"); //ex.printStackTrace(); } // pojo集合 (100) try { List<OrderInfo> orderList = Common.BuildOrderList(); startMili = System.currentTimeMillis(); List<OrderInfo> returnOrderList = null; for (int i = 0; i < loop; i++) { returnOrderList = orderBusiness.LoadOrders(orderList); } endMili = System.currentTimeMillis(); System.out.println("pojo集合 (100) 平均傳輸耗時爲:" + ((endMili - startMili) / (float) loop) + "毫秒 ,返回記錄數:" + returnOrderList.size()); } catch (Exception ex) { System.out.println("pojo集合 (100) 測試失敗!"); } // 1K String try { String str1k = Common.Build1KString(); startMili = System.currentTimeMillis(); String returnStr1k = null; for (int i = 0; i < loop; i++) { returnStr1k = orderBusiness.SendStr(str1k); } endMili = System.currentTimeMillis(); System.out.println("1K String 平均傳輸耗時爲:" + ((endMili - startMili) / (float) loop) + "毫秒,返回字符長度:" + returnStr1k.length()); } catch (Exception ex) { System.out.println("1K String 測試失敗!"); } // 100K String try { String str100K = Common.Build100KString(); startMili = System.currentTimeMillis(); String returnStr100k = null; for (int i = 0; i < loop; i++) { returnStr100k = orderBusiness.SendStr(str100K); } endMili = System.currentTimeMillis(); System.out.println("100K String 平均傳輸耗時爲:" + ((endMili - startMili) / (float) loop) + "毫秒,返回字符長度:" + returnStr100k.length()); } catch (Exception ex) { System.out.println("100K String 測試失敗!"); } // 1M String try { String str1M = Common.Build1MString(); startMili = System.currentTimeMillis(); String returnStr1M = null; for (int i = 0; i < loop; i++) { returnStr1M = orderBusiness.SendStr(str1M); } endMili = System.currentTimeMillis(); System.out.println("1M String 平均傳輸耗時爲:" + ((endMili - startMili) / (float) loop) + "毫秒,返回字符長度:" + returnStr1M.length()); } catch (Exception ex) { System.out.println("1M String 測試失敗!"); } System.out.println("all test done!"); } }
三、測試數據耗時記錄
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 序列化
<dubbo:protocol name="dubbo" server="netty" port="30001" serialization="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 序列化
<dubbo:protocol name="rmi" server="netty" port="1099" serialization="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 序列化
<dubbo:protocol name="hessian" port="8080" server="servlet" serialization="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毫秒 |
四、性能對比
一、因爲測試機器配置較低,爲了不達到CPU瓶頸,測試設定服務消費方Consumer併發10個線程,每一個線程連續對遠程方法執行5次調用,服務提供方設置容許最大鏈接數100個,同時5個鏈接並行執行,超時時間設置爲5000ms,要求全部事務都能正確返回沒有異常,統計包含首次創建鏈接的消耗時間。
二、服務消費方測試代碼
三、測試數據耗時記錄
A、dubbo 協議、netty 傳輸、hessian2 序列化
<dubbo:protocol name="dubbo" server="netty" port="30001" serialization="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毫秒 |
四、性能對比
測試過程當中儘管考慮了很是多的影響因素,但仍然有不少侷限性,包括鏈接數限制、併發量、線程池策略、Cache、IO、硬件性能瓶頸等等因素,並且各自的適用場景不一樣,測試結果僅供參考。
從單線程測試結果能夠看出,dubbo協議採用NIO複用單一長鏈接更適合知足高併發小數據量的rpc調用,而在大數據量下的傳輸性能並很差,建議使用rmi協議,多線程測試中dubbo協議對小數據量的rpc調用一樣保持優點,在大數據量的傳輸中因爲長鏈接的緣由對比rmi協議傳輸耗時差距並不明顯,這點一樣驗證了上述觀點。關於數據的序列化方式選擇須要考慮序列化和反序列化的效率問題,傳輸內容的大小,以及格式的兼容性約束,其中hessian2做爲duobb協議下的默認序列化方式,推薦使用。
若是有描述錯誤或者不當的地方歡迎指正。