找回密码
 立即注册
查看: 323|回复: 0

rpc原理基础学习-极客时间

[复制链接]
发表于 2022-2-18 09:45 | 显示全部楼层 |阅读模式
1、rpc的概念与作用


概念:

帮助我们屏蔽网络编程细节,实现调用远程方法就跟调用本地(同一个项目中的方法)一样的体验,我们不需要因为这个方法是远程调用就需要编写很多与业务无关的代码。

作用

1)、屏蔽远程调用跟本地调用的区别,让我们感觉就是调用项目内的方法;

2)、隐藏底层网络通信的复杂性,让我们更专注于业务逻辑。

2、rpc通信流程



1)我们已经知道 RPC 是一个远程调用,那肯定就需要通过网络来传输数据,并且 RPC 常用于业务系统之间的数据交互,需要保证其可靠性,所以 RPC 一般默认采用 TCP 来传输。我们常用的 HTTP 协议也是建立在 TCP 之上的。

2)网络传输的数据必须是二进制数据,通过序列化实现

3)服务提供方从 TCP 通道里面收到二进制数据,那如何知道一个请求的数据到哪里结束?

   需要通过协议进行区分,多数的协议会分成两部分,分别是数据头和消息体。数据头一般用于身份识别,包括协议标识、数据大小、请求类型、序列化类型等信息;消息体主要是请求的业务参数信息和扩展属性等。

    根据协议格式,服务提供方就可以正确地从二进制数据中分割出不同的请求来,同时根据请求类型和序列化类型,把二进制的消息体逆向还原成请求对象。这个过程叫作“反序列化”。

4)动态代理:实现调用过程中细节屏蔽,让使用方只需要关注业务接口,像调用本地一样来调用远程

    RPC 框架根据调用的服务接口提前生成动态代理实现类,并通过依赖注入等技术注入到声明了该接口的相关业务逻辑里面。该代理实现类会拦截所有的方法调用,在提供的方法处理逻辑里面完成一整套的远程调用,并把远程调用结果返回给调用方,这样调用方在调用远程方法的时候就获得了像调用本地接口一样的体验。

3、rpc协议



    RPC 并不会把请求参数的所有二进制数据整体一下子发送到对端机器上,中间可能会拆分成好几个数据包,也可能会合并其他请求的数据包(合并的前提是同一个 TCP 连接上的数据),至于怎么拆分合并,这其中的细节会涉及到系统参数配置和 TCP 窗口大小。对于服务提供方应用来说,他会从 TCP 通道里面收到很多的二进制数据,那这时候怎么识别出哪些二进制是第一个请求的呢?

    在 RPC 传输数据的时候,为了能准确地“断句”,我们也必须在应用发送请求的数据包里面加入“句号”,这样才能帮我们的接收方应用从数据流里面分割出正确的数据。这个数据包里面的句号就是消息的边界 ;这个边界语义的表达,就是我们所说的协议。

   1、协议包括哪些:固定部分、协议头内容、协议体内容

   固定头:固定长度0-103

   协议头:用于存储扩展参数,例如接口超时时间等

   协议体:内容都是经过序列化出来的,也就是说你要获取到你参数的值

4、序列化



    网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象。对象是不能直接在网络中传输的,所以我们需要提前把它转成可传输的二进制,并且要求转换算法是可逆的,这个过程我们一般叫做“序列化”,

序列化就是将对象转换成二进制数据的过程,而反序列就是反过来将二进制转换为对象的过程。

比如发快递,我们要发一个需要自行组装的物件。发件人发之前,会把物件拆开装箱,这就好比序列化;这时候快递员来了,不能磕碰呀,那就要打包,这就好比将序列化后的数据进行编码,封装成一个固定格式的协议;过了两天,收件人收到包裹了,就会拆箱将物件拼接好,这就好比是协议解码和反序列化。

常见序列化的方法

1)、JDK 原生序列化

优点:使用起来非常简单

2)、JSON序列化

问题:JSON 进行序列化的额外空间开销比较大,对于大数据量服务这意味着需要巨大的内存和磁盘开销;JSON 没有类型,但像 Java 这种强类型语言,需要通过反射统一解决,所以性能不会太好。

3)、Hessian序列化

