Motan RPC是基于Netty实现服务的调用。实现过程很简单明了。
基本流程如下: NettyClient构建Request对Request进行编码,发送到目标NettyServer; 目标NettyServer接收到编码后的Request进行解码,还原Request,再Invoke到本地服务,对调用的返回结果Response进行编码,NettyClient接收到编码后的Response进行解码还原结果。 流程示意图:
先不考虑Netty之间是如何传输的,本文重点是分析是Motan协议如何实现编码解密。
Request和Response
Request和Response实现了对RPC调用的请求参数和返回结果的封装。
Request构成:核心参数RequestId+接口类+方法+方法参数+方法参数实例,这也是我们正常调用一个接口方法的基本参数构成。
package com.weibo.api.motan.rpc;
import java.util.Map;
public interface Request {
/**
*
* 接口类包的完整路径
*
* @return
*/
String getInterfaceName();
/**
* 调用接口方法
*
* @return
*/
String getMethodName();
/**
* 调用接口参数描述
*
* @return
*/
String getParamtersDesc();
/**
* 调用接口参数实例
*
* @return
*/
Object[] getArguments();
/**
* 框架参数
*
* @return
*/
Map<String, String> getAttachments();
/**
* request id
*
* @return
*/
long getRequestId();
/**
* 重试次数
*
* @return
*/
int getRetries();
/**
*rpc协议版本
*/
byte getRpcProtocolVersion();
}
Response构成: 需要处理情况有1正常返回、返回空值、异常返回,相比Request复杂一点。
package com.weibo.api.motan.rpc;
import java.util.Map;
public interface Response {
/**
* <pre>
* 如果 request 正常处理,那么会返回 Object value,而如果 request 处理有异常,那么 getValue 会抛出异常
* </pre>
*
* @throws RuntimeException
* @return
*/
Object getValue();
/**
* 如果request处理有异常,那么调用该方法return exception 如果request还没处理完或者request处理正常,那么return null
* @return
*/
Exception getException();
/**
* 与 Request 的 requestId 相对应
*
* @return
*/
long getRequestId();
/**
* 业务处理时间
*
* @return
*/
long getProcessTime();
/**
*超时时间
**/
int getTimeout();
/**
*framework 参数
**/
Map<String, String> getAttachments();
// 获取rpc协议版本,可以依据协议版本做返回值兼容
void setRpcProtocolVersion(byte rpcProtocolVersion);
byte getRpcProtocolVersion();
}
编码
Motan对Request和Response采用了统一的编码方式;统一在com.weibo.api.motan.protocol.rpc.DefaultRpcCodec中实现。 Motan通信数据由2部分组成: Header:数据头,16字节; Body:数据体,变长。
Header构造(依次写入): Header部分都是定长的数据类型,按short、byte、int、long等java类型写入。
2字节:Magic魔术字常量,标名这是motan协议,(short) 0xF0F0;
1字节:VERSION_1基本版本,VERSION_2数据包压缩版本,压缩版的需要对body压缩解压处理;
1字节:flag标记类型;0:request,1:response及其他response;
public static final byte FLAG_REQUEST = 0x00;
public static final byte FLAG_RESPONSE = 0x01;
public static final byte FLAG_RESPONSE_VOID = 0x03;
public static final byte FLAG_RESPONSE_EXCEPTION = 0x05;
public static final byte FLAG_RESPONSE_ATTACHMENT = 0x07;
public static final byte FLAG_OTHER = (byte) 0xFF;
8字节:long型,requestId,业务流水ID;
4字节:body数据体长度
Body构成: Body部分根据类型分别处理,通过ObjectOutput依次写入返回byte[]:
Request类型:
output.writeUTF(request.getInterfaceName());
output.writeUTF(request.getMethodName());
output.writeUTF(request.getParamtersDesc());
将接口参数实例序列化后写入
output.writeObject(serialize.serialize(message));//写序列化对象
依次写入框架参数,如果空则写入0;
Response类型: 根据返回结果进行写入。
output.writeLong(value.getProcessTime());
if (value.getException() != null) {//异常处理
output.writeUTF(value.getException().getClass().getName());
serialize(output, value.getException(), serialization);
flag = MotanConstants.FLAG_RESPONSE_EXCEPTION;
} else if (value.getValue() == null) {//空值处理
flag = MotanConstants.FLAG_RESPONSE_VOID;
} else {//默认处理
output.writeUTF(value.getValue().getClass().getName());
serialize(output, value.getValue(), serialization);
flag = MotanConstants.FLAG_RESPONSE;
}
解码
解码就是对编码过程的逆向解析,先解析头数据,根据头里面的数据体长度读取数据体;再根据flag判断解码Request还是Response对象。
整体上来说,Motan协议还是很简单,当然大部分的通信协议都是基于这样的方式处理。
Motan还实现了基于GZIP实现协议数据体压缩,当然,你也可以基于SPI规则自己扩展Snappy、LZ4等压缩方式;
Motan协议没有去实现请求版本管理,CRC校验,可能都是基于内部跑,没必要控制吧。