查看原文
其他

这次,带大家造个轮子!

鱼皮 程序员鱼皮 2024-03-18

大家好,我是程序员鱼皮。

年前刚刚做完 代码生成器共享平台项目 的教程,现在我又要出新项目了。

之前做的项目基本都是和业务相关的,但这次我要带大家做一个轮子类项目 —— 从 0 到 1 开发 RPC 框架。

这个项目的教程鱼皮已经写完了,3 月 15 号前会全部发布。

为什么要带做这个项目?

很多同学听到 “开发框架” 可能会有点胆怯,但其实开发 RPC 框架并不难,最多几个小时 就能学会核心流程!能够快速给简历增加一个区别于增删改查的框架项目。

最主要的是,开发 RPC 框架涉及很多常用的技术知识点、还能学习到很多架构设计方面的思路和技巧。

因此,强烈建议所有后端方向的同学,动手做个自己的 RPC 框架!

值得一提的是,我在决定做这个项目前,已经完整地看了好几套网上主流的 RPC 框架教程,但或多或少有些我觉得没讲明白的地方、或者有更好的实现方式。所以为了帮助大家更快学习,我还是决定用自己的方式做套教程。

当然,这也是我原创项目教程系列的一部分。

鱼皮原创项目教程系列:https://yuyuanweb.feishu.cn/wiki/SePYwTc9tipQiCktw7Uc7kujnCd

本项目代码开源:https://github.com/liyupi/yu-rpc

这篇文章,我就先给大家分享一下 RPC 的基本概念,以及 RPC 框架的设计实现思路。感兴趣的同学可以 加入编程导航 学习,也可以自己阅读源码学习。

一、基本概念

什么是 RPC?

专业定义:RPC(Remote Procedure Call)即远程过程调用,是一种计算机通信协议,它允许程序在不同的计算机之间进行通信和交互,就像本地调用一样。

简单理解,新开了一家卖鱼皮的熟食店,现在你作为消费者想要把鱼皮购买到家。如果是以前,你只能自己跑腿到线下店铺购买,耗时耗力。但现在有了手机、网络、外卖平台,你只需要在家动动手指,就能点个外卖让骑手把鱼皮配送到家,你不需要关注网络是怎么传输的、外卖平台是怎么操作的、骑手小哥是怎么配送的,只负责享受鱼皮就行了。

为什么需要 RPC?

回到 RPC 的概念,RPC 允许一个程序(称为服务消费者)像调用自己程序的方法一样,调用另一个程序(称为服务提供者)的接口,而不需要了解数据的传输处理过程、底层网络通信的细节等。这些都会由 RPC 框架帮你完成,使得开发者可以轻松调用远程服务,快速开发分布式系统。

举个例子,现在有个项目 A 提供了点餐服务,项目 B 需要调用点餐服务完成下单。

点餐服务和接口的示例伪代码如下:

interface OrderService {
    // 点餐,返回 orderId
    long order(参数1, 参数2, 参数3);
}

如果没有 RPC 框架,项目 B 怎么调用项目 A 的服务呢?

首先,由于项目 A 和项目 B 都是独立的系统,不能像 SDK 一样作为依赖包引入。那么就需要项目 A 提供 web 服务,并且编写一个点餐接口暴露服务,比如访问 http://yupi.icu 就能调用点餐服务;然后项目 B 作为服务消费者,需要自己构造请求,并通过 HttpClient 请求上述地址。如果项目 B 需要调用更多第三方服务,每个服务和方法的调用都编写一个 HTTP 请求,那么会非常麻烦!

示例伪代码如下:

url = "http://yupi.icu"
req = new Req(参数1, 参数2, 参数3)
res = httpClient.post(url).body(req).execute()
orderId = res.data.orderId

而有了 RPC 框架,项目 B 可以通过一行代码完成调用!

示例伪代码如下:

orderId = orderService.order(参数1, 参数2, 参数3)

看起来就跟调用自己项目的方法没有任何区别!是不是很丝滑?

二、RPC 框架实现思路

基本设计

