1. 关于RPC
(1)RPC的概念
RPC(Remote Procedure Call)–远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如;TCP或者UDP,为通信程序之间携带信息数据,在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式程序在内的应用程序更加容易。
(2)OSI网络七层模型
第一层:物理层。这一层主要就是传输这些二进制数据。
第二层:链路层。将上面的网络层的数据包封装成数据帧,便于物理层传输;
第三层:网络层。定义网络设备间如何传输数据;
第四层:传输层。管理着网络中的端到端的数据传输;
第五层:会话层。管理用户的会话,控制用户间逻辑连接的建立和中断;
第六层:表示层。定义不同的系统中数据的传输格式,编码和解码规范等;
第七层:应用层。定义了用于在网络中进行通信和传输数据的接口;
(3)RPC工作机制
RPC 采用客户机/服务器模式:请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在 服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进 程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收 答复信息,获得进程结果,然后调用执行继续进行。
通俗的说: RPC 是指远程过程调用,也就是说两台服务器 A,B,一个应用部署在 A 服务器, 想要调用 B 服务器提供的函数和方法,由于不在一个内存空间,不能直接调用,需要通过 网络来表达调用的语义和传达调用的数据。也可以理解成不同进程之间的服务调用。
实现的具体步骤:
第一(通讯问题):主要是通过在客户端和服务器之间建立 TCP 连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
第二(寻址问题):A 服务器上的应用怎么告诉底层的 RPC 框架,如何连接到 B 服务器(如主机或 IP 地址)以及特定的端口,方法的名称以及名称是什么,这样才能完成调用。
第三(方法参数的网络传递):当 A 服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如 TCP 传递到 B 服务器,由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制 的形式,也就是序列化(Serialize)或编组(marshal),通过寻址和传输将序列化的二进制 发送给 B 服务器。
第四(反序列化):B 服务器收到请求后,需要对参数进行反序列化(序列化的逆操作),恢复为内存中 的表达方式,然后找到对应的方法(寻址的一部分)进行本地调用,然后得到返回值。
第五(返回值):返回值还要发送回服务器 A 上的应用,也要经过序列化的方式发送,服务器 A 接到 后,再反序列化,恢复为内存中的表达方式,交给 A 服务器上的应用
(4)RPC的作用
– 让调用方感觉就像调用本地函数一样调用远端函数、让服务提供方感觉就像实现一个本地函数一样来实现服务,并且屏蔽编程语言的差异性。
– 让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性
– 在客户端 A 上来说,会生成一个服务器 B 的代理。(总结)
(5)JAVA中流行的RPC框架
RMI(远程方法调用):JAVA 自带的远程方法调用工具,不过有一定的局限性,毕竟是 JAVA 语言最开始时的设计, 后来很多框架的原理都基于 RMI。
Hessian(基于 HTTP 的远程方法调用):基于 HTTP 协议传输,在性能方面还不够完美,负载均衡和失效转移依赖于应用的负载均衡 器,Hessian 的使用则与 RMI 类似,区别在于淡化了 Registry 的角色,通过显示的地址调用, 利用 HessianProxyFactory 根据配置的地址 create 一个代理对象,另外还要引入 Hessian 的 Jar 包。
Dubbo(阿里开源的基于 TCP 的 RPC 框架):基于 Netty 的高性能 RPC 框架。
2. Hadoop的RPC
(1)相关概念介绍
同其他 RPC 框架一样,Hadoop RPC 分为四个部分:
- 序列化层:Client 与 Server 端通信传递的信息采用了 Hadoop 里提供的序列化类或自定义 的 Writable 类型;
- 函数调用层:Hadoop RPC 通过动态代理以及 Java 反射实现函数调用;
- 网络传输层:Hadoop RPC 采用了基于 TCP/IP 的 socket 机制;
- 服务器端框架层:RPC Server利用Java NIO以及采用了事件驱动的I/O模型,提高RPC Server 的并发处理能力;
Hadoop RPC 在整个 Hadoop 中应用非常广泛,Client、DataNode、NameNode 之间的通讯全 靠它了。例如:我们平时操作 HDFS 的时候,使用的是 FileSystem 类,它的内部有个 DFSClient 对象,这个对象负责与 NameNode 打交道。在运行时,DFSClient 在本地创建一个 NameNode 的代理,然后就操作这个代理,这个代理就会通过网络,远程调用到 NameNode 的方法, 也能返回值。
(2)涉及到的相关技术点
- 代理:动态代理可以提供对另一个对象的访问,同时隐藏实际对象的具体事实,代理对 象对客户隐藏了实际对象。目前 Java 开发包中提供了对动态代理的支持,但现在只支持对 接口的实现。
- 反射:动态加载类
- 序列化:用于将对象进行网络传输
- NIO:非阻塞的异步
(3)HadoopRPC的构建步骤
- 定义PRC协议:RPC 协议是客户端和服务器端之间的通信接口,它定义了服务器端对外提供的服务接口。
- 实现PRC协议:Hadoop RPC 协议通常是一个 Java 接口,用户需要实现该接口。
- 构造和启动RPC server :直接使用静态类 Builder 构造一个 RPC Server,并调用函数 start()启动该 Server。
- 构建RPC client 并发送请求:使用静态方法 getProxy 构造客户端代理对象,直接通过代理对象调用远程端的方法。
(3)HadoopRPC 入门案例
1.定义协议
//定义PRC协议
public interface BussinessProtocol {
//在client和server通信的时候,需要版本一致
long versionID=11511056056L;
void mkdir(String name);
void hello(String message);
}
2.实现协议
//实现RPC协议
public class BussinessProtocolImpl implements BussinessProtocol {
@Override
public void mkdir(String name) {
System.out.println("创建了:"+name);
}
@Override
public void hello(String message) {
System.out.println("你好"+message);
}
}
3.构建服务端
//构建RPCserver并启动服务端
public class MyRPCServer {
public static void main(String[] args) {
RPC.Server server = null;
try {
/**
* 在new RPC.Builder:传入Configuration对象
* setProtocol(BussinessProtocol.class):设置协议类
* setInstance(new BussinessProtocolImpl()):协议实现类对象
* setBindAddress("localhost"):绑定的主机
* setPort(6789):设置端口
*/
server = new RPC.Builder(new Configuration())
.setProtocol(BussinessProtocol.class)
.setInstance(new BussinessProtocolImpl())
.setBindAddress("localhost")
.setPort(6666)
.build();
} catch (IOException e) {
e.printStackTrace();
}
//启动服务
server.start();
}
}
4.构建客户端
//构建 RPC Client 并发出请求
public class MyRPCClient {
public static void main(String[] args) {
try {
/**
* 获取代理类对象,客户端通过这个代理可以调用服务端的方法进行逻辑处理:
* Class<T> protocol 协议类的class对象
* long clientVersion:版本
* InetSocketAddress addr:主机端口
* Configuration conf:配置文件对象
*/
BussinessProtocol proxy = RPC.getProxy(BussinessProtocol.class,
BussinessProtocol.versionID,
new InetSocketAddress("localhost", 6666),
new Configuration());
//在客户端调用了服务端的代理对象的方法
//其实正真的代码在服务端运行
proxy.hello("zs");
proxy.mkdir("/root/zy");
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.启动测试
启动服务端:运行:
启动客户端 运行:
观察服务端console:
原创文章,作者:3628473679,如若转载,请注明出处:https://blog.ytso.com/192924.html