基于dubbo框架下的RPC通信协议性能测试

1、前言

  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等。

2、测试方案

  基于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容器  

 

3、传输测试数据

一、单POJO对象,嵌套复杂集合类型

二、POJO集合,包含100个单POJO对象

三、1K字符串

四、100K字符串

五、1M字符串 

 

4、服务接口和实现

一、服务接口相关代码: 

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);  
}

copy


二、服务实现相关代码,测试数据在服务器端不作任何处理原样返回:


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;  
    }  
}

 cop 

5、单线程测试

一、测试仅记录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毫秒

 

  四、性能对比

 

 

6、多线程测试

  一、因为测试机器配置较低,为了不达到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毫秒

 

  四、性能对比

 

7、性能分析

  测试过程当中尽管考虑了很是多的影响因素,但仍然有不少局限性,包括链接数限制、并发量、线程池策略、Cache、IO、硬件性能瓶颈等等因素,并且各自的适用场景不一样,测试结果仅供参考

  从单线程测试结果能够看出,dubbo协议采用NIO复用单一长链接更适合知足高并发小数据量的rpc调用,而在大数据量下的传输性能并很差,建议使用rmi协议,多线程测试中dubbo协议对小数据量的rpc调用一样保持优点,在大数据量的传输中因为长链接的缘由对比rmi协议传输耗时差距并不明显,这点一样验证了上述观点。关于数据的序列化方式选择须要考虑序列化和反序列化的效率问题,传输内容的大小,以及格式的兼容性约束,其中hessian2做为duobb协议下的默认序列化方式,推荐使用。

  若是有描述错误或者不当的地方欢迎指正。

相关文章
相关标签/搜索