一张图来理解RPC
2017-06-23 16:35
169 查看
企业级的RPC框架像dubbo,thrift体量还是很大的,要拿来自学没有天赋和毅力还是很难的,反正自己下了dubbo的源码之后还没系统的看过一次。本文主要结合一张图来讲解下黄勇写的NettyRpc轻量级的RPC框架,毕竟文件不多,啃下来还是相对容易些,主要是介绍下RPC大概是个什么流程,我们在client调用一个服务方法时, 其到底干了些啥??
客户端我们可以像声明普通的变量一样定义一个服务对象,然后可以用来调用服务了,这是怎么做到的呢?比如像dubbo我们会在spring的配置文件进行相关的描述,其本质就是生成一个代理对象然后放在上下文 ,最后注入到我们需要的对象的属性中。对象的生成通常使用动态代理,常用的有javassist、cglib以及java 动态代理。和我们普通使用动态代理不同的地方是Client端只有接口的定义没有实现,意味着没有目标对象(正常动态代理都会有个目标对象,在调用目标对象的方法之前可以进行些切面操作,这就是所 谓的AOP),在RPC中目标对象其实并不是必须的,我们只是在切面中做文章。
截图中的步骤1我们调用代理对象的方法时将会执行invoke方法(java动态代理生成,其他方式类似),invoke()方法里面的主要逻辑就是构造Request对象,然后向服务端发起一个tcp请求。这里涉及三点,第一 往那发?当然是我们提供服务实现的机器了,ip及端口现在都流行放在zookeep中。第二 构造的Request需要哪些属性?第一步的ip和端口定位到了服务的进程,现在需要更细粒度的标识,那就是方法的签名了,所以需要类名(className属性,包括包名),方法名(methodName),参数类型(paraTypes)以及参数值(paras),这样我们就能定位到具体的服务方法了。第三怎么发? 将一个java对象通过网络发送,这涉及到序列化,序列化的方式很多,但通常不会使用java内置的序列化,nettyRpc中使用的是protostuff(包装了protobuff)。具体发送时就是网络编程的事了,现在流行使用netty(一个优秀的nio框架)进行异步通信(返回一个Future,事后从里面获取到执行结果)。
生成动态代理对象时InvocationHandler的invoke方法体
数据到达了Server端(步骤4),我们需要反序列化client发送过来的字节数组,这样才能被使用。反序列化之前要解决的一个问题是拆包和粘包,因为我们基于tcp通信(它只负责数据可靠性到达),有可能在底层它将我们小数据包合在一块传送过来也可能将大数据包拆为多个小包传递过来,netty提供了多种实现来解决拆包和粘包的问题,直接拿来使用。
第5步就是我们调用真正的服务了,第4步我们反序列化出来的信息可以通过反射来调用server端的服务方法。server端的服务对象,在启动服务的时候可以事先存放在一个可以访问的Map中(key为类名,value为对应的服务对象)。调用的结果最后封装在Response中(步骤6),接下来的流程就和之前客户端请求流程一样了,序列化Response 通过网络传回给client,client反序列化获取到结果,getUserById()这个方法调用就完成了。
服务端处理request部分逻辑:
写的还是很粗暴的,不对地方还请指正,相互交流学习。
客户端我们可以像声明普通的变量一样定义一个服务对象,然后可以用来调用服务了,这是怎么做到的呢?比如像dubbo我们会在spring的配置文件进行相关的描述,其本质就是生成一个代理对象然后放在上下文 ,最后注入到我们需要的对象的属性中。对象的生成通常使用动态代理,常用的有javassist、cglib以及java 动态代理。和我们普通使用动态代理不同的地方是Client端只有接口的定义没有实现,意味着没有目标对象(正常动态代理都会有个目标对象,在调用目标对象的方法之前可以进行些切面操作,这就是所 谓的AOP),在RPC中目标对象其实并不是必须的,我们只是在切面中做文章。
截图中的步骤1我们调用代理对象的方法时将会执行invoke方法(java动态代理生成,其他方式类似),invoke()方法里面的主要逻辑就是构造Request对象,然后向服务端发起一个tcp请求。这里涉及三点,第一 往那发?当然是我们提供服务实现的机器了,ip及端口现在都流行放在zookeep中。第二 构造的Request需要哪些属性?第一步的ip和端口定位到了服务的进程,现在需要更细粒度的标识,那就是方法的签名了,所以需要类名(className属性,包括包名),方法名(methodName),参数类型(paraTypes)以及参数值(paras),这样我们就能定位到具体的服务方法了。第三怎么发? 将一个java对象通过网络发送,这涉及到序列化,序列化的方式很多,但通常不会使用java内置的序列化,nettyRpc中使用的是protostuff(包装了protobuff)。具体发送时就是网络编程的事了,现在流行使用netty(一个优秀的nio框架)进行异步通信(返回一个Future,事后从里面获取到执行结果)。
生成动态代理对象时InvocationHandler的invoke方法体
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class == method.getDeclaringClass()) { String name = method.getName(); if ("equals".equals(name)) { return proxy == args[0]; } else if ("hashCode".equals(name)) { return System.identityHashCode(proxy); } else if ("toString".equals(name)) { return proxy.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(proxy)) + ", with InvocationHandler " + this; } else { throw new IllegalStateException(String.valueOf(method)); } } /* 构造request对象 */ RpcRequest request = new RpcRequest(); request.setRequestId(UUID.randomUUID().toString()); request.setClassName(method.getDeclaringClass().getName()); request.setMethodName(method.getName()); request.setParameterTypes(method.getParameterTypes()); request.setParameters(args); for (int i = 0; i < method.getParameterTypes().length; ++i) { LOGGER.debug(method.getParameterTypes()[i].getName()); } for (int i = 0; i < args.length; ++i) { LOGGER.debug(args[i].toString()); } RpcClientHandler handler = ConnectManage.getInstance().chooseHandler(); //发送数据并持有一个future RPCFuture rpcFuture = handler.sendRequest(request); return rpcFuture.get(); }
数据到达了Server端(步骤4),我们需要反序列化client发送过来的字节数组,这样才能被使用。反序列化之前要解决的一个问题是拆包和粘包,因为我们基于tcp通信(它只负责数据可靠性到达),有可能在底层它将我们小数据包合在一块传送过来也可能将大数据包拆为多个小包传递过来,netty提供了多种实现来解决拆包和粘包的问题,直接拿来使用。
第5步就是我们调用真正的服务了,第4步我们反序列化出来的信息可以通过反射来调用server端的服务方法。server端的服务对象,在启动服务的时候可以事先存放在一个可以访问的Map中(key为类名,value为对应的服务对象)。调用的结果最后封装在Response中(步骤6),接下来的流程就和之前客户端请求流程一样了,序列化Response 通过网络传回给client,client反序列化获取到结果,getUserById()这个方法调用就完成了。
服务端处理request部分逻辑:
private Object handle(Request request) throws Throwable { String className = request.getClassName(); Object serviceBean = handlerMap.get(className); Class<?> serviceClass = serviceBean.getClass(); String methodName = request.getMethodName(); Class<?>[] parameterTypes = request.getParameterTypes(); Object[] parameters = request.getParameters(); LOGGER.debug(serviceClass.getName()); LOGGER.debug(methodName); for (int i = 0; i < parameterTypes.length; ++i) { LOGGER.debug(parameterTypes[i].getName()); } for (int i = 0; i < parameters.length; ++i) { LOGGER.debug(parameters[i].toString()); } // Cglib reflect FastClass serviceFastClass = FastClass.create(serviceClass); FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes); return serviceFastMethod.invoke(serviceBean, parameters); }
写的还是很粗暴的,不对地方还请指正,相互交流学习。
相关文章推荐
- 一张图理解prototype、proto和constructor的三角关系
- 一张图快速理解golang中的并行与并发
- 理解RPC和LPC的概念
- RPC理解
- 6.hadoop中rpc中的动态代理的理解
- 一张图理解prototype、proto和constructor的三角关系
- 我理解的现在网站结构,只有一张图
- 一张图理解网页布局
- 理解Android系统的进程间通信原理(一)----RPC中的代理模式
- (转)理解Android系统的进程间通信原理(一)----RPC中的代理模式
- 【一张图系列】理解安全上下文
- 一张图更好的帮助你理解css中的一些概念
- Android 一张图理解getWidth和getMeasuredWidth
- 理解Android系统的进程间通信原理(二)----RPC机制
- 谈谈自己对REST、SOA、SOAP、RPC、ICE、ESB、BPM知识汇总及理解
- 一张图理解prototype、proto和constructor的三角关系
- 简单理解 RPC(转载)
- 理解Android系统的进程间通信原理(二)----RPC机制
- 谈谈自己对REST、SOA、SOAP、RPC、ICE、ESB、BPM知识汇总及理解(转载)
- HDFS与RPC理解