微服务架构之RPC调用

news2024/12/29 10:04:31

在单体应用时,一次服务调用发生在同一台机器上的同一个进程内部,也就是说调用发生在本机内部,因此也被叫作本地方法调用。在进行服务化拆分之后,服务提供者和服务消费者运行在两台不同物理机上的不同进程内,它们之间的调用相比于本地方法调用,可称之为远程方法调用,简称RPC(Remote Procedure Call),那么RPC调用是如何实现的呢?

在介绍RPC调用的原理之前,先来想象一下一次电话通话的过程。首先,呼叫者A通过查询号码簿找到被呼叫者B的电话号码,然后拨打B的电话。B接到来电提示时,如果方便接听的话就会接听;如果不方便接听的话,A就得一直等待。当等待超过一段时间后,电话会因超时被挂断,这个时候A需要再次拨打电话,一直等到B空闲的时候,才能接听。

RPC调用的原理与此类似,我习惯把服务消费者叫作 客户端,服务提供者叫作 服务端,两者通常位于网络上两个不同的地址,要完成一次RPC调用,就必须先建立网络连接。建立连接后,双方还必须按照某种约定的协议进行网络通信,这个协议就是通信协议。双方能够正常通信后,服务端接收到请求时,需要以某种方式进行处理,处理成功后,把请求结果返回给客户端。为了减少传输的数据大小,还要对数据进行压缩,也就是对数据进行序列化。

上面就是RPC调用的过程,由此可见,想要完成调用,你需要解决四个问题:

  • 客户端和服务端如何建立网络连接?

  • 服务端如何处理请求?

  • 数据传输采用什么协议?

  • 数据该如何序列化和反序列化?

客户端和服务端如何建立网络连接?

根据我的实践经验,客户端和服务端之间基于TCP协议建立网络连接最常用的途径有两种。

1. HTTP通信

HTTP通信是基于应用层HTTP协议的,而HTTP协议又是基于传输层TCP协议的。一次HTTP通信过程就是发起一次HTTP调用,而一次HTTP调用就会建立一个TCP连接,经历一次下图所示的“ 三次握手”的过程来建立连接。

alt
三次握手过程详解
  • 第一次握手:
    • 1、【客户端】向【服务端】发送连接请求报文,标记ACK=1, SYN=1,客户端序列号seq=x,客户端进入等待状态。
  • 第二次握手:
    • 1、【服务端】收到请求报文,将收到的报文缓存起来,缓存客户端seq=x
    • 3、【服务端】向【客户端】发送确认报文,生成一个【服务端】seq=y,标记ACK=1,SYN=1,【服务端】自己的序列号seq=y,确认序列号ACK_Number=x+1,发送给【客户端】
  • 第三次握手:
    • 1、【客户端】收到服务端发送的确认报文,将收到的报文存起来,缓存【服务端】seq=y
    • 2、【客户端】发送确认报文给【服务端】,标记ACK=1,SYN=0,【客户端】自己的序列号seq=x+1,确认序列号ACK_Number=y+1
    • 3、【客户端】、【服务端】都会进入ESTABLISHED (连接已建立状态)

完成请求后,再经历一次“四次挥手”的过程来断开连接。

