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协议下的默认序列化方式,推荐使用。
若是有描述错误或者不当的地方欢迎指正。