业务流程
基于第25章Nginx的负载模型,第26章动态刷新Nginx的配置和实现,终于在本章节中可以把整个流程串联起来,完成网关算力节点的动态负载功能实现。本节我们要做的就是在网关算力节点进行注册的时候,基于网关算力节点本身的配置信息,刷新nginx的配置信息,从而实现有关算力节点动态负载功能.
在不使用Nginx代理的时候,前面章节使用网关都是通过直接方式的方式操作,如http://IP:7397/wg/activity/sayHi?str=1
那么现在因为有负载的设计,希望把来自于不同URL的请求负载到不同的网关算力上去,所以这里的访问地址将变更为:http://IP:8090/10001/wg/activity/sayHi?str=10001
。而8090端口的请求是会达到docker中的nginx配置中的,nginx根据请求的信息通过反向代理location的配置转而打到负载均衡upsream的服务上去,而其中的服务信息正是算力节点的监听地址(如http://IP:7397/wg/activity/sayHi?str=1
),当对应算力节点的监听地址接受到请求后,就会通过从网关注册中心拉取而来的配置(httpstatement映射信息),进行RPC的泛化调用,调用真正的RPC服务,获取响应,最后由算力节点将响应包装返回给请求方。
从第1个地址到第2个地址来看,变化的点主要是端口由原来的访问网关算力节点到访问Nginx,同时多了一个 10001 的路径。这个 10001 就是数据库中 group_id 网关分组的配置。我们也是用这个配置来区分访问哪一组网关。所以整体设计如图;
api-gateway-center 管理着网关算力的注册,并把注册的配置信息动态刷新到 Nginx 配置中。
同时在 Nginx 的配置中会重新URL,也就是把 10001 这个根目录路径给去掉,让它的功能只是负责路由即可,剩下的与原有直接访问网关算力不变。这样即使以后不需要做负载也可以直接访问网关算力节点。
业务实现
本章节改动点如下
GatewayConfigManage#registerGatewayServerNode:在网关算力节点注册的时候,调用在第26章实现的动态刷新Nginx配置接口。此外注意本章节新添加了Nginx路径重新配置,以及在yml配置文件中添加了 Nginx IP 的配置。
ConfigManageService#queryApplicationSystemRichInfo:bug 优化,null 判断字符串,改为 if (StringUtils.isEmpty(systemId)) 这样更准确。
算力节点注册时刷新Nginx配置
在上一章节中,我们已经完成了刷新Nginx配置的功能,本节我们就要让算力节点在注册时,根据自身的信息调用刷新Nginx配置的功能
代码如下GatewayConfigManage.java
@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 done = configManageService.registerGatewayServerNode(groupId, gatewayId, gatewayName, gatewayAddress);
//查询这个组下的所有网关算力节点信息
List<GatewayServerDetailVO> gatewayServerDetailVOList=configManageService.queryGatewayServerDetailList();
//组装Nginx网关刷新配置信息,根据分组进行区分
Map<String, List<GatewayServerDetailVO>> gatewayServerDetailMap = gatewayServerDetailVOList.stream().collect(Collectors.groupingBy(GatewayServerDetailVO::getGroupId));
Set<String> uniqueGroupIdList = gatewayServerDetailMap.keySet();
//配置刷新nginx的信息,upstream,location
List<LocationVO> locationVOList=new ArrayList<>();
for(String name:uniqueGroupIdList){
// location /api01/ {
// rewrite ^/api01/(.*)$ /$1 break;
// proxy_pass http://api01;
// }
locationVOList.add(new LocationVO("/"+name+"/","http://"+name+"/;"));
}
List<UpstreamVO> upstreamVOList=new ArrayList<>();
for(String name:uniqueGroupIdList){
// upstream api01 {
// least_conn;
// server 172.20.10.12:9001;
// #server 172.20.10.12:9002;
// }
//拿到每个分组下的所有可用的网关算力节点address
List<String> servers = gatewayServerDetailMap.get(name).stream().map(GatewayServerDetailVO::getGatewayAddress).collect(Collectors.toList());
upstreamVOList.add(new UpstreamVO(name,"least_conn;",servers));
}
//刷新Nginx配置
loadBalancingService.updateNginxConfig(new NginxConfig(upstreamVOList,locationVOList));
return new Result<>(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getInfo(), done);
}catch (Exception e){
logger.error("注册网关服务节点异常", e);
return new Result<>(ResponseCode.UN_ERROR.getCode(), e.getMessage(), false);
}
}
由于我们昨天写的刷新Nginx配置的功能,是直接再写一版配置覆盖掉原有的配置,因此注册功能在调用时,需要先查询对应分组的所有算力节点,即configManageService.queryGatewayServerDetailList()
,其只有到达仓储层才有具体的功能实现,其余就是不断地掉下一层的函数
ConfigManageRepository.java
@Override
public List<GatewayServerDetailVO> queryGatewayServerDetailList() {
List<GatewayServerDetail> gatewayServerDetailList=gatewayServerDetailDao.queryGatewayServerDetailList();
List<GatewayServerDetailVO> gatewayServerDetailVOList=new ArrayList<>(gatewayServerDetailList.size());
for(GatewayServerDetail gatewayServerDetail:gatewayServerDetailList){
GatewayServerDetailVO gatewayServerDetailVO=new GatewayServerDetailVO();
gatewayServerDetailVO.setId(gatewayServerDetail.getId());
gatewayServerDetailVO.setGroupId(gatewayServerDetail.getGroupId());
gatewayServerDetailVO.setGatewayId(gatewayServerDetail.getGatewayId());
gatewayServerDetailVO.setGatewayName(gatewayServerDetail.getGatewayName());
gatewayServerDetailVO.setGatewayAddress(gatewayServerDetail.getGatewayAddress());
gatewayServerDetailVO.setStatus(gatewayServerDetail.getStatus());
gatewayServerDetailVO.setCreateTime(gatewayServerDetail.getCreateTime());
gatewayServerDetailVO.setUpdateTime(gatewayServerDetail.getUpdateTime());
gatewayServerDetailVOList.add(gatewayServerDetailVO);
}
return gatewayServerDetailVOList;
}
其中dao对应的sql为gateway_server_detail.xml
<select id="queryGatewayServerDetailList" resultMap="gatewayServerDetailMap">
SELECT id, group_id, gateway_id, gateway_name, gateway_address, status, create_time, update_time
FROM gateway_server_detail
</select>
到此具体的在注册时进行Nginx配置刷新的功能已经实现。
其余修改
本章节除了上述功能性代码外,还有一些其它代码的小修改
首先是GatewayServerDetailVO.java
增加部分属性字段
package com.zshunbao.gateway.center.domain.manage.model.vo;
import java.util.Date;
/**
* @program: api-gateway-center
* @ClassName GatewayServerDetailVO
* @description: 网关服务明细VO
* @author: zs宝
* @create: 2025-08-19 16:28
* @Version 1.0
**/
public class GatewayServerDetailVO {
/** 自增ID */
private Integer id;
/** 分组标识 */
private String groupId;
/** 网关标识 */
private String gatewayId;
/** 网关名称 */
private String gatewayName;
/** 网关地址 */
private String gatewayAddress;
/** 服务状态 */
private Integer status;
/** 创建时间 */
private Date createTime;
/** 更新时间 */
private Date updateTime;
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;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
}
其次是LoadBalancingService.java
的default_nginx_server_name;
属性字段原本是写死的,这里改为由配置文件处读取配置
@Value("${nginx.server_name}")
private String default_nginx_server_name;
nginx:
server_name: 127.0.0.1
测试
本次测试:
开启服务;Docker、zookeeper、Nginx、redis,并按照网关所需启动以及修改对应的IP信息。如果相关内容失败,也可以修改后再启动。
启动api-gateway-center 提供注册中心服务
启动api-gateway-engine ,启动的时候会由引擎下的 assist 助手组件拉取注册中心所归属此算力节点的配置信息,并在本地完成HTTP和RPC的映射。
这里启动,我们启动两个实例
注意启动的时候配置文件要不一样application.yml
server:
port: 8007
#api-gateway:
# address: http://10.16.81.112:8001 # 注册中心;从这里获取接口信息以及完成注册网关操作【你需要更换为你自己的IP】
# groupId: 10001 # 网关分组;每一个网关通信组件都分配一个对应的分组
# gatewayId: api-gateway-g4 # 网关标识;
# gatewayName: 电商配送网关 # 网关名称
# gatewayAddress: 10.16.81.112:7397 # 网关服务;网关的通信服务Netty启动时使用IP和端口【你需要更换为你自己的IP】
api-gateway:
address: http://localhost:8001 # 注册中心;从这里获取接口信息以及完成注册网关操作
groupId: 10001 # 网关分组;每一个网关通信组件都分配一个对应的分组
gatewayId: api-gateway-g4 # 网关标识;
gatewayName: 电商配送网关 # 网关名称
gatewayAddress: 127.0.0.1:7397 # 网关服务;网关的通信服务Netty启动时使用IP和端口
# server:
# port: 8008
#
# #api-gateway:
# # address: http://10.16.81.112:8001 # 注册中心;从这里获取接口信息以及完成注册网关操作【你需要更换为你自己的IP】
# # groupId: 10001 # 网关分组;每一个网关通信组件都分配一个对应的分组
# # gatewayId: api-gateway-g4 # 网关标识;
# # gatewayName: 电商配送网关 # 网关名称
# # gatewayAddress: 10.16.81.112:7397 # 网关服务;网关的通信服务Netty启动时使用IP和端口【你需要更换为你自己的IP】
#
#
# api-gateway:
# address: http://localhost:8001 # 注册中心;从这里获取接口信息以及完成注册网关操作
# groupId: 10001 # 网关分组;每一个网关通信组件都分配一个对应的分组
# gatewayId: api-gateway-g5 # 网关标识;
# gatewayName: 电商配送网关 # 网关名称
# gatewayAddress: 127.0.0.1:7398
api-gateway-test-provider 提供RPC接口测试服务
刷新后Nginx配置如下,注意其中的由于我们的Nginx是部署到docker容器中的,而项目却是在本地windows环形下的idea中运行,因此,对于upstream的server我们在docker中无法使用127.0.0.1,需要改为host.docker.internal。所以windows环境下不好测试,最好还是在linux或者MAC环境下。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
#include /etc/nginx/conf.d/*.conf;
# 设定负载均衡的服务器列表 命令:docker exec Nginx nginx -s reload
upstream 10001{
least_conn;
server host.docker.internal:7397;
server host.docker.internal:7398;
}
# HTTP服务器
server {
# 监听80端口,用于HTTP协议
listen 80;
# 定义使用IP/域名访问
server_name 127.0.0.1;
# 首页
index index.html;
# 反向代理的路径(upstream绑定),location 后面设置映射的路径
# location / {
# proxy_pass http://192.168.1.102:9001;
# }
location /10001/ {
proxy_pass http://10001/;
}
}
}
测试通过