通过nacos动态配置springCloud gateway的路由规则

现状

目前对gateway的routes的配置有两种方式,一种是在代码里面配置,一重是在配置文件里配置。这两种配置方式配置后都需要重启网关才能生效。对于请求量很大的项目,如果重启可能造成请求丢失。那么如何做到不重启让配置生效呢?

源码分析

先看看gateway是怎样拿路由规则的

每一个路由规则都会被解析成为一个RouteDefinition类

springgateway 刷新路由 spring gateway nacos路由_重启


获取RouteDefinition是通过**RouteDefinitionLocator(路由定义定位器)**接口的是实现类,有五个是实现类具体各个实现类的用法可以参https://www.jianshu.com/p/b02c7495eb5e

我们这里主要看一下InMemoryRouteDefinitionRepository、CompositeRouteDefinitionLocatorInMemoryRouteDefinitionRepository 里面定义了一个map用来存放规则,key为规则id,value为RouteDefinition,然后还对外提供了三个方法。

分别是save:保存路由;delete:删除路由;getRouteDefinitions:获取路由规则。

springgateway 刷新路由 spring gateway nacos路由_java_02


所以我们可以通过提供的这些方法来配置路由规则。

再来看看CompositeRouteDefinitionLocator

springgateway 刷新路由 spring gateway nacos路由_spring_03


所以CompositeRouteDefinitionLocator中的getRouteDefinitions方法是将各个定义定位器中的routes合并。

既然是合并所以我们只需要通过调用InMemoryRouteDefinitionRepository中的save和delete方法来配置路由规则就可以了。

来看看GatewayAutoConfiguration

在项目启动的时候,首先会加载GatewayAutoConfiguration,实例化里面的bean

先定义RouteDefinition

springgateway 刷新路由 spring gateway nacos路由_java_04


再定义routes,routes是定义在RouteLocator的实现类CachingRouteLocator

springgateway 刷新路由 spring gateway nacos路由_spring_05


routes是最后也是通过CompositeRouteDefinitionLocator中的getRouteDefinitions方法获得的,感兴趣的朋友可以自己点点源码。我们用的时候就是通过获取CachingRouteLocator中的routes得到路由规则

springgateway 刷新路由 spring gateway nacos路由_gateway_06

项目配置

在项目里面我们只需要通过调用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" }
                }]
}
]