十三章 服务发现组件搭建和注册网关连接

以封装 api-gateway-core 为目的,搭建 SpringBoot Starter 组件,用于服务注册发现的相关内容处理。

设计

13-设计

本章最大目的在于搭建起用于封装网关算力服务的 api-gateway-core 系统为目的,提供网关服务注册发现能力的组件。那么之所以要开发一个这样的组件,也就是 SpringBoot Starter,是因为我们希望把这样的统一公用能力进行一致的管理,如果没有这样的组件服务,那么将需要每一个 SpringBoot 服务都要做类似这样的事情,整体来看就会耗费很大的成本,所以要把这样的功能进行收口。

  • api-gateway-core 是网关的算力服务,api-gateway-center 是网关的注册中心,那么为了把这块服务链接起来,中间则需要一套 api-gateway-engin 网关的引擎,用于启动网关的算力服务。
  • 但由于启动网关的算力服务还需要一些功能的整合,来包装网关算力到注册中心的连接,所以这部分需要整合到 api-gateway-assist 这个辅助组件,它是一个 SpringBoot Starter ,起到包装和连接的作用。

通常我们使用一个公用的 starter 的时候,只需要将相应的依赖添加的Maven的配置文件当中即可,免去了自己需要引用很多依赖类,并且 SpringBoot 会自动进行类的自动配置。而我们自己开发一个 starter 也需要做相应的处理;

  1. SpringBoot 在启动时会去依赖的 starter 包中寻找 resources/META-INF/spring.factories 文件,然后根据文件中配置的 Jar 包去扫描项目所依赖的Jar包,这类似于 Java 的 SPI 机制。
  2. 根据 spring.factories 配置加载 AutoConfigure 类。
  3. 根据 @Conditional 注解的条件,进行自动配置并将 Bean 注入 Spring Context 上下文当中。也可以使用@ImportAutoConfiguration({MyServiceAutoConfiguration.class}) 指定自动配置哪些类。
  4. 日常使用的 Spring 官方的 Starter 一般采取 spring-boot-starter-{name} 的命名方式,如 spring-boot-starter-web 。而非官方的 Starter ,官方建议 artifactId 命名应遵循 {name}-spring-boot-starter 的格式。 例如:door-spring-boot-starter 。

实现

GatewayServiceProperties 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package cn.ray.gateway.assist.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* @author Ray
* @date 2023/5/25 10:14
* @description 网关服务注册配置
*/
@ConfigurationProperties("api-gateway")
public class GatewayServiceProperties {

/** 注册中心地址 */
private String address;
/** 分组ID */
private String groupId;
/** 网关ID */
private String gatewayId;
/** 网关名称 */
private String gatewayName;
/** 网关地址 */
private String gatewayAddress;

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

public String getGroupId() {
return groupId;
}

public void setGroupId(String groupId) {
this.groupId = groupId;
}

public String getGatewayId() {
return gatewayId;
}

public void setGatewayId(String gatewayId) {
this.gatewayId = gatewayId;
}

public String getGatewayName() {
return gatewayName;
}

public void setGatewayName(String gatewayName) {
this.gatewayName = gatewayName;
}

public String getGatewayAddress() {
return gatewayAddress;
}

public void setGatewayAddress(String gatewayAddress) {
this.gatewayAddress = gatewayAddress;
}
}

在 SpringBoot Starter 的组件开发中,需要使用注解 @ConfgurationProperties("'api-gateway") 标记出作为配置的文件类。在类中添加属性信息,这些属性信息就是最后的配置到 yml 中的配置属性。

RegisterGatewayService 网关注册服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package cn.ray.gateway.assist.service;

import cn.hutool.http.HttpUtil;
import cn.ray.gateway.assist.GatewayException;
import cn.ray.gateway.assist.common.Result;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

/**
* @author Ray
* @date 2023/5/25 10:18
* @description 网关注册服务
*/
public class RegisterGatewayService {

private Logger logger = LoggerFactory.getLogger(RegisterGatewayService.class);

public void doRegister(String address, String groupId, String gatewayId, String gatewayName, String gatewayAddress) {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("groupId", groupId);
paramMap.put("gatewayId", gatewayId);
paramMap.put("gatewayName", gatewayName);
paramMap.put("gatewayAddress", gatewayAddress);
String resultStr = HttpUtil.post(address, paramMap, 350);
Result result = JSON.parseObject(resultStr, Result.class);
logger.info("向网关中心注册网关算力服务 gatewayId:{} gatewayName:{} gatewayAddress:{} 注册结果:{}", gatewayId, gatewayName, gatewayAddress, resultStr);
if (!"0000".equals(result.getCode()))
throw new GatewayException("网关服务注册异常 [gatewayId:" + gatewayId + "] 、[gatewayAddress:" + gatewayAddress + "]");
}
}

