rpc核心原理

dinl_vin dinl_vin     2023-04-01     640

关键词:

了解RPC

RPC全称Remote Procedure Call,即远程过程调用.其中远程需要跨机器,跨机器需要可靠的网络编程技术实现,无论是Java原生的网络编程模型还是Netty都会让代码中出现大量与业务无关的网络编程代码,RPC技术则是为了解决这个问题的.它帮助我们屏蔽网络编程的细节,实现调用远程方法和调用本地方法一样的体验,让我们更专注于业务逻辑的编写.

RPC通信流程

前文提及,RPC的作用是完成远程调用,我们将发起调用请求的那一方叫做调用方,被调用的叫做服务提供方,为了实现远程调用,一个完整的RPC会涉及哪些步骤呢?

RPC需要通过网络传输数据,并且通常用于系统之间的交互,所以需要保证可靠性,在网络协议上一般会采用TCP,常用的HTTP就是TCP协议之上的.

对于调用方来说,使用远程调用的是某段业务代码,对于一段面向对象的程序来说,调用请求出入参都会是对象,而网络传输的是二进制,所以需要有对象转字节再转对象的这么一套可逆转化机制,不难看出RPC需要一套"序列化"机制.

为了准确接收并解析调用方的请求,计算机通信通常会设计一个双方都能明白的"协议",比如消息长度多少,消息类型是什么,传入的参数是什么,需要调用什么方法等等.这些就是RPC的协议.

上述几个流程就是RPC的全部?

上述过程只是RPC基本通信原理或者说细节,看起来跟HTTP请求没什么两样,RPC还要求屏蔽网络编程的细节.否则就算不上一套易用的API.

我们知道Spring 的 AOP技术,通过动态代理的方式为用户屏蔽了很多细节,我们可以用AOP技术作权限校验,日志记录等功能,而使用的时候,往往只需要一个注解.那么这个动态代理技术,应用到RPC里,就可以解决封装网络编程的细节的作用.

一图言之

RPC的应用场景

RPC 框架能够帮助我们解决系统拆分后的通信问题,并且能让我们像调用本地一样去调用远程方法。利用 RPC 我们不仅可以很方便地将应用架构从“单体”演进成“微服务化”,而且还能解决实际开发过程中的效率低下、系统耦合等问题,这样可以使得我们的系统架构整体清晰、健壮,应用可运维度增强

RPC在分布式系统和微服务系统中被大量应用.

RPC协议知多少

