从零开始手写RPC框架(1)

news2024/11/20 20:20:17

学习java后端也有一段时间了,在网上寻一些教程和github上的开源库,学习从零开始手写一个RPC,学习各位大牛的代码适当修改,并贴上自己的一些见解和注释。

目录

  • RPC简介
    • RPC和HttpClient的区别和共同点
    • 常见RPC框架
  • RPC框架设计
  • 常见序列化协议

RPC简介

RPC (Remote Procedure Call)是一种技术,允许程序调用在不同地址空间(通常是在另一台计算机上)的过程或函数,就像调用本地过程一样,隐藏了网络通信的细节。RPC 框架通常负责打包调用的参数,通过网络发送给服务提供者,然后将结果返回给调用者。

RPC由六个部分实现:

1. 客户端(服务消费端) :调用远程方法的一端。
2. 客户端 Stub(桩) : 这其实就是一代理类。代理类主要做的事情很简单,就是把你调用方法、类、方法参数等信息传递到服务端。
其负责将方法、参数等组装成能够进行网络传输的消息体(序列化):RpcRequest;找到远程服务的地址,并将消息发送到服务提供端;接收到消息并将消息反序列化为 Java 对象:RpcResponse ,这样也就得到了最终结果。
3. 网络传输 : 网络传输就是你要把你调用的方法的信息比如说参数啊这些东西传输到服务端,然后服务端执行完之后再把返回结果通过网络传输给你传输回来。网络传输的实现方式有很多种比如最基本的 Socket 或者性能以及封装更加优秀的 Netty(推荐)。
4. 服务端 Stub(桩) :这个桩就不是代理类了。理解为桩实际不太好。这里的服务端 Stub 实际指的就是接收到客户端执行方法的请求后,去指定对应的方法然后返回结果给客户端的类。
其收到消息将消息反序列化为 Java 对象: RpcRequest;根据RpcRequest中的类、方法、方法参数等信息调用本地的方法;得到方法执行结果并将组装成能够进行网络传输的消息体:RpcResponse(序
列化)发送至消费方;
5. 服务端(服务提供端) :提供远程方法的一端。

RPC和HttpClient的区别和共同点

之前学习过HttpClient

它们都是在网络编程中常见的技术,用于在不同的计算机之间进行数据交换和通信。它们各自有不同的应用场景和特点,但也存在一些共同点。

RPC特点:

透明性: 对于开发者而言,远程调用和本地调用非常相似,隐藏了网络请求的复杂性。
多协议支持: 支持多种传输协议,如 HTTP、TCP、UDP 等。
高效: 通常使用专门的二进制协议,效率比 HTTP 更高。
强类型: 调用通常基于接口定义,具有强类型校验。

RPC应用:

微服务架构中服务间通信
分布式系统中不同组件间的交互

HttpClient 是一个用于发送 HTTP 请求和接收 HTTP 响应的类库或接口,存在于多种编程语言和框架中。它是 Web 开发中常用的工具,用于执行 HTTP 操作(如 GET、POST、PUT、DELETE 等)。

HttpClient 特点:

灵活性: 支持广泛的 HTTP 功能,包括各种方法、头部、身份验证等。
文本基础: 基于文本的请求和响应(如 JSON、XML),易于阅读和调试。
广泛支持: 几乎所有的 Web 开发语言和框架都支持 HTTP 客户端库。
状态无关: HTTP 是无状态的,每个请求都是独立的。

HttpClient 应用:

Web API 调用
微服务架构中的服务间通信
任何需要 Web 资源交互的场景

尽管 RPC 和 HttpClient 在设计和使用场景上有所不同,但它们也有一些共同点:

网络通信: 两者都用于在网络中的不同计算机之间进行数据交换。
服务消费: 无论是通过 RPC 还是 HttpClient,都可以作为服务消费者,调用远程服务或 API。
抽象层: 它们提供了与远程系统通信的抽象层,封装了底层的网络细节。

总的来说,选择 RPC 还是 HttpClient 取决于具体的应用场景和需求。RPC 更适合于对性能有高要求和需要强类型支持的内部服务通信,而 HttpClient 更适合于 Web API 调用和需要灵活性和广泛兼容性的场景。



