您的位置 首页 java

通过几段 Java 代码带你理解 RPC

RPC 远程过程调用可以说是分布式系统的基础,为什么 RPC 要叫作远程过程调用,而不叫作 RMC 远程方法调用。远程调用的是某个过程,不一定是一个具体的方法。

通过几段 Java 代码带你理解 RPC

  这整个过程可以用一句话概括:机器 A 通过网络与机器B建立连接,A 发送一些参数给 B,B 执行某个过程,并把结果返回给 A。

  在实际的使用中,我们会编写各种各样的远程调用,打个比方,IProductService接口以后可能会扩展成这样:

public interface IProductService {

  Product getProductById(Integer id);

  Product getProductByName(String name);

  Product getMostExpensiveProduct();

  }

  我们总不可能为每个方法都编写一段网络连接的代码吧,我们得想到一种办法为所有的方法都嵌入一段共用的网络连接代码。

  那具体应该怎样嵌入呢?这里我们可以用到代理模式。

  在 Java 中许多优秀的框架都用到了代理模式做代码嵌入,比如说 Mybatis。它把 JDBC 连接部分的代码通过代理模式嵌入到 sql 语句的周围,让我们专注于写 sql。

  首先,服务端的代码要进行修改,由于多个方法共用一套联网的代码,我们要在服务端识别调用的是哪一个方法:

public class Server {

  private static boolean running = true;

  public static void main(String[] args) throws Exception {

  //……

  }

  private static void process(Socket socket) throws Exception {

  //获取输入流,输出流

  InputStream is = socket.getInputStream();

  OutputStream os = socket.getOutputStream();

  ObjectInputStream ois = new ObjectInputStream(is);

  ObjectOutputStream oos = new ObjectOutputStream(os);

  //获取本次远程调用的方法名

  String methodName = ois.readUTF();

  //获取本次远程调用方法的参数类型

  Class[] parameterTypes = (Class[]) ois.readObject();

  //获取具体的参数对象

  Object[] args = (Object[]) ois.readObject();

  //创建商品服务类实例,(往后这里可以继续优化)

  IProductService service = new ProductServiceImpl();

  //根据远程获取的方法名和参数,调用相应的方法

  Method method = service.getClass().getMethod(methodName, parameterTypes);

  Product product = (Product) method.invoke(service, args);

  //把结果写回给客户端

  oos.writeObject(product);

  oos.close();

  ois.close();

  socket.close();

  }

  }

  然后在客户端,我们创建一个新的代理类,对外提供一个 getStub 获取代理类的方法。使用 JDK 的动态代理需要三个参数,一个是类加载器,一个是接口的 class 类,最后一个是InvocationHandler实例。

  JDK 动态代理背后的逻辑是这样的:JVM 会根据接口的 class 类动态创建一个代理类对象,这个代理对象实现了传入的接口,也就是说它拥有了接口中所有方法的实现。方法具体的实现可以由用户指定,也就是调用InvocationHandler的invoke方法。

  在invoke方法中有三个参数,分别是proxy代理类,method调用的方法,args调用方法的参数。我们可以在invoke方法中对具体的实现方法进行增强,在本案例中就是进行网络调用。

public class Stub {

  public static IProductService getStub() {

  InvocationHandler h = new InvocationHandler() {

  @Override

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

  //和服务端建立Socket连接

  Socket socket = new Socket(“127.0.0.1”, 8888);

  ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

  //拿到远程调用的方法名

  String methodmethodName = method.getName();

  //拿到远程调用方法的参数类型

  Class[] parametersTypes = method.getParameterTypes();

  //把方法名传递给服务端

  oos.writeUTF(methodName);

  //把方法参数类型传递给服务端

  oos.writeObject(parametersTypes);

  //把方法参数传递给服务端

  oos.writeObject(args);

  oos.flush();

  //获取远程调用的返回结果

  ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());

  Product product = (Product) ois.readObject();

  ois.close();

  oos.close();

  socket.close();

  return product;

  }

  };

  Object o = Proxy.newProxyInstance(IProductService.class.getClassLoader(), new Class[]{IProductService.class}, h);

  return (IProductService) o;

  }

  }

  这个新版本比第一个版本又美好了一些,但是其实还可以继续优化。现在我们的代理只能够返回IProductService的实现类,得想办法让它返回任意类型的服务实现类。

  思路和远程调用方法相似,在远程调用方法时,我们把方法的名称,参数类型,参数传递给服务端;现在要动态创建服务类,我们可以把服务接口的名字传给服务端。服务端拿到远程接口的名字后,就可以从服务注册表中找到对应服务实现类。

  至于服务实现类如何注册到服务注册表,这里提供一个思路:可以考虑使用 Spring 的注解注入。这和我们平时写 spring 代码是相似的,在创建完服务实现类后我们会加上注解 @Service,这样我们就可以在收到远程调用后,遍历使用了 @Service的Bean,找到对应的实现类。

   总结

  如果想要搭建一个简易的RPC框架,阿粉认为有4个点:

  代理问题

  服务实例化问题

  序列化问题

  通信问题

  本篇 文章 的侧重点是前两个,代理问题和服务实例化问题,我们回顾一下:

  首先通过代理模式解决了每个远程方法联网的问题

  通过传递方法名,方法参数类型,参数,以及后面提及的直接传接口名字的形式解决服务实例化问题,这背后的原理是反射,传这些参数也是给反射用的。

   关于另外两个点的延伸

  在网络上传输数据避免不了序列化问题。Java 自带的序列化方式效率比较低,如果要造一个 RPC 框架的话,我们可以考虑引入其他第三方的序列化框架。

  通信问题也很重要,现有的 RPC 框架使用的通信协议也不相同,各有各的优缺点。但这部分手动实现可能比较困难,可以根据语言自身封装好的 API 进行选择。

文章来源:智云一二三科技

文章标题:通过几段 Java 代码带你理解 RPC

文章地址:https://www.zhihuclub.com/194772.shtml

关于作者: 智云科技

热门文章

网站地图