在工作开发中,我们可能遇到最多的就是HTTP协议,上图是HTTP协议的请求数据包.仔细看这个数据包,我们能认出很多"老朋友",‘GET’,‘HTTP/1.1’,‘Accept: */*’,'keep-alive’等等,即便是不包含请求体消息的HTTP数据包,也会包含这么多的信息.有了这些信息,接收方就可以按照约定的去作出相应的响应.

可以说协议规定了交互的语义和边界,按照边界分割,按照语义解析,才能保证一次正确的交互.

“为什么不直接用HTTP协议,而是要设计RPC协议呢”?

不直接使用HTTP协议,主要有以下原因:

  • HTTP协议的数据包大小比请求本身要大很多,其中包含了很多无用的内容,浪费宝贵的网络带宽;
  • HTTP协议是无状态协议,客户端没办法关联请求和响应,而且每次请求都要建立和关闭连接,效率较低.

那怎么设计一个合适的RPC协议呢?

协议设计主要考虑分为以下两种: 定长协议和不定长协议, 所谓定长协议,就是消息头长度固定:

一个完整的数据包一般包括消息头和消息体.消息头要尽量完整的包括一次消息的元数据的同时,尽可能少的使用存储空间,定长协议在保证协议完整性的同时,尽可能少的占用存储.

如下图是一张定长协议的例子:

  • 8位的魔数一般是为了安全,像Java的 0xCAFEBABE一样,如果不是该魔数开头的消息直接过滤;
  • 32位的整体长度,规定了消息的长度上限是0~2^32;
  • 16位的消息ID,用于关联一次消息的请求和响应;
  • 8位的协议版本用于迭代升级;
  • 8位的消息类型,可以用于解码时的反射区分;
  • 8位的序列化方式,允许使用不同的序列化算法;
  • 还可以设计压缩算法等其他元信息.

如同Kafka v2版本消息的升级一般,单纯的定长协议有时候不能满足用户可变消息头的需求.所以还有一种不定长协议的版本,如下图

总结: 设计RPC协议的重点在于完整性,兼容性和降低资源消耗

序列化算法怎么选

网络传输是二进制字节,编程调用的是对象,所以一般需要有将对象转化成字节然后再把字节转化为对象的可逆机制,一个完整的RPC,会涉及两次序列化反序列化转换,在实际生产开发中,选择一种快速而稳定的序列化算法,也是一个好的RPC框架重要部分.

下面来盘点一下常用的序列化反序列化的机制:

JDK原生

jdk原生的序列化机制对于使用者而言比较简单,通过ObjectOutputStream序列化,在读取对象数据的时候加入特殊的分隔符和对象元数据信息,如果有继承或者对象引用就会递归写对象,再通过读取ObjectInputStream,按照分隔符切割,读取元数据信息生成对象,再写入对象完成反序列化.

优点: 使用简单,对象转换不存在偏差,性能比较好
缺点: 消息较大,存在安全漏洞,只能用于两端Java系语言的框架.

JSON

json可能是我们最熟悉的序列化机制了,可读性强,应用广泛都是它的优点,被大量用于前后端传输,还有基于HTTP的RPC框架中(一般基于HTTP的RPC框架,传输的数据量相对较小.)

优点: 使用简单, 可读性强, 适用于所有编程语言
缺点: 额外空间开销比较高, 而且Java这种强类型语言一般需要反射完成反序列化,性能不会太高.

Hessian

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

优点: Hessian 协议要比 JDK、JSON 更加紧凑,性能上要比 JDK、JSON 序列化高效很多,而且生成的字节数也更小,有非常好的兼容性和稳定性.

缺点: 不支持部分常见的对象类型:Liked系列需要扩展CollectionDeserializer;Locale需要扩展ContextSerializerFactory; Byte/Short 反序列化的时候变成 Integer。

Protobuf/Protostuff

Protobuf 是 Google 公司内部的混合语言数据标准,是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列化,支持Java、Python、C++、Go等语言。Protobuf使用的时候需要定义 IDL(Interface description language),然后使用不同语言的IDL编译器,生成序列化工具类.

Protobuf非常高效,但是对于具有反射和动态能力的语言来说,这样用起来很费劲,这一点就不如Hessian,比如用Java的话,这个预编译过程不是必须的,可以考虑使用Protostuff。Protostuff不需要依赖 IDL 文件,可以直接对 Java 领域对象进行反 / 序列化操作,在效率上跟 Protobuf 差不多,生成的二进制格式和 Protobuf 是完全相同的,可以说是一个 Java版本的Protobuf序列化框架.

缺点: 不支持null,不支持单纯的Map,List集合对象,需要包在对象里面

如何选用

考察原则: 安全性 > 通用性 > 兼容性 > 性能 > 效率 > 空间开销

基于上述原则,总结一下序列化算法选型: 首选的还是Hessian与Protobuf,因为他们在性能、时间开销、空间开销、通用性、兼容性和安全性上,都满足了我们的要求。其中Hessian在使用上更加方便,在对象的兼容性上更好;Protobuf则更加高效,通用性上更有优势。

网络IO

说完了协议和序列化,再看一下RPC对于网络IO模型的选择:

常见的IO模型

阻塞IO(BIO)

同步阻塞IO是最简单,最常见的IO模型,Linux的默认Socket都是同步阻塞IO,阻塞是指发起请求之后,发起线程进入阻塞态,转到内核态等待数据,等到数据之后,再将内核态数据拷贝到用户内存,唤醒阻塞线程完成后续业务逻辑.

同步阻塞IO每个请求都需要占用一个线程到操作结束.使用简单但不适合高并发场景.

同步非阻塞IO(NIO)

与BIO不同的是,NIO的线程在发起请求后,会调用recvfrom尝试读取数据,并立即返回是否已经准备好数据,不会造成线程阻塞,通过多次调用recvfrom,最终会读取到想要的数据.

与BIO相比,NIO不会占用线程,但是每个线程都要多次调用recvfrom,会对服务器造成巨大压力.

IO多路复用

为了解决NIO带来的多个线程调用recvfrom的问题,recvfrom的时候采用了"复用",由一个线程来监视是否有数据准备好.具体就是所有发起调用的线程都注册到一个选择器(selector)上在Netty编程模型中对应bossGroup,该select轮询所有的socket,任何一个socket有数据,就分发该数据到指定线程,这就是Douge Lee的Reactor经典模型.总结起来就是专门一个selector解决所有recvfrom,有数据了分发到对应的socket.

IO多路复用模型是高并发场景下最常见的编程模型,Netty框架可以减少编写多路复用代码的难度.

AIO 异步非阻塞模型

异步模型是指,应用只用发送一次读取请求,其他的都由内核自动完成.这是一种操作简单,高效的IO模型,但是对操作系统要求比较高(Windows支持,但是常用于做服务器的Linux不支持),Netty5也尝试过但是没能实现所以目前最好用的高并发IO模型还是IO多路复用.

总结: RPC的出现多用于微服务分布式,一般对于高并发有一定要求,所以RPC最青睐的IO模型应该是IO多路复用.

动态代理

使用过Spring AOP的朋友们都知道,AOP可以通过代理类对bean进行功能增强,将统一拦截,授权认证,性能统计等非业务功能与业务代码解耦.

那动态代理在RPC中是怎么用上的呢?

在项目中,当我们要用到RPC的时候,一般做法是服务方提供一个接口模块,调用方通过Maven或Gradle将接口类引入到项目中,如果要调用远程方法,直接在业务中注入该接口即可.

对于原理图可以看出来,其实调用方在导入接口类之后,还会自动生成一个接口代理类,当我们注入接口调用的时候,运行时其实绑定的是接口的代理类,在代理类中完成请求的序列化反序列化和网络IO.

有哪些方法可以实现动态代理呢?

JDK默认的InvocationHandler

单纯从代理功能上来看,JDK 默认的代理功能是有一定的局限性的,它要求被代理的类只能是接口。原因是因为生成的代理类会继承Proxy类,如果被代理类不是接口,是没法多继承的.
虽说这个局限很重要,但是对于约定好了基于接口编程双方来说,这不是特别大的问题,其实JDK代理的最大问题是性能问题,因为代理类是通过反射来完成方法调用的,这比直接编码性能要差.

Javassist

相对 JDK 自带的代理功能,Javassist 的定位是能够操纵底层字节码,所以使用起来并不简单,要生成动态代理类恐怕是有点复杂了。但好的方面是,通过 Javassist 生成字节码,不需要通过反射完成方法调用,所以性能肯定是更胜一筹的。在使用中,我们要注意一个问题,通过 Javassist 生成一个代理类后,此 CtClass 对象会被冻结起来,不允许再修改;否则,再次生成时会报错。

ByteBuddy

Byte Buddy 则属于后起之秀,在很多优秀的项目中,像 Spring、Jackson 都用到了 Byte Buddy 来完成底层代理。相比 Javassist,Byte Buddy 提供了更容易操作的 API,编写的代码可读性更高。更重要的是,生成的代理类执行速度比 Javassist 更快。

rpc核心原理

了解RPCRPC全称RemoteProcedureCall,即远程过程调用.其中远程需要跨机器,跨机器需要可靠的网络编程技术实现,无论是Java原生的网络编程模型还是Netty都会让代码中出现大量与业务无关的网络编程代码,RPC技术则是为了解决这个问题的.... 查看详情

《rpc实战与核心原理》学习笔记day7

这篇文章主要关注服务发现,会讨论基于DNS、VIP、ZooKeeper以及消息总线的服务发现机制,研究出在服务发现需要AP还是CP。08|服务发现:到底是要CP还是AP?我们为什么需要“服务发现”?从高可用的角度出发,在生产环境中,服... 查看详情

动态代理-rpc实现核心原理(代码片段)

...码前提下,还能实现非业务逻辑跟业务逻辑的解耦。核心就是动态代理,通过对字节码进行增强,在方法调用时进行拦截,以便于在方法调用前后,增加处理逻辑。1远程调用的魔法使用RPC,一般先找服务... 查看详情

2020阿里京东等大厂核心岗位必须掌握的“rpc”就该这么学!(千万级流量架构必备的rpc框架)

...问了他几个原理方面的问题,比如,“大概说下RPC框架的核心原理”“、描述下序列化部分的逻辑”。但聊了半天,我发现他其实并不熟,他的回答基本都是在告诉我怎么用,以及怎么更好地用好这些框架。紧接着,我追问到,... 查看详情

简述rpc原理实现(代码片段)

...架,来支撑业务技术架构升级。 服务框架服务架构的核心是服务调用 查看详情

mq消息队列的12点核心原理总结

1.消息生产者、消息者、队列消息生产者Producer:发送消息到消息队列。消息消费者Consumer:从消息队列接收消息。Broker:概念来自与ApacheActiveMQ,指MQ的服务端,帮你把消息从发送端传送到接收端。消息队列Queue:一个先进先出的... 查看详情

基于框架的rpc通信技术原理解析

...构 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。 此时,用于提高业务复用及整合的分布式服务框架(RPC)... 查看详情

springcloudalibaba微服务调用组件feign原理+实战(代码片段)

...是什么?HTTP调用vsFeign(RPC)调用单独使用Feign实战Feign核心源码解读Feign整体设计架构SpringCloudOpenFeign实战Feign在实际项目的通常做法最后前言通过上文,我们掌握了SpringCloudAlibaba微服务框架的初始环境搭建,并能通过Naco... 查看详情

rpc实现原理

RPC实现原理 查看详情

rpc实现原理

RPC实现原理 查看详情

hadoop详解——hdfs的命令,执行过程,java接口,原理详解。rpc机制(代码片段)

HDFS是Hadoop的一大核心,关于HDFS需要掌握的有:分布式系统与HDFS、HDFS的体系架构和基本概念、HDFS的shell操作、Java接口以及常用的API、Hadoop的RPC机制、远程debugDistributed FileSystem数据量越来越多,在一个操作系统管理的... 查看详情

RPC:分段错误(核心转储)

】RPC:分段错误(核心转储)【英文标题】:RPC:SegmentationFault(coredumped)【发布时间】:2018-05-2615:38:15【问题描述】:由于我偶然发现了这个问题并且没有任何答案here,我决定针对我的情况寻求帮助。我想制作一个简单的RPC服务... 查看详情

powershell核心错误:未加载指定的模块'RPC-Client'

】powershell核心错误:未加载指定的模块\\\'RPC-Client\\\'【英文标题】:powershellcoreerror:Thespecifiedmodule\'RPC-Client\'wasnotloadedpowershell核心错误:未加载指定的模块\'RPC-Client\'【发布时间】:2021-02-2704:40:41【问题描述】:如何为powershell找... 查看详情

dubbo系列dubbo的核心技术--rpc调用

dubbo的核心技术--RPC调用:分为俩部分RPC协议Protocol和方法调用Invoke;一、RPC协议Protocol(RemoteProcedureCall)远程过程调用协议1、我们平时使用最多的http协议其实也属于RPC协议,下图分别是普通的传输层TCP和应用层http与dubbo优化后... 查看详情

rpc的实现与远程调用的实现机制

本文主要是RPC的实现代码总结:1、RPC的Server端实现2、RPC的Client端实现3、RPC原理分析注意:核心jar包使用的是Hadoop中的RPCjar包;1、RPC的Server实现1)创建RPCServer工程:LoginServiceInterface.javapackagecn.rpc.demo;public 查看详情

rpc和注册中心的简介

...的序列化、反序列化以及序列化后数据的传输。RPC协议的核心组成部分:网络传输协议:http,tcp(推荐使用tcp);dubbo序列化和反序列化:可以使用Java原生的序列化和反序列化,也可以使用高性能序列化/反序列化工具,如Hessian... 查看详情

分段错误(“核心”创建)RPC - 客户端不执行

】分段错误(“核心”创建)RPC-客户端不执行【英文标题】:Segmentationfault(\'core\'created)RPC-clientdoesn\'texecute【发布时间】:2016-02-2203:49:45【问题描述】:希望你能在这方面帮助我。我几乎整天都在试图找出我的错误在哪里。这是... 查看详情

rpc-client异步收发核心细节?

第一章聊了【“为什么要进行服务化,服务化究竟解决什么问题”】第二章聊了【“微服务的服务粒度选型”】第三章聊了【“为什么说要搞定微服务架构,先搞定RPC框架?”】上一章聊了【“微服务架构之RPC-client序列化细节... 查看详情