RPC 框架架构设计

news2024/11/8 21:35:47

RPC 框架架构设计

RPC 又称远程过程调用(Remote Procedure Call),用于解决分布式系统中服务之间的调用问题。通俗地讲,就是开发者能够像调用本地方法一样调用远程的服务。下面我们通过一幅图来说说 RPC 框架的基本架构。

在这里插入图片描述
RPC 框架包含三个最重要的组件,分别是客户端、服务端和注册中心。在一次 RPC 调用流程中,这三个组件是这样交互的:

  • 服务端在启动后,会将它提供的服务列表发布到注册中心,客户端向注册中心订阅服务地址;
  • 客户端会通过本地代理模块 Proxy 调用服务端,Proxy 模块收到负责将方法、参数等数据转化成网络字节流;
  • 客户端从服务列表中选取其中一个的服务地址,并将数据通过网络发送给服务端;
  • 服务端接收到数据后进行解码,得到请求信息;
  • 服务端根据解码后的请求信息调用对应的服务,然后将调用结果返回给客户端。
  • 虽然 RPC 调用流程很容易理解,但是实现一个完整的 RPC 框架设计到很多内容,例如服务注册与发现、通信协议与序列化、负载均衡、动态代理等,下面我们一一进行初步地讲解。

服务注册与发现

在分布式系统中,不同服务之间应该如何通信呢?传统的方式可以通过 HTTP 请求调用、保存服务端的服务列表等,这样做需要开发者主动感知到服务端暴露的信息,系统之间耦合严重。为了更好地将客户端和服务端解耦,以及实现服务优雅上线和下线,于是注册中心就出现了。

在 RPC 框架中,主要是使用注册中心来实现服务注册和发现的功能。服务端节点上线后自行向注册中心注册服务列表,节点下线时需要从注册中心将节点元数据信息移除。客户端向服务端发起调用时,自己负责从注册中心获取服务端的服务列表,然后在通过负载均衡算法选择其中一个服务节点进行调用。以上是最简单直接的服务端和客户端的发布和订阅模式,不需要再借助任何中间服务器,性能损耗也是最小的。

现在思考一个问题,服务在下线时需要从注册中心移除元数据,那么注册中心怎么才能感知到服务下线呢?我们最先想到的方法就是节点主动通知的实现方式,当节点需要下线时,向注册中心发送下线请求,让注册中心移除自己的元数据信息。但是如果节点异常退出,例如断网、进程崩溃等,那么注册中心将会一直残留异常节点的元数据,从而可能造成服务调用出现问题。

为了避免上述问题,实现服务优雅下线比较好的方式是采用主动通知 + 心跳检测的方案。除了主动通知注册中心下线外,还需要增加节点与注册中心的心跳检测功能,这个过程也叫作探活。心跳检测可以由节点或者注册中心负责,例如注册中心可以向服务节点每 60s 发送一次心跳包,如果 3 次心跳包都没有收到请求结果,可以任务该服务节点已经下线。

由此可见,采用注册中心的好处是可以解耦客户端和服务端之间错综复杂的关系,并且能够实现对服务的动态管理。服务配置可以支持动态修改,然后将更新后的配置推送到客户端和服务端,无须重启任何服务。

通信协议与序列化

既然 RPC 是远程调用,必然离不开网络通信协议。客户端在向服务端发起调用之前,需要考虑采用何种方式将调用信息进行编码,并传输到服务端。因为 RPC 框架对性能有非常高的要求,所以通信协议应该越简单越好,这样可以减少编解码的性能损耗。RPC 框架可以基于不同的协议实现,大部分主流 RPC 框架会选择 TCP、HTTP 协议,出名的 gRPC 框架使用的则是 HTTP2。TCP、HTTP、HTTP2 都是稳定可靠的,但其实使用 UDP 协议也是可以的,具体看业务使用的场景。成熟的 RCP 框架能够支持多种协议,例如阿里开源的 Dubbo 框架被很多互联网公司广泛使用,其中可插拔的协议支持是 Dubbo 的一大特色,这样不仅可以给开发者提供多种不同的选择,而且为接入异构系统提供了便利。

客户端和服务端在通信过程中需要传输哪些数据呢?这些数据又该如何编解码呢?如果采用 TCP 协议,你需要将调用的接口、方法、请求参数、调用属性等信息序列化成二进制字节流传递给服务提供方,服务端接收到数据后,再把二进制字节流反序列化得到调用信息,然后利用反射的原理调用对应方法,最后将返回结果、返回码、异常信息等返回给客户端。所谓序列化和反序列化就是将对象转换成二进制流以及将二进制流再转换成对象的过程。因为网络通信依赖于字节流,而且这些请求信息都是不确定的,所以一般会选用通用且高效的序列化算法。比较常用的序列化算法有 FastJson、Kryo、Hessian、Protobuf 等,这些第三方序列化算法都比 Java 原生的序列化操作都更加高效。Dubbo 支持多种序列化算法,并定义了 Serialization 接口规范,所有序列化算法扩展都必须实现该接口,其中默认使用的是 Hessian 序列化算法。

