十一章 网关注册算力节点领域服务实现

在网关注册中心提供网关算力节点的注册服务接口,便于后续网关启动后向网关中心注册信息。

设计

11-设计

网关注册中心首先要接收来自各个网关服务的注册,任何一组用于处理 HTTP 协议请求的网关算力节点,都要注册到网关中心进行统一维护和管理。只有注册到网关中心才能把 RPC 服务分配到各个网关算力节点上进行使用。

  • 网关中心维护网关算力节点的库表:

    • gateway_server 维护网关服务分组信息
    • gateway_server_detail 维护分组内具体的网关服务信息
  • 目前先不用引入 zookeeper 这样的注册中心去探活服务。后期功能地额外完成后再进行陆续补充。

  • 先来开发这样一块的功能接口:允许外部通过 HTTP 接口进行注册网关算力节点服务,使其能被注册中心调用,便于后续分配 RPC 服务。

实现

【infrastructure层】网关配置仓储服务

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
package cn.ray.gateway.center.infrastructure.repository;

import cn.ray.gateway.center.domain.manage.model.vo.GatewayServerDetailVO;
import cn.ray.gateway.center.domain.manage.model.vo.GatewayServerVO;
import cn.ray.gateway.center.domain.manage.repository.IConfigManageRepository;
import cn.ray.gateway.center.infrastructure.dao.IGatewayServerDao;
import cn.ray.gateway.center.infrastructure.dao.IGatewayServerDetailDao;
import cn.ray.gateway.center.infrastructure.pojo.GatewayServer;
import cn.ray.gateway.center.infrastructure.pojo.GatewayServerDetail;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
* @author Ray
* @date 2023/5/23 14:59
* @description 网关配置仓储服务
*/
@Repository
public class ConfigManageRepository implements IConfigManageRepository {

@Resource
private IGatewayServerDao gatewayServerDao;

@Resource
private IGatewayServerDetailDao gatewayServerDetailDao;

@Override
public List<GatewayServerVO> queryGateServerList() {
List<GatewayServer> gatewayServers = gatewayServerDao.queryGatewayServerList();
List<GatewayServerVO> gatewayServerVOList = new ArrayList<>(gatewayServers.size());
for (GatewayServer gatewayServer : gatewayServers) {
// 可以按照 IDEA 插件 vo2dto 方便转换
GatewayServerVO gatewayServerVO = new GatewayServerVO();
gatewayServerVO.setGroupId(gatewayServer.getGroupId());
gatewayServerVO.setGroupName(gatewayServer.getGroupName());
gatewayServerVOList.add(gatewayServerVO);
}
return gatewayServerVOList;
}

@Override
public boolean registerGatewayServerNode(String groupId, String gatewayId, String gatewayName, String gatewayAddress, Integer available) {
GatewayServerDetail gatewayServerDetail = new GatewayServerDetail();
gatewayServerDetail.setGroupId(groupId);
gatewayServerDetail.setGatewayId(gatewayId);
gatewayServerDetail.setGatewayName(gatewayName);
gatewayServerDetail.setGatewayAddress(gatewayAddress);
gatewayServerDetail.setStatus(available);
gatewayServerDetailDao.insert(gatewayServerDetail);
return true;
}

@Override
public GatewayServerDetailVO queryGatewayServerDetail(String gatewayId, String gatewayAddress) {
GatewayServerDetail req = new GatewayServerDetail();
req.setGatewayId(gatewayId);
req.setGatewayAddress(gatewayAddress);
GatewayServerDetail gatewayServerDetail = gatewayServerDetailDao.queryGatewayServerDetail(req);
if (null == gatewayServerDetail) {
return null;
}
// 可以按照 IDEA 插件 vo2dto 方便转换
GatewayServerDetailVO gatewayServerDetailVO = new GatewayServerDetailVO();
gatewayServerDetailVO.setGatewayId(gatewayServerDetail.getGatewayId());
gatewayServerDetailVO.setGatewayName(gatewayServerDetail.getGatewayName());
gatewayServerDetailVO.setGatewayAddress(gatewayServerDetail.getGatewayAddress());
gatewayServerDetailVO.setStatus(gatewayServerDetail.getStatus());
return gatewayServerDetailVO;
}

@Override
public boolean updateGatewayStatus(String gatewayId, String gatewayAddress, Integer available) {
GatewayServerDetail gatewayServerDetail = new GatewayServerDetail();
gatewayServerDetail.setGatewayId(gatewayId);
gatewayServerDetail.setGatewayAddress(gatewayAddress);
gatewayServerDetail.setStatus(available);
return gatewayServerDetailDao.updateGatewayStatus(gatewayServerDetail);
}
}

