应用架构之RPC架构

RPC(远程过程调用)协议简化了进程间通信,使得调用远程服务如同本地方法调用一般。本文介绍了RPC的基本概念,阐述了RPC框架出现的原因,并详细讲解了RPC的运行原理,包括其内部的十步操作。接着,通过Java原生技术展示了简单的RPC框架实现,涉及服务提供者、服务发布者、本地服务代理的角色分配。最后,指出了RPC框架在服务治理上的局限性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RPC框架简介

  • 什么叫RPC?
    RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
  • 为什么会出现RPC?
    应用之间的交互不可避免,将核心业务抽取出来,作为独立的服务。同时将通用的API抽取出来,供其他系统调用者使用。应用拆分之后会按照模块独立部署,接口调用本地接口变成了跨进程的远程调用,这个时候就出现了RPC框架。
  • 说白了RPC是一种进程间的通信方式,允许像调用本地方法一样调用远程服务

RPC框架原理

RPC框架的目标是是远程服务调用更加简单,透明。它屏蔽了底层的传输方式(TCP或者UDP),序列化方式(XML/JSON/二进制)和通信细节。其实框架的作用就是屏蔽了底层的操作细节,让我们程序员更加方便高效的编程。但是了解底层的实现对我们熟练使用框架是有很大的帮助的。下图简述RPC框架的原理:

这里写图片描述

运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步:
1.调用客户端句柄;执行传送参数
2.调用本地系统内核发送网络消息
3.消息传送到远程主机
4.服务器句柄得到消息并取得参数
5.执行远程过程
6.执行的过程将结果返回服务器句柄
7.服务器句柄返回结果,调用远程系统内核
8.消息传回本地主机
9.客户句柄由内核接收消息
10.客户接收句柄返回的数据

简单的PRC框架的实现

下面通过Java原生的序列化,Socket通信,动态代理和反射机制实现最简单的RPC框架。

  • 组成

    • 服务提供者,运行在服务端,负责提供服务接口定义和服务实现类。
    • 服务发布者,运行在RPC服务端,负责将本地服务发布成远程服务,供消费者使用。
    • 本地服务代理,运行在RPC客户端通过代理调用远程的服务提供者,然后将结果进行封装返还给本地消费者。
  • 实现

    • 服务端接口定义

      public interface EchoService {
          String echo( String ping );
      }
    • 服务端接口实现类

      public class EchoServiceImpl implements EchoService {
      
          @Override
          public String echo(String ping) {
              return ping != null ? ping + " -->I am ok." : " I am ok.";
          }
      
      }
    • RPC服务端服务发布者代码

      //ObjectInputStream 对象输入流,可以将序列化过的对象反序列化到内存中,对应的还有个ObjectOutputStream 可以将一个对象序列化到本地!
      public class RpcExporter {
      
          static Executors executors = (Executors) 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 ) );
          }
      
          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();
                          }
                      }
                      if( input != null ) {
                          try {
                              input.close();
                          } catch (IOException e) {
                              e.printStackTrace();
                          }
                      }
                      if( client != null ) {
                          try {
                              client.close();
                          } catch (IOException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }
          }
      }
      
      • 服务发布者的主要职责:
        • 做为服务器,监听客户端的TCP连接,接收到新的客户端连接后,将其封装成Task,由线程池执行。
        • 将客户端发送的码反序列化成对象,反射调用服务实现者,获得执行结果。
        • 将执行结果对象反序列化,通过Socket发送给客户端。
        • 远程调用完成之后,释放Socket等连接资源,防止句柄泄漏。
    • RPC客户端本地服务代理代码

      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() {
      
                          @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());//同步阻塞等待
                              } finally {
                                  if( socket != null ) {
                                      socket = null;
                                  }
                                  if( output != null ) {
                                      output = null;
                                  }
                                  if( input != null ) {
                                      input = null;
                                  }
                              }
                              return input.readObject();
                          }
                      });
          }
      }
      
      • 本地服务代理的职责
        • 将本地接口调用转化成JDK的动态代理,在代理类中实现接口的远程调用。
        • 创建Socket客户端,根据指定地址连接远程服务提供者
        • 将远程服务调用所需的接口和方法名等编码后发送给服务提供者。
        • 同步阻塞等待服务器返回应答,获取应答之后返回。
    • 测试代码

      public class RpcTest {
          public static void main(String[] args) {
              //用于接受PRC客户端的请求
              new Thread(new Runnable() {
      
                  @Override
                  public void run() {
                      try {
                          RpcExporter.exporter("127.0.0.1", 8080);
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                  }
              }).start();
              RpcImporter<EchoService> importer = new RpcImporter< EchoService >();
              EchoService echo = importer.importer( EchoServiceImpl.class, new InetSocketAddress("127.0.0.1", 8080) );
              System.out.println( echo.echo("Are you OK?") );
          }
      }

RPC的不足

纯粹的RPC框架服务与治理能力都不健全,当应用大规模服务化之后会面临许多服务治理方面的挑战,要解决这些问题,必须通过服务框架 + 服务治理来完成,单凭RPC框架无法解决服务治理问题。
什么是服务治理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值