常见RPC框架

RPC 框架指的是可以让客户端直接调用服务端方法,就像调用本地方法一样简单的框架

Apache Dubbo

一款微服务框架,为大规模微服务实践提供高性能 RPC 通信、流量治理、可观测性等解决方案, 涵盖 Java、Golang 等多种语言 SDK 实现。提供了从服务定义、服务发现、服务通信到流量管控等几乎所有的服务治理能力,支持 Triple 协议(基于 HTTP/2 之上定义的下一代 RPC 通信协议)、应用级服务发现、Dubbo Mesh (Dubbo3 赋予了很多云原生友好的新特性)等特性。
由阿里开源,后来加入了 Apache 。正是由于 Dubbo 的出现,才使得越来越多的公司开始使用以及接受分布式架构。是比较优秀的国产开源项目。

Dubbo 不论是从功能完善程度、生态系统还是社区活跃度来说都是最优秀的

github

官网

Motan

新浪微博开源的一款 RPC 框架,网上的资料比较少,更像是一个精简版的 Dubbo,设计更加精简,功能更加纯粹。公司实际使用的话,还是推荐 Dubbo 。

Motan 中文文档

从 Motan 看 RPC 框架设计

Dubbo 和 Motan 主要是给 Java 语言使用。如果需要跨多种语言调用的话,可以考虑使用 gRPC。

gRPC

Google 开源的一个高性能、通用的开源 RPC 框架。其由主要面向移动应用开发并基于 HTTP/2协议标准而设计(支持双向流、消息头压缩等功能,更加节省带宽),基于 ProtoBuf 序列化协议开发,并且支持众多开发语言。gRPC 的设计导致其几乎没有服务治理能力。如果你想要解决这个问题的话,就需要依赖其他组件比如腾讯的 PolarisMesh(北极星)了。

Github

官网

Thrift

Facebook 开源的跨语言的 RPC 通信框架,目前已经捐献给 Apache 基金会管理,由于其跨语言特性和出色的性能,在很多互联网公司得到应用。相比于 gRPC 支持的语言更多。
gRPC 和 Thrift 虽然支持跨语言的 RPC 调用,但是它们只提供了最基本的 RPC 框架功能,缺乏一系列配套的服务化组件和服务治理功能的支撑。

官网




RPC框架设计

参考RPC框架Dubbo的架构:

在这里插入图片描述

可以看到大体分为这几个部分服务提供方与运行服务的容器、服务注册与发现中心、服务消费方、监控中心。

注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。我们不实现监控模块,有兴趣可以研究一下Dubbo 的监控模块的设计。

实现一个最基本的 RPC 框架需要哪些东西:

注册中心

注册中心负责服务地址的注册与查找,相当于目录服务。服务端启动的时候将服务名称及其对应的地址(ip+port)注册到注册中心,服务消费端根据服务名称找到对应的服务地址。有了服务地址之后,服务消费端就可以通过网络请求服务端了。


比较推荐使用 Zookeeper 作为注册中心。也可以使用 Nacos ,甚至是 Redis。ZooKeeper 为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。并且,ZooKeeper 将数据保存在内存中,性能是非常棒的。在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景)。

网络传输

要调用远程的方法,就要发送网络请求来传递目标类和方法的信息以及方法的参数等数据到服务提供端。
网络传输具体推荐使用基于 NIO 的网络编程框架 Netty ,是最好的选择。Socket是Java 中最原始、最基础的网络通信方式,Socket 阻塞IO、性能低并且功能单一。使用同步非阻塞的 I/O 模型 NIO ,但是用它来进行网络编程太麻烦。
Netty 是一个基于 NIO 的 client-server(客户端服务器)框架,使用它可以快速简单地开发网络应用程序。它极大地简化并简化了 TCP 和 UDP 套接字服务器等网络编程,并且性能以及安全性等很多方面甚至都要更好。支持多种协议如 FTP,SMTP,HTTP 以及各种二进制和基于文本的传统协议。

序列化和反序列化

我们的 Java 对象没办法直接在网络中传输。为了能够让Java 对象在网络中传输我们需要将其序列化为二进制的数据。不仅网络传输的时候需要用到序列化和反序列化,将对象存储到文件、数据库等场景都需要用到序列化和反序列化。