RPC 调用方式

成熟的 RPC 框架一般会提供四种调用方式,分别为同步 Sync、异步 Future、回调 Callback和单向 Oneway。RPC 框架的性能和吞吐量与合理使用调用方式是息息相关的,下面我们逐一介绍下四种调用方式的实现原理。

  • Sync 同步调用。客户端线程发起 RPC 调用后,当前线程会一直阻塞,直至服务端返回结果或者处理超时异常。Sync 同步调用一般是 RPC 框架默认的调用方式,为了保证系统可用性,客户端设置合理的超时时间是非常重要的。虽说 Sync 是同步调用,但是客户端线程和服务端线程并不是同一个线程,实际在 RPC 框架内部还是异步处理的。Sync 同步调用的过程如下图所示。
    在这里插入图片描述

  • Future 异步调用。客户端发起调用后不会再阻塞等待,而是拿到 RPC 框架返回的 Future 对象,调用结果会被服务端缓存,客户端自行决定后续何时获取返回结果。当客户端主动获取结果时,该过程是阻塞等待的。Future 异步调用过程如下图所示。

  • Callback 回调调用。如下图所示,客户端发起调用时,将 Callback 对象传递给 RPC 框架,无须同步等待返回结果,直接返回。当获取到服务端响应结果或者超时异常后,再执行用户注册的 Callback 回调。所以 Callback 接口一般包含 onResponse 和 onException 两个方法,分别对应成功返回和异常返回两种情况。
    3.png

  • Oneway 单向调用。客户端发起请求之后直接返回,忽略返回结果。Oneway 方式是最简单的,具体调用过程如下图所示。
    在这里插入图片描述

四种调用方式都各有优缺点,很难说异步方式一定会比同步方式效果好,在不用的业务场景可以按需选取更合适的调用方式。

线程模型

线程模型是 RPC 框架需要重点关注的部分,与我们之前介绍的 Netty Reactor 线程模型有什么区别和联系吗?

首先我们需要明确 I/O 线程和业务线程的区别,以 Dubbo 框架为例,Dubbo 使用 Netty 作为底层的网络通信框架,采用了我们熟悉的主从 Reactor 线程模型,其中 Boss 和 Worker 线程池就可以看作 I/O 线程。I/O 线程可以理解为主要负责处理网络数据,例如事件轮询、编解码、数据传输等。如果业务逻辑能够立即完成,也可以使用 I/O 线程进行处理,这样可以省去线程上下文切换的开销。如果业务逻辑耗时较多,例如包含查询数据库、复杂规则计算等耗时逻辑,那么 I/O 必须将这些请求分发到业务线程池中进行处理,以免阻塞 I/O 线程。

那么哪些请求需要在 I/O 线程中执行,哪些又需要在业务线程池中执行呢?Dubbo 框架的做法值得借鉴,它给用户提供了多种选择,它一共提供了 5 种分发策略,如下表格所示。

负载均衡

在分布式系统中,服务提供者和服务消费者都会有多台节点,如何保证服务提供者所有节点的负载均衡呢?客户端在发起调用之前,需要感知有多少服务端节点可用,然后从中选取一个进行调用。客户端需要拿到服务端节点的状态信息,并根据不同的策略实现负载均衡算法。负载均衡策略是影响 RPC 框架吞吐量很重要的一个因素,下面我们介绍几种最常用的负载均衡策略。

  • Round-Robin 轮询。Round-Robin 是最简单有效的负载均衡策略,并没有考虑服务端节点的实际负载水平,而是依次轮询服务端节点。
  • Weighted Round-Robin 权重轮询。对不同负载水平的服务端节点增加权重系数,这样可以通过权重系数降低性能较差或者配置较低的节点流量。权重系数可以根据服务端负载水平实时进行调整,使集群达到相对均衡的状态。
  • Least Connections 最少连接数。客户端根据服务端节点当前的连接数进行负载均衡,客户端会选择连接数最少的一台服务器进行调用。Least Connections 策略只是服务端其中一种维度,我们可以演化出最少请求数、CPU 利用率最低等其他维度的负载均衡方案。
  • Consistent Hash 一致性 Hash。目前主流推荐的负载均衡策略,Consistent Hash 是一种特殊的 Hash 算法,在服务端节点扩容或者下线时,尽可能保证客户端请求还是固定分配到同一台服务器节点。Consistent Hash 算法是采用哈希环来实现的,通过 Hash 函数将对象和服务器节点放置在哈希环上,一般来说服务器可以选择 IP + Port 进行 Hash,然后为对象选择对应的服务器节点,在哈希环中顺时针查找距离对象 Hash 值最近的服务器节点。

