rpc原理及rpc实例分析

上传人:第*** 文档编号:32696093 上传时间:2018-02-12 格式:DOC 页数:16 大小:89KB
返回 下载 相关 举报
rpc原理及rpc实例分析_第1页
第1页 / 共16页
rpc原理及rpc实例分析_第2页
第2页 / 共16页
rpc原理及rpc实例分析_第3页
第3页 / 共16页
rpc原理及rpc实例分析_第4页
第4页 / 共16页
rpc原理及rpc实例分析_第5页
第5页 / 共16页
点击查看更多>>
资源描述

《rpc原理及rpc实例分析》由会员分享,可在线阅读,更多相关《rpc原理及rpc实例分析(16页珍藏版)》请在金锄头文库上搜索。

1、RPC 原理及 RPC 实例分析第一部分在学校期间大家都写过不少程序,比如写个 hello world 服务类,然后本地调用下,如下所示。这些程序的特点是服务消费方和服务提供方是本地调用关系。java view plain copy print?public class Test public static void main(String args) HelloWorldService helloWorldService = new HelloWorldServiceImpl(); helloWorldService.sayHello(test); 而一旦踏入公司尤其是大型互联网公司就会发现

2、,公司的系统都由成千上万大大小小的服务组成,各服务部署在不同的机器上,由不同的团队负责。这时就会遇到两个问题:要搭建一个新服务,免不了需要依赖他人的服务,而现在他人的服务都在远端,怎么调用?其它团队要使用我们的新服务,我们的服务该怎么发布以便他人调用?下文将对这两个问题展开探讨。1. 如何调用他人的远程服务?由于各服务部署在不同机器,服务间的调用免不了网络通信过程,服务消费方每调用一个服务都要写一坨网络通信相关的代码,不仅复杂而且极易出错。如果有一种方式能让我们像调用本地服务一样调用远程服务,而让调用者对网络通信这些细节透明,那么将大大提高生产力,比如服务消费方在执行 helloWorldSe

3、rvice.sayHello(“test”)时,实质上调用的是远端的服务。这种方式其实就是 RPC(Remote Procedure Call Protocol),在各大互联网公司中被广泛使用,如阿里巴巴的 hsf、dubbo (开源)、Facebook 的 thrift(开源)、Google grpc(开源)、 Twitter 的 finagle(开源)等。要让网络通信细节对使用者透明,我们需要对通信细节进行封装,我们先看下一个 RPC 调用的流程涉及到哪些通信细节:服务消费方(client)调用以本地调用方式调用服务;client stub 接收到调用后负责将方法、参数等组装成能够进行网络

4、传输的消息体;client stub 找到服务地址,并将消息发送到服务端;server stub 收到消息后进行解码;server stub 根据解码结果调用本地的服务;本地服务执行并将结果返回给 server stub;server stub 将返回结果打包成消息并发送至消费方;client stub 接收到消息,并进行解码;服务消费方得到最终结果。RPC 的目标就是要 28 这些步骤都封装起来,让用户对这些细节透明。1.1 怎么做到透明化远程服务调用?怎么封装通信细节才能让用户像以本地调用方式调用远程服务呢?对 Java 来说就是使用代理!java 代理有两种方式:jdk 动态代理字节码生

5、成尽管字节码生成方式实现的代理更为强大和高效,但代码维护不易,大部分公司实现 RPC 框架时还是选择动态代理方式。下面简单介绍下动态代理怎么实现我们的需求。我们需要实现 RPCProxyClient 代理类,代理类的 invoke 方法中封装了与远端服务通信的细节,消费方首先从 RPCProxyClient 获得服务提供方的接口,当执行 helloWorldService.sayHello(“test”)方法时就会调用 invoke 方法。java view plain copy print?public class RPCProxyClient implements java.lang.re

6、flect.InvocationHandler private Object obj; public RPCProxyClient(Object obj) this.obj=obj; /* * 得到被代理对象 ; */ public static Object getProxy(Object obj) return java.lang.reflect.Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new RPCProxyClient(obj); /* * 调用此方法

7、执行 */ public Object invoke(Object proxy, Method method, Object args) throws Throwable /结果参数; Object result = new Object(); / .执行通信相关逻辑 / . return result; java view plain copy print?public class Test public static void main(String args) HelloWorldService helloWorldService = (HelloWorldService)RPCProx

8、yClient.getProxy(HelloWorldService.class); helloWorldService.sayHello(test); 1.2 怎么对消息进行编码和解码?1.2.1 确定消息数据结构上节讲了 invoke 里需要封装通信细节(通信细节再后面几章详细探讨),而通信的第一步就是要确定客户端和服务端相互通信的消息结构。客户端的请求消息结构一般需要包括以下内容:1)接口名称在我们的例子里接口名是“HelloWorldService ”,如果不传,服务端就不知道调用哪个接口了;2)方法名一个接口内可能有很多方法,如果不传方法名服务端也就不知道调用哪个方法;3)参数类型&

