06

1. 项目概述

本项目是一个面向开发者的 API 平台,提供 API 接口供开发者调用。用户通过注册登录,可以开通接口调用权限,并可以浏览和调用接口。每次调用都会进行统计,用户可以根据统计数据进行分析和优化。管理员可以发布接口、下线接口、接入接口,并可视化接口的调用情况和数据。本项目侧重于后端,涉及多种编程技巧和架构设计层面的知识。

2. 本期时间点

4d52325dde9a4dbb866cb6d96973194d

3. 本期计划

  1. 补充完整网关的业务逻辑(怎么去操作数据库、怎么复用之前的方法?RPC)
  2. 完善系统、开发一个监控统计功能

4. 网关业务逻辑

**问题:**网关项目比较纯净,没有操作数据库的包、并且还要调用我们之前写过的代码,复制粘贴维护麻烦。

**理想:**直接请求到其他项目的方法。

我们的网关项目遇到一个问题,就是网关项目比较纯净,没有涉及数据库操作的包,但同时需要调用之前我们编写过的代码。尽管复制粘贴一开始并不麻烦,但是随着次数增多以及未来的修改维护,就变得相当繁琐了。

因此,我们的理想情况就是希望能够直接请求 yuapi-backend 项目中的 invokeCount 方法,怎么做呢?在这里,涉及到一个知识点,就是远程过程调用(RPC)。

1. RPC讲解

简单讲一下这个 RPC 是什么,假设你之前没有听说过 RPC,那么想象一下情景:

你在项目 A 中编写了一个非常有用的函数,现在你在项目 B 中也想要使用这个函数。但问题是,项目 A 和项目 B 是独立运行的,它们不共享同一片内存,也不在同一个进程中。那么,你怎么做才能调用项目 A 中的那个函数呢?

1. 怎么调用其他项目的方法?

  1. 复制代码和依赖、环境
  2. HTTP 请求(提供一个接口,供其他项目调用)
  3. RPC
  4. 把公共的代码打个 jar 包,其他项目去引用(客户端 SDK)

进一步说明:

首先是直接复制粘贴别的项目的方法,然而这可能引发环境依赖和代码问题,因为项目之间各自有独特的设置和条件。另一个方法是使用 HTTP 请求,例如,在我们的 yuapi-backend 项目中,有一个 invokeCount 的方法,我们可以将其写到 controller 中并为其创建一个 API 接口,以便其他项目可以调用,这个方法是可行的,但同样需要注意它的限制和适用性。

除此之外,还有其他一些方法,例如 RPC 和微服务等。尤其是 RPC,它与 HTTP 请求的区别是面试中的一个常见问题,稍后会简要解释。此外,还有一种常见的方法是将公共代码打包成一个 jar 包,供其他项目引用。这种方法在 API 开放平台项目中很常见,就像 yuapi-client-sdk 项目的做法一样,将其打包成 jar 包,yuapi-backend 项目和 yuapi-gateway 项目都了引用这个 jar 包。

2. HTTP 请求怎么调用?

  1. 提供方开发一个接口(地址、请求方法、参数、返回值)
  2. 调用方使用 HTTP Client 之类的代码包去发送 HTTP 请求

3. RPC

**作用:**像调用本地方法一样调用远程方法。

和直接 HTTP 调用的区别:

  1. 对开发者更透明,减少了很多的沟通成本。
  2. RPC 向远程服务器发送请求时,未必要使用 HTTP 协议,比如还可以用 TCP / IP,性能更高。(内部服务更适用)。

RPC 调用模型:

039924c50256478f8ca7dfb44f52f661

进一步说明:

RPC 和 HTTP 请求在本质上有一些区别。RPC 相对于 HTTP 请求更加透明,这意味着它在开发者间更具透明度。**这种透明性是如何理解的呢?**一个广泛的定义是,RPC 的目标是使远程方法的调用就像调用本地方法一样,从而实现更为便捷的远程通信。

以 HTTP 请求为例,调用方需要完成诸多步骤。首先,它需要发送 HTTP 请求,使用一个 HTTP 客户端的代码库。以我们之前提到的 yuapi-client-sdk 项目为例,我们可以看到客户端对象是如何使用 HTTPUtil 这个 hutool 工具类的包来发送请求的,这意味着调用方需要编写特定的代码段来发送请求,然后处理返回值,可能还需要解析返回值并封装参数的映射关系等。

d12be2f3fae442dba51b346a1a2c9f54