此外,负载均衡算法可以是多种多样的,客户端可以记录例如健康状态、连接数、内存、CPU、Load 等更加丰富的信息,根据综合因素进行更好地决策。

动态代理

RPC 框架怎么做到像调用本地接口一样调用远端服务呢?这必须依赖动态代理来实现。需要创建一个代理对象,在代理对象中完成数据报文编码,然后发起调用发送数据给服务提供方,以此屏蔽 RPC 框架的调用细节。因为代理类是在运行时生成的,所以代理类的生成速度、生成的字节码大小都会影响 RPC 框架整体的性能和资源消耗,所以需要慎重选择动态代理的实现方案。动态代理比较主流的实现方案有以下几种:JDK 动态代理、Cglib、Javassist、ASM、Byte Buddy,我们简单做一个对比和介绍。

  • JDK 动态代理。在运行时可以动态创建代理类,但是 JDK 动态代理的功能比较局限,代理对象必须实现一个接口,否则抛出异常。因为代理类会继承 Proxy 类,然而 Java 是不支持多重继承的,只能通过接口实现多态。JDK 动态代理所生成的代理类是接口的实现类,不能代理接口中不存在的方法。JDK 动态代理是通过反射调用的形式代理类中的方法,比直接调用肯定是性能要慢的。
  • Cglib 动态代理。Cglib 是基于 ASM 字节码生成框架实现的,通过字节码技术生成的代理类,所以代理类的类型是不受限制的。而且 Cglib 生成的代理类是继承于被代理类,所以可以提供更加灵活的功能。在代理方法方面,Cglib 是有优势的,它采用了 FastClass 机制,为代理类和被代理类各自创建一个 Class,这个 Class 会为代理类和被代理类的方法分配 index 索引,FastClass 就可以通过 index 直接定位要调用的方法,并直接调用,这是一种空间换时间的优化思路。
  • Javassist 和 ASM。二者都是 Java 字节码操作框架,使用起来难度较大,需要开发者对 Class 文件结构以及 JVM 都有所了解,但是它们都比反射的性能要高。Byte Buddy 也是一个字节码生成和操作的类库,Byte Buddy 功能强大,相比于 Javassist 和 ASM,Byte Buddy 提供了更加便捷的 API,用于创建和修改 Java 类,无须理解字节码的格式,而且 Byte Buddy 更加轻量,性能更好。

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

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

相关文章

(二)WPF - 应用程序