【domain层】网关配置服务

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

import cn.ray.gateway.center.application.IConfigManageService;
import cn.ray.gateway.center.domain.manage.model.vo.GatewayServerDetailVO;
import cn.ray.gateway.center.domain.manage.model.vo.GatewayServerVO;
import cn.ray.gateway.center.domain.manage.repository.IConfigManageRepository;
import cn.ray.gateway.center.infrastructure.common.Constants;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
* @author Ray
* @date 2023/5/23 14:40
* @description 网关配置服务
*/
@Service
public class ConfigManageService implements IConfigManageService {

@Resource
private IConfigManageRepository configManageRepository;

@Override
public List<GatewayServerVO> queryGatewayServerList() {
return configManageRepository.queryGateServerList();
}

@Override
public boolean registerGatewayServerNode(String groupId, String gatewayId, String gatewayName, String gatewayAddress) {
GatewayServerDetailVO gatewayServerDetailVO = configManageRepository.queryGatewayServerDetail(gatewayId, gatewayAddress);
if (null == gatewayServerDetailVO) {
try {
return configManageRepository.registerGatewayServerNode(groupId, gatewayId, gatewayName, gatewayAddress, Constants.GatewayStatus.Available);
} catch (DuplicateKeyException e) {
// 唯一索引冲突导致重复注册,则修改当前网关服务状态
return configManageRepository.updateGatewayStatus(gatewayId, gatewayAddress, Constants.GatewayStatus.Available);
}
} else {
// 找到对应网关信息,也修改网关服务状态
return configManageRepository.updateGatewayStatus(gatewayId, gatewayAddress, Constants.GatewayStatus.Available);
}
}
}

注册时主要要判断下当前网关是否注册过,如果没有注册,则直接进行添加注册。如果注册过则进行更新。是否注册过根据网关ID和对应的IP地址一起做联合索引。为了避免服务端重启注册故障,加了一段唯一索引判断,如果数据库已经存在数据,抛出索引冲突的异常。冲突后则直接更新,从而避免服务端发起连续调用,导致网关重复注册。

【interfaces层】网关配置管理;服务分组、网关注册、服务关联

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

import cn.ray.gateway.center.application.IConfigManageService;
import cn.ray.gateway.center.domain.manage.model.vo.GatewayServerVO;
import cn.ray.gateway.center.infrastructure.common.ResponseCode;
import cn.ray.gateway.center.infrastructure.common.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

/**
* @author Ray
* @date 2023/5/23 15:25
* @description 网关配置管理;服务分组、网关注册、服务关联
*/
@RestController
@RequestMapping("/wg/admin/config")
public class GatewayConfigManage {

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

@Resource
private IConfigManageService configManageService;

@GetMapping(value = "queryServerConfig", produces = "application/json;charset=utf-8")
public Result<List<GatewayServerVO>> queryServerConfig() {
try {
logger.info("查询网关服务配置项信息");
List<GatewayServerVO> gatewayServerVOList = configManageService.queryGatewayServerList();
return new Result<>(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getInfo(), gatewayServerVOList);
} catch (Exception e) {
logger.error("查询网关服务配置项信息异常", e);
return new Result<>(ResponseCode.UN_ERROR.getCode(), e.getMessage(), null);
}
}

/**
* 注册网关服务节点
*
* @param groupId 分组标识
* @param gatewayId 网关标识
* @param gatewayName 网关名称
* @param gatewayAddress 网关地址
* @return
*/
@PostMapping(value = "registerGateway")
public Result<Boolean> registerGatewayServerNode(@RequestParam String groupId, @RequestParam String gatewayId, @RequestParam String gatewayName, @RequestParam String gatewayAddress) {
try {
logger.info("注册网关服务节点 gatewayId:{} gatewayName:{} gatewayAddress:{}", gatewayId, gatewayName, gatewayAddress);
boolean isSuccess = configManageService.registerGatewayServerNode(groupId, gatewayId, gatewayName, gatewayAddress);
return new Result<>(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getInfo(), isSuccess);
} catch (Exception e) {
logger.error("注册网关服务节点异常", e);
return new Result<>(ResponseCode.UN_ERROR.getCode(), e.getMessage(), false);
}
}
}

