一、概述
1. RPC 的全称是 Remote Procedure Call(远程过程调用)是一种进程间通信方式
2. 它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。 即程序员无论是调用本地的还是远程的,本质上编写的调用代码基本相同
二、起源
1. 布鲁斯·纳尔逊1974年毕业于哈维·穆德学院,1976年在斯坦福大学获得计算机科学硕士学位,1982年在卡内基梅隆大学获得计算机科学博士学位
2. 在追求博士学位时,他开发了远程过程调用(RPC)的概念
3. 他和他的合作者安德鲁·伯雷尔因在RPC方面的工作而获得了1994年ACM软件系统奖
1996他加入思科系统担任首席科学官。
三、特点
1. 简单:RPC 概念的语义十分清晰和简单,这样建立分布式计算就更容易
2. 高效:过程调用看起来十分简单而且高效
通用:在单机计算中过程往往是不同算法部分间最重要的通信机制。 通俗一点说,就是一般程序员对于本地的过程调用很熟悉,那么我们在通过网络做远程通信时,通过RPC 把远程调用做得和本地调用完全类似,那么就更容易被接受,使用起来也就毫无障碍。
四、RPC架构
1. 用户(User)
2. 用户存根(User-Stub)
3. RPC通信包(称为RPCRuntime)
4. 服务器存根(Server-Stub)
服务器(Server)
客户端(client)
五、基本过程
1. 服务消费方(User)调用以本地调用方式调用服务
2. User-stub(存根)接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体,并交给RPCRuntime模块
3. RPCRuntime找到服务地址,并将消息发送到服务端
4. 服务端的RPCRuntime收到消息后,传给Server-stub
5. Server-stub根据解码结果调用本地的服务
6. 本地服务执行并将结果返回给Server-stub
7. server stub将返回结果打包成消息并发送至消费方
8. client stub接收到消息,并进行解码
服务消费方得到最终结果
六、RPC调用细节
1. 接口方式的调用:RPC设计的目的在于可以让调用者可以像以本地调用方式一样调用远程服务,具体实现的方式是调用接口的方式来调用。在java中,底层通过Java动态代理方式生成接口的代理类,代理类中封装了与远程服务通信的细节:
a. 客户端的请求消息:
i. 接口名称
ii. 方法名
iii. 参数类型以及相应的参数值
iv. requestID,标识唯一请求id
b. 服务器端的返回消息:
i. 返回值
ii. requestID
2. 序列化
通信:一般RPC通信都是基于NIO进行通信
七、RPC框架特点
1. 基于RPC的进程通信方式
2. 有自定义的一套序列化和反序列的机制
3. 客户端通过代理机制调用远程方法
服务端通过回调机制执行方法及返回结果
八、流行的RPC框架
dubbo: 阿里巴巴公司开源的一个Java高性能优秀的服务框架,使得应用可通过高性能的RPC实现服务的输出和输入功能,可以和Spring框架无缝集成。
motan: 新浪微博开源的一个Java 框架。它诞生的比较晚,起于2013年,2016年5月开源。Motan 在微博平台中已经广泛应用,每天为数百个服务完成近千亿次的调用。
rpcx: Go语言生态圈的Dubbo,比Dubbo更轻量,实现了Dubbo的许多特性,借助于Go语言优秀的并发特性和简洁语法,可以使用较少的代码实现分布式的RPC服务。
gRPC: Google开发的高性能、通用的开源RPC框架,主要面向移动应用开发并基于HTTP2协议标准而设计,基于
ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。
thrift: Apache的一个跨语言的高性能的服务框架
JSON-RPC: JSON-RPC是一个无状态且轻量级的远程过程调用(RPC)协议。
九、Avro实现RPC
1. 创建maven工程,导入pom依赖
2. 在指定的目录下编辑avdl文件
a. 如果传递的是基本类型,则示例格式如下:
@namespace("rpc.service")
protocol AddService{
int add(int i,int y);
}
b. 如果传递的是对象,则示例格式如下:
@namespace("rpc.service")
protocol TransferService{
import schema "User.avsc";
void parseUser(avro.domain.User user);
}
c. 如果传递的是map,并且map中包含对象,则示例格式如下:
@namespace("rpc.service")
protocol MapService{
import schema "User.avsc";
void parseUserMap(map<avro.domain.User> userMap);
}
d.这个是我们的User.avdl文件
@namespace("com.service")
protocol AddService{
import schema "user.avsc";
int add(int x , int y);
void parseUser(com.domain.User user);
}
3. 选中pom.xml文件,右键,选择Run as,点击Maven generate-resources
4. 实现服务器端接口
package com.service;
import com.domain.User;
import org.apache.avro.AvroRemoteException;
public class AddServiceImpl implements AddService {
@Override
public int add(int x, int y) throws AvroRemoteException {
System.out.println(x+y);
return x+y;
}
@Override
public Void parseUser(User user) throws AvroRemoteException {
System.out.println(user);
return null;
}
}
5. 实现服务器端
package com.server;
import com.service.AddService;
import com.service.AddServiceImpl;
import org.apache.avro.ipc.NettyServer;
import org.apache.avro.ipc.specific.SpecificResponder;
import java.net.InetSocketAddress;
public class RPC_Server {
public static void main(String[] args) {
NettyServer server = new NettyServer(
new SpecificResponder(AddService.class, new AddServiceImpl()),
new InetSocketAddress(8888));
}
}
6. 实现客户端
package com.client;
import com.service.AddService;
import org.apache.avro.ipc.NettyTransceiver;
import org.apache.avro.ipc.specific.SpecificRequestor;
import java.io.IOException;
import java.net.InetSocketAddress;
public class RPC_Client {
public static void main(String[] args) throws IOException {
NettyTransceiver client=new NettyTransceiver(new InetSocketAddress("127.0.0.1",8888));
//--因为接口不能直接使用,avro底层是通过jdk动态代理生成接口的代理对象
AddService proxy = SpecificRequestor.getClient(AddService.class,client;
int result = proxy.add(2,3);
System.out.println("客户端收到结果:"+result);
}
}