一、RPC调用原理图

下面这张图是我们微服务一次Http调用请求图:

首先在请求的过程中我们知道是有三次握手,四次挥手的流程,具体流程如下:

1.浏览器请求服务器(订单服务),请求建立连接,首先客户端向服务器端发送一段 TCP 报文
2.服务器(订单服务)相应浏览器,可以建立连接,并且询问浏览器是否马上建立连接。
3.浏览器相应服务器(订单服务),可以建立连接。-----开始传输数据------------###下面要断开连接就进入四次挥手环节
1.浏览器向服务器(订单服务)发起请求,需要断开连接(分手)
2.服务器(订单服务)相应浏览器,说我收到断开请求了,但是需要在想一下(一般是数据传输)
3.服务器(订单服务)接受完数据之后,向浏览器发送请求,说可以断开连接了(想好了,可以分手了)
4.浏览器接受到服务器(订单服务),回复服务器,我断开连接了。

1.1RPC概述

RPC(Remote Procedure Call Protocol)远程过程调用协议。RPC的主要功能是让构建分布式计算更加容易,再提供强大的远程调用能力的同时而不损失本地调用的语义简洁性。为实现该目标,RPC框架需要提供一套透明的调用机制,使使用者不用显示的区分本地调用还是分布式调用。

RPC的优点:

  • 分布式设计
  • 部署灵活
  • 解耦服务
  • 扩展性强

RPC框架的优势:

  • RPC框架一般使用长连接,不必要每次通信都三次握手、四次挥手。
  • RPC框架一般有注册中心,有丰富的监控管理、发布、下线接口、动态扩展等。对调用方来说是无感知的、统一化的操作、协议私密,安全性高。
  • RPC更简单内容更小、效率更高、服务化架构、服务化治理、RPC框架是一个强力的支撑。
  • RPC框架基于TCP实现,也可以基于Http2实现。

1.2RPC框架

主流的RPC框架:

  • Dubbo:国内最早开源的RPC框架,由阿里巴巴公司开发并与2011年对外发布,不过仅支持Java语言。
  • Motan:新浪微博内部使用的RPC框架,与2016年对外开源,仅支持Java语言。
  • Tars:腾讯内部使用的RPC框架,与2017年对外开源,仅支持C++语言。
  • Spring Cloud:国外 Pivotal 公司 2014 年对外开源的 RPC 框架,提供了丰富的生态组件。
  • gRPC:Goolge与2015年对外开源的跨语言rpc框架,2007年贡献给了Apache基金,成为了Apache开源项目之一,支持多种语言。

1.3应用场景

应用举例:

  • 分布式操作系统的进程间通讯:进程间通讯是操作系统必须提供的基本设施之一,分布式操作系统必须提供分布于异构的结点机上进 程间的通讯机制,RPC是实现消息传送模式的分布式进程间通讯方式之一。
  • 构造分布式设计的软件环境:由于分布式软件设计,服务与环境的分布性, 它的各个组成成份之间存在大量的交互和通讯, RPC是 其基本的实现方法之一。Dubbo分布式服务框架基于RPC实现,Hadoop也采用了RPC方式实现客 户端与服务端的交互。
  • 远程数据库服务:在分布式数据库系统中,数据库一般驻存在服务器上,客户机通过远程数据库服务功能访问数据库 服务器,现有的远程数据库服务是使用RPC模式的。例如,Sybase和Oracle都提供了存储过程机 制,系统与用户定义的存储过程存储在数据库服务器上,用户在客户端使用RPC模式调用存储过 程。
  • 分布式应用程序设计:RPC机制与RPC工具为分布式应用程序设计提供了手段和方便, 用户可以无需知道网络结构和协议细 节而直接使用RPC工具设计分布式应用程序。
  • 分布式程序调试:RPC可用于分布式程序的调试。使用反向RPC使服务器成为客户并向它的客户进程发出RPC,可以 调试分布式程序。例如,在服务器上运行一个远端调试程序,它不断接收客户端的RPC,当遇到一 个调试程序断点时,它向客户机发回一个RPC,通知断点已经到达,这也是RPC用于进程通讯的例 子。

