十八章 容器关闭监听和异常管理

把网关在注册和拉取时的异常抛出来,交给容器管理做关闭动作,以及处理网关的服务关闭。

设计

18-设计

在 api-gateway-assist 启动的过程中,我们希望它所发生的一些动作,包括启动中的异常、拉取接口信息的失败以及容器关闭后优雅的处理网关通信的关闭。

  1. 这里需要把网关的注册和拉取配置操作,放到 ApplicationContextAware 接口对应的 setApplicationContext 方法中。这样可以在注册服务以及拉取配置的过程中出现失败情况时,则直接抛异常关闭容器。
  2. 另外这里还需要做一个容器关闭的监听动作 ApplicationListener\ 容器关闭时则把网关中的通信模块下的 Netty 服务也一起关闭掉。

实现

感知容器以及监听关闭事件

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package cn.ray.gateway.assist.applicaton;

import cn.ray.gateway.assist.config.GatewayServiceProperties;
import cn.ray.gateway.assist.domain.model.aggregates.ApplicationSystemRichInfo;
import cn.ray.gateway.assist.domain.model.vo.ApplicationInterfaceMethodVO;
import cn.ray.gateway.assist.domain.model.vo.ApplicationInterfaceVO;
import cn.ray.gateway.assist.domain.model.vo.ApplicationSystemVO;
import cn.ray.gateway.assist.domain.service.RegisterGatewayService;
import cn.ray.gateway.core.mapping.HttpRequestType;
import cn.ray.gateway.core.mapping.HttpStatement;
import cn.ray.gateway.core.session.Configuration;
import com.alibaba.fastjson.JSON;
import io.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;

import java.util.List;

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

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

private GatewayServiceProperties properties;

private RegisterGatewayService registerGatewayService;

private Configuration configuration;

private Channel gatewaySocketServerChannel;

public GatewayAssistantApplication(GatewayServiceProperties properties, RegisterGatewayService registerGatewayService, Configuration configuration, Channel gatewaySocketServerChannel) {
this.properties = properties;
this.registerGatewayService = registerGatewayService;
this.configuration = configuration;
this.gatewaySocketServerChannel = gatewaySocketServerChannel;
}

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

// 2. 拉取网关配置;每个网关算力都会在注册中心分配上需要映射的RPC服务信息,包括;系统、接口、方法
ApplicationSystemRichInfo applicationSystemRichInfo = registerGatewayService.pullApplicationSystemRichInfo(properties.getAddress(), properties.getGatewayId());

List<ApplicationSystemVO> applicationSystemVOList = applicationSystemRichInfo.getApplicationSystemVOList();
for (ApplicationSystemVO system : applicationSystemVOList) {
List<ApplicationInterfaceVO> interfaceList = system.getInterfaceList();
for (ApplicationInterfaceVO interfaceVO : interfaceList) {
// 2.1 创建配置信息加载注册
configuration.registerConfig(system.getSystemId(), system.getSystemRegistry(), interfaceVO.getInterfaceId(), interfaceVO.getInterfaceVersion());
List<ApplicationInterfaceMethodVO> methodList = interfaceVO.getMethodList();
// 2.2 注册系统服务接口信息
for (ApplicationInterfaceMethodVO method : methodList) {
HttpStatement httpStatement = new HttpStatement(
system.getSystemId(),
interfaceVO.getInterfaceId(),
method.getMethodId(),
method.getUri(),
HttpRequestType.valueOf(method.getHttpCommandType()),
method.getParameterType(),
method.isAuth());
configuration.addMapper(httpStatement);
logger.info("网关服务注册映射 系统:{} 接口:{} 方法:{}", system.getSystemId(), interfaceVO.getInterfaceId(), method.getMethodId());
}
}
}
} catch (Exception e) {
logger.error("网关服务启动失败,停止服务。{}", e.getMessage(), e);
throw e;
}
}

@Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
try {
if (gatewaySocketServerChannel.isActive()) {
logger.info("应用容器关闭,Api网关服务关闭。localAddress:{}", gatewaySocketServerChannel.localAddress());
gatewaySocketServerChannel.close();
}
} catch (Exception e) {
logger.error("应用容器关闭,Api网关服务关闭失败", e);
}
}

}

这是一个Java类,名为GatewayAssistantApplication,用于将网关应用与Spring框架连接起来,并进行网关注册和接口拉取的操作。

该类实现了ApplicationContextAware接口和ApplicationListener<ContextClosedEvent>接口,以便获取应用程序上下文和监听应用程序关闭事件。

以下是该类的主要方法和功能:

  • setApplicationContext(ApplicationContext applicationContext): 实现了ApplicationContextAware接口的方法,用于在应用程序上下文加载完毕后执行操作。在该方法中,首先调用registerGatewayService.doRegister()方法进行网关服务注册,然后调用registerGatewayService.pullApplicationSystemRichInfo()方法拉取网关配置信息,并根据配置信息注册系统服务接口信息。
  • onApplicationEvent(ContextClosedEvent contextClosedEvent): 实现了ApplicationListener<ContextClosedEvent>接口的方法,用于监听应用程序关闭事件。在该方法中,检查网关Socket服务通道是否处于活动状态,如果是,则关闭该通道。

