SpringCloud Gateway基于Nacos的动态网关
Nacos动态网关,解决修改路由信息和网关服务配置信息必须重启服务才能生效,网关作为服务入口,避免重启,造成服务不稳定。
·
前言
gateway可以默认通过配置开启,服务自动注册服务名为网关路由地址,比如:服务sc-xxx,可以通过:http://gateway/sc-xx/** 进行访问,开启对应的配置如下:
spring:
cloud:
gateway:
routes:
discovery:
locator:
enabled: true
但有时候我们不想这个样做,原因:
- 开启默认的服务名方式路由映射,会将所有服务的路径都自动暴露在网关下。
- 微服务体系中有多个网关,路由不同的服务,不能默认所有服务都进行路由配置。
- 有时候需要对网关进行额外的配置,比如做个反向代理、转发服务名改为其他名称,默认locator无法满足
- 修改网关配置,需要重启服务,有没有可以不重新打包,不重启就能设置网关配置项的方式哪?
动态网关解决的问题
- 解决修改路由信息和网关服务配置信息必须重启服务才能生效
- 网关作为服务入口,避免重启,造成服务不稳定
实现思路
- 路由信息、配置信息放在nacos中,gateway项目监听nacos对应的配置修改
- 基于SpringCloud Gateway的 RouteDefinitionWriter 接口删除和新增路由配置
- 基于事件 RefreshRoutesEvent 刷新系统中的路由配置
实现细节
新建一个动态路由操作类 DynamicRouteManager 因为要发布 RefreshRoutesEvent 事件,所以需要实现spring 的 ApplicationEventPublisherAware 接口,提供如下方法:
- 增加路由 void add(RouteDefinition definition)
- 更新路由 void update(RouteDefinition definition)
- 删除路由 void delete(String id)
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* 动态路由OP类
*/
@Component
public class DynamicRouteManager implements ApplicationEventPublisherAware {
private final RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
public DynamicRouteManager(RouteDefinitionWriter routeDefinitionWriter) {
this.routeDefinitionWriter = routeDefinitionWriter;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
/**
* 增加路由
*/
public void add(RouteDefinition definition) {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
publisher.publishEvent(new RefreshRoutesEvent(this));
}
/**
* 更新路由
*/
public void update(RouteDefinition definition) {
routeDefinitionWriter.delete(Mono.just(definition.getId()));
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
publisher.publishEvent(new RefreshRoutesEvent(this));
}
/**
* 批量更新路由
*/
public void updateList(List<RouteDefinition> routeDefinitions) {
routeDefinitions.forEach(this::update);
}
/**
* 删除路由
*/
public void delete(String id) {
routeDefinitionWriter.delete(Mono.just(id));
}
}
新建一个 NacosDynamicRouteListener 监听类nacos路由信息变化
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* 动态路由监听器
*
* @author Chill
*/
@Order
@Slf4j
@Component
public class NacosDynamicRouteListener implements InitializingBean {
private final DynamicRouteManager dynamicRouteManager;
private final NacosDiscoveryProperties nacosDiscoveryProperties;
private final NacosConfigProperties nacosConfigProperties;
private final DynamicRouteProperties dynamicRouteProperties;
public NacosDynamicRouteListener(DynamicRouteManager dynamicRouteManager,
NacosDiscoveryProperties nacosDiscoveryProperties,
NacosConfigProperties nacosConfigProperties,
DynamicRouteProperties dynamicRouteProperties) {
this.dynamicRouteManager = dynamicRouteManager;
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.nacosConfigProperties = nacosConfigProperties;
this.dynamicRouteProperties = dynamicRouteProperties;
}
/**
* 监听Nacos的动态路由配置
*/
private void dynamicRouteListener() {
try {
String dataId = dynamicRouteProperties.getDataId();
String group = nacosConfigProperties.getGroup();
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.NAMESPACE, nacosDiscoveryProperties.getNamespace());
properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosDiscoveryProperties.getServerAddr());
ConfigService configService = NacosFactory.createConfigService(properties);
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
log.info("---监听到路由信息刷新---\n{}\n---------------------------------------", configInfo);
List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
dynamicRouteManager.updateList(routeDefinitions);
}
@Override
public Executor getExecutor() {
return null;
}
});
String configInfo = configService.getConfig(dataId, group, 1000 * 10);
if (configInfo != null) {
List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
dynamicRouteManager.updateList(routeDefinitions);
}
} catch (NacosException e) {
log.error("动态路由监听异常", e);
}
}
@Override
public void afterPropertiesSet() throws Exception {
dynamicRouteListener();
}
}
实例展示
监听路由配置信息:xxx.xxx.xxx.json 配置文件,初始配置如下:
[
{
"id":"service-xxx",
"uri":"lb://service-xxx/",
"predicates":[
{
"name":"Path",
"args":{
"pattern1":"/service-xxx/**"
}
}
],
"order":1
}
]
访问地址:http://localhost:8000/service-xxx ,响应结果报错500说明服务路由识别成功,并没有404,可以用一个正常的接口测试就不会500了。
{
"msg": "Failed to handle request [GET http://localhost:8002/service-xxx]: ",
"code": 500,
"data": null
}
修改一下Naco对应json配置,pattern1的service-xxx1 改成 service-xxx1/如下:
[
{
"id":"service-xxx",
"uri":"lb://service-xxx/",
"predicates":[
{
"name":"Path",
"args":{
"pattern1":"/service-xxx1/**"
}
}
],
"order":1
}
]
日志打印:
2022-06-20 18:24:14.362 [xxx-gateway] INFO 509450 [ternal.notifier] o.s.v.g.d.NacosDynamicRouteListener 76 [TID: N/A] : ---监听到路由信息刷新---
[
{
"id":"service-xxx",
"uri":"lb://service-xxx/",
"predicates":[
{
"name":"Path",
"args":{
"pattern1":"/service-xxx1/**"
}
}
],
"order":1
}
]
再次访问原接口:http://localhost:8000/service-xxx ,404 报错,路由信息已经改变了。
{
"msg": "Failed to handle request [GET http://localhost:8002/service-xxx]: 404 NOT_FOUND",
"code": 404,
"data": null
}
更多推荐
已为社区贡献2条内容
所有评论(0)