题目
如何设计一个 RPC 框架
答案
@[toc]
参考 Dubbo ,我们说一下设计一个最基本的 RPC 框架的思路或者说实现一个最基本的 RPC 框架需要哪些东西:
### 注册中心
**服务注册与发现的注册中心 Registry 负责服务地址的注册与查找,相当于目录服务。** 服务端启动的时候将服务名称及其对应的地址 (ip+port) 注册到注册中心,服务消费端根据服务名称找到对应的服务地址。有了服务地址之后,服务消费端就可以通过网络请求服务端了。
### 网络传输
**既然我们要调用远程的方法,那必然需要发送网络请求来传递目标类和方法的信息以及方法的参数等数据到服务提供端。**
网络传输具体实现可以使用 Socket 、NIO、Netty:
- **Socket**:Java 中最原始、最基础的网络通信方式。但是Socket 是阻塞 IO、性能低并且功能单一
- **NIO**:同步非阻塞的 I/O 模型,但是用它来进行网络编程真的太麻烦了
- **Netty**:基于 NIO 的 client-server(客户端服务器)框架,使用它可以快速简单地开发网络应用程序。极大地简化并简化了 TCP 和 UDP 套接字服务器等网络编程, 并且性能以及安全性等很多方面甚至都要更好。支持多种协议如 FTP,SMTP,HTTP 以及各种二进制和基于文本的传统协议。
### 序列化和反序列化
要在网络传输数据就必然涉及到**序列化和反序列化**
因为网络传输的数据必须是二进制的。因此,我们的 Java 对象没办法直接在网络中传输。为了能够让 Java 对象在网络中传输我们需要将其**序列化**为二进制的数据。我们最终需要的还是目标 Java 对象,因此我们还要将二进制的数据 “解析” 为目标 Java 对象,也就是对二进制数据再进行一次**反序列化**。
另外,不仅网络传输的时候需要用到序列化和反序列化,将对象存储到文件、数据库等场景都需要用到序列化和反序列化。

JDK 自带的序列化,只需实现 `java.io.Serializable`接口即可,不过这种方式不推荐,因为不支持跨语言调用并且性能比较差。
比较常见的序列化框架:Kryo、hessian、protostuff 等
### 动态代理
我们知道代理模式就是: 我们给某一个对象提供一个代理对象,并由代理对象来代替真实对象做一些事情。你可以把代理对象理解为一个幕后的工具人。 举个例子:我们真实对象调用方法的时候,我们可以通过代理对象去做一些事情比如安全校验、日志打印等等。但是,这个过程是完全对真实对象屏蔽的。
讲完了代理模式,再来说动态代理在 RPC 框架中的作用。
RPC 的主要目的就是让我们调用远程方法像调用本地方法一样简单,我们不需要关心远程方法调用的细节比如网络传输。
**怎样才能屏蔽程方法调用的底层细节呢?答案就是动态代理**
动态代理机制包括 JDK 动态代理、CGLIB 动态代理、Javassist 动态代理等
### 负载均衡
当我们的系统中的某个服务的访问量特别大的时候,假设我们将这个服务部署在了多台服务器上,当客户端发起请求的时候,多台服务器都可以处理这个请求。那么,如何正确选择处理该请求的服务器就很关键。
**负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题**。
### 传输/通信协议
我们还需要设计一个私有的 RPC 协议(通信/传输协议),这个协议是客户端(服务消费方)和服务端(服务提供方)交流的基础。
简单来说:**通过设计传输协议,我们定义需要传输哪些类型的数据, 并且还会规定每一种类型的数据应该占多少字节。这样我们在接收到二级制数据之后,就可以正确的解析出我们需要的数据。**
通常一些标准的 RPC 协议包含下面这些内容:
- **魔数** : 通常是 4 个字节。这个魔数主要是为了筛选来到服务端的数据包,有了这个魔数之后,服务端首先取出前面四个字节进行比对,能够在第一时间识别出这个数据包并非是遵循自定义协议的,也就是无效数据包,为了安全考虑可以直接关闭连接以节省资源。
- **序列化器编号** :标识序列化的方式,比如是使用 Java 自带的序列化,还是 json,kyro 等序列化方式。
- **消息体长度** : 运行时计算出来。
- ..........