该类的构造函数接受四个参数:

  • properties: 网关服务的配置属性,包括地址、组ID、网关ID、网关名称和网关地址等信息。
  • registerGatewayService: 注册网关服务的对象,用于执行网关注册和接口拉取操作。
  • configuration: 网关的配置对象,用于注册配置信息和映射关系。
  • gatewaySocketServerChannel: 网关Socket服务的通道。

整体来说,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
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
package cn.ray.gateway.assist.domain.service;

import cn.hutool.http.HttpUtil;
import cn.ray.gateway.assist.GatewayException;
import cn.ray.gateway.assist.common.Result;
import cn.ray.gateway.assist.domain.model.aggregates.ApplicationSystemRichInfo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
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 = null;
try {
resultStr = HttpUtil.post(address + "/wg/admin/config/registerGateway", paramMap, 2000);
} catch (Exception e) {
logger.error("网关服务注册异常,链接资源不可用:{}", address + "/wg/admin/config/registerGateway");
throw e;
}
Result<Boolean> result = JSON.parseObject(resultStr, new TypeReference<Result<Boolean>>(){});
logger.info("向网关中心注册网关算力服务 gatewayId:{} gatewayName:{} gatewayAddress:{} 注册结果:{}", gatewayId, gatewayName, gatewayAddress, resultStr);
if (!"0000".equals(result.getCode()))
throw new GatewayException("网关服务注册异常 [gatewayId:" + gatewayId + "] 、[gatewayAddress:" + gatewayAddress + "]");
}

public ApplicationSystemRichInfo pullApplicationSystemRichInfo(String address, String gatewayId) {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("gatewayId", gatewayId);
String resultStr = null;
try {
resultStr = HttpUtil.post(address + "/wg/admin/config/queryApplicationSystemRichInfo", paramMap, 2000);
} catch (Exception e) {
logger.error("网关服务拉取异常,链接资源不可用:{}", address + "/wg/admin/config/queryApplicationSystemRichInfo");
throw e;
}
Result<ApplicationSystemRichInfo> result = JSON.parseObject(resultStr, new TypeReference<Result<ApplicationSystemRichInfo>>(){});
logger.info("从网关中心拉取应用服务和接口的配置信息到本地完成注册。gatewayId:{}", gatewayId);
if (!"0000".equals(result.getCode()))
throw new GatewayException("从网关中心拉取应用服务和接口的配置信息到本地完成注册异常 [gatewayId:" + gatewayId + "]");
return result.getData();
}
}

RegisterGatewayService 类所提供的服务需要从注册中心的接口拉取,如果注册中心接口暂时不可用,那么则需要抛出异常。这个异常就是给容器启动过程中的一个通知,以此来决定是否关闭服务。

思考

ApplicationContextAware

在Spring框架中,ApplicationContextAware接口是一个回调接口,用于在Bean实例化后,自动获取应用程序上下文(ApplicationContext)的引用。通过实现该接口,可以在Bean中访问应用程序上下文以获取其他Bean或执行特定的操作。

当一个实现了ApplicationContextAware接口的Bean被Spring容器创建时,Spring会自动调用该Bean的setApplicationContext()方法,并将当前的应用程序上下文作为参数传递进去。通过这个回调方法,Bean就可以持有对应用程序上下文的引用,并可以使用它来访问Spring容器中的其他Bean或执行与应用程序上下文相关的操作。

通过ApplicationContextAware接口,Bean可以实现以下功能:

  1. 访问其他Bean:可以通过应用程序上下文引用访问容器中的其他Bean,获取它们的实例并进行相应的操作,例如调用它们的方法、获取属性值等。
  2. 获取应用程序上下文信息:可以获取应用程序上下文的元数据和属性信息,如应用程序的环境配置、配置文件路径、Bean的定义信息等。
  3. 执行特定的操作:可以在Bean初始化完成后执行一些特定的操作,如注册监听器、添加自定义的Bean后置处理器等。

需要注意的是,使用ApplicationContextAware接口需要谨慎,因为过度使用该接口可能导致代码与Spring容器的耦合增加。应该优先考虑使用依赖注入(Dependency Injection)来获取所需的Bean引用,而不是直接依赖于应用程序上下文。

大体流程

  1. 启动 zookeeper 和真正的服务提供者 api-gateway-test,暴露RPC服务
  2. 启动网关注册中心 api-gateway-center。
  3. 启动 api-gateway-engine ,而因为其中内嵌了 assist 和 core 网关通信组件,所以整个网关引擎工程此时可以充当一个具有自动配置(服务拉取,注册,初始化)的算力节点。(相当于 assist+core 的一个胖jar)
  4. 当用户访问网关监听地址时,HTTP请求会打到 engine 中的 Netty 服务端线程,触发监听事件,并根据 uri 从缓存中取出对应的 Dubbo 配置,向 RPC 服务提供者请求并响应给用户。
  5. 当网关服务启动后,如果前端操作增添接口,后台拿到接口数据需要

    1. 向注册中心 center(相当于数据库)的注册接口发送请求,完成数据库中接口信息的写入。
    2. 获取到 engine 中的交给Spring管理的 Contguration Bean 实例(@Autowired),然后通过手动式编程将当前接口信息加入配置缓存,完成注册。

网关大体流程