RPC架构
RPC的全称是 Remote Procedure Call,它是一种进程间通信方式。允许像调用本地服务一样调用远程服务,它的具体实现方式可以不同,例如Spring 的 HTTP Invoker, Facebook的Thrift二进制私有协议通信。
RPC概念术语在上世纪80年代由Bruce Jay Nelson提出,在他的论文中对RPC进行了如下总结。
(1)简单:RPC概念的语义十分清晰和简单,这样建立分布式计算就更容易。
(2)高效:过程调用看起来十分简单而且高效。
(3)通用:在单机计算中过程往往是不同算法和API,跨进程调用最重要的是通用的通信机制。
2006年之后,随着移动互联网的发展,各种智能终端的普及,远程分布式调用已经成为主流,RPC框架也如雨后春笋般诞生,开源和自研的RPC框架的普及标志着传统垂直应用架构时代的终结。
RPC框架原理
RPC框架的目标就是让远程过程(服务)调用更加简单、透明,RPC框架负责屏蔽底层的传输方式(TCP或者UDP)、序列化方式(XMLIJSON/二进制)和通信细节。框架使用者只需要了解谁在什么位置提供了什么样的远程服务接口即可,开发者不需要关心底层通信细节和调用过程。
编辑
RPC框架实现的几个核心技术点如下:
(1)远程服务提供者需要以某种形式提供服务调用相关的信息,包括但不限于服务接口定义、数据结构,或者中间态的服务定义文件,例如Thrift的IDL文件,WS-RPC的WSDL文件定义,甚至也可以是服务端的接口说明文档;服务调用者需要通过一定的途径获取远程服务调用相关信息,例如服务端接口定义Jar包导入,获取服务端IDL文件等。
(2)远程代理对象:服务调用者调用的服务实际是远程服务的本地代理,对于Java语言,它的实现就是JDK的动态代理,通过动态代理的拦截机制,将本地调用封装成远程服务调用。
(3)通信:RPC框架与具体的协议无关,例如Spring的远程调用支持HTTP Invoke、RMl Invoke,MessagePack使用的是私有的二进制压缩协议。
(4)序列化:远程通信,需要将对象转换成二进制码流进行网络传输,不同的序列化框架,支持的数据类型、数据包大小、异常类型及性能等都不同。不同的RPC框架应用场景不同,因此技术选择也会存在很大差异。一些做得比较好的RPC框架,可以支持多种序列化方式,有的甚至支持用户自定义序列化框架(Hadoop Avro) 。
简单的RPC框架实现
下面通过Java原生的序列化、Socket通信、动态代理和反射机制,实现最简单的RPC框架。
它由三部分组成:
(1)服务提供者,它运行在服务端,负责提供服务接口定义和服务实现类。
(2)服务发布者,它运行在RPC服务端,负责将本地服务发布成远程服务,供其他消费者调用。
(3)本地服务代理,它运行在RPC客户端,通过代理调用远程服务提供者,然后将结果进行封装返回给本地消费者。
下面看具体代码实现,代码如下:
EchoService接口代码
/** * 服务端接口 */ public interface EchoService { String echo(String ping); }
EchoService接口的实现类代码
public class EchoServiceImpl implements EchoService { public String echo(String ping) { return ping != null ? ping +" --> I am OK." : "I am OK."; } }
RPC服务端服务发布者代码
/** * 服务端发布者 */ public class RpcExporter { static Executor executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); public static void exporter(String hostName,int port) throws Exception{ ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(new InetSocketAddress(hostName,port)); try{ while (true){ executor.execute(new ExporterTask(serverSocket.accept())); } }finally { serverSocket.close(); } } private static class ExporterTask implements Runnable{ Socket client = null; public ExporterTask(Socket client){ this.client = client; } public void run() { ObjectInputStream inputStream = null; ObjectOutputStream outputStream = null; try{ inputStream = new ObjectInputStream(client.getInputStream()); String interfaceName = inputStream.readUTF(); Class<?> service = Class.forName(interfaceName); String methodName = inputStream.readUTF(); Class<?>[] parameterTypes = (Class<?>[]) inputStream.readObject(); Object[] arguments = (Object[]) inputStream.readObject(); Method method = service.getMethod(methodName, parameterTypes); Object result = method.invoke(service.newInstance(), arguments); outputStream = new ObjectOutputStream(client.getOutputStream()); outputStream.writeObject(result); }catch (Exception e){ e.printStackTrace(); }finally { if (outputStream != null) { try { outputStream.close(); }catch (IOException e){ e.printStackTrace(); } } if (inputStream != null) { try{ inputStream.close(); }catch (IOException e){ e.printStackTrace(); } } if (client != null) { try{ client.close(); }catch (IOException e){ e.printStackTrace(); } } } } } }
服务发布者的主要职责如下:
(1)作为服务端,监听客户端的TCP连接,接收到新的客户端连接之后,将其封装成Task,由线程池执行。
(2)将客户端发送的码流反序列化成对象,反射调用服务实现者,获取执行结果。
(3)将执行结果对象反序列化,通过Socket发送给客户端。
(4)远程服务调用完成之后,释放Socket等连接资源,防止句柄泄漏。
RPC客户端本地服务代理:
/** * 客户端本地服务 * @param <S> */ public class RpcImporter<S> { public S importer(final Class<?>serviceClass, final InetSocketAddress addr){ return (S)Proxy.newProxyInstance(serviceClass.getClassLoader(), new Class<?>[] { serviceClass.getInterfaces()[0] }, new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Socket socket = null; ObjectInputStream inputStream = null; ObjectOutputStream outputStream = null; try{ socket = new Socket(); socket.connect(addr); outputStream = new ObjectOutputStream(socket.getOutputStream()); outputStream.writeUTF(serviceClass.getName()); outputStream.writeUTF(method.getName()); outputStream.writeObject(method.getParameterTypes()); outputStream.writeObject(args); inputStream = new ObjectInputStream(socket.getInputStream()); return inputStream.readObject(); }finally { if (socket != null) { socket.close(); } if (outputStream != null) { outputStream.close(); } if (inputStream != null) { inputStream.close(); } } } } ); } }
本地服务代理的主要功能如下:
(1)将本地的接口调用转换成JDK的动态代理,在动态代理中实现接口的远程调用。
(2)创建Socket客户端,根据指定地址连接远程服务提供者。
(3)将远程服务调用所需的接口类、方法名、参数列表等编码后发送给服务提供者。
(4)同步阻塞等待服务端返回应答,获取应答之后返回。
测试代码如下:
public class RpcTest { public static void main(String[] args) { new Thread(new Runnable() { public void run() { try { RpcExporter.exporter("localhost",8888); } catch (Exception e) { e.printStackTrace(); } } }).start(); RpcImporter<EchoService> importer = new RpcImporter<EchoService>(); EchoService echo = importer.importer(EchoServiceImpl.class, new InetSocketAddress("localhost", 8888)); System.out.println(echo.echo(" Hello! I am BanQ! ")); } }
首先,创建一个异步发布服务端的线程并启动,用于接收RPC客户端的请求,根据请求参数调用服务实现类,返回结果给客户端。随后,创建客户端服务代理类,构造 RPC请求参数,发起RPC调用,将调用结果输出到控制台上。
执行结果如下:
Hello! I am BanQ! --> Hi! BanQ
最后附上源码: 最简单的RPC框架源码