9、参数值参数类型有很多,比如有 bool、int 、long、double、string、map、list ,甚至如 struct(class);以及相应的参数值;4)超时时间5)requestID,标识唯一请求 id,在下面一节会详细描述 requestID 的用处。同理服务端返回的消息结构一般包括以下内容。1)返回值2)状态 code3)requestID1.2.2 序列化一旦确定了消息的数据结构后,下一步就是要考虑序列化与反序列化了。什么是序列化?序列化就是将数据结构或对象转换成二进制串的过程,也就是编码的过程。什么是反序列化?将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。

10、为什么需要序列化?转换为二进制串后才好进行网络传输嘛!为什么需要反序列化?将二进制转换为对象才好进行后续处理!现如今序列化的方案越来越多,每种序列化方案都有优点和缺点,它们在设计之初有自己独特的应用场景,那到底选择哪种呢?从 RPC 的角度上看,主要看三点:通用性,比如是否能支持 Map 等复杂的数据结构;性能,包括时间复杂度和空间复杂度,由于 RPC 框架将会被公司几乎所有服务使用,如果序列化上能节约一点时间,对整个公司的收益都将非常可观,同理如果序列化上能节约一点内存,网络带宽也能省下不少;可扩展性,对互联网公司而言,业务变化飞快,如果序列化协议具有良好的可扩展性,支持自动增加新的业务字段

11、,而不影响老的服务,这将大大提供系统的灵活度。目前互联网公司广泛使用 Protobuf、Thrift、Avro 等成熟的序列化解决方案来搭建 RPC 框架,这些都是久经考验的解决方案。1.3 通信消息数据结构被序列化为二进制串后,下一步就要进行网络通信了。目前有两种常用 IO 通信模型:1)BIO;2)NIO。一般 RPC 框架需要支持这两种 IO 模型。如何实现 RPC 的 IO 通信框架呢?使用 Java nio 方式自研,这种方式较为复杂,而且很有可能出现隐藏 bug,但也见过一些互联网公司使用这种方式;基于 mina,mina 在早几年比较火热,不过这些年版本更新缓慢;基于 netty

12、,现在很多 RPC 框架都直接基于 netty 这一 IO 通信框架,省力又省心,比如阿里巴巴的 HSF、dubbo,Twitter 的 finagle 等。1.4 消息里为什么要有 requestID?如果使用 netty 的话,一般会用 channel.writeAndFlush()方法来发送消息二进制串,这个方法调用后对于整个远程调用 (从发出请求到接收到结果 )来说是一个异步的,即对于当前线程来说,将请求发送出来后,线程就可以往后执行了,至于服务端的结果,是服务端处理完成后,再以消息的形式发送给客户端的。于是这里出现以下两个问题:怎么让当前线程“暂停”,等结果回来后,再向后执行?如果有

13、多个线程同时进行远程方法调用,这时建立在 client server 之间的 socket 连接上会有很多双方发送的消息传递,前后顺序也可能是随机的,server 处理完结果后,将结果消息发送给client, client 收到很多消息,怎么知道哪个消息结果是原先哪个线程调用的?如下图所示,线程 A 和线程 B 同时向 client socket 发送请求 requestA 和 requestB,socket 先后将 requestB 和 requestA 发送至 server,而 server 可能将 responseA 先返回,尽管 requestA 请求到达时间更晚。我们需要一种机制保证

14、 responseA 丢给 ThreadA,responseB 丢给 ThreadB。怎么解决呢?client 线程每次通过 socket 调用一次远程接口前,生成一个唯一的 ID,即 requestID(requestID 必需保证在一个 Socket 连接里面是唯一的),一般常常使用 AtomicLong 从 0 开始累计数字生成唯一 ID;将处理结果的回调对象 callback,存放到全局 ConcurrentHashMap 里面 put(requestID, callback);当线程调用 channel.writeAndFlush()发送消息后,紧接着执行 callback 的 ge

15、t()方法试图获取远程返回的结果。在 get()内部,则使用 synchronized 获取回调对象 callback 的锁,再先检测是否已经获取到结果,如果没有,然后调用 callback 的 wait()方法,释放 callback 上的锁,让当前线程处于等待状态。服务端接收到请求并处理后,将 response 结果(此结果中包含了前面的 requestID)发送给客户端,客户端 socket 连接上专门监听消息的线程收到消息,分析结果,取到 requestID,再从前面的 ConcurrentHashMap 里面 get(requestID),从而找到 callback 对象,再用 sy

16、nchronized 获取 callback 上的锁,将方法调用结果设置到 callback 对象里,再调用 callback.notifyAll()唤醒前面处于等待状态的线程。java view plain copy print?public Object get() synchronized (this) / 旋锁 while (!isDone) / 是否有结果了 wait(); /没结果是释放锁,让当前线程处于等待状态 java view plain copy print?private void setDone(Response res) this.res = res; isDone = true; synchronized (this) /获取锁,因为前面 wait()已经释

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 中学教育 > 职业教育

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号