手把手教你基于Netty实现一个基础的RPC框架(通俗易懂)
阅读这篇文章之前,建议先阅读和这篇文章关联的内容。
[1]详细剖析分布式微服务架构下网络通信的底层实现原理(图解)
[2][年薪60W的技巧]工作了5年,你真的理解Netty以及为什么要用吗?(深度干货)
[3]深度解析Netty中的核心组件(图解+实例)
[4]BAT面试必问细节:关于Netty中的ByteBuf详解
[5]通过大量实战案例分解Netty中是如何解决拆包黏包问题的?
[6]基于Netty实现自定义消息通信协议(协议设计及解析应用实战)
[7]全网最详细最齐全的序列化技术及深度解析与应用实战
在前面的内容中,我们已经由浅入深的理解了Netty的基础知识和实现原理,相信大家已经对Netty有了一个较为全面的理解。那么接下来,我们通过一个手写RPC通信的实战案例来带大家了解Netty的实际应用。
为什么要选择RPC来作为实战呢?因为Netty本身就是解决通信问题,而在实际应用中,RPC协议框架是我们接触得最多的一种,所以这个实战能让大家了解到Netty实际应用之外,还能理解RPC的底层原理。
什么是RPC
RPC全称为(Remote Procedure Call),是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议,简单理解就是让开发者能够像调用本地服务一样调用远程服务。
既然是协议,那么它必然有协议的规范,如图6-1所示。
为了达到“让开发者能够像调用本地服务那样调用远程服务”的目的,RPC协议需像图6-1那样实现远程交互。
- 客户端调用远程服务时,必须要通过本地动态代理模块来屏蔽网络通信的细节,所以动态代理模块需要负责将请求参数、方法等数据组装成数据包发送到目标服务器
- 这个数据包在发送时,还需要遵循约定的消息协议以及序列化协议,最终转化为二进制数据流传输
- 服务端收到数据包后,先按照约定的消息协议解码,得到请求信息。
- 服务端再根据请求信息路由调用到目标服务,获得结果并返回给客户端。
业内主流的RPC框架
凡是满足RPC协议的框架,我们成为RPC框架,在实际开发中,我们可以使用开源且相对成熟的RPC框架解决微服务架构下的远程通信问题,常见的rpc框架:
- Thrift:thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。
- Dubbo:Dubbo是一个分布式服务框架,以及SOA治理方案。其功能主要包括:高性能NIO通讯及多协议集成,服务动态寻址与路由,软负载均衡与容错,依赖分析与降级等。 Dubbo是阿里巴巴内部的SOA服务化治理方案的核心框架,Dubbo自2011年开源后,已被许多非阿里系公司使用。
手写RPC注意要点
基于上文中对于RPC协议的理解,如果我们自己去实现,需要考虑哪些技术呢? 其实基于图6-1的整个流程应该有一个大概的理解。
- 通信协议,RPC框架对性能的要求非常高,所以通信协议应该是越简单越好,这样可以减少编解码带来的性能损耗,大部分主流的RPC框架会直接选择TCP、HTTP协议。
- 序列化和反序列化,数据要进行网络传输,需要对数据进行序列化和反序列化,前面我们说过,所谓的序列化和反序列化是不把对象转化成二进制流以及将二进制流转化成对象的过程。在序列化框架选择上,我们一般会选择高效且通用的算法,比如FastJson、Protobuf、Hessian等。这些序列化技术都要比原生的序列化操作更加高效,压缩比也较高。
- 动态代理, 客户端调用远程服务时,需要通过动态代理来屏蔽网络通信细节。而动态代理又是在运行过程中生成的,所以动态代理类的生成速度、字节码大小都会影响到RPC整体框架的性能和资源消耗。常见的动态代理技术: Javassist、Cglib、JDK的动态代理等。
基于Netty手写实现RPC
理解了RPC协议后,我们基于Netty来实现一个RPC通信框架。
代码详见附件 netty-rpc-example
需要引入的jar包:
org.springframework.boot
spring-boot-starter
org.projectlombok
lombok
com.alibaba
fastjson
1.2.72
io.netty
netty-all
模块依赖关系:
-
provider依赖 netty-rpc-protocol和netty-rpc-api
-
cosumer依赖 netty-rpc-protocol和netty-rpc-api
netty-rpc-api模块
IUserService
public interface IUserService {
String saveUser(String name);
}
netty-rpc-provider模块
UserServiceImpl
@Service
@Slf4j
public class UserServiceImpl implements IUserService {
@Override
public String saveUser(String name) {
log.info("begin saveUser:"+name);
return "Save User Success!";
}
}
NettyRpcProviderMain
注意,在当前步骤中,描述了case的部分,暂时先不用加,后续再加上
@ComponentScan(basePackages = {"com.example.spring","com.example.service"}) //case1(后续再加上)
@SpringBootApplication
public class NettyRpcProviderMain {
public static void main(String[] args) throws Exception {
SpringApplication.run(NettyRpcProviderMain.class, args);
new NettyServer("127.0.0.1",8080).startNettyServer(); //case2(后续再加上)
}
}
netty-rpc-protocol
开始写通信协议模块,这个模块主要做几个事情
- 定义消息协议
- 定义序列化反序列化方法
- 建立netty通信
定义消息协议
之前我们讲过自定义消息协议,我们在这里可以按照下面这个协议格式来定义好。
/*
+----------------------------------------------+
| 魔数 2byte | 序列化算法 1byte | 请求类型 1byte |
+----------------------------------------------+
| 消息 ID 8byte | 数据长度 4byte |
+----------------------------------------------+
*/
Header
@AllArgsConstructor
@Data
public class Header implements Serializable {
/*
+----------------------------------------------+
| 魔数 2byte | 序列化算法 1byte | 请求类型 1byte |
+----------------------------------------------+
| 消息 ID 8byte | 数据长度 4byte |
+----------------------------------------------+
*/
private short magic; //魔数-用来验证报文的身份(2个字节)
private byte serialType; //序列化类型(1个字节)
private byte reqType; //操作类型(1个字节)
private long requestId; //请求id(8个字节)
private int length; //数据长度(4个字节)
}
RpcRequest
@Data
public class RpcRequest implements Serializable {
private String className;
private String methodName;
private Object[] params;
private Class<?>[] parameterTypes;
}
RpcResponse
@Data
public class RpcResponse implements Serializable {
private Object data;
private String msg;
}
RpcProtocol
@Data
public class RpcProtocol implements Serializable {
private Header header;
private T content;
}
定义相关常量
上述消息协议定义中,涉及到几个枚举相关的类,定义如下
ReqType
消息类型
public enum ReqType {
REQUEST((byte)1),
RESPONSE((byte)2),
HEARTBEAT((byte)3);
private byte code;
private ReqType(byte code) {
this.code=code;
}
public byte code(){
return this.code;
}
public static ReqType findByCode(int code) {
for (ReqType msgType : ReqType.values()) {
if (msgType.code() == code) {
return msgType;
}
}
return null;
}
}
SerialType
序列化类型
public enum SerialType {
JSON_SERIAL((byte)0),
JAVA_SERIAL((byte)1);
private byte code;
SerialType(byte code) {
this.code=code;
}
public byte code(){
return this.code;
}
}
RpcConstant
public class RpcConstant {
//header部分的总字节数
public final static int HEAD_TOTAL_LEN=16;
//魔数
public final static short MAGIC=0xca;
}
定义序列化相关实现
这里演示两种,一种是JSON方式,另一种是Java原生的方式
ISerializer
public interface ISerializer {
byte[] serialize(T obj);
T deserialize(byte[] data,Class clazz);
byte getType();
}
JavaSerializer
public class JavaSerializer implements ISerializer{
@Override
public byte[] serialize(T obj) {
ByteArrayOutputStream byteArrayOutputStream=
new ByteArrayOutputStream();
try {
ObjectOutputStream outputStream=
new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(obj);
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[0];
}
@Override
public T deserialize(byte[] data, Class clazz) {
ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(data);
try {
ObjectInputStream objectInputStream=
new ObjectInputStream(byteArrayInputStream);
return (T) objectInputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
@Override
public byte getType() {
return SerialType.JAVA_SERIAL.code();
}
}
JsonSerializer
public class JsonSerializer implements ISerializer{
@Override
public byte[] serialize(T obj) {
return JSON.toJSONString(obj).getBytes();
}
@Override
public T deserialize(byte[] data, Class clazz) {
return JSON.parseObject(new String(data),clazz);
}
@Override
public byte getType() {
return SerialType.JSON_SERIAL.code();
}
}
SerializerManager
实现对序列化机制的管理
public class SerializerManager {
private final static ConcurrentHashMap serializers=new ConcurrentHashMap();
static {
ISerializer jsonSerializer=new JsonSerializer();
ISerializer javaSerializer=new JavaSerializer();
serializers.put(jsonSerializer.getType(),jsonSerializer);
serializers.put(javaSerializer.getType(),javaSerializer);
}
public static ISerializer getSerializer(byte key){
ISerializer serializer=serializers.get(key);
if(serializer==null){
return new JavaSerializer();
}
return serializer;
}
}
定义编码和解码实现
由于自定义了消息协议,所以 需要自己实现编码和解码,代码如下
RpcDecoder
@Slf4j
public class RpcDecoder extends ByteToMessageDecoder {
/*
+----------------------------------------------+
| 魔数 2byte | 序列化算法 1byte | 请求类型 1byte |
+----------------------------------------------+
| 消息 ID 8byte | 数据长度 4byte |
+----------------------------------------------+
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List