1.所有的RPC协议基本组成
2.dubbo中所支持的PRC协议使用
名称 | 实现描述 | 连接描述 | 适用场景 |
dubbo | 传输服务: mina, netty(默认), grizzy 序列化: hessian2(默认), java, fastjson 自定义报文 | 单个长连接 NIO异步传输 | 1、常规RPC调用 2、传输数据量小 3、提供者少于消费者 |
rmi | 传输:java rmi 服务 序列化:java原生二进制序列化 | 多个短连接 BIO同步传输 | 1、常规RPC调用 2、与原RMI客户端集成 3、可传少量文件 4、不支持防火墙穿透 |
hessian | 传输服务:servlet容器 序列化:hessian二进制序列化 | 基于Http 协议传输, 依懒servlet容器配置 | 1、提供者多于消费者 2、可传大字段和文件 3、跨语言调用 |
http | 传输服务:servlet容器 序列化:java原生二进制序列化 | 依懒servlet容器配置 | 1、数据包大小混合 |
thrift | 与thrift RPC 实现集成,并在其基础上修改了报文头 | 长连接、NIO异步传输 |
|
3.dubbo协议的使用和配置
3.1 参数配置说明
name: 协议名称 dubbo|rmi|hessian|http| (默认为dubbo协议)
host:本机IP可不填,则系统自动获取
port:端口、填-1表示系统自动选择 (如果dubbo协议 -1代表从20880 开始一次递增)
server:运行服务 mina|netty|grizzy|servlet|jetty (默认为 netty)
serialization:序列化方式 hessian2|java|compactedjava|fastjson(默认为 hessian2)
4.dubbo支持的序列化
| 特点 |
fastjson | 文本型:体积较大,性能慢、跨语言、可读性高 |
fst | 二进制型:体积小、兼容 JDK 原生的序列化。要求 JDK 1.7 支持。 |
hessian2 | 二进制型:跨语言、容错性高、体积小 |
java | 二进制型:在JAVA原生的基础上 可以写入Null |
compactedjava | 二进制型:与java 类似,内容做了压缩 |
nativejava | 二进制型:原生的JAVA 序列化 |
kryo | 二进制型:体积比hessian2 还要小,但容错性 没有hessian2 好 |
注意的是:支持dubbo的协议并不代表一定指出对应的序列化,比喻http就只能支持然生的java 需要映入依赖
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>org.springframework.integration.httpinvoker</artifactId>
<version>2.0.0.M4</version>
</dependency>
<!-- 配置协议 同时配置会报错 -1默认8080 -->
<dubbo:protocol name="http" port="-1" />
<!-- 默认值 -->
<dubbo:provider protocol="http" port="-1"/>
5.演示容错率(java序列化和hessian2对比)
dubbo序列化放在 利用spi 实现,(spi另外一篇文章中介绍)
5.1对比序列化怎么体现容错率
import java.util.Date;
import java.util.List;
/**
* ......................................
* .没有才怎么怀才不遇,没有志怎么壮志难酬 .
* . ....................................
*
*@Author: lq
*@Date: 2018/12/4 10:14
*@Description: 比较几种序列化
*/
public class UserSerializable implements Serializable{
private static final long serialVersionUID = -7103556216921677848L;
private String name;
private Date dateTime;
private Integer age;
private Integer age1;
private Sex sex;
List<String> list;
public UserSerializable(String name, Date dateTime, Integer age, Sex sex, List<String> list) {
this.name = name;
this.dateTime = dateTime;
this.age = age;
this.sex = sex;
this.list = list;
}
public UserSerializable(){}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDateTime() {
return dateTime;
}
public void setDateTime(Date dateTime) {
this.dateTime = dateTime;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Sex getSex() {
return sex;
}
public void setSex(Sex sex) {
this.sex = sex;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
@Override
public String toString() {
return "UserSerializable{" +
"name='" + name + '\'' +
", dateTime=" + dateTime +
", age=" + age +
", sex='" + sex + '\'' +
", list=" + list +
'}';
}
public enum Sex {
man,woman,ladyBody,girl;
}
}
5.2测试
package com.fashion.common;
import com.alibaba.com.caucho.hessian.io.Hessian2Input;
import com.alibaba.com.caucho.hessian.io.Hessian2Output;
import org.junit.Assert;
import org.junit.Test;
import java.io.*;
import java.util.Date;
import java.util.LinkedList;
import static org.junit.Assert.*;
public class UserSerializableTest {
// java 序列化
@Test
public void javaSerializable() {
String s = System.getProperty("user.dir") + "/target/user_java";
try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(s));
UserSerializable user = new UserSerializable("张三",new Date(),18, UserSerializable.Sex.man,new LinkedList<>());
System.out.println("java serializable success");
outputStream.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
}
}
// java 反序列化
@Test
public void javaDeserializable() {
String s = System.getProperty("user.dir") + "/target/user_java";
try {
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(s));
UserSerializable user = (UserSerializable) inputStream.readObject();
System.out.println(user);
Assert.assertNotNull(user);
} catch (Exception e) {
e.printStackTrace();
}
}
// hessian2 序列化
@Test
public void hessian2Serializable() {
String s = System.getProperty("user.dir") + "/target/user_hessian";
try {
Hessian2Output ho = new Hessian2Output(new FileOutputStream(s));
UserSerializable user = new UserSerializable("张三",new Date(),18, UserSerializable.Sex.man,new LinkedList<>());
ho.writeObject(user);
ho.flush();
System.out.println("hessian2序列化完成");
} catch (Exception e) {
e.printStackTrace();
}
}
// hessian2 反序列化
@Test
public void hessian2Deserializable() {
String s = System.getProperty("user.dir") + "/target/user_hessian";
try {
Hessian2Input in = new Hessian2Input(new FileInputStream(s));
UserSerializable user = (UserSerializable) in.readObject();
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}
}
5.3 通过结果得出 hessian2容错率比java要高,如果bean中增删属性,没有添加
private static final long serialVersionUID = -7103556216921677848L; 那么序列化会报错如果这个id不同就会报错
但是我在岩石中并未发现这个问题。
不过通过对比,java的序列化的内容比hessian2要大的多,所以效率上 hessian2要高
5.4 序列化的兼容性 hessian2
数据通讯 | 情况 | 结果 |
A->B | 类A多一种 属性(或者说类B少一种 属性) | 不抛异常,A多的那 个属性的值,B没有, 其他正常 |
A->B | 枚举A多一种 枚举(或者说B少一种 枚举),A使用多 出来的枚举进行传输 | 抛异常 |
A->B | 枚举A多一种 枚举(或者说B少一种 枚举),A不使用 多出来的枚举进行传输 | 不抛异常,B正常接 收数据 |
A->B | A和B的属性 名相同,但类型不相同 | 抛异常 |
A->B | serialId 不相同 | 正常传输 |
接口增加方法,对客户端无影响,如果该方法不是客户端需要的,客户端不需要重新部署。输入参数和结果集中增加属性,对客户端无影响,如果客户端并不需要新属性,不用重新部署。
输入参数和结果集属性名变化,对客户端序列化无影响,但是如果客户端不重新部署,不管输入还是输出,属性名变化的属性值是获取不到的。
总结:服务器端和客户端对领域对象并不需要完全一致,而是按照最大匹配原则。
6.RPC协议编码解码(拆包和黏包)
RPC的协议的传输是基于 TCP/IP 做为基础使用Socket 或Netty、mina等网络编程组件实现。但有个问题是TCP是面向字节流的无边边界协议,其只管负责数据传输并不会区分每次请求所对应的消息,这样就会出现TCP协义传输当中的拆包与粘包问题
6.1 我们知道tcp是以流动的方式传输数据,传输的最小单位为一个报文段(segment)。tcp Header中有个Options标识位,常见的标识为mss(Maximum Segment Size)指的是,连接层每次传输的数据有个最大限制MTU(Maximum Transmission Unit),一般是1500比特,超过这个量要分成多个报文段,mss则是这个最大限制减去TCP的header,光是要传输的数据的大小,一般为1460比特。换算成字节,也就是180多字节。
6.2 tcp为提高性能,发送端会将需要发送的数据发送到缓冲区,等待缓冲区满了之后,再将缓冲中的数据发送到接收方。同理,接收方也有缓冲区这样的机制,来接收数据。这时就会出现以下情况:
- 应用程序写入的数据大于MSS大小,这将会发生拆包。
- 应用程序写入数据小于MSS大小,这将会发生粘包。
- 接收方法不及时读取套接字缓冲区数据,这将发生粘包。
6.3 拆包和黏包的解决办法(netty中已有实现)本次将dubbo的实现
1.可以设置定长消息,服务端每次读取到消息先判断是不是这个长度,那么直接截取,如果不是等待下一波,得到定长再开始读取。
2.使用带消息头的协议、消息头存储消息开始标识及消息长度信息,服务端获取消息头的时候解析出消息长度,然后向后读取该长度的内容。
3.http 协议的heade 中的 Content-Length标识消息体的大小
6.4 dubbo协议
6.5 固定的定长请求头 16个字节 16 * 8 个字节
源码
会获取到16个字节开始判断
6.6 协议解释:
- magic:类似java字节码文件里的魔数,用来判断是不是dubbo协议的数据包。魔数是常量0xdabb,用于判断报文的开始。
- flag:标志位, 一共8个地址位。低四位用来表示消息体数据用的序列化工具的类型(默认hessian),高四位中,第一位为1表示是request请求,第二位为1表示双向传输(即有返回response),第三位为1表示是心跳ping事件。
- status:状态位, 设置请求响应状态,dubbo定义了一些响应的类型。具体类型见 com.alibaba.dubbo.remoting.exchange.Response
- invoke id:消息id, long 类型。每一个请求的唯一识别id(由于采用异步通讯的方式,用来把请求request和返回的response对应上)
- body length:消息体 body 长度, int 类型,即记录Body Content有多少个字节。
6.7源码
> com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec#encode
>com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec#encodeRequest 读取16个字节
请求头读取后开始进行序列化
>com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec#encodeRequestData
>数据修完后开始进行发送 那么到达了服务端
-》com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec#decode(com.alibaba.dubbo.remoting.Channel, com.alibaba.dubbo.remoting.buffer.ChannelBuffer)
>com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec#decode(com.alibaba.dubbo.remoting.Channel, com.alibaba.dubbo.remoting.buffer.ChannelBuffer, int, byte[])
>com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec#decodeBody
>com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode(com.alibaba.dubbo.remoting.Channel, java.io.InputStream)