1、基本概念
(1)RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议;
(2)RPC协议假定某些传输协议的存在,如TCP或HTTP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层;
(3)RPC使得开发包括网络分布式多程序在内的应用程序更加容易;
(4)RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行;
(5)随着系统业务的不断发展增长,对服务的压力逐渐增大,一方面可以通过硬件的方式提高系统的性能;另一方面,可以通过软件的方式来实现系统整体的性能。主流的解决方案,就是讲一个比较大的系统不断进行拆分,拆分为独立的服务系统,不同服务系统之间通过HTTP请求进行通信,或者其他协议进行通信。RPC就是不同系统之间相互通信交换数据服务的有利工具;
2、优势长处
(1)RPC解决了单台服务器处理能力受硬件成本的限制问题,RPC将原本本地调用转变为调用远端服务器上的方法,极大程度上提高了系统的处理能力和吞吐量;
(2)RPC使得不同系统隔离开来,对于不同模块的开发人员不用关心具体方法的实现,只关心该方法能提供的数据和解决的问题,使得开发效率和系统维护变得简单;
(3)PRC作为分布式应用不得不了解和掌握的一块重要的内容。
3、远程过程调用流程图
上图是一张比较完整的远程控制调用流程图,客户进程作为服务的消费者,服务器进程作为服务的生产者,如下图所示:(当然客户端也可以作为服务生产者存在,这里只是简单说明问题)。
服务调用方发送RPC请求到服务的提供方,服务的提供方根据调用服务的参数和执行的方法进行返回结果,服务的调用方和服务的提供方之间传输的数据需要进行序列化和反序列化操作。
4、对象序列化和反序列化
(1)序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
(2)之所以要进行序列化,是因为在网络上进行传输的话,无论任何类型的数据,最终都需要转化为二进制流在网络上传输。
(3)数据的发送方需要将传输的对象转化为二进制流才能在网络上进行传输,这也就是序列化的过程;数据的接收方则需要将接受到的二进制流在恢复为对象,这个过程就是对象的反序列化的过程。
(4)序列化和反序列化总结:
(5)序列化和反序列化的方式有很多,常见的有使用Java本身内置的序列化方式、Hession、JSON、XML等。Java内置的可以直接使用Java提供的类不需要引用其他jar包,但是效率不是很高,下边是一张各种序列化空间和性能对比:
解析性能:
序列化之空间开销:
(6)基于Java内置的序列化和反序列化关键代码实现:
其他序列化和反序列化的方式也大致如此,基本思路是一样的。
1、基本思路
首先,远程控制调用RPC的本质还是底层的Scoket通信。对于简单的设计实现来说,其基本思路是:
1、服务的调用方Consumer通过Socket建立起与服务的提供方Provider的连接;
2、Consumer将需要调用的方法名称和参数通过Socket发送给Provider;
3、Provider获取Consumer请求的数据并进行解析,执行具体的某一个方法,构造返回数据,返回给Consumer;
4、Consumer获得Provider返回的数据进行相应的处理;
2、具体项目代码
具体代码如下:
(1)ConsumerDemo 代码:
/**
* 服务消费者
* Created by xuliugen on 2016/12/9.
*/
public class ConsumerDemo {
public static void main(String[] args) throws NoSuchMethodException, IOException, ClassNotFoundException {
//获取服务提供者的接口名,一般RPC框架都是暴露服务提供者的接口定义
String providerInterface = ProviderDemo.class.getName();
//需要远程执行的方法,其实就是消费者调用生产者的方法
Method method = ProviderDemo.class.getMethod("printMsg", java.lang.String.class);
//需要传递的参数
Object[] rpcArgs = {"Hello RPC!"};
Socket consumer = new Socket("127.0.0.1", 8899);
//将方法名称和参数 传递给服务生产者
ObjectOutputStream output = new ObjectOutputStream(consumer.getOutputStream());
output.writeUTF(providerInterface);
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(rpcArgs);
//从生产者读取返回的结果
ObjectInputStream input = new ObjectInputStream(consumer.getInputStream());
Object result = input.readObject();
System.out.println(result.toString());
}
}
(2)ProviderDemo 代码如下:
/**
* 服务提供者接口,用于暴露给服务消费者进行消费
* Created by xuliugen on 2016/12/9.
*/
public interface ProviderDemo {
/**
* 服务提供者打印Msg方法
*/
public String printMsg(String msg);
}
(3)ProviderDemoImpl代码如下:
/**
* 服务提供者实现类
* Created by xuliugen on 2016/12/9.
*/
public class ProviderDemoImpl implements ProviderDemo {
public String printMsg(String msg) {
System.out.println("----" + msg + "----");
return "Ni Hao " + msg;
}
}
(4)ProviderServer代码如下:
/**
* 生产者运行的服务器
* Created by xuliugen on 2016/12/9.
*/
public class ProviderServer {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
//用于存放生产者服务接口的Map,实际的框架中会有专门保存服务提供者的
Map serviceMap = new HashedMap();
serviceMap.put(ProviderDemo.class.getName(), new ProviderDemoImpl());
//服务器
ServerSocket server = new ServerSocket(8899);
while (true) {
Socket socket = server.accept();
ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
String interfaceName = input.readUTF(); //获取服务消费者需要消费服务的接口名
String methodName = input.readUTF(); ////获取服务消费者需要消费服务的方法名
//参数的类型
Class>[] parameterTypes = (Class>[]) input.readObject();
//参数的对象
Object[] rpcArgs = (Object[]) input.readObject();
//执行调用过程
Class providerInteface = Class.forName(interfaceName); //得到接口Class
Object provider = serviceMap.get(interfaceName);//取得服务实现的对象
//获取需要执行的方法
Method method = providerInteface.getMethod(methodName, parameterTypes);
//通过反射进行调用
Object result = method.invoke(provider, rpcArgs);
//返回给客户端即服务消费者数据
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
output.writeObject(result);
}
}
}
(5)先开发服务器ProviderServer,然后执行ConsumerDemo,执行结果如下:
客户端执行一次请求,返回正确的结果。
上述显示了两条信息,这是因为服务器是while循环接受客户端请求的,共执行了两次。
上述过程完成了一个简单的RPC远程控制调用的案例,实际使用框架的时候,比这考虑的要多很多,这只是简单介绍一下思路。关于基于HTTP协议的RPC的简单实现其思想和TCP的一样,这里不再做过多说明。
博主使用最多的框架是Dubbo,服务的生产者将服务注册到注册中心,消费者向注册中心查找服务并获取服务生产者的信息,调用服务,详细请移步查看:Dubbo详细介绍与安装使用过程
参考文章:
1、百度百科RPC远程过程调用协议
2、序列化和反序列化
3、《大型分布式网站架构设计与实践–陈康贤 著》