通过nacos动态配置springCloud gateway的路由规则
现状
目前对gateway的routes的配置有两种方式,一种是在代码里面配置,一重是在配置文件里配置。这两种配置方式配置后都需要重启网关才能生效。对于请求量很大的项目,如果重启可能造成请求丢失。那么如何做到不重启让配置生效呢?
源码分析
先看看gateway是怎样拿路由规则的
每一个路由规则都会被解析成为一个RouteDefinition类
获取RouteDefinition是通过**RouteDefinitionLocator(路由定义定位器)**接口的是实现类,有五个是实现类具体各个实现类的用法可以参https://www.jianshu.com/p/b02c7495eb5e
我们这里主要看一下InMemoryRouteDefinitionRepository、CompositeRouteDefinitionLocatorInMemoryRouteDefinitionRepository 里面定义了一个map用来存放规则,key为规则id,value为RouteDefinition,然后还对外提供了三个方法。
分别是save:保存路由;delete:删除路由;getRouteDefinitions:获取路由规则。
所以我们可以通过提供的这些方法来配置路由规则。
再来看看CompositeRouteDefinitionLocator
所以CompositeRouteDefinitionLocator中的getRouteDefinitions方法是将各个定义定位器中的routes合并。
既然是合并所以我们只需要通过调用InMemoryRouteDefinitionRepository中的save和delete方法来配置路由规则就可以了。
来看看GatewayAutoConfiguration类
在项目启动的时候,首先会加载GatewayAutoConfiguration,实例化里面的bean
先定义RouteDefinition
再定义routes,routes是定义在RouteLocator的实现类CachingRouteLocator中
routes是最后也是通过CompositeRouteDefinitionLocator中的getRouteDefinitions方法获得的,感兴趣的朋友可以自己点点源码。我们用的时候就是通过获取CachingRouteLocator中的routes得到路由规则
项目配置
在项目里面我们只需要通过调用InMemoryRouteDefinitionRepository中的save和delete方法来配置路由规则就可以了。
nacos监听获取路由规则
@Configuration
public class NacosConfiguration {
@Value("${nacos.serverAddr}")
private String serverAddr;
@Value("${nacos.namespace}")
private String namespace;
/**
* 初始化acmClient
*/
@Bean
ConfigService configService() throws Exception{
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
properties.put("namespace", namespace);
ConfigService configService = NacosFactory.createConfigService(properties);
return configService;
}
/**
* 在服务启动的时候就从nacos更新routes
**/
@Bean
public DynamicRouteServiceImplByNacos routesConfig(ConfigService configService) throws Exception{
DynamicRouteServiceImplByNacos dynamicRouteServiceImplByNacos = new DynamicRouteServiceImplByNacos();
dynamicRouteServiceImplByNacos.setConfigService(configService);
dynamicRouteServiceImplByNacos.init();
return dynamicRouteServiceImplByNacos;
}
}
@Component
public class DynamicRouteServiceImplByNacos {
private static final Logger logger = LoggerFactory.getLogger( DynamicRouteServiceImplByNacos.class);
@Autowired
private DynamicRouteServiceImpl dynamicRouteService;
@Resource
private ConfigService configService;
public void setConfigService(ConfigService configService) {
this.configService = configService;
}
private final static String ROUTES_DATA_ID = "mgmt-gateway-routes-sit"; // 路由规则配置
private final static String GROUP_ID = "mgmt-gateway";
public void init() throws Exception{
configService.addListener(ROUTES_DATA_ID, GROUP_ID, new Listener(){
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
try {
// 从nacos中拿到所以route规则
List<RouteDefinition> list = JSONArray.parseArray(configInfo,RouteDefinition.class);
// 更新
dynamicRouteService.update(list);
} catch (Exception e) {
logger.info("获取nacos配置失败,请检查nacos中的配置的语法规则");
}
}
});
}
}
更新路由规则
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
private static final Logger logger = LoggerFactory.getLogger(DynamicRouteServiceImpl.class);
@Autowired
private InMemoryRouteDefinitionRepository routeDefinitionWriter;
private ApplicationEventPublisher publisher;
/**
* 更新路由
* @param newRoutes
* @return
*/
public void update(List<RouteDefinition> newRoutes) {
logger.info("------路由更新开始-----");
// 得到所有新的route的id列表
List<String> newRouteIds = newRoutes.stream().map(RouteDefinition::getId).collect(Collectors.toList());
// 得到所有的旧 route
Flux<RouteDefinition> routeDefinitionFlux = routeDefinitionWriter.getRouteDefinitions();
routeDefinitionFlux.toIterable().forEach(routeDefinition -> {
// 删除id在新routes的id列表中不存在的route
if (!newRouteIds.contains(routeDefinition.getId())) {
delete(routeDefinition.getId());
}
});
// 重新插入所有新的,之前存在则覆盖
newRoutes.stream().forEach(route -> {
try {
routeDefinitionWriter.save(Mono.just(route)).subscribe();
logger.info("更新路由成功:{}",route.getId());
} catch (Exception e) {
logger.info("更新路由失败:{}",route.getId());
}
});
this.publisher.publishEvent(new RefreshRoutesEvent(this));
logger.info("------路由更新结束-----");
}
/**
* 删除路由
* @param id
* @return
*/
public void delete(String id) {
try {
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
logger.info("删除路由成功:{}",id);
} catch (Exception e) {
e.printStackTrace();
logger.info("删除路由失败:{}",id);
}
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
nacos规则配置(注意一定要配置id,并且id唯一,因为是根据id更新)
[
{"id":"mgmt-bi-dev",
"uri": "lb://mgmt-bi-dev",
"predicates":[{
"name": "Path",
"args": { "_genkey_0": "/mgmt/**"}
}],
"filters": [{
"name": "StripPrefix",
"args": { "_genkey_0": "1"}
}]
},
{"id":"customer-dev",
"uri": "lb://customer-dev",
"predicates":[{
"name": "Path",
"args": { "_genkey_0": "/customer/**"}
}],
"filters": [{
"name": "StripPrefix",
"args": { "_genkey_0": "1" }
}]
},
{"id":"shopSys-site",
"uri": "http://192.168.0.11:9580",
"predicates":[{
"name": "Path",
"args": { "_genkey_0": "/shopSys/**"}
}],
"filters": [{
"name": "StripPrefix",
"args": { "_genkey_0": "1" }
}]
}
]