RPC入门了解
一、历史背景
-
从单机走向分布式,产生了很多分布式的通信方式
-
最古老也是最有效的,并且永不过时的,TCP/UDP的二进制传。事实上所有通信方式归根到底都是TCP/UDP
-
CORBA(Common Object Reqeust Broker Architecute),古老而复杂的,支持面向对象的通信协议
-
Web Service (SOA SOAP RDDI WSDL)
基于http + xml的标准化Web API
-
RestFul (Representational State Transfer)
回归简单化本源的Web API的事实标准
http://www.baidu.com/people/zhangsan
http + json
-
RMI(Remote Method Invocation)
Java内部的分布式通信协议
-
JMS(Java Message Service)
JavaEE中的消息框架标准,为很多MQ所支持
-
RPC(Remote Procedure Call)
远程方法调用,重点在于方法调用(不支持对象的概念),具体实现甚至可以用RMI、RestFul等去实现,但一般不用,因为RMI不能跨语言,而RestFul效率太低。多用于服务器集群间的通信,因此常使用更加高效、短小精悍的传输模式以提高效率
-
二、理论知识
1、概念
RPC(Remote Procedure Call),远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
通俗点:
- 就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据
2、为什么要用RPC
- 可以做到分布式,现代化的微服务
- 部署灵活
- 解耦服务
- 扩展性强
RPC的目的是让你在本地调用远程的方法,而对你来说这个 调用是透明的,你并不知道这个调用的方法是部署哪里。通过RPC能解耦服务,这才是使用RPC的真正目的。
单台服务器处理能力有限,RPC可提升系统处理能力和吞吐量,也是实现分布式计算的基础。
3、工作原理
RPC采用 客户机/服务器模式
。
请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续执行。
远程调用过程:
- 调用客户端句柄:执行传送参数
- 调用本地系统内核发送网络消息
- 消息传送到远程主机
- 服务器句柄得到消息并取得消息
- 执行远程过程
- 执行的过程将结果返回服务器句柄
- 服务器句柄返回结果,调用远程系统内核
- 消息传回本地主机
- 客户句柄向内核接收消息
- 客户接收句柄返回的数据
4、RPC解决了什么问题?
-
通讯问题
即A与B之间的通信,建立TCP连接
-
寻址问题
A通过RPC框架连接到B的服务器及特定端口和调用的方法名
-
参数序列化与反序列化
发起远程调用参数值需要二进制化,服务器收到二进制参数需要反序列化
5、RPC vs HTTP 远程调用方式
常见的远程调用方式有以下几种:
RPC服务
自定义数据格式,基于原生TCP通信,速度快,效率高。
RPC的框架:webservie(cxf)、dubbo
HTTP服务
http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议。也可以用来进行远程服务调用。缺点是消息封装臃肿。
现在热门的Rest风格,就可以通过http协议来实现。
http的实现技术:HttpClient
两者比较
-
相同点:底层通讯都是基于
socket
,都可以实现远程调用,都可以实现服务调用服务。 -
不同点:
-
RPC
-
框架有:dubbo、cxf、(RMI远程方法调用)Hessian
-
当使用RPC框架实现服务间调用的时候,要求服务提供方和服务消费方 都必须使用统一的RPC框架,要么都dubbo,要么都cxf。
-
跨操作系统在同一编程语言内使用
-
优势:调用快、处理快
-
-
HTTP
-
框架有:httpClient
-
当使用http进行服务间调用的时候,无需关注服务提供方使用的编程语言,也无需关注服务消费方使用的编程语言,服务提供方只需要提供restful风格的接口,服务消费方,按照restful的原则,请求服务即可
-
跨系统跨编程语言的远程调用框架
-
优势:通用性强
-
-
补充:
-
从传输速度上来看,因为HTTP封装的数据量更多所以数据传输量更大,所以RPC的传输速度是比RESTFUL更快的。
-
因为HTTP协议是各个框架都普遍支持的。
-
在不知道情况来源的框架、数据形势是什么样的,所以在网关可以使用Restful利用http来接受。
-
而在微服务内部的各模块之间因为各协议方案是公司内部自己定的,所以知道各种数据方式,可以使用TCP传输以使各模块之间的数据传输更快。
-
所以可以网关和外界的数据传输使用RESTFUL,微服务内部的各模块之间使用RPC。
-
-
RESTFUL的API的设计上是面向资源的,对于同一资源的获取、传输、修改可以使用
GET、POST、PUT
来对同一个URL进行区别,而RPC通常把动词直接体现在URL上
。
从图上可以看出,二者其实都可以看出在进行实现RPC的时候,底层的通信协议是可以使用HTTP协议的,另外单独的使用HTTP协议也是可以直接的实现调用的功能,理论上,HTTP的请求也是一种方法的调用,通过get或者post方法和url去调用方法,但是HTTP为了更高的可读性将请求头变得非常的臃肿,传输效率比较低,而RPC则是牺牲了可读性使得效率更高。
换句话说其实二者是一种功能,只不过应用的场景不同,而且针对点不同,HTTP是针对Client和Server ;而 RPC是针对 Server 与Server之间的调用,另外RPC之间的调用效率更加的高,而且增加了ZK进行服务的自动配置与管理。
换句话说,上层的协议还是都是依赖于TCP/IP协议,总的来说就是基于字节流来进行编码。
6、常用RPC框架
-
Dubbo
Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案
-
gRPC
是Google开发的高性能、通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言,本身它不是分布式的,所以要实现上面的框架的功能需要进一步的开发。
三、实现
实现RPC主要是做到两点:
实现远程调用其它计算机的服务
要实现远程调用,就需要通过网络传输数据。A程序提供服务,B程序通过网络请求参数传递给A,A本地执行后得到结果,再将结果返回给B程序。
何种网络通信协议?
现在比较流行的RPC框架,都会采用
TCP协议
作为底层传输协议。RPC通讯协议:http、http2.0(gRPC)、TCP(同步/异步;阻塞/非阻塞)、WebService。
数据传输格式?
两个程序进行通讯,必须约定好数据传输格式,即定义好
请求和响应格式
,另外,数据在网络中传输需要进行序列化,所以还需要约定统一的序列化的方式
像调用本地服务一样调用远程服务
- 如果仅仅是远程调用,还不算是RCP,因为RPC强调的是过程调用,调用的过程对用户而言应该是透明的,用户不应该关心调用的细节,可以像调用本地服务一样调用远程服务。所以RPC一定要对调用的过程进行封装
调用流程图
1、对象序列化
1.1 序列化的原因
任何形式的数据都需要转换成二进制流在网络传输
1.2 概念
-
对象序列化
将对象转换为二进制流的过程
-
对象反序列化
将二进制流恢复为对象的过程
1.3 解决方案(RPC序列化框架)
-
Google的Protocol Buffers
-
Java内置的序列化方式 java.io.Serializable
-
Hessian
-
json序列化框架
- Jackson
- google Gson
- Ali FastJson
-
kyro
-
xmlrpc (xstream)
1.4 代码实现
public class JdkSerializable {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User user = new User("zhangsan", 12);
byte[] bytes = serializableObject(user);
User newUser = (User) deSerializableObject(bytes);
System.out.println(newUser);
}
public static byte[] serializableObject(Object obj) throws IOException {
User user = (User)obj;
// 定义一个字节数组输出流 (作用:获取内存中的缓存数据并转化为数组)
ByteArrayOutputStream os = new ByteArrayOutputStream();
// 对象输出流
// 构造方法,传递字节输出流
ObjectOutputStream out = new ObjectOutputStream(os);
// 将对象写入到字节数组输出,进行序列化
out.writeObject(user);
byte[] zhangsanByte = os.toByteArray();
return zhangsanByte;
}
public static Object deSerializableObject(byte[] bytes) throws IOException, ClassNotFoundException {
// 字节数组输入流
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
// 从流中获取对象
ObjectInputStream in = new ObjectInputStream(is);
return in.readObject();
}
}
2、网络通讯协议
2.1 基于TCP协议实现的RPC
2.1.1 版本一
通过Socket编程,实现网络间的二进制字节数组(ByteArray)传输
2.1.1.1 原理
-
基于Java的反射机制和Socket API实现
-
方法的调用使用反射机制,消费者把需要调用的接口名称方法参数通过Socket通信传到服务端,服务端再通过反射机制调用对应的方法获取到值,然后再通过相同方式把结果返回给消费者
2.1.1.2 场景
服务消费者调用服务提供者的SayHelloService接口的sayHello(String helloArg)方法具体实现获取结果
2.1.1.3 实现代码
-
服务提供者
/** * @ClassName: Server * @Author: whc * @Date: 2021/05/22/0:52 * * 阻塞式I/O,实际生产环境中出于性能的考虑,往往使用非阻塞I/O,以提供更大的吞吐量 */ public class Server { private static HashMap<String, Class> registerTable = new HashMap<>(); static { // key类型是接口,value是具体实现类 registerTable.put(SayHelloService.class.getName(), SayHelloServiceImpl.class); } public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { ServerSocket server = new ServerSocket(8080); while(true) { Socket socket = server.accept(); // 读取服务信息 ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); String interfacename = input.readUTF(); // 接口名称 String methodName = input.readUTF()