Bootstrap

gateway网关路由原理及集成nacos实现动态路由

一. 概述

在这里插入图片描述

网关路由的核心类CachingRouteLocator, 它缓存了所有的路由,每个请求都需要从该类获取所有路由,且该类是一个spring事件处理器,实现刷新路由的事件,清空CachingRouteLocator的路由缓存,而缓存方法CacheFlux.lookup当缓存被清空的时候会自动重新从InMemoryRouteDefinitionRepository加载缓存。所以实现nacos动态路由只需要两步,第一步添加nacos配置变更监听器,第二步在nacos配置变更监听器中使用RouteDefinitionWriter更新路由信息,同时发送spring路由刷新事件。

二. pom.xml添加nacos依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

三. 添加配置

application.yml添加动态路由文件配置
在这里插入图片描述

四. 添加配置属性类

import com.sungrow.gateway.config.DynamicRouteConfig;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @Description 加载动态路由配置,通过此配置加载配置文件 gateway-dynamic-dev.json 里的路由信息
 **/
@Data
@Configuration
@ConfigurationProperties(prefix = "dynamic.route")
@ConditionalOnBean( DynamicRouteConfig.class)
public class DynamicRouteProperties {

    /**
     * nacos 配置管理  dataId
     */
    private String dataId;
    /**
     * nacos 配置管理 group
     */
    private String group;
    /**
     * nacos 服务地址
     */
    private String serverAddr;

    /**
     * 启动动态路由的标志,默认关闭
     */
    private boolean enabled = false;

    private String namespace;

    private String username;

    private String password;

}

五. 添加路由操作接口

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
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.Service;
import reactor.core.publisher.Mono;

import java.util.List;

@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher publisher;

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    /**
     * 增加路由
     * @param definition
     * @return
     */
    public void add(RouteDefinition definition) {
        try {
            addFilter(definition);
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        } catch (Exception ex) {
            log.error(String.format("add -> save route fail【routeId: %s】", definition.getId()), ex);
        }
    }

    /**
     * 更新路由
     * @param definition
     * @return
     */
    public void update(RouteDefinition definition){
        try {
            this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
        } catch (Exception ex) {
            log.error(String.format("update -> update route fail【routeId: %s】", definition.getId()), ex);
        }
        try {
            addFilter(definition);
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        } catch (Exception ex) {
            log.error(String.format("update -> save route fail【routeId: %s】", definition.getId()), ex);
        }
    }

    /**
     * 删除路由
     * @param id
     * @return
     */
    public void delete(String id) {
        try {
            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        } catch (Exception ex) {
            log.error(String.format("delete -> delete route fail【routeId: %s】", id), ex);
        }
    }

    /**
     * 给路由添加过滤器
     */
    private void addFilter(RouteDefinition definition) {
        FilterDefinition filterDefinition = new FilterDefinition();
        filterDefinition.setName(GatewayLimitFilterFactory.class.getSimpleName());
        List<FilterDefinition> filters = definition.getFilters();
        if (CollectionUtils.isEmpty(filters)) {
            filters = Lists.newArrayList();
        }
        filters.add(filterDefinition);
        definition.setFilters(filters);
    }

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


import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;

@Component
public class GatewayLimitFilterFactory extends AbstractGatewayFilterFactory<Object> {

    @Override
    public GatewayFilter apply(Object config) {
        return new GatewayLimitFilter(5, 1, 5);
    }

}

六. 路由配置类

import com.alibaba.fastjson.JSONObject;
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.AbstractListener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @Description 核心配置
 **/
@Slf4j
@Configuration
@ConditionalOnProperty(name = "dynamic.route.enabled", matchIfMissing = true)
public class DynamicRouteConfig {

    private static final List<String> ROUTE_LIST = new ArrayList<>();

    @Autowired//依赖注入,先初始化完成
    private DynamicRouteProperties properties;

    @Autowired
    private DynamicRouteServiceImpl dynamicRouteService;

    @PostConstruct
    private void init() {
        dynamicRouteByNacosListener();
    }

    private void dynamicRouteByNacosListener() {

        try {
            Properties prop = new Properties();
//            prop.setProperty(PropertyKeyConst.NAMESPACE, properties.getNamespace());
            prop.setProperty(PropertyKeyConst.SERVER_ADDR, properties.getServerAddr());
//            prop.setProperty(PropertyKeyConst.USERNAME, properties.getUsername());
//            prop.setProperty(PropertyKeyConst.PASSWORD, properties.getPassword());
            ConfigService configService = NacosFactory.createConfigService(prop);
            String content = configService.getConfigAndSignListener(
                    properties.getDataId(),
                    "DEFAULT_GROUP",
                    5000,
                    new AbstractListener() {
                        @Override
                        public void receiveConfigInfo(String configInfo) {
                            statsConfig(configInfo);
                        }
                    });
            statsConfig(content);
        } catch (NacosException e) {
            log.error("nacos 获取动态路由配置和监听异常", e);
        }
    }

    private void statsConfig(String configInfo) {
        try {
            clearRoute();
            List<RouteDefinition> gatewayRouteDefinitions = JSONObject.parseArray(configInfo, RouteDefinition.class);
            for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
                addRoute(routeDefinition);
            }
        } catch (Exception e) {
            log.error("statsConfig异常", e);
        }
    }

    private void clearRoute() {
        for(String id : ROUTE_LIST) {
            dynamicRouteService.delete(id);
        }
        ROUTE_LIST.clear();
    }

    private void addRoute(RouteDefinition definition) {
        dynamicRouteService.add(definition);
        ROUTE_LIST.add(definition.getId());
    }
}

七. 修改类总览

在这里插入图片描述

;