一、运行程序的过程: (1) Application 对象被构造出来。(2)Run方法被调用。(3)Application.Startup 事件被触发(4)用户代码构造一个或多个 Window 对象。(5&…

Hibernate知识总结

关于Hibernate映射 关于Hibernate的映射要说明的一点就是关于ID的访问权限,property以及field的区别。以前使用的时候根本没有注意过这个问题,这里简单的强调一下。 表的主键在内存中对应一个OID对象描述标识符,需要在xml的配置文件中要指定对…

数据结构——队列的实现

队列,又称为伫列(queue),计算机科学中的一种抽象资料类型,是先进先出(FIFO, First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为rear&…

Hadoop 集群如何升级?

前言 本文隶属于专栏《大数据技术体系》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见大数据技术体系 正文 升级 Hadoop 集群需要细致的规划,特…

Web安全——JavaScript基础

JavaScript基础 一、概述二、嵌入方法1、内嵌式2、外链式3、行内式 三、语句四、注释五、变量六、JavaScript 保留关键字七、JavaScript 作用域1、JavaScript 局部变量2、JavaScript 全局变量 八、数据类型1、判断类型2、数字类型(Number)3、字符串型&am…

windows第三大结构体--KPCR

前面我们介绍了windows的两大结构体,一个是进程结构体,一个是线程结构体。那么第三个就是KPCR。KPCR是什么呢,是用于描述CPU的结构体。每一个CPU都有一个这样的结构体来描述CPU干了什么事。 1.在当线程切换的时候,也就是线程从3环…

Windows和Linux动态注入

摘要:最近对动态注入有一些兴趣因此搜索了些资料,简单整理了下相关的技术实现。本文只能够带你理解何如注入以及大概如何实现,对注入的方法描述的并不详细。   关键字:dll注入,hook,提权   读者须知&am…

hadoop -- Yarn资源管理

Yarn YARN被设计用以解决以往架构的需求和缺陷的资源管理和调度软件。 Apache Hadoop YARN (Yet Another Resource Negotiator,另一种资源协调者)是一种新的 Hadoop 资源管理器,它是一个通用资源管理系统和调度平台,可…

基于uprobe的调试调优浅析

uprobe与krobe对应,动态附加到用户态调用函数的切入点称为uprobe,相比如kprobe 内核函数的稳定性,uprobe 的函数由开发者定义。uprobe是用户态的探针,它和kprobe是相对应的,kprobe是内核态的探针。uprobe需要制定用户态…

深度学习模型压缩方法综述

深度学习因其计算复杂度或参数冗余,在一些场景和设备上限制了相应的模型部署,需要借助模型压缩、系统优化加速等方法突破瓶颈,本文主要介绍模型压缩的各种方法,希望对大家有帮助。 1,模型压缩技术概述 我们知道,一定程度上,网络越深,参数越多,模型也会越复杂,但其最终…

基于Smb协议实现网络文件传输

文章目录 什么是SMB协议SMB与CIFS区别为什么要使用SMB如何对接SMB服务如何用Java实现Smb文件传输SmbV1的实现基于SmbV1的文件上传基于SmbV1的文件下载基于SmbV1的文件重命名基于SmbV1的文件删除基于SmbV1的文件查询 SmbV2的实现基于SmbV2的文件上传基于SmbV2的文件下载基于SmbV…

面向开发人员的 ChatGPT 提示词教程中文版 - ChatGPT 版

面向开发人员的 ChatGPT 提示词教程中文版 - ChatGPT 版 1. 指南1-1. 提示的指南1-2. 配置1-3. 提示语原则原则 1: 写出清晰而具体的指示技巧 1: 使用分隔符来清楚地表明输入的不同部分技巧 2: 要求提供结构化的输出技巧 3: 要求模型检查条件是否得到满足技巧 4: "少许样本…

QML Canvas 元素(绘制文本)

关于 QML Canvas,我们已经学习了如何绘制基本图形、应用样式和颜色、绘制图像等,现在来看一下如何在 Canvas 中绘制文本。 Canvas 提供了两种方式来渲染文本: fillText(text, x, y) 在指定的 (x,y) 位置填充指定的文本 strokeText(text, x, y) 在指定的 (x,y) 位置绘制文本…

基于t分布变异自适应的改进的黏菌算法(TSMA)

目录 一、基于t分布的自适应黏菌优化算法TSAM 1.1 自适应t分布变异 1.2 建立基于t分布的自适应黏菌优化算法TSAM 二、TSMA伪码表示如下: 三、改进对比 黏菌优化算法灵感来自于黏菌的扩张和觅食行为。主要模拟了黏菌在觅食过程中的行为和形态变化,没…

【网络2】MII MDIO

文章目录 1.MII:ISO网络模型中物理层(phy)和数据链路层(mac)属于硬件,其余都属于软件kernel2.MDC/MDIO:不仅管phy,只要支持mdio协议都可以管2.1 3.RGMII时序调整:下面波形…

BUUCTF——九连环1

听这个名字就不正常,不会加密九次吧 打开也是一张图片 依旧是存在隐藏文件信息 分离出来后有两个文件,和两个压缩包,但是都需要密码 哦看错了,原来binwalk直接把里面的给分离出来了 所以现在就asd一个文件夹 爆破不出来&#xff0…

为什么调试很重要?gdb调试分析问题

为什么调试很重要? 一、引言二、调试的定义和分类2.1、调试的定义2.2.、调试的分类 三、调试的重要性四、调试的步骤和技巧4.1、定位问题4.2、重现问题4.3、分析问题4.4、解决问题4.5、调试技巧 五、简单的GDB调试示例:六、总结 一、引言 💡 …

【基于Django框架的在线教育平台开发-02】用户注册功能开发

用户注册功能开发 文章目录 用户注册功能开发1 模型层开发2 视图层开发3 配置urls.py4 表单验证5 模板层开发6 效果展示 1 模型层开发 用户数据表如下所示: FieldTypeExtraidintPrime Key & Auto Incrementpasswordvarchar(128)last_logindatetime(6)Allow Nu…

缓存雪崩和缓存穿透的解决方案

缓存雪崩 缓存雪崩是指存储在缓存里面的大量数据,在同一时刻全部过期,大部分流量直接到达了数据库,导致数据库压力增加,造成数据库崩溃的情况。 缓存雪崩的解决方案如下: 每个缓存的key设置不同的过期时间采用多级缓…

STM32单片机(九)USART串口----第四节:USART串口实战练习(串口发送+接收)

❤️ 专栏简介:本专栏记录了从零学习单片机的过程,其中包括51单片机和STM32单片机两部分;建议先学习51单片机,其是STM32等高级单片机的基础;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 :适用于想要…