在 RegisterGatewayService 注册网关类中,利用 hutool 包中的 HTTPUtil#post 调用 api-gateway-center 提供的服务发现接口,向网关中心注册网关算力节点。这是第一步非常重要的关联作用,有了这块逻辑的处理,才能打通整个网关算力和网关注册中心。

GatewayAssistantApplication 应用监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package cn.ray.gateway.assist.applicaton;

import cn.ray.gateway.assist.config.GatewayServiceProperties;
import cn.ray.gateway.assist.service.RegisterGatewayService;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

/**
* @author Ray
* @date 2023/5/25 10:11
* @description 网关应用 与 Spring 链接,调用网关注册和接口拉取
*/
public class GatewayAssistantApplication implements ApplicationListener<ContextRefreshedEvent> {

private GatewayServiceProperties properties;
private RegisterGatewayService registerGatewayService;

public GatewayAssistantApplication(GatewayServiceProperties properties, RegisterGatewayService registerGatewayService) {
this.properties = properties;
this.registerGatewayService = registerGatewayService;
}

@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
// 1. 注册网关服务;每一个用于转换 HTTP 协议泛化调用到 RPC 接口的网关都是一个算力,这些算力需要注册网关配置中心
registerGatewayService.doRegister(properties.getAddress(),
properties.getGroupId(),
properties.getGatewayId(),
properties.getGatewayName(),
properties.getGatewayAddress());
}
}

这段代码是一个网关应用程序的类,它实现了 Spring 框架的ApplicationListener接口,监听ContextRefreshedEvent事件。在 Spring 容器初始化完成后,会触发ContextRefreshedEvent事件,进而调用onApplicationEvent方法。

该类的作用是与 Spring 框架进行连接,并调用网关注册和接口拉取的功能。

构造函数接收两个参数:GatewayServicePropertiesRegisterGatewayServiceGatewayServiceProperties是网关服务的配置属性,RegisterGatewayService是用于注册网关服务的服务类。

onApplicationEvent方法中,通过调用registerGatewayServicedoRegister方法,将网关服务注册到配置中心。方法的参数包括网关服务的地址、组ID、网关ID、网关名称和网关地址等信息。

总体来说,这段代码实现了在 Spring 容器初始化完成后自动注册网关服务的功能。

因为我们开发的是 SpringBoot Starter 组件,与 Spring 关联。所以这里会用到 Spring 提供的监听类 ApplicationListener 监听容器刷新实践后则调用网关注册中心将网关服务注册上去。重复注册则是标记服务启动

GatewayAutoConfig 网关服务自动配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package cn.ray.gateway.assist.config;

import cn.ray.gateway.assist.applicaton.GatewayAssistantApplication;
import cn.ray.gateway.assist.service.RegisterGatewayService;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author Ray
* @date 2023/5/25 10:15
* @description 网关服务配置
*/
@Configuration
@EnableConfigurationProperties(GatewayServiceProperties.class)
public class GatewayAutoConfig {

@Bean
public RegisterGatewayService registerGatewayService() {
return new RegisterGatewayService();
}

@Bean
public GatewayAssistantApplication gatewayApplication(GatewayServiceProperties properties, RegisterGatewayService registerGatewayService) {
return new GatewayAssistantApplication(properties, registerGatewayService);
}

}

测试

ApiTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package cn.ray.gateway.assist.test;

import cn.hutool.http.HttpUtil;
import cn.ray.gateway.assist.common.Result;
import com.alibaba.fastjson.JSON;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

/**
* @author Ray
* @date 2023/5/25 10:31
* @description
*/
public class ApiTest {

@Test
public void test_register_gateway() {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("groupId", "10001");
paramMap.put("gatewayId", "api-gateway-g4");
paramMap.put("gatewayName", "电商配送网关");
paramMap.put("gatewayAddress", "127.0.0.1");

String resultStr = HttpUtil.post("http://localhost:80/wg/admin/config/registerGateway", paramMap, 2000);
System.out.println(resultStr);

Result result = JSON.parseObject(resultStr, Result.class);
System.out.println(result.getCode());
}
}

测试结果

13-测试-1

13-测试-2

思考

  1. 通常像 MyBatis 的 ORM 框架、RPC 的 Dubbo 服务,在接入到 SpringBoot 都是有自己的 Starter 组件的。因为这些组件可以封装一些共性共用的逻辑处理,一次开发处处使用的目的。
  2. 所以我们要做这样的处理,方便在网关引擎中启动网关算力。可以想象这是把算力注入到引擎中的纽带开发
  3. 本章其实就是在实现:通过获取配置文件相应的配置,调用 center 中的注册网关接口将其配置信息持久化到数据库中。