浅入浅出 RPC

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介:

随着近几年分布式、微服务架构的火热,RPC 在开发工作中使用的越来越多,也变的越来越重要。 作为一个学生,在学校接触到的大多都是 SSM 这类单体应用,但实习后发现,基本是接触不到从 0 到 1 的项目的,更多的是在为整个大系统的某个小模块添砖加瓦。因此,模块与模块之间的通信就变得异常重要。

集群、微服务、分布式

《道德经》是老子的宇宙生成论,其中“一生二,二生三”广为流传,对于一个软件系统来说,笔者认为这句话也同样适用。所谓一,便是系统的业务需求,无论何人,其编写的每行代码最后都是为了服务业务,或是实现业务功能,或是提升业务性能,最终目的均无法逃离业务。一般意义上,一个公司的业务系统发展脉络基本都是类似的:从单个应用到多个应用,从本地调用到远程调用,随着业务规模的发展,需要对远程服务进行高效的资源管理。于是分布式、集群、微服务等“银弹”便应运而生。

在欧洲民间传说的影响下,银弹往往被描绘成具有驱魔功效的武器。 后来也被比喻为具有极端有效性的解决方法。不过佛瑞德·布鲁克斯所发表一篇关于软件工程的论文中提到在软件工程领域是 没有银弹的,复杂的软件工程问题无法靠简单的答案来解决

为了分散业务能力,出现了“微服务”;为了分散机器压力,出现了“集群”和“分布式”。那这三者有何关联?我们以一张图来说明:

某大型系统下有众多功能,如订单、视频、物流等,项目初期可能是写在一个大的工程里,部署在一台机子上,后来业务发展了,每个子功能都变得相当复杂,用 IDE 打开这个项目都要花好久,为了方便开发,开发团队将每个功能分开,并起名为“服务”。每个服务可以操作自己的数据库、缓存等,也可以在本机与其他服务通信(这时项目仍然部署在一台机子上)。再后来,一个 DB、Redis 已经没办法满足这个服务的需求,所以又将单个 DB 扩展成 DB 集群,单个 Redis 扩展成 Redis 集群以此来分担机器的压力。再后来,这些服务所在服务器的性能被压榨的一滴也没有了,没办法,只能将这些服务一个个的分在不同的机器上,这就是“分布式”

由此可见,集群,是在多台机器上部署相同的程序,对于集群内部而言,每台机器是一个不同的节点。但对于集群外部(调用方)而言,集群就是一个整体,操作起来就和操作单个数据库、单个 Redis 没有任何区别。对于整个项目来说,如果集群中某个节点挂了,整个集群仍然可以正常工作,这是一种纵向的扩展

而分布式,是指在多台机器上部署不同的模块。这些模块原本可以放在一台机器上,这叫中心化,一旦这台机器崩溃,上面所有的服务就会崩溃,整个项目也就崩溃了。因此我们可以将系统横向拆分成多个服务后部署到不同的服务器上,如果一台机器崩溃,虽然这台机器上的服务也会崩溃,但不至于导致整个系统发生崩溃,这叫去中心化

所以随着业务的发展,微服务、集群、分布式这些名词的出现是很有必要的。

RPC 的三个问题

上面我们用了一定篇幅解释了微服务、集群、分布式这些比较火的名词,接下来我们回到本文的主角——RPC。

RPC(Remote Procedure Call),即远程过程调用。不同于本地调用,函数与函数之间同属于同一块内存空间,如需调用某个函数,只需要找到所在内存地址即可。远程调用,通俗地说,便是有两台服务器 A,B,一个应用部署在 A 服务器上,想要调用 B 服务器上应用提供的函数/方法,需要通过网络来表达调用的语义和传达调用的数据。

知道了 RPC 是什么,以及为什么需要 RPC 后,接下来我们就要看看如何实现 RPC 了。文末我会给出一个简单的用 Java 实现 RPC 的 demo,这里先从一个有趣例子出发给出需要解决的三个问题:Call ID 映射、序列化和反序列化、网络传输

从一个有趣的例子出发

笔者之前写过一篇《从找对象到多线程》,文中以找对象这个例子出发,介绍了线程,这次就让我们开一个线程来看一下远程调用 RPC 吧。

笔者的好友在一个男生如云的工科学校,机缘巧合下,喜欢上一个隔壁学校的妹子,终于有一天他决定告白。所以,第一步就是要知道那个女生所在的学校、年级、班级、姓名等相关信息,确定 到那个人,这个过程就是Call ID 映射。由于疫情的原因,虽然各自都开学了,但都被学校强制封闭性管理,无法直接见面,因此,男生就想着用 情书 的方式表达自己的爱慕之意,这个过程就是 序列化。男生想着,只要女生收到情书后便能 理解 自己的爱慕之情,就会和自己在一起了,这就是反序列化。剩下的就是如何将情书 送过去 了。可选的方式有很多,比如找快递小哥送、发微信、发邮件之类的,只要能将信息传送过去就可以。这个过程就是网络传输

Call ID 映射

不知道上面的例子有没有很好的解释Call ID 映射、序列化和反序列化、网络传输是什么东西。将上述例子类比到项目中,我们就能很好的理解为什么需要解决这些问题了。

类比本地调用函数,我们需要知道函数名,Call ID 就类似于这样的标志,只有这样才知道你需要的调用的是什么。如果没有 Call ID,我们就无法得知需要调用的方法是什么。