二、深入RPC原理

2.1设计与调用原理

具体调用过程:

  1. 服务消费者(client客户端)通过本地调用的方式调用服务。
  2. 客户端存根(client stub)接收到请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体。
  3. 客户端存根(client stub)找到远程的服务地址,并且将消息通过网络发送给服务端。
  4. 服务端存根(server stub)收到消息后进行解码(反序列化操作)。
  5. 服务端存根(server stub)根据解码结果调用本地的服务进行相关处理。
  6. 本地服务执行具体的业务逻辑并将处理结果返回给服务端存根(server stub)。
  7. 服务端存根(server stub)将返回的结果重新打包成消息(序列化),并通过网络发送到消费方。
  8. 客户端存根(client stub)接收到消息,并进行解码(反序列化)。
  9. 服务消费方得到最终的结果。

所涉及到的技术:

  1. 动态代理:生成Client stub(客户端存根)和Server Stub(服务端存根)的时候需要用到java动态代码技术。
  2. 序列化:在网络中,所有的数据都将会转化为字节进行传送,需要对这些参数进行序列化和反序列化操作。目前主流的开源序列化框架有Kryo、fastJson、Hessian、Protobuf等。
  3. NIO通信:Java提供了NIO的解决方案,Java7也提供了更优秀的NIO.2支持。可以采用Netty或者mina框架来解决数据传输的问题。开源的RPC框架Dubbo就是采用NIO通信,集成netty、mina、grizzly。
  4. 服务注册中心:通过注册中心,让客户端连接调用服务端所发布的服务。主流的注册中心组件:redis、Nacos、Zookeeper、Consul、Etcd。Dubbo采用的是ZooKeeper提供服务注册与发现功能。
  5. 负载均衡:在高并发的场景下,需要多个节点或者集群来提升整体的吞吐能力。
  6. 健康检查:健康检查包括,客户端心跳和服务主动探测两种方式。

2.2 RPC深入解析

2.2.1序列化技术

  •  序列化的作用:

在网络传输中,数据必须采用二进制形式,所以在RPC调用过程中,需要采用序列化技术,对入参对象和返回值对象进行序列化和反序列化。

  • 序列化原理:

自定义二进制协议来实现序列化:

  • 序列化处理要素:
  1. 解析效率:序列化协议应该首要考虑的因素,像json/xml解析起来比较耗时,需要解析dom树,二进制自定义协议解析起来效率要快很多。
  2. 压缩率:同样一个对象,xml/json传输起来有大量的标签冗余信息,信息有消息低,二进制自定义协议占用的空间相对来说会小很多。
  3. 扩展性与可调式性:xml/json可读性会比二进制协议好很多,并且通过网络抓包是可以直接读取,二进制则需要反序列化才能查看能内容。
  4. 跨语言:有些序列化协议与开发语言紧密相关的,例如dobbo的Hessian序列化协议只能支持Java的RPC调用。
  5. 通用性:xml/json非常通用,都有很好的第三方解析库,各个语言解析起来都十分方便,二进制数据的处理方面也有Protobuf和Hessian等插件,在做设计的时候尽量做到较好的通用性。
  • 常用的序列化技术

1.JDK原生序列化,代码如下:

public class JDKSerialization {public static void serialize() throws Exception {//将序列化后的数据存入到D:/TestCode/tradeUser.clazz中String basePath =  "D:/TestCode/";FileOutputStream fos = new FileOutputStream(basePath + "tradeUser.clazz");//创建tradeUser对象TradeUser tradeUser = new TradeUser();tradeUser.setName("Mirson");//将tradeUser写入到tradeUser.clazz中ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(tradeUser);oos.flush();oos.close();//读取D:/TestCode/tradeUser.clazzFileInputStream fis = new FileInputStream(basePath + "tradeUser.clazz");ObjectInputStream ois = new ObjectInputStream(fis);//将读取的数据反序列化到对象中TradeUser deTradeUser = (TradeUser) ois.readObject();ois.close();System.out.println("=== 反序列化结果 ==== ");System.out.println(deTradeUser);}public static void main(String[] args) throws Exception {serialize();}
}@Data
public class TradeUser implements Serializable {/*** 用户编号*/private String userNo;/*** 用户名称*/private String name;/*** 用户密码*/private String userPwd;/*** 电话号码*/private String phone;/*** 公司ID*/private Long companyId;/*** 邮箱*/private String email;/*** 地址*/private String address;/*** 最近一次用户登陆IP*/private String lastLoginIp;/*** 最近一次登陆时间*/private Date lastLoginTime;/*** 状态(0:有效, 1:锁定, 2:禁用)*/private int status;/*** 创建时间*/private Date createTime;}
  1. 在Java中,序列化必须要实现java.io.Serializable接口。
  2. 通过ObjectOutputStream和ObjectInputStream对象进行序列化以及反序列化操作。
  3. 虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的点事两个类的序列化ID是否一致(也就是在代码中定于的序列ID private static final long serialVersionUID);
  4. 序列化并不会保存静态变量。
  5. 想要父类对象也序列化,就需要父类也实现Serializable接口;
  6. Transient关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值被设为初始值,如基本类型int为0,封装对象型则为null;
  7. 服务器给客户端发哦送你个序列化对象数据并非加密的,如果对象中有一些敏感数据比如密码等,那么在对密码字段序列化之前,最好做加密处理,这样子可以一定程度保证序列化对象的数据安全。

2.json序列化

        一般在HTTP协议的RPC框架通信中,会选择JSON方式,因为JSON有较好的扩展性、可读性和通用性。

缺陷:JSON序列化占用空间开销较大,没有JAVA的强类型区分,需要通过反射解决,解析效率和压缩率都较差。

如果对并发和性能要求较高,或者是传输数据量较大的场景,不建议采用JSON序列化方式。

  3.Hessian2序列化

        Hessian是一个动态类型,二进制序列化,并且支持跨语言特性的序列化框架。Hessian性能上要比JDK、JSON序列化高效很多,并且生成的字节数更小,有非常好的兼容性和稳定性,所以Hessian更加适合作为RPC框架远程通信的序列化协议。

代码示例:

public class Hessian2Serialization {public static void main(String[] args) throws Exception {serialize();}public static void serialize() throws Exception {TradeUser tradeUser = new TradeUser();tradeUser.setName("Mirson");tradeUser.setUserNo("100001");//tradeUser对象序列化处理ByteArrayOutputStream bos = new ByteArrayOutputStream();Hessian2Output output = new Hessian2Output(bos);output.writeObject(tradeUser);output.flushBuffer();byte[] data = bos.toByteArray();System.out.println("=== 序列化结果 ==== ");System.out.println(data);bos.close();//tradeUser对象反序列化处理ByteArrayInputStream bis = new ByteArrayInputStream(data);Hessian2Input input = new Hessian2Input(bis);TradeUser deTradeUser = (TradeUser) input.readObject();input.close();System.out.println("=== 反序列化结果 ==== ");System.out.println(deTradeUser);}
}

Dobbo Hessian Lite序列化流程:

Dubbo Hessian Lite反序列化流程:

Hessian自身也存在一些缺陷,大家在使用过程中要注意:

  • 对Linked系列对象不支持,比如LinkedHashMap、LinkedHashSet等,但可以通过CollectionSerializer类修复。
  • Local类不支持,可以通过拓展ContextSerizlizerFactory类修复。
  • Byte/Short在反序列化的时候会转成Integer。

Dubbo2.7.3通讯序列化源码实现分析:

        ExchangeCodec的encode方法:
    public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {if (msg instanceof Request) {this.encodeRequest(channel, buffer, (Request)msg);} else if (msg instanceof Response) {this.encodeResponse(channel, buffer, (Response)msg);} else {super.encode(channel, buffer, msg);}}

反序列化流程:

源码如下:ExchangeCodec的decode方法