JDK 自带的序列化,只需实现java.io.Serializable 接口即可,不过这种方式不支持跨语言调用并且性能比较差。现在比较常用序列化的有 hessian、kryo、protostuff、Protobuf等

动态代理

RPC 的主要目的就是让我们调用远程方法像调用本地方法一样简单,我们不需要关心远程方法调用的细节比如网络传输。动态代理就可以屏蔽远程方法调用的底层细节。

负载均衡

负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题。

传输协议

我们还需要设计一个私有的 RPC 协议,这个协议是客户端(服务消费方)和服务端(服务提供方)交流的基础。定义需要传输哪些类型的数据, 并规定每一种类型的数据应该占多少字节。这样在接收到二进制数据之后,就可以正确的解析出我们需要的数据,这有一点像密文传输。


一些标准的 RPC 协议包含下面这些内容:

魔数 : 通常是 4 个字节。这个魔数主要是为了筛选来到服务端的数据包,有了这个魔数之后,服务端首先取出前面四个字节进行比对,能够在第一时间识别出这个数据包并非是遵循自定义协议的,也就是无效数据包,为了安全考虑可以直接关闭连接以节省资源。
序列化器编号 :标识序列化的方式,比如是使用 Java 自带的序列化,还是 json,kryo 等序列化方式。
消息体长度 : 运行时计算出来。

基于此,我们实现一款基于 Netty+kryo+Zookeeper的RPC 框架。




常见序列化协议

JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题,而且不支持跨语言调用。JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差。

JDK 自带的序列化方式

只需实现 java.io.Serializable接口即可:

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToString
public class RpcRequest implements Serializable {
	private static final long serialVersionUID = 1905122041950251207L;
	private String requestId;
	private String interfaceName;
	private String methodName;
	private Object[] parameters;
	private Class<?>[] paramTypes;
	private RpcMessageTypeEnum rpcMessageTypeEnum;
}
serialVersionUID 作用
序列化号 serialVersionUID 属于版本控制的作用。反序列化时,会检查serialVersionUID 是否和当前类的 serialVersionUID 一致。如果 serialVersionUID 不一致则会抛出InvalidClassException 异常。强烈推荐每个序列化类都手动指定其serialVersionUID ,如果不手动指定,那么编译器会动态生成默认的 serialVersionUID 。

serialVersionUID 不是被 static 变量修饰了吗?为什么还会被“序列化”?
static 修饰的变量是静态变量,位于方法区,本身是不会被序列化的。 static 变量是属于类的而不是对象。你反序列之后, static 变量的值就像是默认赋予给了对象一样,看着就像是static 变量被序列化,实际只是假象罢了。

如果有些字段不想进行序列化怎么办?
使用transient关键字修饰。transient 只能修饰变量,不能修饰类和方法。transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是0 。static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化。

Kryo

Kryo 是一个高性能的序列化/反序列化工具,由于其变长存储特性并使用了字节码生成机制,拥有较高的运行速度和较小的字节码体积。另外,Kryo 已经是一种非常成熟的序列化实现了,已经在 Twitter、Groupon、Yahoo 以及多个著名开源项目(如 Hive、Storm)中广泛的使用。

Kryo专门针对 Java 语言序列化方式并且性能非常好,像 Protobuf、 ProtoStuff、hessian 这类都是跨语言的序列化方式,如果有跨语言需求的话可以考虑使用。

Github

Protobuf

Protobuf 出自于 Google,性能还比较优秀,也支持多种语言,同时还是跨平台的。就是在使用中过于繁琐,因为你需要自己定义 IDL 文件和生成对应的序列化代码。这样虽然不灵活,但是,另一方面导致protobuf 没有序列化漏洞的风险。

Github

ProtoStuff

protostuff 基于 Google protobuf,但是提供了更多的功能和更简易的用法。虽然更加易用,但是不代表 ProtoStuff 性能更差。

Github

Hessian

Hessian 是一个轻量级的,自定义描述的二进制 RPC 协议。Hessian 是一个比较老的序列化实现了,并且同样也是跨语言的。

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

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

相关文章