alt
四次挥手过程详解
  • 第一次挥手:
    • 1、【客户端】向【服务端】发送释放连接报文,并停止发送数据,主动关闭 TCP 连接
    • 2、标记FIN=1,【客户端序列号】seq=x,该序号等于前面已经传送过去的数据的最后一个字节的序号加1
    • 3、这时,客户端 FIN—WAIT-1 (终止等待1)状态,等待服务端确认
  • 第二次挥手:
    • 1、【服务端】收到释放连接报文,将收到的报文缓存起来,缓存【客户端】seq=x
    • 2、【服务端】向【客户端】发出确认释放报文,标记ACK=1,【服务端序列号】seq=y,确认序列号ACK_Number=a+1 (a为【服务端】前面已经传送过的数据的最后一个字节的序号)
    • 3、此时【服务端】进入CLOSE—WAIT(关闭等待)状态
    • 4、此时TCP服务器进程应该通知上层的应用进程,因为【客户端】到【服务端】这个方向的连接就释放了,这时TCP处于半关闭状态,即【客户端】已经没有数据要发了,但【服务端】若发送数据,【客户端】仍要接受,也就是说从【服务端】到【客户端】这个方向的连接并没有关闭,这个状态可能会持续一些时间。
  • 第三次挥手:
    • 1、【客户端】收到【服务端】确认报文,并缓存起来
    • 2、此时【客户端】进入FIN—WAIT(终止等待2)状态,等待【服务端】发起释放连接报文
    • 3、如果【服务端】没有数据要发送给【客户端】了,【服务端】的应用进程就会通知TCP释放连接
    • 4、此时【服务端】向【客户端】发送释放连接报文,标记FIN=1,确认序列号ACK_Number=a+1(与第二次挥手的确认号一致),【服务端序号】seq=z+1(z为半关闭状态发送的数据的最后一个字节的序号)
    • 5、此时【服务端】进入最后确认状态,等待【客户端】确认
  • 第四次挥手:
    • 1、【客户端】收到【服务端】释放连接请求,必须发出确认
    • 2、【客户端】向【服务端】发送确认报文,标记ACK=1,确认号序列号ACK_Numbe=z+1+1,【客户端序号】seq=x+1(x为第一次挥手的seq)
    • 3、此时【客户端】进入等待状态,必须经过时间等待计时器设置的时间2倍MSL(报文最大生存时间)后,【客户端】才进入CLOSED状态,MSL叫做最长报文寿命,RFC建议设为2分钟,实际应用中是30秒。在这2倍MSL期间【客户端】进入TIME—WAIT状态后,要经过4分钟才能进入到CLOSED状态。
    • 4、【服务端】只要收到了【客户端】的确认后,就进入了CLOSED状态
    • 5、当【客户端】和【服务端】都进入CLOSED状态后,连接就完全释放了

2. Socket通信

Socket通信是基于TCP/IP协议的封装,建立一次Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ;另一个运行于服务器端,称为ServerSocket 。就像下图所描述的,Socket通信的过程分为四个步骤:服务器监听、客户端请求、连接确认、数据传输。

  • 服务器监听:ServerSocket通过调用bind()函数绑定某个具体端口,然后调用listen()函数实时监控网络状态,等待客户端的连接请求。

  • 客户端请求:ClientSocket调用connect()函数向ServerSocket绑定的地址和端口发起连接请求。

  • 服务端连接确认:当ServerSocket监听到或者接收到ClientSocket的连接请求时,调用accept()函数响应ClientSocket的请求,同客户端建立连接。

  • 数据传输:当ClientSocket和ServerSocket建立连接后,ClientSocket调用send()函数,ServerSocket调用receive()函数,ServerSocket处理完请求后,调用send()函数,ClientSocket调用receive()函数,就可以得到得到返回结果。

直接理解可能有点抽象,你可以把这个过程套入前面我举的“打电话”的例子,可以方便你理解Socket通信过程。

alt

当客户端和服务端建立网络连接后,就可以发起请求了。但网络不一定总是可靠的,经常会遇到网络闪断、连接超时、服务端宕机等各种异常,通常的处理手段有两种。

  • 链路存活检测:客户端需要定时地发送心跳检测消息(一般是通过ping请求)给服务端,如果服务端连续n次心跳检测或者超过规定的时间都没有回复消息,则认为此时链路已经失效,这个时候客户端就需要重新与服务端建立连接。

  • 断连重试:通常有多种情况会导致连接断开,比如客户端主动关闭、服务端宕机或者网络故障等。这个时候客户端就需要与服务端重新建立连接,但一般不能立刻完成重连,而是要等待固定的间隔后再发起重连,避免服务端的连接回收不及时,而客户端瞬间重连的请求太多而把服务端的连接数占满。

服务端如何处理请求?