在调用本地方法时,我们可以直接传递参数给方法,比如接口统计的方法(如下图所示)。我们之前提到的 yuapi-client-sdk 项目里,你需要自行封装 HTTP 请求,将参数打包成一个参数映射(map),然后按照客户端工具类的方式发送请求。此外,你还需要解析返回值,将其中的 code、data 以及 message 等信息提取出来。

0588fdf198fa42ba8cfd69598207a2bb

但是,如果我们使用 RPC 方式,就能够实现与调用本地方法类似的体验。你可以直接指定要传递的参数,并且也能够直接获得返回值,无论是布尔类型还是字符串。RPC 方式不需要像 HTTP 请求那样进行额外的封装,但如果你需要封装,当然也可以根据需求自行处理。

因此,我们可以说 RPC 的主要目标之一是模仿调用本地方法的方式,从而实现远程调用。比方说,现在在 yuapi-backend 项目中,有一个方法写在了这个项目里,那么我们只需一行代码:userInterfaceInfoService.invokeCount,就可以调用该方法。

027e68c42bb04a55a022ffe46ac78c04eb10b2d6158448ae9620836bca4c783c

同样的,如果切换到另一个项目,比如 yuapi-gateway,在这个网关项目中,我们同样可以直接使用这行代码,做到方法调用的无缝切换。

3b5aab4bfc8d47408a9a3cfcabbd3122

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 地址等等。调用方从这里获取信息,以便进行调用。

b0ae0286a1f74ebe96199a63a10ff334

然而,需要注意的是在整个流程中,最终的调用并不是由注册中心来完成的。虽然注册中心会提供信息,但实际上调用方需要自己进行最后的调用动作。注册中心的作用是告诉调用方提供者的地址等信息,然后调用方会根据这些信息来完成最后的调用。

一般情况下,调用方会直接寻找提供者进行调用,而不是依赖注册中心来完成实际的调用过程。注册中心主要的功能是提供地址信息,而并不会承担将调用方所需的内容传送到提供者的角色,整个过程遵循这样的流程。

5. Dubbo 框架

1. Dubbo 框架介绍

接下来,我们已经建立了整个模型的基本理解,那么我们应该如何实现这一切呢?

建议大家去学习一下 Dubbo 框架,它是目前国内非常主流的 RPC 实现框架。当然,还有其他类似的框架,比如 GRPC 和 TRPC 等等,这些都是不同的 RPC 远程调用框架,Dubbo 和 GRPC 是比较知名的。Dubbo 由阿里开发,而 GRPC 是由 Google 开发,TRPC 则是由腾讯开发。

Dubbo 框架在国内学习起来可能更容易些。有同学可能会问,Dubbo 框架需要花多少时间学习?并不需要花费很多时间。如果你只是想入门,跟着直播回放就足够了。理解了 Dubbo 框架的基本思想后,实际使用起来只需要几行代码。当然,在使用过程中可能会遇到一些小问题,但是🐟会带大家克服这些问题,所以不用担心。

最佳的学习方式是阅读 Dubbo 框架的官方文档,会帮助你更好地理解这个框架。—— Dubbo 框架官方文档open in new window

两种使用方式:

  1. Spring Boot 代码(注解 + 编程式):写 Java 接口,服务提供者和消费者都去引用这个接口。
  2. IDL(接口调用语言):创建一个公共的接口定义文件,服务提供者和消费者读取这个文件。优点是跨语言,所有的框架都认识。

Dubbo底层用的是 Triple 协议:Triple 协议open in new window

示例项目学习:

zookeeper 注册中心:通过内嵌的方式运行,更方便。

**启动流程:**最先启动注册中心,先启动服务提供者,再启动服务消费者。

整合运用:

  1. backend 项目作为服务提供者,提供 3 个方法:
    1. 实际情况应该是去数据库中查是否已分配给用户
    2. 从数据库中查询模拟接口是否存在,以及请求方法是否匹配(还可以校验请求参数)
    3. 调用成功,接口调用次数 + 1 invokeCount
  2. gateway 项目作为服务调用者,调用这 3 个方法

ps.**

期间讲解的知识点:

  1. Zookeeper 是什么呢?

有同学学过的话,就知道 Zookeeper 就是我们之前图中所提到的注册中心。Dubbo 的作用就是将你的提供者、调用方和注册中心这三者通过其框架整合在一起,从而完成整个远程过程调用(RPC)。因此,Dubbo 可以被视为一个实现了 RPC 功能的框架,而 Zookeeper 则是一个可作为注册中心的存储, Nacos 也可以作为注册中心**。**

4afdb682f16545a4b456ab919f86fb51
  1. 为什么一定要知道注册中心的地址?