测试

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
33
34
35
36
37
38
39
40
41
42
package cn.ray.gateway.center.test;

import cn.ray.gateway.center.application.IConfigManageService;
import cn.ray.gateway.center.domain.manage.model.vo.GatewayServerVO;
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import java.util.List;

/**
* @author Ray
* @date 2023/5/21 16:15
* @description
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApiTest {

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

@Resource
private IConfigManageService configManageService;

@Test
public void test_queryGatewayServerList() {
List<GatewayServerVO> gatewayServerVOS = configManageService.queryGatewayServerList();
logger.info("测试结果:{}", JSON.toJSONString(gatewayServerVOS));
}

@Test
public void test_registerGatewayServerNode() {
configManageService.registerGatewayServerNode("10001", "api-gateway-g1", "电商支付网关", "127.0.0.196");
configManageService.registerGatewayServerNode("10001", "api-gateway-g2", "电商支付网关", "127.0.0.197");
configManageService.registerGatewayServerNode("10001", "api-gateway-g3", "电商配送网关", "127.0.0.198");
}
}

测试结果

11-测试-1

11-测试-2

思考

  1. 由于 core 本身是一个 Netty 服务,不希望直接关联到 center 中,经典加一层的思路,可以利用 SpringBoot 将其作为 starter 组件进行包装,需要添加逻辑时在 starter 添加即可,避免 center 中叠加太多逻辑,造成污染。
  2. 网关分配时不考虑端口,只考虑IP地址,分配不同IP下的同一端口,一台虚拟机分配两台tomcat没有必要,将一个虚拟机分配的小一点,尽可能让一个算力服务完整,而不是被两台 tomcat 分配,但可以使用多个虚拟机构造多个服务实例。

当自研网关组件进行网关分配时,一般不考虑端口的原因可能是以下几点:

  1. 简化配置和管理:将端口考虑纳入网关分配会增加配置和管理的复杂性。网关通常用于连接不同的网络,并在网络层上进行路由和转发操作。如果还需要考虑端口,那么配置和管理网关的工作将更加繁琐。简化配置和管理可以提高系统的可用性和可靠性
  2. 重点在网络层级别:自研网关组件通常专注于网络层级别的功能,如路由选择、数据包转发和地址转换等。这些功能与端口无关,主要依赖于IP地址和子网掩码等网络层信息。因此,不考虑端口可以使自研网关组件更专注于实现网络层级别的功能,提高性能和效率
  3. 端口处理由其他组件负责:不考虑端口可以提供更大的灵活性和扩展性。通过将端口的管理交给上层的应用层或服务层处理,可以更容易地实现新的应用程序或服务的添加和更改。这样可以避免对网关进行频繁的配置和调整,而只需在应用层或服务层进行相应的配置即可。在自研网关组件之外,通常还有其他组件负责处理端口相关的功能。例如,防火墙或负载均衡器等组件可能会负责端口的过滤、映射或负载均衡等任务。将端口处理交给专门的组件负责可以更好地分工和解耦,使各组件能够独立进行功能开发和优化

需要注意的是,具体的系统设计和实现可能会因组织和场景的不同而有所差异。在某些情况下,自研网关组件可能需要考虑端口分配,以满足特定的需求。因此,在设计自研网关组件时,需要综合考虑系统的特点、功能需求和性能要求,以确定是否需要考虑端口分配。