RPC实例及总结
RPC的概念
RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
下面是对于RPC的理解:
RPC调用远程服务的过程:
1、客户端client发起服务调用请求。
2、client stub 可以理解成一个代理,会将调用方法、参数按照一定格式进行封装,通过服务提供的地址,发起网络请求。
3、消息通过网络传输到服务端。
4、server stub接受来自socket的消息
5、server stub将消息进行解包、告诉服务端调用的哪个服务,参数是什么
6、结果返回给server stub。
7、sever stub把结果进行打包交给socket
8、socket通过网络传输消息
9、client slub 从socket拿到消息。
10、client stub解包消息将结果返回给client。
一个RPC框架就是把步骤2到9都封装起来。
为什么需要RPC
1、首先要明确一点:RPC可以用HTTP协议实现,并且用HTTP是建立在 TCP 之上最广泛使用的 RPC,但是互联网公司往往用自己的私有协议,比如鹅厂的JCE协议,私有协议不具备通用性为什么还要用呢?因为相比于HTTP协议,RPC采用二进制字节码传输,更加高效也更加安全。
2、现在业界提倡“微服务“的概念,而服务之间通信目前有两种方式,RPC就是其中一种。RPC可以保证不同服务之间的互相调用。即使是跨语言跨平台也不是问题,让构建分布式系统更加容易。
3、RPC框架都会有服务降级、流量控制的功能,保证服务的高可用。
RPC框架职责
通过上面的讨论,RPC框架要向调用方屏蔽各种复杂性,要向服务提供方也屏蔽各类复杂性:
1)调用方感觉就像调用本地函数一样
2)服务提供方感觉就像实现一个本地函数一样来实现服务
所以整个RPC框架又分为client部分与server部分,负责把整个非(1)(2)的各类复杂性屏蔽,这些复杂性就是RPC框架的职责。
再细化一些,client端又包含:序列化、反序列化、连接池管理、负载均衡、故障转移、队列管理,超时管理、异步管理等等等等职责。server端包含:服务端组件、服务端收发包队列、io线程、工作线程、序列化反序列化、上下文管理器、超时管理、异步回调等等等等职责。
RPC要点
1、代理
因为涉及到两个机器,是不能直接调用的,或者说是不能直接访问到目标主机,所以需要一个代理对象来完成这个功能,目前可以使用jdk提供的动态代理,或者是三方提供的动态代理(cglib)的方式来完成整个的代理过程。
2、序列化
在调用别人服务的时候,肯定需要一些参数(入参-出参等等),这些参数需要在网络上进行传输,就涉及到了序列化的问题,因此在实现RPC的是,我们的参数是需要序列话的,序列化的方式我们有很多中选择, 可以选择jdk中自带的,三方的Hessian等等。
3、通信
序列化好的数据(二进制流),是怎么到目标机器上面的,然而这个就需要通信技术来解决了,这就引入了I/o网络模型,有阻塞的,非阻塞的,信号的,多路复用的,以及异步的等,选择一种适合的就显示额外重要了。
4、服务实例化
当目标服务器接受到了我们的请求后,并返回数据,我们需要在本机上面将进行反序列化,然后生成对象(不一定需要),调用服务。
RPC的应用
RPC在分布式系统中的系统环境建设和应用程序设计中有着广泛的应用,应用包括如下方面:
1、分布式操作系统的进程间通讯
进程间通讯是操作系统必须提供的基本设施之一,分布式操作系统必须提供分布于异构的结点机上进程间的通讯机制,RPC是实现消息传送模式的分布式进程间通讯的手段之一。
2、构造分布式计算的软件环境
由于分布式软件环境本身地理上的分布性, 它的各个组成成份之间存在大量的交互和通讯,R P C 是其基本的实现方法之一。ONC+和DCE两个流行的分式布计算软件环境都是使用RPC构造的,其它一些分布式软件环境也采用了RPC方式。
3、远程数据库服务
在分布式数据库系统中,数据库一般驻存在服务器上,客户机通过远程数据库服务功能访问数据库服务器,现有的远程数据库服务是使用RPC模式的。例如,Sybase和Oracle都提供了存储过程机制,系统与用户定义的存储过程存储在数据库服务器上,用户在客户端使用RPC模式调用存储过程。
4、分布式应用程序设计
RPC机制与RPC工具为分布式应用程序设计提供了手段和方便, 用户可以无需知道网络结构和协议细节而直接使用RPC工具设计分布式应用程序。
5、分布式程序的调试
RPC可用于分布式程序的调试。使用反向RPC使服务器成为客户并向它的客户进程发出RPC,可以调试分布式程序。例如,在服务器上运行一个远端调试程序,它不断接收客户端的RPC,当遇到一个调试程序断点时,它向客户机发回一个RPC,通知断点已经到达,这也是RPC用于进程通讯的例子。
RPC实例
下面就举一个1+1=2 的远程调用的例子。客户端发送两个参数,服务端返回两个数字的相加结果。RpcConsumer类调用CalculateService中的Calculate方法, 首先通过RpcFramework中的call方法,注册自己想要调用那个服务,返回代理,然后就像本地调用一样去调用Calculate方法,计算People,”People”有两个属性都被赋值成1,返回这两个属性相加后的结果。
public class RpcConsumer {public static void main(String args[]) { CalculateService service=RpcFramework.call(CalculateService.class,"127.0.0.1",8888); People people=new People(1,1); String hello=service.Calculate(people); System.out.println(hello); }
生成动态代理的代码如下。客户端在调用方法前会先执行invoke方法,建立socket连接,把方法名和参数传递给服务端,然后获取返回结果。
//动态代理机制 public static <T> T call(final Class<?> interfaceClass,String host,int port){ if(interfaceClass==null){ throw new IllegalArgumentException("调用服务为空"); } if(host==null||host.length()==0){ throw new IllegalArgumentException("主机不能为null"); } if(port<=0||port>65535){ throw new IllegalArgumentException("端口不合法"+port); } return (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class<?>[]{interfaceClass},new CallerHandler(host,port)); } static class CallerHandler implements InvocationHandler { private String host; private int port; public CallerHandler(String host, int port) { this.host = host; this.port = port; SERVER = new InetSocketAddress(host, port); } public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable { Socket socket = new Socket(host, port); try { ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); try { output.writeUTF(method.getName()); output.writeObject(method.getParameterTypes()); output.writeObject(arguments); ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); try { Object result = input.readObject(); if (result instanceof Throwable) { throw (Throwable) result; } return result; } finally { input.close(); } } finally { output.close(); } } finally { socket.close(); } } }
RpcProvider 类实现具体的Calculate方法。通过RpcFramework中的publish方法,发布自己的服务。
public class RpcProvider{ public static void main(String[] args) throws Exception { CalculateService service =new CalculateServiceImpl(); RpcFramework.publish(service,8888); }} interface CalculateService{ String Calculate(People p);} class CalculateServiceImpl implements CalculateService{ public String Calculate(People people){ int res=people.getA()+people.getB(); return "计算结果 "+res; } }
发布服务的代码如下。服务端循环监听某个端口,采用java原生的序列化方法,读取客户端需要调用的方法和参数,执行该方法并将结果返回。
public static void publish(final Object service,int port) throws IOException { if(service==null) throw new IllegalArgumentException("发布服务不能是空"); if(port<=0 || port >65535) throw new IllegalArgumentException("端口不合法"+port); ServerSocket server=new ServerSocket(port); while (true) { try{ final Socket socket=server.accept(); new Thread(new Runnable() { @Override public void run() { try { try { ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); try { String methodName = input.readUTF(); Class<?>[] parameterTypes = (Class<?>[]) input.readObject(); Object[] arguments = (Object[]) input.readObject(); ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); try { Method method = service.getClass().getMethod(methodName, parameterTypes); Object result = method.invoke(service, arguments); output.writeObject(result); } catch (Throwable t) { output.writeObject(t); } finally { output.close(); } } finally { input.close(); } } finally { socket.close(); } } catch (Exception e) { e.printStackTrace(); } } }).start(); }catch(Exception e){ e.printStackTrace(); } } }
可以看到正确返回计算结果2。
以上例子转载自简书:https://www.jianshu.com/p/32ca4fd5a7e2 作者:樂浩beyond
总结
RPC 的主要目标是让构建分布式计算(应用)更容易、透明,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明调用机制让使用者不必显式的区分本地调用和远程调用。当我们的业务越来越多、应用也越来越多时,自然的,我们会发现有些功能已经不能简单划分开来或者划分不出来。此时,可以将公共业务逻辑抽离出来,将之组成独立的服务Service应用 。而原有的、新增的应用都可以与那些独立的Service应用 交互,以此来完成完整的业务功能。所以此时,我们急需RPC来完成这种需求。可以说,今后我们用到RPC的地方只会越来越多,它可以有效的减轻我们的工作量。