因为我们的服务提供者需要将自己提供的接口方法告知注册中心,所以它必须知道注册中心的地址,这样才能将自己的信息上报给注册中心,这也是为什么我们必须要配置注册中心。同样的道理,我们的消费者调用方也需要知道注册中心的地址,以便将注册中心的地址配置到项目中。

  1. IDL 是什么呢?

IDL(接口定义语言)是一种约定俗成的语言,用于定义接口和数据结构的语法。它是一种人为约定的语言,通过这种语法,可以明确地定义接口和数据的结构,使各方在交流和协作时能够达成一致。这种约定的语言为不同的系统、平台或语言提供了一种统一的描述方式,使得不同环境下的应用程序能够理解和交互。

2. Nacos

1. 快速开始

创建一个 code 目录。

ps.之前已经强调过,在使用各种框架和库时,尽量避免将项目存放在包含中文字符的路径下。一定要注意,路径名称不能包含中文字符,因为如果不小心将项目放在中文路径下,可能会导致各种莫名其妙的错误出现。

3cbcc20485644ae5a783b289aa9f648a

把 yuapi-backend 项目拖到 code 目录下。

073a7345b585429d8b29fc9d82fcd5ad

然后查看 Nacos 的官方文档。 —— Nacos 官方文档open in new window

ef71315d1ffb4a1c86cd5d9379edd374

往下滑,根据提示下载压缩包。

859dcb52b08441048b21da123df8f9a4

来到 github,找到官方推荐的稳定版本 2.1.1。

10dbc13a5997416d92015b8803087984
▼bash

复制代码https://github.com/alibaba/nacos/releases/download/2.1.1/nacos-server-2.1.1.zip

用 macOS M1 的 xdm 要使用 1.4.2 版本,来自鱼友小王提供的解决方法~( ̄▽ ̄)~

dc64aaac0e5d46afb425291687a1e021
https://github.com/alibaba/nacos/releases/download/1.4.2/nacos-server-1.4.2.zip

相关博客:macbook pro m1 芯片,2021版本安装nacos报错踩坑open in new window

d811e73405ef41699b34082a09bb9ca6

macOS M1 的 yuapi-backend、yuapi-gateway 项目的依赖设置为:

fe120cc8bcad436bbdec7d8bd56dd35eb927fac3836245ce84bdd32bbd0d06de

下载完成后,把它解压到 D 盘;

解压完后,把压缩包删掉(它没用了.jpg)。

6a9505ca8d3942b783063ae12b6a1ae4

这里说 2.2.0.1、2.2.1 版本必须修改配置文件,其他版本建议设置,我们是 2.1.1,跳过,看下一个。

190236e09d76447ba309e55cbcd38d65

下一步,它告诉了我们启动服务器的命令,复制命令。

6f05bf14208243b5974989b0b529c319
▼bash

复制代码# Linux/Unix/Mac
sh startup.sh -m standalone

# ubuntu
bash startup.sh -m standalone

# Windows
startup.cmd -m standalone

进入 bin 目录,选中上面的路径栏,输入 cmd,按[Enter]回车进入。

84db4e11da7f4a81b6ec04037918d515

把命令粘贴进去,先以单机模式运行,在 8848 端口启动起来了。

ps.别关掉,按右上角的最小化。

4d08772c50234a1a8826d37905e5efeef1a5002e7e6a45a6ae0197be31e27b9d

2. 整合 Nacos 注册中心

看一下 Dubbo 怎么用 Nacos。 —— Dubbo 配置 Nacos 文档open in new window

540bb0337a304d5bbf7ab97715560b29

要引入依赖,复制依赖。

<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>

粘贴依赖并加载。

79f11b35b14d43fdad0c002b77097f2a

回到 Dubbo 文档,往下滑,它这里提示 Dubbo 3.0.0 及以上版本需要 nacos-client 2.0.0 及以上版本。

11e79a43d2744475bd3d33272d08bc4d

