全站最帅😎
发布于 2020-08-18 / 2462 阅读
0
0

SpringCloud Gateway 基于 Nacos 实现动态路由

1. 原理

SpringCloud Gateway中路默认实现是 InMemoryRouteDefinitionRepository ,这里面的核心是 routeDefinitionWriter 这个对象,而 RouteDefinitionWriter 唯一的继承是RouteDefinitionRepository 类, RouteDefinitionRepository
唯一的继承是 InMemoryRouteDefinitionRepository, 在 InMemoryRouteDefinitionRepository 中有这样的一段代码

image.png

接着往下看,在 GatewayAutoConfiguration 自动配置类中,有这样的一段代码,当RouteDefinitionRepository 没有自定义的实现的时候,所以会默认创建了 InMemoryRouteDefinitionRepository 这个Bean。

image.png

从而我们知道,Gateway中路由的默认存储方式是由 InMemoryRouteDefinitionRepository 方式实现,因此我们只要通过
RouteDefinitionWriter 对象即可实现路由的添加与删除,但是因为是存储在内存中,应用重启会丢失路由,那么我们得保证Gateway在启动的时候在外部拉取路由,这里我们存储路由用的是Nacos,通过configService对路由文件进行刷新监听。

image.png

最后我们通过实现 ApplicationEventPublisherAware 接口来发布路由刷新事件
image.png

2. 代码

GatewayConfig.java,读取nacos配置信息合路由文件data-id,后续需要对路由文件路由文件gateway-router.json进行监听

@Configuration
public class GatewayConfig {
    public static final long DEFAULT_TIMEOUT = 30000;

    public static String NACOS_SERVER_ADDR;

    public static String NACOS_NAMESPACE;

    public static String NACOS_ROUTE_DATA_ID;

    public static String NACOS_ROUTE_GROUP;

    @Value("${spring.cloud.nacos.config.server-addr}")
    public void setNacosServerAddr(String nacosServerAddr){
        NACOS_SERVER_ADDR = nacosServerAddr;
    }

    @Value("${spring.cloud.nacos.config.namespace}")
    public void setNacosNamespace(String nacosNamespace){
        NACOS_NAMESPACE = nacosNamespace;
    }

    @Value("${spring.cloud.nacos.config.customize.data-id}")
    public void setNacosRouteDataId(String nacosRouteDataId){
        NACOS_ROUTE_DATA_ID = nacosRouteDataId;
    }

    @Value("${spring.cloud.nacos.config.customize.group}")
    public void setNacosRouteGroup(String nacosRouteGroup){
        NACOS_ROUTE_GROUP = nacosRouteGroup;
    }

}

DynamicRouteServiceImplByNacos.java,在这里初始化configService,对路由文件gateway-router.json进行监听,保证当更新nacos中gateway-router.json内容时,能够及时的监听变更然调用dynamicRouteService来更新路由

@Component
@Slf4j
@DependsOn({"gatewayConfig"})
public class DynamicRouteServiceImplByNacos {

    @Autowired
    private DynamicRouteServiceImpl dynamicRouteService;

    @NacosInjected
    private ConfigService configService;

    @PostConstruct
    public void init() {
        log.info("gateway route init...");
        try{
            configService = initConfigService();
            if(configService == null){
                log.warn("initConfigService fail");
                return;
            }
            String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATA_ID, GatewayConfig.NACOS_ROUTE_GROUP, GatewayConfig.DEFAULT_TIMEOUT);
            log.info("获取网关当前配置:\r\n{}",configInfo);
            List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
            for(RouteDefinition definition : definitionList){
                log.info("update route : {}",definition.toString());
                dynamicRouteService.add(definition);
            }
        } catch (Exception e) {
            log.error("初始化网关路由时发生错误", e);
        }
        dynamicRouteByNacosListener(GatewayConfig.NACOS_ROUTE_DATA_ID,GatewayConfig.NACOS_ROUTE_GROUP);
    }

    /**
     * 监听Nacos下发的动态路由配置
     */
    public void dynamicRouteByNacosListener (String dataId, String group){
        try {
            configService.addListener(dataId, group, new Listener()  {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    log.info("进行网关更新:\n\r{}",configInfo);
                    List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
                    for(RouteDefinition definition : definitionList){
                        log.info("update route : {}",definition.toString());
                        dynamicRouteService.update(definition);
                    }
                }
                @Override
                public Executor getExecutor() {
                    log.info("getExecutor\n\r");
                    return null;
                }
            });
        } catch (NacosException e) {
            log.error("从nacos接收动态路由配置出错!!!",e);
        }
    }

    /**
     * 初始化网关路由 nacos config
     */
    private ConfigService initConfigService(){
        try{
            Properties properties = new Properties();
            properties.setProperty("serverAddr", GatewayConfig.NACOS_SERVER_ADDR);
            properties.setProperty("namespace", GatewayConfig.NACOS_NAMESPACE);
            return configService= NacosFactory.createConfigService(properties);
        } catch (Exception e) {
            log.error("初始化网关路由时发生错误",e);
            return null;
        }
    }
}

DynamicRouteServiceImpl.java,操作路由,并且发布路由刷新事件,刷新内存中的路由信息。这里有一个很关键的代码,切记不能遗漏
routeDefinitionWriter.save(Mono.just(definition)).subscribe()
路由的save方法一定要加上subscribe(),不然路由不会生效。

@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    /**
     * 发布事件
     */
    @Autowired
    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    /**
     * 删除路由
     * @param id
     * @return
     */
    public String delete(String id) {
        try {
            log.info("gateway delete route id {}",id);
            this.routeDefinitionWriter.delete(Mono.just(id));
            return "delete success";
        } catch (Exception e) {
            return "delete fail";
        }
    }
    /**
     * 更新路由
     * @param definition
     * @return
     */
    public String update(RouteDefinition definition) {
        try {
            log.info("gateway update route {}",definition);
            this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
        } catch (Exception e) {
            return "update fail,not find route  routeId: "+definition.getId();
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        } catch (Exception e) {
            return "update route fail";
        }
    }

    /**
     * 增加路由
     * @param definition
     * @return
     */
    public String add(RouteDefinition definition) {
        log.info("gateway add route {}",definition);
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }
}

3. gateway-route.json文件格式

这里我分享一下自己的文件格式,根据需要自己进行修改。

[{
    "filters": [{
         "args": {
            "parts": "2"
        },
        "name": "StripPrefix"
    }],
    "id": "app-user",
    "order": 0,
    "predicates": [{
        "args": {
            "pattern": "/v1/api-user/**"
        },
        "name": "Path"
    }],
    "uri": "lb://kinhoodcloud-app-user"
}]

评论