白宫敦促开发者弃用C和C++,转而使用“内存安全”的编程语言。

美国白宫国家网络总监办公室&#xff08;ONCD&#xff09;在一份新报告中呼吁开发者停止使用C或C等编程语言&#xff0c;转而使用“内存安全编程语言”&#xff0c;这是美国总统拜登网络安全战略的一部分&#xff0c;旨在保护网络空间的基础构建块。内存安全是指防止因内存访问…

6.WEB渗透测试-前置基础知识-AI应用

内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;5.WEB渗透测试-前置基础知识-常用的dos命令-CSDN博客 1.AI在生活中扮演的角色越来越重要&#xff0c;AI现在可以用来帮我们办许多事情 目前比较好用的AI模型例如Chatgpt&#xff0c;Claude&#xff0c;必应…

推荐一款ssh工具 xshell替代品 electerm

下载地址 electerm&#xff1a; https://electerm.github.io/electerm/ windows版本 产品优势 复制粘贴&#xff0c;可以直接使用ctrlc/v 非常的方便 而且不想xshell 需要账号登陆&#xff0c;有更新弹窗&#xff0c;自身集成了sftp 界面设计更新&#xff0c;比MobaXterm的…

TCP的三次握手和四次挥手 | 查看网络状态

三次握手和四次挥手是在计算机网络中用于建立和终止TCP连接的协议。这两个过程是TCP协议的重要组成部分&#xff0c;确保数据的可靠传输。 三次握手指的是在客户端和服务器之间建立连接时的步骤。具体流程如下&#xff1a; 客户端向服务器发送一个连接请求报文段&#xff08;…

0201sherlock(福尔摩斯)-通过名称寻找媒体账号(地址)-github-开源项目学习

文章目录 一 项目简介二 项目安装和演示1 安装2 演示 三 源码分析1 项目结构2 主程序源代码分析 四 添加自定义网址结语 一 项目简介 二 项目安装和演示 1 安装 # clone the repo $ git clone https://github.com/sherlock-project/sherlock.git# change the working direct…

推荐几款优秀免费开源的导航网站

&#x1f9a9;van-nav 项目地址&#xff1a;van-nav项目介绍&#xff1a;一个轻量导航站&#xff0c;汇总你的所有服务。项目亮点&#xff1a;全平台支持&#xff0c;单文件部署&#xff0c;有配套浏览器插件。效果预览 &#x1f9a9;发现导航 项目地址&#xff1a;nav项目…

【开源项目】自动字幕生成和字幕翻译

文章目录 1. 安装ffmpeg2. 克隆项目3. 配置项目运行环境4. 填写配置文件5. 运行项目&#xff08;仅使用CPU&#xff09; 项目地址&#xff1a;Github - qinL-cdy/auto_ai_subtitle 项目原理&#xff1a;使用ffmpeg提取视频的音频&#xff0c;使用whisper将音频转写为字幕&#…

消息中间件之RocketMQ源码分析(二十三)

Broker的关机恢复机制 概述 Broker关机恢复是指恢复CommitLog、Consume Queue、Index File等数据文件。Broker关机分为正常调用命令关机和异常被迫进程终止关机两种情况。恢复过程的设计目标是使正常停止的进程实现零数据丢失&#xff0c;异常停止的进程实现最少量的数据丢失…

Python:练习:输出int值a占b的百分之几。例如:输入1和4,输出:25%。

案例&#xff1a; 输出int值a占b的百分之几。例如&#xff1a;输入1和4&#xff0c;输出&#xff1a;25%。 思考&#xff1a; 所有的一步步思考&#xff0c;最后综合起来。 首先&#xff0c;确定 输出&#xff0c;那么就用input&#xff0c;而且是int值&#xff0c;所以肯定…

javaee教程郑阿奇,一线互联网架构师筑基必备技能之Java篇

一、什么情况下会发生栈内存溢出&#xff1f; 1、栈是线程私有的&#xff0c;栈的生命周期和线程一样&#xff0c;每个方法在执行的时候就会创建一个栈帧&#xff0c;它包含局部变量表、操作数栈、动态链接、方法出口等信息&#xff0c;局部变量表又包括基本数据类型和对象的引…