继续往下滑,要进行配置了,复制配置(它这个配置还少了"😊。

4764f9e61bec4372869456415629ee8d

这个配置还要补充点信息,不然它注册不了。

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

粘贴到这里,点击启动。

8808a89b28db4ce0826b3d1acd79f6ea

把 redis 启动后,启动项目。

e99db09cccfd457ba95cb358ae8ab09c

然后把 yuapi-gateway 项目拖到 yuapi-backend 项目中,方便管理。

e8c3871cb8774ec680fb5e58d8efdf37

把 yuapi-gateway 项目用 idea 单独打开。

b4b6ca648209403a865747e6a9c1bc64

yuapi-backend 项目启动成功之后,我们把配置也粘贴到 yuapi-gateway 项目中。

39a81ed4199d43998a4274537ae18a58

再把依赖引进 yuapi-gateway 项目中,点击加载。

f16b228261ce461cb3e7a034b876888e

点击启动,启动成功。

a1568520239a496a8af1ef70392225e9

我们去 Nacos 注册中心看一下,访问 http://自己服务的open in new window IP 地址:8848/nacos/index.html。

b364bd22841c4779a73ba3146c87afaa

默认用户名和密码都是nacos

81eb6db0db2446038d8253fbfb997102

我们现在还没有整合示例,所以这里没有服务。

dfe38797e7994e81a3a074472bf0d5ce

3. 整合示例

先停止这两个项目的运行。

aa3eed2b53fa4329b41bc3248e161a7905aac2050bcd4eb9b7c1ec4199d0aa17

ps.本来🐟讲解 Zookeeper,用了官方的示例,不过它更新的着实快,代码更新,文档也更新🤣我们直接去🐟那,把示例代码下载下来(嘿嘿.jpg) —— 本期后端项目代码open in new window

1de083bde3d64223a3dbba4218d2956d

解压压缩包,进入解压后的项目。

ps.为啥不直接替换掉自己的,咳咳,有点怕🐟设置的依赖和自己的依赖冲突🐶 这翻车时长(瑟瑟发抖.jpg)

10515b25d73f4d34af6fcbbea20b254b

复制 provider 目录。

baf1845438ac49638c68bcfc6c7c7df3

粘贴到 yuapi-backend 项目中。

de2a42e4f04a436c8f79e52a98af781a

在 yuapi-backend 项目中的 MyApplication 添加注解。

a71587bf7f854d67be039d81b00515ed

复制 project 目录。

c7f0508acbc6465395135fb64433c73b

粘贴到 yuapi-gateway 项目中,消费方和服务方都需要 DemoService。

4aa7355313034cdbae956a41fde81c06

复制 YuapiGatewayApplication.java 的代码。

a587a3e5f60841749615107b98ae1b04

替换掉 yuapi-gateway 项目中 YuapiGatewayApplication.java 的代码。

289c50bec64842e3938ba44cce2dca0c
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();
    //    }

}

启动项目。

dc2aa33f9d9a4401b2f4a0a725eb9ffbcaab6c5a3ec246f8868e5809769baf6e

yuapi-gateway 项目报错了,看看报什么错,第一个是因为缓存,这个不用在意,看下一个报错。

567291987f1b4150a88a58e801ad7f1b

第二个是端口被占用,它这边服务自动绑定 22222 端口,但是绑定失败。

936749975248450c8a5d43480d007ca5

yuapi-backend 项目可以启动成功,yuapi-gateway 项目却报错了,还是端口占用,我们也没设置多少端口,怎么就占用了呢?(挠头.jpg)


思考过程:

按照报错信息,我们应该要解除占用端口的服务,解除之后,发现 yuapi-backend 项目停止启动了。

原来,因为我们写 dubbo 端口配置是-1(随机端口),但是这个系统啊,竟然把两个项目都随机成相同的端口🤣

所以我们只需把这两个项目端口配置的端口号定死即可。


把 yuapi-backend 项目的端口设置成 22221,yuapi-gateway 项目设置成 22223;

然后把 yuapi-backend 项目和 yuapi-gateway 项目关闭,然后再打开。

ps.为什么关闭呢?因为修改之后,发现它又默认在 22222 端口启动,可能有缓存的原因,关闭再打开就没事。

317dda77815b4b7598bbbedcef807414dc02a5cad0ec43cfa8efb1ad4a19dc0f

启动 yuapi-backend 项目之后, 再启动 yuapi-gateway 项目。

d320098da9d2460fbb0474855894c6b31ae8972655b148c48c13383b0c3c34aa

控制台成功输出结果,有报错没关系,能输出结果就行。

18baf95929c143a1afb313dded1af5db5ac59e2bcc7347949618a7e0878f0440

去 Nacos 注册中心看一下,访问 http://自己服务的open in new window IP 地址:8848/nacos/index.html,注册上了。

8e655cb55a374480b3ae6cc4cd06fb63

拜拜咯(ㄏ ̄▽ ̄)ㄏbye

4. 问题总结

注意:

  1. 服务接口类必须要在同一个包下,建议是抽象出一个公共项目(放接口、实体类等)
  2. 设置注解(比如启动类的 EnableDubbo、接口实现类和 Bean 引用的注解)
  3. 添加配置
  4. 服务调用项目和提供者项目尽量引入相同的依赖和配置