    public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {int readable = buffer.readableBytes();byte[] header = new byte[Math.min(readable, 16)];buffer.readBytes(header);return this.decode(channel, buffer, readable, header);}protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {byte flag = header[2];byte proto = (byte)(flag & 31);long id = Bytes.bytes2long(header, 4);if ((flag & -128) == 0) {Response res = new Response(id);if ((flag & 32) != 0) {res.setEvent(true);}byte status = header[3];res.setStatus(status);try {ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto);if (status == 20) {Object data;if (res.isHeartbeat()) {data = this.decodeHeartbeatData(channel, in);} else if (res.isEvent()) {data = this.decodeEventData(channel, in);} else {data = this.decodeResponseData(channel, in, this.getRequestData(id));}res.setResult(data);} else {res.setErrorMessage(in.readUTF());}} catch (Throwable var12) {res.setStatus((byte)90);res.setErrorMessage(StringUtils.toString(var12));}return res;} else {Request req = new Request(id);req.setVersion(Version.getProtocolVersion());req.setTwoWay((flag & 64) != 0);if ((flag & 32) != 0) {req.setEvent(true);}try {ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto);Object data;if (req.isHeartbeat()) {data = this.decodeHeartbeatData(channel, in);} else if (req.isEvent()) {data = this.decodeEventData(channel, in);} else {data = this.decodeRequestData(channel, in);}req.setData(data);} catch (Throwable var13) {req.setBroken(true);req.setData(var13);}return req;}}

4.Protobuf序列化

        Protobuf是Google推出的开源的序列库,它是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列化,支持Java、Python、C++、Go等多种语言,

Protobuf使用的时候需要定义IDL(inteface description language),然后使用不同语言的IDL编译器,生成序列化工具类,它具备一下有点:

  • 压缩比高,体积小,序列化体积相比JSON、Hessian小很多;
  • IDL能清晰的描述语义,可以帮助保证应用程序之间的类型不会丢失,无需类似XML解析器;
  • 序列化反序列化速度很快,不需要通过反射获取类型;
  • 消息格式的拓展、升级和兼容性都不错,可以做到向后兼容;

脚本示例:

// 定义Proto版本
syntax = "proto3";
// 是否允许生成多个JAVA文件
option java_multiple_files = false;
// 生成的包路径
option java_package = "com.itcast.rpc.samples.serial.proto";
// 生成的JAVA类名
option java_outer_classname = "TradeUserProto";// 预警通知消息体
message TradeUser {/*** 用户ID*/int64 userId = 1 ;/*** 用户名称*/string userName = 2 ;
}

代码操作:

public class ProtoSerialization {public static void serialize() throws Exception{// 创建TradeUser的Protobuf对象TradeUserProto.TradeUser.Builder builder = TradeUserProto.TradeUser.newBuilder();builder.setUserId(100001);builder.setUserName("Mirson");//将TradeUser做序列化处理TradeUserProto.TradeUser msg = builder.build();byte[] data = msg.toByteArray();System.out.println("=== 序列化结果 ==== ");System.out.println(data);//反序列化处理, 将刚才序列化的byte数组转化为TradeUser对象TradeUserProto.TradeUser deTradeUser = TradeUserProto.TradeUser.parseFrom(data);System.out.println("=== 反序列化结果 ==== ");System.out.println(deTradeUser);}
}

RPC原理(1)之深入RPC原理简介相关推荐

  1. 理解Android系统的进程间通信原理(二)----RPC机制

    理解Android系统中的轻量级解决方案RPC的原理,需要先回顾一下JAVA中的RMI(Remote Method Invocation)这个易于使用的纯JAVA方案(用来实现分布式应用).有关RMI ...

  2. 牛逼哄哄的 RPC 框架,底层到底什么原理?

    1. RPC框架的概念 RPC(Remote Procedure Call)–远程过程调用,通过网络通信调用不同的服务,共同支撑一个软件系统,微服务实现的基石技术. 使用RPC可以解耦系统,方便维护, ...

  3. 自编写RPC通信实例解析HadoopRPC通信原理