RPC 框架为什么能帮我们简化调用?如何实现一个 RPC 框架呢?

其实很简单,开局一张图,有服务消费者和服务提供者两个角色:

消费者想要调用提供者,就需要提供者启动一个 web 服务,然后通过 请求客户端发送 HTTP 或者其他协议的请求来调用。

比如请求 yupi.icu/order 地址后,提供者会调用 orderService 的 order 方法:

但如果提供者提供了多个服务和方法,每个接口和方法都要单独写一个接口?消费者要针对每个接口写一段 HTTP 调用的逻辑么?

其实可以提供一个统一的服务调用接口,通过 请求处理器 根据客户端的请求参数来进行不同的处理、调用不同的服务和方法。

可以在服务提供者程序维护一个 本地服务注册器,记录服务和对应实现类的映射。

举个例子,消费者要调用 orderService 服务的 order 方法,可以发送请求,参数为 service=orderService,method=order,然后请求处理器会根据 service 从服务注册器中找到对应的服务实现类,并且通过 Java 的反射机制调用 method 指定的方法。

需要注意的是,由于 Java 对象无法直接在网络中传输,所以要对传输的参数进行 序列化反序列化

为了简化消费者发请求的代码,实现类似本地调用的体验。可以基于代理模式,为消费者要调用的接口生成一个代理对象,由代理对象完成请求和响应的过程。

所谓代理,就是有人帮你做一些事情,不用自己操心。

至此,一个最简易的 RPC 框架架构图诞生了:

上图中的虚线框部分,就是 RPC 框架需要提供的模块和能力。

扩展设计

虽然上述设计已经跑通了基本调用流程,但离一个完备的 RPC 框架还有很大的差距,让我们带着问题来进一步完善下架构设计。

1、服务注册发现

问题 1:消费者如何知道提供者的调用地址呢?

类比生活场景,我们点外卖时,外卖小哥如何知道我们的地址和店铺的地址?肯定是买家和卖家分别填写地址,由平台来保存的。因此,我们需要一个 注册中心,来保存服务提供者的地址。消费者要调用服务时,只需从注册中心获取对应服务的提供者地址即可。

架构图如下:

一般用现成的第三方注册中心,比如 Redis、Zookeeper 即可。

2、负载均衡

问题 2:如果有多个服务提供者,消费者应该调用哪个服务提供者呢?

我们可以给服务调用方增加负载均衡能力,通过指定不同的算法来决定调用哪一个服务提供者,比如轮询、随机、根据性能动态调用等。

架构图如下:

3、容错机制

问题 3:如果服务调用失败,应该如何处理呢?

为了保证分布式系统的高可用,我们通常会给服务的调用增加一定的容错机制,比如失败重试、降级调用其他接口等等。

架构图如下:

4、其他

除了上面几个经典设计外,如果想要做一个优秀的 RPC 框架,还要考虑很多问题。

比如:

  • 服务提供者下线了怎么办?需要一个失效节点剔除机制。
  • 服务消费者每次都从注册中心拉取信息,性能会不会很差?可以使用缓存来优化性能。
  • 如何优化 RPC 框架的传输通讯性能?比如选择合适的网络框架、自定义协议头、节约传输体积等。
  • 如何让整个框架更利于扩展?比如使用 Java 的 SPI 机制、配置化等等。

所以,完成 RPC 项目并不难,但做一个完美的 RPC 项目却是难于上青天啊!



总结一下,我们可以通过做一个 RPC 项目学习到网络、序列化、代理、服务注册发现、负载均衡、容错、可扩展设计等知识,相信完成项目后会收获满满。

最后,感兴趣的同学,可以访问:https://yupi.icu,加入编程导航学习所有鱼皮的原创项目~

👇🏻 点击下方阅读原文,获取鱼皮往期编程干货。

往期推荐

今年,我要做这几件大事!

我做了个工具,几秒就能生成项目!

挺看好的一位前端学妹,顶峰见!

项目启动失败?不用慌!

刚入职大厂,老板让我转语言。。

拒绝拖延!我是怎么提高行动力的?

继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存