所以,在 RPC 中,所有的函数都必须有自己的一个 ID。这个 ID 在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个 ID。然后我们还需要在客户端和服务端分别维护一个 {函数 - >Call ID} 的对应表。两者的表不一定需要完全相同,但相同的函数对应的 Call ID 必须相同。当客户端需要进行远程调用时,它就查一下这个表,找出相应的 Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。

序列化和反序列化

序列化可以简单理解为对象 –> 字节的过程,同理,反序列化则是相反的过程。这一过程的目的可以理解为转义,然后方便传输,就和上文例子中的把爱慕之情->文字(情书)也是为了方便传输。因为网络传输只认字节,所以互信的过程依赖于序列化与反序列化。

我们知道,在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用 C++,客户端用 Java 或者 Python)。这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。

常见的序列化方式有 JDK 自带序列化(Serializable 接口),HESSIAN 序列化,Kryo 序列化等。后面我们可以详细聊一聊这些序列化方式。

网络传输

不管采取什么样的序列化方式,最终目的都是为了方便传输,所有的数据都需要通过网络传输,因此 RPC 的实现就需要有一个网络传输层。

网络传输层需要把 Call ID 和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。因此,它所使用的协议其实是不限的,能完成传输就行。

常见的有 HTTP、TCP、当然 UDP 也是可以用于 RPC 的。至于为什么已经有了 HTTP 传输协议,为什么许多 RPC 框架还是会使用 TCP,之后我会单独写一篇文章谈谈我自己的看法。

一个简单的 RPC 实现

为了直观的感受 RPC 通信,笔者实现了一个简易的 RPC 通信的 demo,开两个 terminal 分别执行 java -jar server-0.0.1-SNAPSHOT.jarjava -jar client-0.0.1-SNAPSHOT.jar 便可以在控制台看到相应的输出。

server

client

这时,我们再开另一个 terminal 执行 curl http://127.0.0.1:4321/getUserInfo 来模拟请求,我们可以得到如下的响应 body:

➜ curl http://127.0.0.1:4321/getUserInfo
{"sex":0,"name":"name","id":1,"schoolName":"Sunny School","email":"name@sample.com","age":19}

这时我们再来看 server 和 client 的输出有什么变化

server

client

由此我们看见,我们请求 client 的接口,进而转发到 server 上,如果以后想要扩展需要的服务,只需要多加一个服务的 jar 包就可以了(当然这是最简单的实现)。

如果想要了解具体实现的,可以点击下面链接获取代码 👇

https://github.com/YueYongDev/rpcdemo/tree/master

该项目简化了 RPC 中的一些操作,例如 Call ID 映射直接通过指定字符串来实现,实际项目中是通常会有一个配置中心负责持久化调用的 ip、端口、函数名、参数等信息。为了简化通信,使用了 HTTP 作为网络传输协议,通信框架采用 OkHttp。另外,本项目涉及到一些前置知识,例如动态代理、自定义注解等,有兴趣的小伙伴也可以学习了解下。

以上就是本篇文章的全部内容了,如果觉得文章对你有所帮助,不妨给个赞支持一下。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
4月前
|
Java 应用服务中间件 API
干翻RPC系列之HesssionRPC:HesssionRPC的开发体验和源码分析
干翻RPC系列之HesssionRPC:HesssionRPC的开发体验和源码分析
|
7月前
|
弹性计算 Java Unix
搭稳Netty开发的地基,用漫画帮你分清同步异步阻塞非阻塞
Netty Netty是一款非常优秀的网络编程框架,是对NIO的二次封装,本文将重点剖析Netty客户端的启动流程,深入底层了解如何使用NIO编程客户端。 Linux网络编程5种IO模型 根据UNIX网络编程对于IO模型的分类,UNIX提供了5种IO模型,分别是 阻塞IO 、 非阻塞IO、 IO复用 、 信号驱动IO 、 异步IO 。这几种IO模型在《UNIX网络编程》中有详解,这里作者只简单介绍,帮助大家回忆一下这几种模型。 对于Linux来说,所有的操作都是基于文件的,也就是我们非常熟悉的fd,在缺省的情况下,基于文件的操作都是 阻塞的 。下面就通过系统调用 recvfrom 来回顾下
63 0
|
10月前
|
负载均衡 网络协议 Dubbo
卷起来了!手把手带你写一个中高级程序员必会的分布式RPC框架
什么是RPC? 远程服务调用 官方:一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想 通俗一点:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样。 市面上常见的rpc框架:dobbo,springCloud,gRPC...
|
11月前
|
存储 缓存 负载均衡
计网 - 怎样实现 RPC 框架
计网 - 怎样实现 RPC 框架
77 0
|
Java API 定位技术
Java后台专业术语
OOD(Object Oriented Design):面向对象设计 OOA(Object Oriented Analysis):面向对象分析
84 0
|
编解码 运维 负载均衡
深入浅出RPC框架|青训营笔记
由于课程涉及到的RPC知识需要自己对其有较为全面的理解后才能比较好的get到课程中提及的各种框架设计的点,因此我建议阅读Kitex框架的源码,再结合课程目录去体会Kitex设计的初衷。
153 0
深入浅出RPC框架|青训营笔记
|
负载均衡 Dubbo JavaScript
架构设计91-闲聊03-我为什么开始不推荐RPC
架构设计91-闲聊03-我为什么开始不推荐RPC
126 0
|
分布式计算 Dubbo 网络协议
RPC学习笔记初遇篇(一)
RPC学习笔记初遇篇(一)
RPC学习笔记初遇篇(一)