假设这时候客户端和服务端已经建立了网络连接,服务端又该如何处理客户端的请求呢?通常来讲,有三种处理方式。

  • 同步阻塞方式(BIO),客户端每发一次请求,服务端就生成一个线程去处理。当客户端同时发起的请求很多时,服务端需要创建很多的线程去处理每一个请求,如果达到了系统最大的线程数瓶颈,新来的请求就没法处理了。

  • 同步非阻塞方式 (NIO),客户端每发一次请求,服务端并不是每次都创建一个新线程来处理,而是通过I/O多路复用技术进行处理。就是把多个I/O的阻塞复用到同一个select的阻塞上,从而使系统在单线程的情况下可以同时处理多个客户端请求。这种方式的优势是开销小,不用为每个请求创建一个线程,可以节省系统开销。

  • 异步非阻塞方式(AIO),客户端只需要发起一个I/O操作然后立即返回,等I/O操作真正完成以后,客户端会得到I/O操作完成的通知,此时客户端只需要对数据进行处理就好了,不需要进行实际的I/O读写操作,因为真正的I/O读取或者写入操作已经由内核完成了。这种方式的优势是客户端无需等待,不存在阻塞等待问题。

从前面的描述,可以看出来不同的处理方式适用于不同的业务场景:

  • BIO适用于连接数比较小的业务场景,这样的话不至于系统中没有可用线程去处理请求。这种方式写的程序也比较简单直观,易于理解。

  • NIO适用于连接数比较多并且请求消耗比较轻的业务场景,比如聊天服务器。这种方式相比BIO,相对来说编程比较复杂。

  • AIO适用于连接数比较多而且请求消耗比较重的业务场景,比如涉及I/O操作的图片服务器。这种方式相比另外两种,编程难度最大,程序也不易于理解。

上面两个问题就是“通信框架”要解决的问题,你可以基于现有的Socket通信,在服务消费者和服务提供者之间建立网络连接,然后在服务提供者一侧基于BIO、NIO和AIO三种方式中的任意一种实现服务端请求处理,最后再花费一些精力去解决服务消费者和服务提供者之间的网络可靠性问题。这种方式对于Socket网络编程、多线程编程知识都要求比较高,感兴趣的话可以尝试自己实现一个通信框架。 但我建议最为稳妥的方式是使用成熟的开源方案,比如Netty、MINA等,它们都是经过业界大规模应用后,被充分论证是很可靠的方案。

假设客户端和服务端的连接已经建立了,服务端也能正确地处理请求了,接下来完成一次正常地RPC调用还需要解决两个问题,即数据传输采用什么协议以及数据该如何序列化和反序列化。

数据传输采用什么协议?

首先来看第一个问题,数据传输采用什么协议?

最常用的有HTTP协议,它是一种开放的协议,各大网站的服务器和浏览器之间的数据传输大都采用了这种协议。还有一些定制的私有协议,比如阿里巴巴开源的Dubbo协议,也可以用于服务端和客户端之间的数据传输。无论是开放的还是私有的协议,都必须定义一个“契约”,以便服务消费者和服务提供者之间能够达成共识。服务消费者按照契约,对传输的数据进行编码,然后通过网络传输过去;服务提供者从网络上接收到数据后,按照契约,对传输的数据进行解码,然后处理请求,再把处理后的结果进行编码,通过网络传输返回给服务消费者;服务消费者再对返回的结果进行解码,最终得到服务提供者处理后的返回值。

通常协议契约包括两个部分:消息头和消息体。其中消息头存放的是协议的公共字段以及用户扩展字段,消息体存放的是传输数据的具体内容。

以HTTP协议为例,主要分为消息头和消息体两部分,其中消息头中存放的是协议的公共字段,比如Server代表是服务端服务器类型、Content-Length代表返回数据的长度、Content-Type代表返回数据的类型;消息体中存放的是具体的返回结果,这里就是一段HTML网页代码。

数据该如何序列化和反序列化?

再看第二个问题,数据该如何序列化和反序列化。

一般数据在网络中进行传输前,都要先在发送方一端对数据进行编码,经过网络传输到达另一端后,再对数据进行解码,这个过程就是序列化和反序列化。

为什么要对数据进行序列化和反序列化呢?要知道网络传输的耗时一方面取决于网络带宽的大小,另一方面取决于数据传输量。要想加快网络传输,要么提高带宽,要么减小数据传输量,而对数据进行编码的主要目的就是减小数据传输量。比如一部高清电影原始大小为30GB,如果经过特殊编码格式处理,可以减小到3GB,同样是100MB/s的网速,下载时间可以从300s减小到30s。

