揭秘系统远程调用思想

前言

在xxl-job中存在调度中心远程调用执行器的过程, 在此可以很清晰的学习到远程调用的思想。  

常规的远程调用协议,dubbo, thrift , Protocol Buffer, 以及更轻量级的hessian协议。 这些市面上大同小异的

框架,在我们集成这些框架的时候,我们总会在调用端引用服务端暴露出来的接口,然后通过代理工厂创建代理对象

最终通过spring的自动注入的方式,在我们的容器里面生成代理对象,然后当我们需要使用的时候,直接使用代理对象,

就可以调用到另一个系统里面的服务了, 常规的配置我们都知道,但是真正内部是怎么实现的,为何我们调用代理

对象的方法,就能调用到远程系统的服务,这是我们值得思考的。 此处主要以xxl-job为原型,讲解这种模式, 其他专业

的RPC框架肯定比这个更加复杂, 所以这里主要是讲解一个思想。 



例:当A系统需要调用B系统的方法时,我们通常的做法是这样的。

B系统 暴露一个interface, 如:   exampleInterface.jar 

A系统引用exampleInterface.jar ,   同时在spring.xml 文件中配置B系统暴露出来的接口类(例:userService)

最终我们的使用方式如下: 

Example
@Authwired
 
UserService userService ;
  
public User getUsers(){
    return userService.getUsers()
}

上面那一段,非常简短的代码, 还原了一个非常简短的系统之间的调用, 但是为什么在A系统调用userService.getUsers() , 

这个方法最终就能调用到B系统去呢? 


代理对象生成

在xxl-job中调度中心需要调用执行器的代码, 下面来仔细分析一下、 

获取代理对象
public static ExecutorBiz getExecutorBiz(String address) throws Exception {
    // valid
    if (address==null || address.trim().length()==0) {
        return null;
    }
 
    // load-cache
    address = address.trim();
    // 从内存中获取执行器对象
    ExecutorBiz executorBiz = executorBizRepository.get(address);
    if (executorBiz != null) {
        return executorBiz;
    }
 
    // 通过代理类,创建代理对象,主要看这个创建过程
    executorBiz = (ExecutorBiz) new NetComClientProxy(ExecutorBiz.class, address, accessToken).getObject();
    // 放入内存中,以便下次调用
    executorBizRepository.put(address, executorBiz);
    return executorBiz;
}

从下面的代码中,可以看到NetComClientProxy 这个代理类,是一个factoryBean, 所以主要看他的getObject方法即可

NetComClientProxy
public class NetComClientProxy implements FactoryBean<Object> {
   private static final Logger logger = LoggerFactory.getLogger(NetComClientProxy.class);
 
 
   private Class<?> iface;
   private String serverAddress;
   private String accessToken;
   private JettyClient client = new JettyClient();
   public NetComClientProxy(Class<?> iface, String serverAddress, String accessToken) {
      this.iface = iface;
      this.serverAddress = serverAddress;
      this.accessToken = accessToken;
   }
 
   @Override
   public Object getObject() throws Exception {
      return Proxy.newProxyInstance(Thread.currentThread()
            .getContextClassLoader(), new Class[] { iface },
            new InvocationHandler() {
               @Override
               public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 
                  // filter method like "Object.toString()"
                    if (Object.class.getName().equals(method.getDeclaringClass().getName())) {
                     logger.error(">>>>>>>>>>> xxl-rpc proxy class-method not support [{}.{}]", method.getDeclaringClass().getName(), method.getName());
                     throw new RuntimeException("xxl-rpc proxy class-method not support");
                  }
                   
                     //生成这个代理对象的invoke方法里面,并没有调用目标类的方法,而是将目标的信息发送给远程服务器。
                    RpcRequest request = new RpcRequest();
                    request.setServerAddress(serverAddress); // 服务器地址
                    request.setCreateMillisTime(System.currentTimeMillis()); // 创建时间, 用于判断请求是否超时
                    request.setAccessToken(accessToken);  // 数据校验
                    request.setClassName(method.getDeclaringClass().getName()); // 将目标类的class名称传给执行器,让那边来创建对象,并执行逻辑代码
                    request.setMethodName(method.getName());   // 方法名称为run
                    request.setParameterTypes(method.getParameterTypes());  // 参数类型
                    request.setParameters(args); // 参数
                    RpcResponse response = client.send(request); // 发送HTTP请求
                        
                       // valid response
                  if (response == null) {
                     logger.error(">>>>>>>>>>> xxl-rpc netty response not found.");
                     throw new Exception(">>>>>>>>>>> xxl-rpc netty response not found.");
                   }
                       if (response.isError()) {
                           throw new RuntimeException(response.getError());
                       else {
                           return response.getResult();
                       }
                       
               }
            });
   }
   @Override
   public Class<?> getObjectType() {
      return iface;
   }
   @Override
   public boolean isSingleton() {
      return false;
   }
 
}

从上面可以清晰的看到,生成的代理对象里面的 InvocationHandler 中的invoke方法,并没有真实的执行方法,而是将类名,方法名,参数等信息发送给远程服务器。 

仅仅只是做了这些操作而已。 这就是他的设计比较巧妙的地方。 

下面可以看一下,远程服务器拿到请求之后如何处理的。 

public static RpcResponse invokeService(RpcRequest request, Object serviceBean) {
   // request中的数据结构
   if (serviceBean==null) {
      //  这个serviceBean 就是在执行器启动的时候,initExecutorServer () 这个方法中,将一个ExecutorBiz的实例放进去了,此处通过
      // classname来获取这个实例
      serviceBean = serviceMap.get(request.getClassName());
   }
   if (serviceBean == null) {
      // TODO
   }
  
   RpcResponse response = new RpcResponse();
  // 判断是否超时
   if (System.currentTimeMillis() - request.getCreateMillisTime() > 180000) {
      response.setResult(new ReturnT<String>(ReturnT.FAIL_CODE, "The timestamp difference between admin and executor exceeds the limit."));
      return response;
   }
    // 数据校验,验证token是否匹配,前提是token不为空
   if (accessToken!=null && accessToken.trim().length()>0 && !accessToken.trim().equals(request.getAccessToken())) {
      response.setResult(new ReturnT<String>(ReturnT.FAIL_CODE, "The access token[" + request.getAccessToken() + "] is wrong."));
      return response;
   }
  
   try {
      // 获取class
      Class<?> serviceClass = serviceBean.getClass();
      // 拿到请求中的方法名字, 此处这个值 是  run 方法
      String methodName = request.getMethodName();
        //方法类型
      Class<?>[] parameterTypes = request.getParameterTypes();
      // 方法参数
      Object[] parameters = request.getParameters();
      // spring的工具类, 创建一个fastClass 实例
      FastClass serviceFastClass = FastClass.create(serviceClass);
      FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes);
       // 拿到方法之后执行方法的invoke ,
      Object result = serviceFastMethod.invoke(serviceBean, parameters);
  
      // 返回执行结果
      response.setResult(result);
   catch (Throwable t) {
      t.printStackTrace();
      response.setError(t.getMessage());
   }
  
   return response;
}


由此我们就可以知道,远程调用的大致思想。 


揭秘系统远程调用思想