0.RPC简介
RPC, 英文全称:Remote Process Call. 中文全称:远程过程调用.
客户端通过网络请求调用远程服务端对外暴露服务。常用的两种RPC协议:TCP、HTTP。
举例描述:两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
最简单的场景:通过百度搜索所需要的信息。
当我们在百度的搜索栏中填入所需要查找的信息时,浏览器客户端会创建一个网络请求,将所要查询的信息标识传给百度的远程服务器端。服务器得到请求之后,调用自身内部的处理操作,再将查询到的信息返回给浏览器,浏览器解析展示给用户。
1.RPC的优缺点
优点:
a) 解决单应用的性能瓶颈。
b) 实现应用之间的解耦,服务拆分。
c) 提升功能代码的复用性。
缺点:
a) 网络交互
b) 增加应用的复杂性
2.RPC实现流程
- 首先,要解决通讯的问题,主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
- 第二,要解决寻址的问题,也就是说,A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称名称是什么,这样才能完成调用。比如基于Web服务协议栈的RPC,就要提供一个endpoint URI,或者是从UDDI服务上查找。如果是RMI调用的话,还需要一个RMI Registry来注册服务的地址。
- 第三,当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制的形式,也就是序列化(Serialize)或编组(marshal),通过寻址和传输将序列化的二进制发送给B服务器。
- 第四,B服务器收到请求后,需要对参数进行反序列化(序列化的逆操作),恢复为内存中的表达方式,然后找到对应的方法(寻址的一部分)进行本地调用,然后得到返回值。
- 第五,返回值还要发送回服务器A上的应用,也要经过序列化的方式发送,服务器A接到后,再反序列化,恢复为内存中的表达方式,交给A服务器上的应用
3.Code实现
定义接口:
/** * Rpc接口 * * @author apple */ public interface HelloService { String hello(String str); }
接口实现:
public class HelloServiceImpl implements HelloService { @Override public String hello(String str) { return return "hello " + str; } }
RPC服务端:
public class RpcExporter { static Executor executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); public static void exporter(String hostName,int port) throws Exception{ ServerSocket server = new ServerSocket(); server.bind(new InetSocketAddress(hostName, port)); try{ while(true){ executor.execute(new ExporterTask(server.accept())); } }finally{ server.close(); } } private static class ExporterTask implements Runnable{ Socket client = null; public ExporterTask(Socket client){ this.client = client; } @Override public void run() { ObjectInputStream input = null; ObjectOutputStream output = null; try { input = new ObjectInputStream(client.getInputStream()); String interfaceName = input.readUTF(); Class<?> service = Class.forName(interfaceName); String methodName = input.readUTF(); Class<?>[] parameterTypes = (Class<?>[])input.readObject(); Object[] arguments = (Object[])input.readObject(); Method method = service.getMethod(methodName, parameterTypes); Object result = method.invoke(service.newInstance(),arguments); output = new ObjectOutputStream(client.getOutputStream()); output.writeObject(result); } catch (Exception e) { e.printStackTrace(); }finally { if(output != null){ try { output.close(); } catch (IOException e) { e.printStackTrace(); }finally{ output = null; } } if(input != null){ try { input.close(); } catch (IOException e) { e.printStackTrace(); }finally{ input = null; } } if(client != null){ try { client.close(); } catch (IOException e) { e.printStackTrace(); }finally{ input = null; } } } } } }
RPC客户端:
public class RpcImporter<S> { @SuppressWarnings("unchecked") public S importer(final Class<?> serviceClass,final InetSocketAddress addr){ return (S) Proxy.newProxyInstance(serviceClass.getClassLoader(), new Class<?>[]{serviceClass.getInterfaces()[0]}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Socket socket = null; ObjectOutputStream output = null; ObjectInputStream input = null; try{ socket = new Socket(); socket.connect(addr); output = new ObjectOutputStream(socket.getOutputStream()); output.writeUTF(serviceClass.getName()); output.writeUTF(method.getName()); output.writeObject(method.getParameterTypes()); output.writeObject(args); input = new ObjectInputStream(socket.getInputStream()); return input.readObject(); }finally{ if(socket != null) socket.close(); if(output != null) output.close(); if(input != null) input.close(); } } }); } }
测试类:
public class RpcTest { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { try { RpcExporter.exporter("localhost", 8088); } catch (Exception e) { e.printStackTrace(); } } }).start(); RpcImporter<HelloService> importer = new RpcImporter<HelloService>(); HelloService service = importer.importer(HelloServiceImpl.class, new InetSocketAddress("localhost", 8088)); while(true){ System.out.println(service.hello("word")); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
执行结果:
4.常用的RPC框架
Java RMI
Web Service
Hessian
Thrift
Http Rest API
Dubbo
5.参考资料
谁能用通俗的语言解释一下什么是 RPC 框架?