什么是远程调用
RPC,全称为Remote Procedure Call,即远程过程调用,它是一个计算机通信协议。它允许像调用本地服务一样调用远程服务。它可以有不同的实现方式。如RMI(远程方法调用)、Hessian、Http invoker等。另外,RPC是与语言无关的。
RPC示意图
- 服务消费方(client)调用以本地调用方式调用服务;
- client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
- client stub找到服务地址,并将消息发送到服务端;
- server stub收到消息后进行解码;
- server stub根据解码结果调用本地的服务;
- 本地服务执行并将结果返回给server stub;
- server stub将返回结果打包成消息并发送至消费方;
- client stub接收到消息,并进行解码;
- 服务消费方得到最终结果。
RPC 调用分类
RPC 调用的分类方式有很多种。
从通信协议层面可以分为:
基于 HTTP 协议的 RPC;
基于二进制协议的 RPC;
基于 TCP 协议的 RPC。
从是否跨平台可分为:
单语言 RPC,如 RMI, Remoting;
跨平台 RPC,如 google protobuffer, restful json,http XML。
从调用过程来看,可以分为同步通信RPC和异步通信RPC:
同步 RPC:指的是客户端发起调用后,必须等待调用执行完成并返回结果;
异步 RPC:指客户方调用后不关心执行结果返回,如果客户端需要结果,可用通过提供异步 callback 回调获取返回信息。大部分 RPC 框架都同时支持这两种方式的调用。
RPC 的实现方式有很多,可以基于常见的 HTTP 协议,也可以在TCP上层封装自定义协议,常见的 Web Service 就是基于 HTTP 协议的 RPC,HTTP 协议的优点是具有良好的跨平台性,特别适合异构系统较多的公司,但是由于 HTTP 报头较为冗长,性能较差,基于 TCP 协议的 RPC 可以建立长连接,速度和效率明显,但是难度和复杂程度很高。
我们所说的远程调用 基于网络通信,目前有两种常用IO通信模型:1)BIO;2)NIO。一般RPC框架需要支持这两种IO模型。
- 使用java nio方式自研,这种方式较为复杂,而且很有可能出现隐藏bug,但也见过一些互联网公司使用这种方式;
- 基于netty,现在很多RPC框架都直接基于netty这一IO通信框架,省力又省心,比如阿里巴巴的HSF、dubbo,Twitter的finagle等。
RPC调用的原理实现
上代码:定义一个HelloService的接口及其实现类HelloServiceImpl
-
public interface HelloService { public String hello(String name); } public class HelloServiceImpl implements HelloService{ @Override public String hello(String name) { return "Hello, " + name; } }
定义PRCServer,利用到了线程池及其反射
-
public class PRCServer { private ExecutorService threadPool; private static final int DEFAULT_THREAD_NUM = 10; public PRCServer(){ threadPool = Executors.newFixedThreadPool(DEFAULT_THREAD_NUM); } public void register(Object service, int port){ try { System.out.println("server starts..."); ServerSocket server = new ServerSocket(port); Socket socket = null; while((socket = server.accept()) != null){ System.out.println("client connected..."); threadPool.execute(new Processor(socket, service)); } } catch (IOException e) { e.printStackTrace(); } } class Processor implements Runnable{ Socket socket; Object service; public Processor(Socket socket, Object service){ this.socket = socket; this.service = service; } public void process(){ } @Override public void run() { try { ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); String methodName = in.readUTF(); Class<?>[] parameterTypes = (Class<?>[]) in.readObject(); Object[] parameters = (Object[]) in.readObject(); Method method = service.getClass().getMethod(methodName, parameterTypes); try { Object result = method.invoke(service, parameters); ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); out.writeObject(result); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } } } }
定义PRCClient
-
public class PRCClient { public static void main(String args[]){ HelloService helloService = getClient(HelloService.class, "127.0.0.1", 50002); System.out.println(helloService.hello("jim")); } @SuppressWarnings("unchecked") public static <T> T getClient(Class<T> clazz, String ip, int port){ return (T) Proxy.newProxyInstance(PRCClient.class.getClassLoader(), new Class<?>[]{clazz}, new InvocationHandler() { public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable { Socket socket = new Socket(ip, port); ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); out.writeUTF(arg1.getName()); out.writeObject(arg1.getParameterTypes()); out.writeObject(arg2); ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); return in.readObject(); } }); } }
定有一个启动类
-
public class Main { public static void main(String args[]){ HelloService helloService = new HelloServiceImpl(); PRCServer server = new PRCServer(); server.register(helloService, 50002); } }
启动main,再启动PRClient即可。