二十一 应用服务接口注册到注册中心

封装采集到的RPC应用服务接口信息,随着应用向网关中心注册应用接口。

设计

21-设计

每一个提供 HTTP 接口的 RPC 应用服务,都需要基于引入的 SDK 组件,采集自身的接口向网关中心注册。因为每一个 RPC 服务本身是在 RPC 注册中心维护的,具备负载均衡的能力。所以通常向网关中心注册的都是 RPC 的接口描述信息,不过网关中心可以在这个过程记录上 RPC 接口的总数以及 IP 信息。这里暂时不需要,所以不提供注册。

结合第20章采集到的服务信息,这里把这些服务信息向网关中心注册。

通常提供服务接口的实现类,只会有一个接口以及对应的实现类。如果有多个接口会抛出异常提醒。

实现

注册服务

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
package cn.ray.gateway.sdk.domain.service;

import cn.hutool.http.HttpUtil;
import cn.ray.gateway.sdk.GatewayException;
import cn.ray.gateway.sdk.common.Result;
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/30 09:32
* @description 网关中心服务
*/
public class GatewayCenterService {

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

public void doRegisterApplication(String address, String systemId, String systemName, String systemType, String systemRegistry) {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("systemId", systemId);
paramMap.put("systemName", systemName);
paramMap.put("systemType", systemType);
paramMap.put("systemRegistry", systemRegistry);
String resultStr;
try {
resultStr = HttpUtil.post(address + "/wg/admin/register/registerApplication", paramMap, 2000);
} catch (Exception e) {
logger.error("应用服务注册异常,链接资源不可用:{}", address + "/wg/admin/register/registerApplication");
throw e;
}
Result<Boolean> result = JSON.parseObject(resultStr, new TypeReference<Result<Boolean>>() {
});
logger.info("向网关中心注册应用服务 systemId:{} systemName:{} 注册结果:{}", systemId, systemName, resultStr);
if (!"0000".equals(result.getCode()) && !"0003".equals(result.getCode())) {
throw new GatewayException("注册应用服务异常 [systemId:" + systemId + "] 、[systemRegistry:" + systemRegistry + "]");
}
}

public void doRegisterApplicationInterface(String address, String systemId, String interfaceId, String interfaceName, String interfaceVersion) {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("systemId", systemId);
paramMap.put("interfaceId", interfaceId);
paramMap.put("interfaceName", interfaceName);
paramMap.put("interfaceVersion", interfaceVersion);
String resultStr;
try {
resultStr = HttpUtil.post(address + "/wg/admin/register/registerApplicationInterface", paramMap, 2000);
} catch (Exception e) {
logger.error("应用服务接口注册异常,链接资源不可用:{}", address + "/wg/admin/register/registerApplicationInterface");
throw e;
}
Result<Boolean> result = JSON.parseObject(resultStr, new TypeReference<Result<Boolean>>() {
});
logger.info("向网关中心注册应用接口服务 systemId:{} interfaceId:{} interfaceName:{} 注册结果:{}", systemId, interfaceId, interfaceName, resultStr);
if (!"0000".equals(result.getCode()) && !"0003".equals(result.getCode())) {
throw new GatewayException("向网关中心注册应用接口服务异常 [systemId:" + systemId + "] 、[interfaceId:" + interfaceId + "]");
}
}

public void doRegisterApplicationInterfaceMethod(String address,
String systemId,
String interfaceId,
String methodId,
String methodName,
String parameterType,
String uri,
String httpCommandType,
Integer auth) {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("systemId", systemId);
paramMap.put("interfaceId", interfaceId);
paramMap.put("methodId", methodId);
paramMap.put("methodName", methodName);
paramMap.put("parameterType", parameterType);
paramMap.put("uri", uri);
paramMap.put("httpCommandType", httpCommandType);
paramMap.put("auth", auth);

String resultStr;
try {
resultStr = HttpUtil.post(address + "/wg/admin/register/registerApplicationInterfaceMethod", paramMap, 2000);
} catch (Exception e) {
logger.error("应用服务接口注册方法异常,链接资源不可用:{}", address + "/wg/admin/register/registerApplicationInterfaceMethod");
throw e;
}
Result<Boolean> result = JSON.parseObject(resultStr, new TypeReference<Result<Boolean>>() {
});
logger.info("向网关中心注册应用接口方法服务 systemId:{} interfaceId:{} methodId:{} 注册结果:{}", systemId, interfaceId, methodId, resultStr);
if (!"0000".equals(result.getCode()) && !"0003".equals(result.getCode()))
throw new GatewayException("向网关中心注册应用接口方法服务异常 [systemId:" + systemId + "] 、[interfaceId:" + interfaceId + "]、[methodId:]" + methodId + "]");
}
}

