06
1. 项目概述
本项目是一个面向开发者的 API 平台,提供 API 接口供开发者调用。用户通过注册登录,可以开通接口调用权限,并可以浏览和调用接口。每次调用都会进行统计,用户可以根据统计数据进行分析和优化。管理员可以发布接口、下线接口、接入接口,并可视化接口的调用情况和数据。本项目侧重于后端,涉及多种编程技巧和架构设计层面的知识。
2. 本期时间点
3. 本期计划
- 补充完整网关的业务逻辑(怎么去操作数据库、怎么复用之前的方法?RPC)
- 完善系统、开发一个监控统计功能
4. 网关业务逻辑
**问题:**网关项目比较纯净,没有操作数据库的包、并且还要调用我们之前写过的代码,复制粘贴维护麻烦。
**理想:**直接请求到其他项目的方法。
我们的网关项目遇到一个问题,就是网关项目比较纯净,没有涉及数据库操作的包,但同时需要调用之前我们编写过的代码。尽管复制粘贴一开始并不麻烦,但是随着次数增多以及未来的修改维护,就变得相当繁琐了。
因此,我们的理想情况就是希望能够直接请求 yuapi-backend 项目中的 invokeCount 方法,怎么做呢?在这里,涉及到一个知识点,就是远程过程调用(RPC)。
1. RPC讲解
简单讲一下这个 RPC 是什么,假设你之前没有听说过 RPC,那么想象一下情景:
你在项目 A 中编写了一个非常有用的函数,现在你在项目 B 中也想要使用这个函数。但问题是,项目 A 和项目 B 是独立运行的,它们不共享同一片内存,也不在同一个进程中。那么,你怎么做才能调用项目 A 中的那个函数呢?
1. 怎么调用其他项目的方法?
- 复制代码和依赖、环境
- HTTP 请求(提供一个接口,供其他项目调用)
- RPC
- 把公共的代码打个 jar 包,其他项目去引用(客户端 SDK)
进一步说明:
首先是直接复制粘贴别的项目的方法,然而这可能引发环境依赖和代码问题,因为项目之间各自有独特的设置和条件。另一个方法是使用 HTTP 请求,例如,在我们的 yuapi-backend 项目中,有一个 invokeCount 的方法,我们可以将其写到 controller 中并为其创建一个 API 接口,以便其他项目可以调用,这个方法是可行的,但同样需要注意它的限制和适用性。
除此之外,还有其他一些方法,例如 RPC 和微服务等。尤其是 RPC,它与 HTTP 请求的区别是面试中的一个常见问题,稍后会简要解释。此外,还有一种常见的方法是将公共代码打包成一个 jar 包,供其他项目引用。这种方法在 API 开放平台项目中很常见,就像 yuapi-client-sdk 项目的做法一样,将其打包成 jar 包,yuapi-backend 项目和 yuapi-gateway 项目都了引用这个 jar 包。
2. HTTP 请求怎么调用?
- 提供方开发一个接口(地址、请求方法、参数、返回值)
- 调用方使用 HTTP Client 之类的代码包去发送 HTTP 请求
3. RPC
**作用:**像调用本地方法一样调用远程方法。
和直接 HTTP 调用的区别:
- 对开发者更透明,减少了很多的沟通成本。
- RPC 向远程服务器发送请求时,未必要使用 HTTP 协议,比如还可以用 TCP / IP,性能更高。(内部服务更适用)。
RPC 调用模型:
进一步说明:
RPC 和 HTTP 请求在本质上有一些区别。RPC 相对于 HTTP 请求更加透明,这意味着它在开发者间更具透明度。**这种透明性是如何理解的呢?**一个广泛的定义是,RPC 的目标是使远程方法的调用就像调用本地方法一样,从而实现更为便捷的远程通信。
以 HTTP 请求为例,调用方需要完成诸多步骤。首先,它需要发送 HTTP 请求,使用一个 HTTP 客户端的代码库。以我们之前提到的 yuapi-client-sdk 项目为例,我们可以看到客户端对象是如何使用 HTTPUtil 这个 hutool 工具类的包来发送请求的,这意味着调用方需要编写特定的代码段来发送请求,然后处理返回值,可能还需要解析返回值并封装参数的映射关系等。
在调用本地方法时,我们可以直接传递参数给方法,比如接口统计的方法(如下图所示)。我们之前提到的 yuapi-client-sdk 项目里,你需要自行封装 HTTP 请求,将参数打包成一个参数映射(map),然后按照客户端工具类的方式发送请求。此外,你还需要解析返回值,将其中的 code、data 以及 message 等信息提取出来。
但是,如果我们使用 RPC 方式,就能够实现与调用本地方法类似的体验。你可以直接指定要传递的参数,并且也能够直接获得返回值,无论是布尔类型还是字符串。RPC 方式不需要像 HTTP 请求那样进行额外的封装,但如果你需要封装,当然也可以根据需求自行处理。
因此,我们可以说 RPC 的主要目标之一是模仿调用本地方法的方式,从而实现远程调用。比方说,现在在 yuapi-backend 项目中,有一个方法写在了这个项目里,那么我们只需一行代码:userInterfaceInfoService.invokeCount,就可以调用该方法。
同样的,如果切换到另一个项目,比如 yuapi-gateway,在这个网关项目中,我们同样可以直接使用这行代码,做到方法调用的无缝切换。
RPC 的主要职责就是这个,它的最大作用在于模拟本地方法调用的体验。看上去是请求本地代码,实际上,它可能会请求到其他项目、其他服务器等等。这就是 RPC 的最大价值所在。如果大家不理解,可以想一下刚刚的例子,你只需知道 RPC 最大的优势在于它的透明性,你不需要了解它是如何在 HTTP Client 中怎么封装参数,只需直接调用现成的方法即可,这样可以大大减少沟通成本。
💡 有同学问:feign 不也是动态生成的 httpclient 吗?
Feign 本质上也是动态生成的 HTTP 客户端,但它在调用远程方法时更加精简了 HTTP 请求的步骤。尽管 Feign 使远程调用看起来像是调用本地方法,但实际上与 RPC 仍然有一些微小的区别。虽然两者都可以实现类似的功能,但它们在底层协议上存在差异。
RPC(Remote Procedure Call 远程过程调用) 的一个关键优势在于,它可以使用多种底层协议进行远程调用,而不限于 HTTP 协议。虽然 HTTP 协议可以实现类似的功能,但考虑到性能,RPC 可以选择更原生的协议,如 TCP/IP。而且,网络上还存在其他性能更高的协议,可以根据需要进行选择。
在微服务项目中,对于内部接口,使用 RPC 可能会获得更好的性能。然而,选择使用 Feign 还是 RPC 取决于具体的技术需求,没有绝对的优劣之分。需要注意的是,RPC 和 HTTP 请求可以结合使用,因为 RPC 的底层协议可以是 HTTP,也可以是其他协议,如 TCP/IP 或自定义协议。
综上所述,RPC 和 HTTP 请求是可以互相结合的,但 RPC 在协议的选择上更加灵活。当面试被问到这方面的问题时,首先强调 RPC 的主要作用,然后阐述 RPC 和 HTTP 之间的关系。
让我们简单绘制一张图,来说明 RPC 的工作流程。我们刚刚已经了解了 RPC 的作用以及为什么需要使用 RPC,现在详细说明实现类似本地方法调用的方式来调用远程方法。这需要我们的两个项目,即方法提供者项目和方法调用者项目,来进行一些特定的操作。
在这里,引入一个简单的概念,对于学习过操作系统的同学应该很熟悉。为了实现 RPC,我们需要涉及几个关键角色。
\1. 提供者(Producer/Provider): 首先,我们需要一个项目来提供方法,这个项目被称为提供者。它的主要任务是为其他人提供已经写好的代码,让其他人可以使用。举例来说,我们可以提供一个名为 invokeCount 的方法。
\2. 调用方(Invoker/Consumer): 一旦服务提供者提供了服务,调用方需要能够找到这个服务的位置。这就需要一个存储,用于存储已提供的方法,调用方需要知道提供者的地址和 invokeCount 方法,这里需要一个公共的存储。
\3. 存储: 这是一个公共存储,用于存储提供者提供的方法信息。调用方可以从这个存储中获取提供者的地址和方法信息,例如,提供者的地址可能是 123.123.123.1,而方法是 invokeCount,这些信息会存储在这个存储器中。
调用方可以从存储中获取信息后,就知道调用 invokeCount 方法时需要访问 123.123.123.1,这就是 RPC 的基本流程,这三个角色构成了整个 RPC 模型。
存储有时也会被称为注册中心,它管理着服务信息,包括提供者的 IP 地址等等。调用方从这里获取信息,以便进行调用。
然而,需要注意的是在整个流程中,最终的调用并不是由注册中心来完成的。虽然注册中心会提供信息,但实际上调用方需要自己进行最后的调用动作。注册中心的作用是告诉调用方提供者的地址等信息,然后调用方会根据这些信息来完成最后的调用。
一般情况下,调用方会直接寻找提供者进行调用,而不是依赖注册中心来完成实际的调用过程。注册中心主要的功能是提供地址信息,而并不会承担将调用方所需的内容传送到提供者的角色,整个过程遵循这样的流程。
5. Dubbo 框架
1. Dubbo 框架介绍
接下来,我们已经建立了整个模型的基本理解,那么我们应该如何实现这一切呢?
建议大家去学习一下 Dubbo 框架,它是目前国内非常主流的 RPC 实现框架。当然,还有其他类似的框架,比如 GRPC 和 TRPC 等等,这些都是不同的 RPC 远程调用框架,Dubbo 和 GRPC 是比较知名的。Dubbo 由阿里开发,而 GRPC 是由 Google 开发,TRPC 则是由腾讯开发。
Dubbo 框架在国内学习起来可能更容易些。有同学可能会问,Dubbo 框架需要花多少时间学习?并不需要花费很多时间。如果你只是想入门,跟着直播回放就足够了。理解了 Dubbo 框架的基本思想后,实际使用起来只需要几行代码。当然,在使用过程中可能会遇到一些小问题,但是🐟会带大家克服这些问题,所以不用担心。
最佳的学习方式是阅读 Dubbo 框架的官方文档,会帮助你更好地理解这个框架。—— Dubbo 框架官方文档
两种使用方式:
- Spring Boot 代码(注解 + 编程式):写 Java 接口,服务提供者和消费者都去引用这个接口。
- IDL(接口调用语言):创建一个公共的接口定义文件,服务提供者和消费者读取这个文件。优点是跨语言,所有的框架都认识。
Dubbo底层用的是 Triple 协议:Triple 协议。
示例项目学习:
zookeeper 注册中心:通过内嵌的方式运行,更方便。
**启动流程:**最先启动注册中心,先启动服务提供者,再启动服务消费者。
整合运用:
- backend 项目作为服务提供者,提供 3 个方法:
- 实际情况应该是去数据库中查是否已分配给用户
- 从数据库中查询模拟接口是否存在,以及请求方法是否匹配(还可以校验请求参数)
- 调用成功,接口调用次数 + 1 invokeCount
- gateway 项目作为服务调用者,调用这 3 个方法
ps.**
期间讲解的知识点:
- Zookeeper 是什么呢?
有同学学过的话,就知道 Zookeeper 就是我们之前图中所提到的注册中心。Dubbo 的作用就是将你的提供者、调用方和注册中心这三者通过其框架整合在一起,从而完成整个远程过程调用(RPC)。因此,Dubbo 可以被视为一个实现了 RPC 功能的框架,而 Zookeeper 则是一个可作为注册中心的存储, Nacos 也可以作为注册中心**。**
- 为什么一定要知道注册中心的地址?
因为我们的服务提供者需要将自己提供的接口方法告知注册中心,所以它必须知道注册中心的地址,这样才能将自己的信息上报给注册中心,这也是为什么我们必须要配置注册中心。同样的道理,我们的消费者调用方也需要知道注册中心的地址,以便将注册中心的地址配置到项目中。
- IDL 是什么呢?
IDL(接口定义语言)是一种约定俗成的语言,用于定义接口和数据结构的语法。它是一种人为约定的语言,通过这种语法,可以明确地定义接口和数据的结构,使各方在交流和协作时能够达成一致。这种约定的语言为不同的系统、平台或语言提供了一种统一的描述方式,使得不同环境下的应用程序能够理解和交互。
2. Nacos
1. 快速开始
创建一个 code 目录。
ps.之前已经强调过,在使用各种框架和库时,尽量避免将项目存放在包含中文字符的路径下。一定要注意,路径名称不能包含中文字符,因为如果不小心将项目放在中文路径下,可能会导致各种莫名其妙的错误出现。
把 yuapi-backend 项目拖到 code 目录下。
然后查看 Nacos 的官方文档。 —— Nacos 官方文档
往下滑,根据提示下载压缩包。
来到 github,找到官方推荐的稳定版本 2.1.1。
▼bash
复制代码https://github.com/alibaba/nacos/releases/download/2.1.1/nacos-server-2.1.1.zip
用 macOS M1 的 xdm 要使用 1.4.2 版本,来自鱼友小王提供的解决方法~( ̄▽ ̄)~
https://github.com/alibaba/nacos/releases/download/1.4.2/nacos-server-1.4.2.zip
相关博客:macbook pro m1 芯片,2021版本安装nacos报错踩坑
macOS M1 的 yuapi-backend、yuapi-gateway 项目的依赖设置为:
下载完成后,把它解压到 D 盘;
解压完后,把压缩包删掉(它没用了.jpg)。
这里说 2.2.0.1、2.2.1 版本必须修改配置文件,其他版本建议设置,我们是 2.1.1,跳过,看下一个。
下一步,它告诉了我们启动服务器的命令,复制命令。
▼bash
复制代码# Linux/Unix/Mac
sh startup.sh -m standalone
# ubuntu
bash startup.sh -m standalone
# Windows
startup.cmd -m standalone
进入 bin 目录,选中上面的路径栏,输入 cmd,按[Enter]回车进入。
把命令粘贴进去,先以单机模式运行,在 8848 端口启动起来了。
ps.别关掉,按右上角的最小化。
2. 整合 Nacos 注册中心
看一下 Dubbo 怎么用 Nacos。 —— Dubbo 配置 Nacos 文档
要引入依赖,复制依赖。
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>3.0.9</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>2.1.0</version>
</dependency>
粘贴依赖并加载。
回到 Dubbo 文档,往下滑,它这里提示 Dubbo 3.0.0 及以上版本需要 nacos-client 2.0.0 及以上版本。
继续往下滑,要进行配置了,复制配置(它这个配置还少了"😊。
这个配置还要补充点信息,不然它注册不了。
ps.因为它只配置了注册中心的地址,但没有提供其他必要的配置信息,例如应用名称、协议、服务提供者/消费者等。Dubbo 的配置需要包含完整的信息,以便正确地启动和配置服务。
▼properties
复制代码# 以下配置指定了应用的名称、使用的协议(Dubbo)、注册中心的类型(Nacos)和地址
dubbo:
application:
# 设置应用的名称
name: dubbo-springboot-demo-provider
# 指定使用 Dubbo 协议,且端口设置为 -1,表示随机分配可用端口
protocol:
name: dubbo
port: -1
registry:
# 配置注册中心为 Nacos,使用的地址是 nacos://localhost:8848
id: nacos-registry
address: nacos://localhost:8848
粘贴到这里,点击启动。
把 redis 启动后,启动项目。
然后把 yuapi-gateway 项目拖到 yuapi-backend 项目中,方便管理。
把 yuapi-gateway 项目用 idea 单独打开。
yuapi-backend 项目启动成功之后,我们把配置也粘贴到 yuapi-gateway 项目中。
再把依赖引进 yuapi-gateway 项目中,点击加载。
点击启动,启动成功。
我们去 Nacos 注册中心看一下,访问 http://自己服务的 IP 地址:8848/nacos/index.html。
默认用户名和密码都是nacos
。
我们现在还没有整合示例,所以这里没有服务。
3. 整合示例
先停止这两个项目的运行。
ps.本来🐟讲解 Zookeeper,用了官方的示例,不过它更新的着实快,代码更新,文档也更新🤣我们直接去🐟那,把示例代码下载下来(嘿嘿.jpg) —— 本期后端项目代码
解压压缩包,进入解压后的项目。
ps.为啥不直接替换掉自己的,咳咳,有点怕🐟设置的依赖和自己的依赖冲突🐶 这翻车时长(瑟瑟发抖.jpg)
复制 provider 目录。
粘贴到 yuapi-backend 项目中。
在 yuapi-backend 项目中的 MyApplication 添加注解。
复制 project 目录。
粘贴到 yuapi-gateway 项目中,消费方和服务方都需要 DemoService。
复制 YuapiGatewayApplication.java 的代码。
替换掉 yuapi-gateway 项目中 YuapiGatewayApplication.java 的代码。
package com.yupi.yuapigateway;
import com.yupi.project.provider.DemoService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Service;
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class})
@EnableDubbo
@Service
public class YuapiGatewayApplication {
@DubboReference
private DemoService demoService;
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(YuapiGatewayApplication.class, args);
YuapiGatewayApplication application = context.getBean(YuapiGatewayApplication.class);
String result = application.doSayHello("world");
String result2 = application.doSayHello2("world");
System.out.println("result: " + result);
System.out.println("result: " + result2);
}
public String doSayHello(String name) {
return demoService.sayHello(name);
}
public String doSayHello2(String name) {
return demoService.sayHello2(name);
}
// @Bean
// public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
// return builder.routes()
// .route("tobaidu", r -> r.path("/baidu")
// .uri("https://www.baidu.com"))
// .route("toyupiicu", r -> r.path("/yupiicu")
// .uri("http://yupi.icu"))
// .build();
// }
}
启动项目。
yuapi-gateway 项目报错了,看看报什么错,第一个是因为缓存,这个不用在意,看下一个报错。
第二个是端口被占用,它这边服务自动绑定 22222 端口,但是绑定失败。
yuapi-backend 项目可以启动成功,yuapi-gateway 项目却报错了,还是端口占用,我们也没设置多少端口,怎么就占用了呢?(挠头.jpg)
思考过程:
按照报错信息,我们应该要解除占用端口的服务,解除之后,发现 yuapi-backend 项目停止启动了。
原来,因为我们写 dubbo 端口配置是-1
(随机端口),但是这个系统啊,竟然把两个项目都随机成相同的端口🤣
所以我们只需把这两个项目端口配置的端口号定死即可。
把 yuapi-backend 项目的端口设置成 22221,yuapi-gateway 项目设置成 22223;
然后把 yuapi-backend 项目和 yuapi-gateway 项目关闭,然后再打开。
ps.为什么关闭呢?因为修改之后,发现它又默认在 22222 端口启动,可能有缓存的原因,关闭再打开就没事。
启动 yuapi-backend 项目之后, 再启动 yuapi-gateway 项目。
控制台成功输出结果,有报错没关系,能输出结果就行。
去 Nacos 注册中心看一下,访问 http://自己服务的 IP 地址:8848/nacos/index.html,注册上了。
拜拜咯(ㄏ ̄▽ ̄)ㄏbye
4. 问题总结
注意:
- 服务接口类必须要在同一个包下,建议是抽象出一个公共项目(放接口、实体类等)
- 设置注解(比如启动类的 EnableDubbo、接口实现类和 Bean 引用的注解)
- 添加配置
- 服务调用项目和提供者项目尽量引入相同的依赖和配置