第6章 RPC之道

摘要:
RPC基本的调用过程如下图。远程服务器端RPCRuntime实例收到请求后再通过serverstub进行反序列化,发起最终的servermethod调用。以上都是考量一个RPC框架好坏的标准。所有提到的这些因素都会影响一次RPC调用的时间。RPC框架一般以TCP协议为基础,因为它需要可靠的通信来保障每一次调用的顺利进行。

 6.1 认识RPC

分布式、微服务的架构思维中都不能缺少 RPC 的影子

RPC(Remote Procedure Call)远程过程调用。通过网络在跨进程的两台服务器之间传输信息,我们使用的时候不用关心网络底层的实现,通过RPC调用远程服务就像本地调用系统内部方法一样方便。

在 OSI 网络通信模型中,RPC跨越了传输层和应用层,使开发分布式应用程序变得非常方便。

RPC基本的调用过程如下图。客户端发起一个 RPC请求,本地调用 client stub 负责将调用的接口、方法和参数按照事先约定好的协议进行序列化,然后由 RPC 框架的 RPCRuntime 实例通过 socket 传输到远程服务器上。

远程服务器端 RPCRuntime 实例收到请求后再通过 server stub 进行反序列化,发起最终的 server method 调用。

第6章 RPC之道第1张

一个良好的 RPC框架要兼具可靠性和易用性,可靠性方面要保证 I/O、序列化等准确处理,还要考虑网络的不确定性,心跳、网络闪断等因素;易用性方面要考虑超时与重试机制的控制,同步和异步调用的使用等。以上都是考量一个 RPC框架好坏的标准。

目前有很多优秀的开源 RPC框架,比如国内的 Dubbo(阿里)、Motan(新浪微博)等。

  6.2 RPC是如何实现通信的

 在两台服务器之间进行通信,首先必备的条件是要有一个网络通信基础,即建立网络连接,其次在内存中的数据如果要经过网络传输,则必须先序列化为字节流,最后调用RPC的时候一定不会期望接口增多系统也跟着变复杂,希望有一个代理做这件事情。

下面从动态代理、反射、序列化、网络编程等方面去理解 RPC 的实现原理。

6.2.1 动态代理

代理指我们要做一件事不用亲自去做,找一个代理,只跟这个代理打交道,让这个代理去处理各种事情。动态指我们要代理做10件事情,将来也可能做20件事情,这个不是固定的,是可以动态增加的。程序设计上也需要这种机制,一个系统现在需要调用订单、商品、用户的接口,随着业务的不断发展,该系统还需要调用促销、物流等业务方的接口。我们不希望让所有的外部接口都嵌入这个系统,希望找一个代理去做调用接口的事情,代理逻辑的代码和业务是无关联的,不会因为接口的增多导致这个系统逐渐庞大。

以 JDK自带的动态代理机制为例来说明如何做到动态代理。JDK有一个重要的类 java.reflect.Proxy,用这个类就可以生成代理类,它的主要方法是 newProxyInstance 代码如下:

第6章 RPC之道第2张

有三个参数,第一个参数是类加载器对象(代理对象的类加载器,加载代理类到 JVM 方法区),第二个参数是接口(代理类需要实现的接口,可以指定多个接口),第三个参数是调用具体的处理器类实例(具体要处理的逻辑都在这个类实例里面)。

查看 JDK源码,newProxyInstance主要做的事情如下

第6章 RPC之道第3张

这样根据传入的不同接口,我们就可以获取不同的业务处理逻辑对象,而且是在运行的过程中实现的,达到动态代理的目的。

上面仅以 JDK 原生的支持机制为例来阐述动态代理的实现方式,原生的动态代理有个缺点就是只能针对接口进行代理。另外还有一些比较好的开源的实现,比如字节码方式的 CGLIB,CGLIB可以针对类进行代理。

6.2.2 反射

反射是指计算机程序在运行时(Runtime)可以访问、检测和修改它本身状态或行为的一种能力。

上面介绍动态代理过程中有一个类没有讲到,那就是 InvocationHandler 类,它是一个接口,所有代理类都必须实现这个接口,代理类需要完成的具体逻辑实现都在InvocationHandler 接口类中。

第6章 RPC之道第4张

传入远程服务名和方法名,通过反射自动定位到需要被调用的方法,再传入入参,从而进行 RPC调用

6.2.3 序列化

序列化的目的是将内存中数据体转换为字节流,因此在网络传输前需要先进行序列化。涉及网络通信,就要考虑序列化之后的内容大小,序列化和反序列化的耗时,以及对 CPU 的影响。所有提到的这些因素都会影响一次 RPC调用的时间。

