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

原文: 基于dubbo框架下的RPC通讯协议性能测试,作者: lengfo

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
2
3
4
5
6
7
8
9
10
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);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}
}

单线程测试

1、测试仅记录rpc调用时间,测试数据的读取组装以及首次建立连接等相关耗时时间不作统计,循环执行100次取平均值。  

2、服务消费方测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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!");
}
}

3、测试数据耗时记录

A、dubbo 协议、netty 传输、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 序列化

单个POJO 1.91毫秒
POJO集合 (100) 4.48毫秒
1K String 1.0毫秒
100K String 3.3毫秒
1M String 18.09毫秒

D、RMI 协议、netty 传输、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序列化

单个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 序列化

单个POJO 1220毫秒
POJO集合 (100) 1437毫秒
1K String 1145毫秒
100K String 1253毫秒
1M String 2065毫秒

C、dubbo 协议、netty 传输、java 序列化

单个POJO 1188毫秒
POJO集合 (100) 1401毫秒
1K String 1123毫秒
100K String 1227毫秒
1M String 1884毫秒

D、RMI 协议、netty 传输、java 序列化

单个POJO 1751毫秒
POJO集合 (100) 1569毫秒
1K String 1766毫秒
100K String 1356毫秒
1M String 1741毫秒

E、RMI 协议、netty 传输、hessian2 序列化

单个POJO 1759毫秒
POJO集合 (100) 1968毫秒
1K String 1239毫秒
100K String 1339毫秒
1M String 1736毫秒

F、Hessian协议、servlet、hessian2 序列化

单个POJO 1341毫秒
POJO集合 (100) 2223毫秒
1K String 1800毫秒
100K String 1916毫秒
1M String 2445毫秒

G、WebService协议、servlet、SOAP序列化

单个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协议下的默认序列化方式,推荐使用。

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