    1.HDFS.YARN.MapReduce三者关系 2.需求解说 模拟RPC的客户端.服务端.通信协议三者如何工作的 3.代码编写 (0)在pom.xml中增加如下依赖 <dependencie ...

  4. DNSSEC 原理、配置与布署简介

    本文转载自:http://netsec.ccert.edu.cn/duanhx/archives/1479 作者:段海新,清华大学信息网络工程研究中心 ------------------------ ...

  5. 高级架构师_Docker_第2章_ Docker核心原理_ 第5节 Dockerfile简介

    高级架构师_Docker_第2章_ Docker核心原理_ 第5节 Dockerfile简介 文章目录 高级架构师_Docker_第2章_ Docker核心原理_ 第5节 Dockerfile简介 D ...

  6. RPC框架(一)RPC简介

    一.概述 二.RPC 2.1.RPC定义 2.2.RPC主要组成部分 三.影响RPC框架性能的因素 四.工业界的 RPC 框架一览 4.1.国内 4.2.国外 五.如何选择RPC框架 一.概述 随着公 ...

  7. JAVA层HIDL服务的获取原理-Android10.0 HwBinder通信原理(九)

    摘要:本节主要来讲解Android10.0 JAVA层HIDL服务的获取原理 阅读本文大约需要花费19分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的 ...

  8. JAVA层HIDL服务的注册原理-Android10.0 HwBinder通信原理(八)

    摘要:本节主要来讲解Android10.0 JAVA层HIDL服务的注册原理 阅读本文大约需要花费22分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的 ...

  9. Native层HIDL服务的注册原理-Android10.0 HwBinder通信原理(六)

    摘要:本节主要来讲解Android10.0 Native层HIDL服务的注册原理 阅读本文大约需要花费23分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Androi ...

最新文章

  1. ajax模拟省市级联动,省市区三级联动和ajax模拟请求(示例代码)
  2. 文曲星猜数游戏,无测试代码
  3. 【收藏】从 0 到 1 学习 elasticsearch ,这一篇就够了!
  4. googlehelper手机版ios_二次元漫画控iOS苹果手机版下载v1.0.0下载|免费二次元漫画控iOS苹果手机版下载绿色版...
  5. VTK:Utilities之BoundingBox
  6. Django实战1-权限管理功能实现-01:搭建开发环境
  7. python能不能用c打开文件_C/C++/Python等 使用二进制模式打开文件与不使用二进制模式的区别...
  8. .NET库和向后兼容的技巧——第3部分
  9. oracle零碎要点---oracle em的web访问地址忘了
  10. SpringBoot+Quartz实现动态可配定时任务(动态定时任务)
  11. 计算机网络原理学习笔记
  12. stm32 W25QXX系列驱动 W25Q80 W25Q16 W25Q32 W25Q64 W25Q128 W25Q256
  13. 【软件】XPS格式文件怎么打开,用XPSViewer(百度云免费下载链接)
  14. 【wpa_supplicant】 初始化
  15. 什么是计算机的超级用户账号,administrator是什么意思
  16. 非服务器模式下运行getImageData函数出现 the operation is insecure
  17. AM5728核心板出厂测试笔记
  18. 大数据推荐算法概念简述
  19. 微型计算机接口与技术期末,北邮《微机原理与接口技术》期末复习题(含答案).doc...
  20. 2021-2027全球与中国便携式X射线荧光光谱仪市场现状及未来发展趋势

热门文章

  1. 高斯投影转换(3度带)
  2. 服务器招标系统,招投标系统方案
  3. 关于印发医疗卫生机构网络安全管理办法的通知
  4. 说话人聚类--谱聚类和层次聚类
  5. 外贸、财务相关名词解释
  6. Python-Django毕业设计租房管理信息系统(程序+Lw)
  7. 修复hex/s19文件中的校验和
  8. 如何使用百度AI智能审核
  9. COBIT5给企业带来什么样的价值
  10. 程序员求职攻略(《程序员面试笔试宝典》)之自己的强项或是研究方向与中意的工作岗位不一致怎么办?...