常用的序列化方式分为两类:文本类如XML/JSON等,二进制类如PB/Thrift等,而具体采用哪种序列化方式,主要取决于三个方面的因素。

  • 支持数据结构类型的丰富度。数据结构种类支持的越多越好,这样的话对于使用者来说在编程时更加友好,有些序列化框架如Hessian 2.0还支持复杂的数据结构比如Map、List等。

  • 跨语言支持。序列化方式是否支持跨语言也是一个很重要的因素,否则使用的场景就比较局限,比如Java序列化只支持Java语言,就不能用于跨语言的服务调用了。

  • 性能。主要看两点,一个是序列化后的压缩比,一个是序列化的速度。以常用的PB序列化和JSON序列化协议为例来对比分析,PB序列化的压缩比和速度都要比JSON序列化高很多,所以对性能和存储空间要求比较高的系统选用PB序列化更合适;而JSON序列化虽然性能要差一些,但可读性更好,更适合对外部提供服务。

总结

今天我们一起了解服务调用需要解决的几个问题:

  • 通信框架。它主要解决客户端和服务端如何建立连接、管理连接以及服务端如何处理请求的问题。

  • 通信协议。它主要解决客户端和服务端采用哪种数据传输协议的问题。

  • 序列化和反序列化。它主要解决客户端和服务端采用哪种数据编解码的问题。

这三个部分就组成了一个完整的RPC调用框架,通信框架提供了基础的通信能力,通信协议描述了通信契约,而序列化和反序列化则用于数据的编/解码。一个通信框架可以适配多种通信协议,也可以采用多种序列化和反序列化的格式,比如服务化框架Dubbo不仅支持Dubbo协议,还支持RMI协议、HTTP协议等,而且还支持多种序列化和反序列化格式,比如JSON、Hession 2.0以及Java序列化等。

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/581516.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Segment Anything——论文笔记

home page:segment-anything.com code:segment-anything 1. 概述 介绍:SAM是最近提出的一种通用分割大模型,其表现出了强大的零样本泛化能力,视觉感知模型的通用化又前进了一步。为了达到文章标题字面意义“segment a…

chatgpt赋能python:Python下载PyQt5教程

Python下载PyQt5教程 简介 PyQt5是一款Python编程语言的GUI框架,它基于QT库,专门用于开发图形界面应用程序。PyQt5可以实现跨平台开发,可在Windows,MacOS和Linux操作系统上运行。本文将介绍如何下载PyQt5,并在Python…

14、JavaEE--Mybatis注解与Ajax技术

目录 单表注解 Param注解 基于注解的一对一关联查询(one) 一对多查询(many) Ajax的概念 Ajax 开发 Ajax的核心代码: Ajax五步使用法 步骤1 创建 XMLHttpRequest 对象 步骤2 指定异步提交的目标和提交方式 步骤3 指定 X…

IT知识百科:什么是分布式云?

在当今信息技术高速发展的时代,云计算已经成为了企业和个人的重要组成部分。而在云计算领域中,分布式云是一种重要的架构模式,它允许资源的分散部署和管理,以实现高可用性、可伸缩性和弹性的服务提供。 本文将详细介绍什么是分布…

IT公司的吉祥“树” 二叉树-(堆)C语言创建

目录 🍪前言 一、树概念及结构 ✅基本概念 ✅树的专有名词 ✅ 树的表示 🚩孩子兄弟表示法 二、二叉树概念及结构 ✅概念 😍😍现实中的二叉树(又称IT公司的吉祥物)😍😍 ✅…

Nginx常用操作说明

Nginx常用操作说明 介绍Nginx概念深入浅出Nginx环境搭建Nginx反向代理-负载均衡Nginx动静分离Nginx动静分离先在部署Nginx的机器,Nginx目录下创建一个目录static_resources将项目中所有的静态资源全部拷贝到该目录下,而后将项目中的静态资源移除重新打包…

Hexo 个人博客主题美化

anzhiyu主题文档:https://anzhiy.cn/posts/220c.html anzhiyu主题插件地址:https://github.com/anzhiyu-c/hexo-theme-anzhiyu anzhiyu 主题安装 在博客根目录里执行命令,安装 anzhiyu 主题: git clone -b main https://github.co…

sql partition by和rank的统计用法