首先需要提供一个网关中心服务的实现类 GatewayCenterService 来包装注册应用、注册接口、注册方法。用于向网关中心注册服务信息。

接口注册

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
package cn.ray.gateway.sdk.application;

import cn.ray.gateway.sdk.GatewayException;
import cn.ray.gateway.sdk.annotation.ApiProducerClazz;
import cn.ray.gateway.sdk.annotation.ApiProducerMethod;
import cn.ray.gateway.sdk.config.GatewaySDKServiceProperties;
import cn.ray.gateway.sdk.domain.service.GatewayCenterService;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

import java.lang.reflect.Method;

/**
* @author Ray
* @date 2023/5/29 15:35
* @description 应用服务注册
*/
public class GatewaySDKApplication implements BeanPostProcessor {

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

private GatewaySDKServiceProperties properties;

private GatewayCenterService gatewayCenterService;

public GatewaySDKApplication(GatewaySDKServiceProperties properties, GatewayCenterService gatewayCenterService) {
this.properties = properties;
this.gatewayCenterService = gatewayCenterService;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
ApiProducerClazz apiProducerClazz = bean.getClass().getAnnotation(ApiProducerClazz.class);
if (null == apiProducerClazz) {
return bean;
}
// 1. 系统信息
logger.info("\n应用注册:系统信息 \nsystemId: {} \nsystemName: {} \nsystemType: {} \nsystemRegistry: {}", properties.getSystemId(), properties.getSystemName(), "RPC", properties.getSystemRegistry());
gatewayCenterService.doRegisterApplication(properties.getAddress(), properties.getSystemId(), properties.getSystemName(), "RPC", properties.getSystemRegistry());

// 2. 接口信息
Class<?>[] interfaces = bean.getClass().getInterfaces();
if (interfaces.length != 1) {
throw new GatewayException(bean.getClass().getName() + "interfaces not one this is " + JSON.toJSONString(interfaces));
}
String interfaceId = interfaces[0].getName();
logger.info("\n应用注册:接口信息 \nsystemId: {} \ninterfaceId: {} \ninterfaceName: {} \ninterfaceVersion: {}", properties.getSystemId(), interfaceId, apiProducerClazz.interfaceName(), apiProducerClazz.interfaceVersion());
gatewayCenterService.doRegisterApplicationInterface(
properties.getAddress(),
properties.getSystemId(),
interfaceId,
apiProducerClazz.interfaceName(),
apiProducerClazz.interfaceVersion()
);

// 3. 方法信息
Method[] methods = bean.getClass().getMethods();
for (Method method : methods) {
ApiProducerMethod apiProducerMethod = method.getAnnotation(ApiProducerMethod.class);
if (null == apiProducerMethod) {
continue;
}
// 解析参数
Class<?>[] parameterTypes = method.getParameterTypes();
StringBuilder parameters = new StringBuilder();
for (Class<?> clazz : parameterTypes) {
parameters.append(clazz.getName()).append(",");
}
String parameterType = parameters.toString().substring(0,parameters.toString().lastIndexOf(","));
logger.info("\n应用注册:方法信息 \nsystemId: {} \ninterfaceId: {} \nmethodId: {} \nmethodName: {} \nparameterType: {} \nuri: {} \nhttpCommandType: {} \nauth: {}",
properties.getSystemId(),
bean.getClass().getName(),
method.getName(),
apiProducerMethod.methodName(),
parameterType,
apiProducerMethod.uri(),
apiProducerMethod.httpRequestType(),
apiProducerMethod.auth());
gatewayCenterService.doRegisterApplicationInterfaceMethod(
properties.getAddress(),
properties.getSystemId(),
interfaceId,
method.getName(),
apiProducerMethod.methodName(),
parameterType,
apiProducerMethod.uri(),
apiProducerMethod.httpRequestType(),
apiProducerMethod.auth()
);
}
return bean;
}
}