Hessian 是动态类型、二进制、紧凑的,并且可跨语言移植的一种序列化框架

优点:Hessian 协议要比 JDK、JSON 更加紧凑,性能上要比 JDK、JSON 序列化高效很多,而且生成的字节数也更小。相对于 JDK、JSON,由于 Hessian 更加高效,生成的字节数更小,有非常好的兼容性和稳定性,所以 Hessian 更加适合作为 RPC 框架远程通信的序列化协议。

缺点:

Linked 系列,LinkedHashMap、LinkedHashSet 等,但是可以通过扩展 CollectionDeserialzer 类修复;

Locale 类,可以通过扩展 ContextSerializerFactory 类修复;

Byte/Short 反序列化的时候变成 Integer。

4)、Protobuf序列化

Protobuf 使用的时候需要定义 IDL(Interface description language),然后使用不同语言的 IDL 编译器,生成序列化工具类,

优点:

序列化后体积相比 JSON、Hessian 小很多;

IDL 能清晰地描述语义,所以足以帮助并保证应用程序之间的类型不会丢失,无需类似 XML 解析器;

序列化反序列化速度很快,不需要通过反射获取类型;

消息格式升级和兼容性不错,可以做到向后兼容。

缺点:

Protobuf 非常高效,但是对于具有反射和动态能力的语言来说,这样用起来很费劲,这一点就不如 Hessian,比如用 Java 的话,这个预编译过程不是必须的,可以考虑使用 Protostuff。

在 RPC 框架中如何去选择序列化协议,我们有这样几个很重要的参考因素,优先级从高到低依次是安全性、通用性和兼容性,之后我们会再考虑序列化框架的性能、效率和空间开销。

在使用 RPC 框架的过程中,我们构造入参、返回值对象,主要记住以下几点:

1)、对象要尽量简单,没有太多的依赖关系,属性不要太多,尽量高内聚;

2)、入参对象与返回值对象体积不要太大,更不要传太大的集合;

3)、尽量使用简单的、常用的、开发语言原生的对象,尤其是集合类;

4)、对象不要有复杂的继承关系,最好不要有父子类的情况。

5、网络通信



一次 RPC 调用,本质就是服务消费者与服务提供者间的一次网络信息交换的过程

IO模型的选用

常见的网络 IO 模型分为四种:同步阻塞 IO(BIO)、同步非阻塞 IO(NIO)、IO 多路复用和异步非阻塞 IO(AIO)。在这四种 IO 模型中,只有 AIO 为异步 IO,其他都是同步 IO。

在这四种常用的 IO 模型中,应用最多的、系统内核与编程语言支持最为完善的,便是阻塞 IO 和 IO 多路复用。

RPC 调用在大多数的情况下,是一个高并发调用的场景,考虑到系统内核的支持、编程语言的支持以及 IO 模型本身的特点,在 RPC 框架的实现中,在网络通信的处理上,我们会选择 IO 多路复用的方式

零拷贝

RPC 框架的开发与使用过程中,我们要深入了解网络通信相关的原理知识,尽量做到零拷贝,如使用 Netty 框架

什么是零拷贝:

就是取消用户空间与内核空间之间的数据拷贝操作,应用进程每一次的读写操作,都可以通过一种方式,让应用进程向用户空间写入或者读取数据,就如同直接向内核空间写入或者读取数据一样,再通过 DMA 将内核中的数据拷贝到网卡,或将网卡中的数据 copy 到内核。


零拷贝有两种解决方式,分别是 mmap+write 方式和 sendfile 方式

mmap是利用了虚拟内存(划分为用户内存和内核内存)映射到用户空间中,sendfile可以直接从磁盘读取到内核中然后直接从内核发送到网卡,不需要进行用户态和内核态的转换拷贝

下图就是虚拟内存的图

netty零拷贝原理介绍(具体在详细研究吧)
6、动态代理



RPC 会自动给接口生成一个代理类,当我们在项目中注入接口的时候,运行过程中实际绑定的是这个接口生成的代理类。这样在接口方法被调用的时候,它实际上是被生成代理类拦截到了,这样我们就可以在生成的代理类里面,加入远程调用逻辑。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-6-8 08:14 , Processed in 0.123574 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表