常见的序列化框架有 Hessian、Protobuf、Thrift(更是一个 RPC框架)等。

6.2.4 网络编程

我们使用动态代理解决了代理逻辑代码和业务隔离的问题,通过反射实现了自动定位到具体的远程方法,序列化为网络传输做好了准备。还需要一个通道把内容通过网络发送出去。

RPC框架一般以 TCP 协议为基础,因为它需要可靠的通信来保障每一次调用的顺利进行。我们熟悉的网络代码类似下面的示例。

第6章 RPC之道第5张

现在大多数 RPC 框架底层的网络通信都使用 Netty 将基础的网络代码进行了封装,我们在使用 RPC 的时候并不会注意到这些内容。

  6.3 一次RPC调用的时间

  用户组调用了订单组提供的一个接口 methodA(),这是再正常不过的两个系统交互的现象,但出现一个问题:用户组这边的方法性能监控显示这个接口的 TP99性能在 500ms,订单组监控的该接口的 TP99 性能在 100ms,这个差距有点大。

  那么一次 RPC 调用时间都去哪里了?

    一次正常的调用统计的耗时如下图:

第6章 RPC之道第6张

一次正常的调用统计的耗时主要包括:①调用端 RPC 框架执行时间 + ②网络发送时间 + 3服务端 RPC 框架执行时间 + ④服务端业务代码时间

①调用端调用的时候 RPC 框架会先拦截业务请求,同时将对象序列化,在收到响应的时候会反序列化。这时框架耗时主要与 CPU、JVM 运行情况相关,序列化耗时主要和传输对象复杂程度相关。

②网络时间就是数据包在网络传输过程中的时间,包括请求+响应,耗时主要与数据包大小、网络情况相关。

3.服务端主要是队列等待时间,包括请求拦截 + 反序列、响应的序列化;如果 RPC 框架用了队列,则可能有一定的等待时间,成熟的 RPC 框架都会支持队列的方式,不然就是默认将线程当队列使用了。

④服务端业务代码处理业务逻辑时间,通常是监控系统收集的服务端耗时(上面提到订单组的系统监控主要就是指这段时间)

另外,如果是一些异常请求(例如服务端线程池已满,客户端超时重试等),其实根本没有执行服务端的业务代码,服务端未记录耗时,但调用端已经记录了这些耗时。针对这种现象还要看下有无异常情乱。

定位方法:

(1)首先想到的就是要分析网络情况,查看网络延迟是否严重,是否有 TCP 重传,TCP重传次数不能太大。

(2)分析服务端和调用端的运行情况,查看是否压力较大,比如 CPU使用率、CPU负载、内存占用大小等。

(3)查看传输对象是否很大、很复杂,这个对序列化有很大的影响。

(4)如果服务端有队列,则试着减少队列,或者改为固定线程池,线程特别多,可以试试减少线程大小。

(5)控制 CPU 使用率不要太高,尽量不超过 80%,该扩容的时候就要扩容。另外大部分业务都是 I/O密集型,并非计算密集型,这里有公式,核数*7,然后取百分比,比如 4C 的容器,那么 2.8% 的使用率属于正常水平,当达到 80% 的时候就要注意了,如果不是业务正常的量上来了,那么可能有线程阻塞。

(6)有可能的化,可以以单次耗时为准,查看单次耗时的差距,从而确定是否是某些参数的请求导致的耗时比较长。

(7)查看服务端是否有 full gc,因为会发生 STW。如果频繁 young gc 也不是好事,无论 young 还是 full 都会 STW,只是耗时长短问题。

下图所示为采样一天时间内的 fg 次数,这是一个比较正常的情况,如果平均耗时较大,那么肯定会影响当时的调用响应时间。  

第6章 RPC之道第7张

(8)排除了以上种种因素,还没定位到原因,就需要尝试如下方法:在服务端通过 tcpdump 抓包,用 wireshark 分析 RPC 请求在服务端的耗时,定位是服务端还是调用端的耗时长,然后进一步确定原因。

  6.4 异步RPC

   成熟的RPC 框架都会支持异步调用、异步监听、callback调用,下面为3中异步方式的用法及注意事项

    6.4.1 异步调用

有一个功能需要调用3个接口来满足业务需求,这3个接口的耗时如下:

* A接口(耗时400ms)

* B接口(耗时 200ms)

* C接口(耗时 700ms)

如果使用普通的同步调用,完成这个功能需要的总耗时为:400ms+200ms+700ms=1300ms,如果采用异步调用,那么总耗时将是耗时最长的那个接口的耗时,即 700ms。

示例代码如下:

第6章 RPC之道第8张