【Kubernetes】安装K3S

目录 前言一、原理单体架构高可用架构 二、初始化1.配置yum源2.关掉防火墙3.关掉selinux4. 修改内核参数5.关掉swap交换分区 三、安装master节点1. 安装container2.启动master服务 四、安装node节点五、卸载六、总结 前言 各位小伙伴们&#xff0c;大家好&#xff0c;小涛又来…

【MATLAB源码-第149期】基于MATLAB的2ASK,2FSK,2PSK,2DPSK等相干解调仿真,输出各节点波形。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 2ASK&#xff08;二进制幅移键控&#xff09;、2FSK&#xff08;二进制频移键控&#xff09;、2PSK&#xff08;二进制相移键控&#xff09;和2DPSK&#xff08;二进制差分相移键控&#xff09;是数字调制技术中的基本调制方…

1.2 debug的六种指令的使用,四个通用寄存器

汇编语言 首先进入环境 mount c d:masm //把c挂载在d盘中的masm当中 c: //进入c&#xff0c;进入到编译环境 dir //查看文件&#xff0c;可有可无Debug是DOS、Windows都提供的实模式&#xff08;8086 方式&#xff09;程序的调试工具。使用它可以查看CPU各种寄存器中的内容…

同芯.共赢 | 暴雨服务器亮相AMD EPYC合作伙伴峰会

2月29日&#xff0c;AMD EPYC合作伙伴峰会活动在北京成功举行&#xff0c;暴雨作为AMD重要生态合作伙伴应邀参加。作为AMD开年首场活动&#xff0c;此次活动意义非凡&#xff0c;AMD在现场向合作伙伴分享了AMD数据中心全新产品路线、解决方案以及生态建设领域的最新进展。 AMD是…

centos服务配置springboot服务开机启动

在做后端服务运维时&#xff0c;经常遇到服务器重启时&#xff0c;需要移动一堆后端服务。服务器故障自动重启时&#xff0c;通常无人通知。把springboot服务的jar包配置开机启动太有必要了&#xff0c;虽然不是很复杂&#xff0c;这里记录一下太有必要了。 创建jar包启动和停…

linux下gcc编译时默认是32位还是64位,怎么指定为32or64?

本来是想研究一下long的字节大小sizeof(long)&#xff0c;于是写了代码&#xff1a; #include <stdio.h> int main() {long a 10;printf("%d\n", sizeof(a));return 0; } 我当时使用的是win10系统&#xff0c;使用的是vs 2022&#xff0c;然后对以上代码进行…

嵌入式 Linux 下的 LVGL 移植

目录 准备创建工程修改配置修改 lv_drv_conf.h修改 lv_conf.h修改 main.c修改 Makefile 编译运行更多内容 LVGL&#xff08;Light and Versatile Graphics Library&#xff0c;轻量级通用图形库&#xff09;是一个轻量化的、开源的、在嵌入式系统中广泛使用的图形库&#xff0c…

【刷题】 Leetcode 1022.从根到叶的二进制数之和

刷题 1022.从根到叶的二进制数之和题目描述&#xff1a;思路一&#xff08;dfs深搜万能版&#xff09;思路二 &#xff08;栈迭代巧解版&#xff09;总结 Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&#xff01;下一篇文章见&#xff…

Qt6.8 GRPC功能使用(2)标准 Qt实现客户端

简介 基于之前的文章所说&#xff0c; Qt6.7之后才开始支持客户端、服务端、及双向流&#xff0c;恰好电脑需要重装&#xff0c;看到Qt6.8版本就直接安装了&#xff0c;内容也是使用Qt6.8的版本进行编译的 客户端实现步骤 1. 安装Qt6.8, 包含GRPC功能模块 Qt 6.8安装目录下包…

浅谈XSS简单漏洞xss-labs-master(初级)

一、环境以及xss漏洞简介 网上很多gethub自己下就行 XSS简介&#xff1a; 当用户访问被XSS注入的网页&#xff0c;XSS代码就会被提取出来。用户浏览器就会解析这段XSS代码&#xff0c;也就是说用户被攻击了。 用户最简单的动作就是使用浏览器上网&#xff0c;并且浏览器中有J…