实现 Spring BeanPostProcessor 接口的实现类,每采集到一个 Bean 对象的信息,就把相关的信息注册到网关中心。

测试

  1. 启动 zookeeper 注册中心,网关中心 api-gateway-center 。
  2. 可以清空表 application_system,applicaton_interface,application_interface_method,更方便观察是否注册成功。
  3. 打包构建 sdk 。
  4. 在 api-gateway-test RPC 服务提供者引入sdk,并配置 application.yml,添加注解信息,这样就可以在 api-gateway-test 启动时把接口信息注册到网关中心上了。

测试结果

api-gateway-test

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
package cn.ray.gateway.interfaces;

import cn.ray.gateway.rpc.IActivityBooth;
import cn.ray.gateway.rpc.dto.XReq;
import cn.ray.gateway.sdk.annotation.ApiProducerClazz;
import cn.ray.gateway.sdk.annotation.ApiProducerMethod;
import com.alibaba.fastjson.JSON;
import org.apache.dubbo.config.annotation.Service;

/**
* @author Ray
* @date 2023/5/12 22:36
* @description
*/
@Service(version = "1.0.0")
@ApiProducerClazz(interfaceName = "活动服务", interfaceVersion = "1.0.0")
public class ActivityBooth implements IActivityBooth {

@Override
@ApiProducerMethod(methodName = "探活方法", uri = "wg/activity/sayHi", httpRequestType = "GET", auth = 0)
public String sayHi(String str) {
return "hi " + str + " by api-gateway-test";
}

@Override
@ApiProducerMethod(methodName = "插入方法", uri = "wg/activity/insert", httpRequestType = "POST", auth = 1)
public String insert(XReq req) {
return "hi " + JSON.toJSONString(req) + " by api-gateway-test";
}

@Override
@ApiProducerMethod(methodName = "测试方法", uri = "wg/activity/test", httpRequestType = "POST", auth = 0)
public String test(String str, XReq req) {
return "hi " + str + JSON.toJSONString(req) + " by api-gateway-test";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server:
port: 8082

dubbo:
application:
name: api-gateway-test
version: 1.0.0
registry:
#address: N/A 泛化调用不能使用此方式
address: zookeeper://127.0.0.1:2181
protocol:
name: dubbo
port: 20881
scan:
base-packages: cn.ray.gateway.rpc

api-gateway-sdk:
address: http://127.0.0.1:80 # 注册中心;从这里获取接口信息以及完成注册网关操作
systemId: api-gateway-test
systemName: 网关SDK测试工程
systemRegistry: zookeeper://10.17.167.105:2181

21-测试-1

注意这里的uri前面少了 / ,这也导致我后面一个多小时全在试错,最后才发现问题出现在这里!

数据库

application_system

21-测试-2

application_interface

21-测试-3

application_interface_method

21-测试-4

api-gateway-engine

21-测试-5

Postman

21-测试-6

21-测试-7

思考

本章以完成服务接口向网关中心注册为目的,注册后在数据库中就有了对应的应用信息,也会随着网关算力的启动把配置到当下网关的服务接口拉取到自己本身进行注册使用。

另外这里需要注意,目前如果是网关算力启动完成后,如果有新增加的服务接口注册上来,这个时候是没法直接映射到网关算力的。所以这部分后续需要进行处理。

问题

在 处理 HTTP 请求时,网关协议调用失败,但是使用 ApiTest 测试却没有问题。

21-思考-1

21-思考-2

一步步测试发现,是由于鉴权处理器没有获取到 HttpStatement,报空指针异常。

21-思考-3

21-思考-4

21-思考-5

再一进步发现,全局处理器根本就没拿到 HttpStatement!

21-思考-6

21-思考-7

21-思考-8

最后找到了错误,你猜怎个事?uri 写错了!

21-思考-9

被自己蠢哭了

Dubbo 的 @Service 为什么会被 BeanPostProcessor 管理?

在 Dubbo 中,Service 也会作为 Bean 加载到 Spring 容器中。Dubbo 在启动时会自动扫描所有的 Dubbo Service 接口,并将其作为 Spring Bean 注册到 Spring 容器中。这样,我们就可以在应用程序中像使用普通的 Spring Bean 一样使用 Dubbo Service。同时,Dubbo 还提供了一些注解(如 @Service)来简化 Service 的配置。