RPC的异步调用是指客户端发起请求之后,不必等待获取结果,而是返回 null 值,同时可以获取一个 Future 对象,然后从 Future 中去获取结果,如上面代码所示,这样客户端在调用的时候不需要启动多个线程就可以并行调用多个远程服务接口。

    6.4.2 异步监听

有时候我们发起一个调用请求后,并不想通过Future 的 get 获取结果(因为“get”的时候是阻塞的),而是希望调用请求之后可以去干其他的事情,通过一个监听去侦测,当有结果返回的时候直接去获取结果,然后进行逻辑处理。

比如下面的代码示例,有一个监听类 TestResponseListener,里面的 handleResult 方法负责处理结果数据

第6章 RPC之道第9张

发送请求后不需要再调用 getFuture 方法。当有结果返回的时候,会自动调用事先注入好的上面代码示例中的 TetsResponseListener 方法,发起异步回调的示例代码如下:

第6章 RPC之道第10张

在使用异步监听的时候,建议最好限制发送的频率,发送太快会导致内存溢出等问题。

    6.4.3 callback调用

    成熟的RPC框架基本都支持前面两中方式的调用,callback方式,RPC是以TCP全双工的协议进行通信的,基于长连接,服务端便具备了可以“调用”客户端 callback 函数的能力。

有的 RPC框架也会支持 callback 调用方式,使用方法示例如下:

第6章 RPC之道第11张

如果在服务端接口里面完成一个业务逻辑有 3 个过程(A-->B-->C),那么在这 3 个过程中可以分别调用 callback 方法,形成"一次调用,多次通知"的机制,这一点在异步监听中是没有办法实现的,异步回调更像是“一次性买卖”。

在高并发场景下建议使用第二种方式实现异步监听,因为 callback 方式客户端会对 Callback 实例的个数有限制。

免责声明:文章转载自《第6章 RPC之道》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇esp8266物联网开发一:MicroPython初战江湖多项目管理中PMO的作用下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

RPC(Remote Procedure Calls)远程过程调用

很长时间以来都没有怎么好好搞清楚RPC(即Remote Procedure Call,远程过程调用)和HTTP调用的区别,不都是写一个服务然后在客户端调用么?这里请允许我迷之一笑~Naive!本文简单地介绍一下两种形式的C/S架构,先说一下他们最本质的区别,就是RPC主要是基于TCP/IP协议的,而HTTP服务主要是基于HTTP协议的,我们都知道HTTP协...

使用nmcli配置主备模式链路聚合

主备模式的链路聚合将其中一个接口置于备份状态,并且仅当活动接口断开链接时才会使其处于活动状态。 现在让我们在CentOS 7中配置网卡绑定,运行ip link命令查看可以使用的网卡 [root@localhost ~]# ip link 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue...

聊一聊Jmeter用IF控制器处理接口依赖

背景 上一篇关于 JMeter 的多接口测试,虽说可以把几个步骤的串联起来,但是会比较简单和单调。 还有许多业务场景,会根据上一个接口的返回,再决定要不要调用第二个接口或者是掉用其他接口。 还是拿大家常见的查询订单举个例,首先可以是查询订单列表,然后再查订单详情。 试想一下如果订单列表是空,还有必要再查询订单详情吗? 其实再查询订单详情其实意义已经不大了,...

使用.Net访问Office编程接口

在这篇文章里面,我将向大家介绍如何在.Net中访问Office所公开的编程接口。其实,不管是使用哪种具体的技术来针对Office进行开发(比如VSTO,或者用C#编写一个Office Add-in,或者在一个WinForms程序中调用Office的功能,甚至在一个ASP.NET应用的服务器端启动一个Excel进程),只要是基于.Net平台,这篇文章所描述的...

谷歌F12捕获不到登录接口的原因

在接口自动化或者性能测试中,经常有这样的场景,需要先请求登录接口,然后获取cookie,再进行后续操作。 但是chromeF12经常看不到login接口,这是因为在发出 post 请求之后,页面会进行跳转,请求被清空导致。 解决方法: 将Preserve log选项勾选上,这样页面跳转或重载时,请求就不会被清空了。...

eNSP——利用三层交换机实现VLAN间路由

原理: VLAN将一个物理的LAN在逻辑上划分成多个广播域。VLAN内的主机间可以直接通信,而VLAN间不能直接互通。 在现实网络中,经常会遇到需要跨VLAN相互访问的情况,工程师通常会选择一些方法来实现不同VLAN间主机的相互访问,例如单臂路由。但是单臂路由技术中由于存在一些局限性,比如带宽、转发效率等,使得这项技术应用较少。 三层交换机在原有二层交换机...