问题背景 最近公司有个项目需要用到某种特殊的统计用法, 例如从所有的数据中找出每个账号最新的一条余额信息(根据某个关键信息进行排序并获取排序值最高的记录)。 当时用的是非常普通的语句,也就是多个子查询嵌套select出每个账…

chatgpt赋能python:Python下载jieba:优化中文分词的必备工具

Python下载jieba:优化中文分词的必备工具 在中文自然语言处理的领域中,分词是一项基础且重要的任务。jieba是一个优秀的中文分词组件,它支持三种分词模式,并且具有高效、准确、易用等优点。本文将介绍如何通过Python来下载jieba&…

chatgpt赋能python:Python中8//3**2*10的解析与运算

Python中8//3**2*10的解析与运算 Python是一种高效,多范式,解释性编程语言,广泛用于Web开发,数据科学,人工智能等领域。在Python的运算中,有一个8//3**2*10的表达式,本篇文章将对其进行解析与运…

Springboot 配置文件脱敏的实践

写作目的 数据安全这块还是挺严重的,尤其是自己专注于业务开发,不能总停留在一个地方,还要关注其他的一些问题,比如数据安全。 配置脱敏 实现配置的脱敏我使用了Java的一个加解密工具Jasypt。该工具支持对称加密和非对称加密。…

pytorch基础学习-tensorboardX

最近训练总是出问题,听取建议,在pytorch环境下引入了tensorboard 1、安装tensorboardX tensorboardX是在tensorboard前提下进行安装的,所以我们需要先安装tensorboard pip install tensorboardpip install tensorboardX2、简单使用 这里我…

应急响应-windows

win系统常见的安全事件 1.病毒,木马,蠕虫事件 2.web服务器入侵事件或第三方服务入侵事件 3.系统入侵事件,用win漏洞入侵系统,利用弱口令等。 4.网络攻击事件,如DDos,ARP欺骗等。 win系统安全事件发现的…

基于自适应反馈调节因子的阿基米德优化算法(IAOA)-附代码

基于自适应反馈调节因子的阿基米德优化算法(IAOA) 文章目录 基于自适应反馈调节因子的阿基米德优化算法(IAOA)1.阿基米德优化算法2. 改进阿基米德优化算法2.1 佳点集种群初始化2.2 自适应反馈调节因子2.3 莱维旋转变换策略 3.实验结果4.参考文献5.Matlab代码6.Python代码 摘要&…

软考A计划-试题模拟含答案解析-卷七

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资源分享&am…

Maven学习笔记(单一架构案例)22版

第一节 创建工程,引入依赖 1 架构 ①架构的概念 『架构』其实就是『项目的结构』,只是因为架构是一个更大的词,通常用来形容比较大规模事物的结构。 ②单一架构 单一架构也叫『all-in-one』结构,就是所有代码、配置文件、各种…

基于hdoop的短视频用户画像研究_kaic

基于hadoop的短视频用户画像研究 摘 要 在这个互联网迅速发展的时代,网络和信息技术都跟上了时代的潮流,在互联网中的用户数据也出现了爆炸性的增长。用户的各种日常行为都通过互联网被记录下来,对于所有的互联网企业来说,想要从…

TCP报文中序列号的作用

TCP(传输控制协议)中的序列号用于标识TCP报文段中的数据部分。每个TCP报文段都包含一个序列号字段,该字段指示了报文段中第一个字节的序号。在后续的报文段中,序列号将递增,以指示下一个字节的序号。 TCP序列号是一个…

升级Springboot2.7.11之后内嵌tomcat启动成功, 但访问任何接口都是404

背景 最近项目在升级JDK17, 但原先低版本Springboot不能识别jdk17编译的字节码, 为了能够使用JDK17的语法, 因此对SpringBoot也做了升级, 直接升级到了SpringBoot 2.7.11版本. 对一些变更修改升级完成后, 本地启动服务运行, 一切正常!!! 于是发布到公司的容器环境运行, 发布平…

queue的常见接口说明(基于c++标准库的STL)

前言 队列是一种容器适配器,专门用于先进先出(FIFO)的操作中 ,其容器从一端插入数据,从另一端取出数据。队列作为一种容器适配器实现,容器适配器即将特定的容器封装起来,queue提供一组特定的成员…