一、說明

網關的核心概念就是路由配置和路由規則,而作爲所有請求流量的入口,在實際生產環境中爲了保證高可靠和高可用,是儘量要避免重啓的,所以實現動態路由是非常有必要的;本文主要介紹實現的思路,並且以 Nacos 爲數據源來講解

二、實現要點

要實現動態路由只需關注下面4個點

  1. 網關啓動時, 動態路由 的數據怎樣加載進來
  2. 靜態路由動態路由 以那個爲準,ps: 靜態路由 指的是配置文件裏寫死的路由配置
  3. 監聽 動態路由 的數據源變化
  4. 數據有變化時怎樣 通知zuul 刷新路由

三、具體實現

3.1. 實現動態路由的數據加載

  • 重寫 SimpleRouteLocator 類的 locateRoutes 方法,此方法是加載路由配置的,父類中是獲取properties中的路由配置,可以通過擴展此方法,達到動態獲取配置的目的
  • 這裏採用 靜態路由動態路由 共存,相同路由id以 動態路由 優先覆蓋的實現方式

AbstractDynRouteLocator類可查看: AbstractDynRouteLocator.java

public abstract class AbstractDynRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
    private ZuulProperties properties;

    public AbstractDynRouteLocator(String servletPath, ZuulProperties properties) {
        super(servletPath, properties);
        this.properties = properties;
    }

    /**
     * 刷新路由
     */
    @Override
    public void refresh() {
        doRefresh();
    }

    @Override
    protected Map<String, ZuulRoute> locateRoutes() {
        LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
        // 從application.properties中加載靜態路由信息
        routesMap.putAll(super.locateRoutes());
        // 從數據源中加載動態路由信息
        routesMap.putAll(loadDynamicRoute());
        // 優化一下配置
        LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
        for (Map.Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
            String path = entry.getKey();
            // Prepend with slash if not already present.
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            if (StringUtils.hasText(this.properties.getPrefix())) {
                path = this.properties.getPrefix() + path;
                if (!path.startsWith("/")) {
                    path = "/" + path;
                }
            }
            values.put(path, entry.getValue());
        }
        return values;
    }

    /**
     * 加載路由配置,由子類去實現
     */
    public abstract Map<String, ZuulRoute> loadDynamicRoute();
}

由於動態路由的數據可以有很多種 途徑 ,如:Nacos、Redis、Zookeeper、DB等,所以這裏定義一個抽象類,由具體的實現類去定義 loadDynamicRoute 方法

3.2. Nacos路由實現類

NacosDynRouteLocator類完整的代碼實現可查看: NacosDynRouteLocator.java

3.2.1. 實現 loadDynamicRoute 方法獲取動態數據

@Override
    public Map<String, ZuulProperties.ZuulRoute> loadDynamicRoute() {
        Map<String, ZuulRoute> routes = new LinkedHashMap<>();
        if (zuulRouteEntities == null) {
            zuulRouteEntities = getNacosConfig();
        }
        for (ZuulRouteEntity result : zuulRouteEntities) {
            if (StrUtil.isBlank(result.getPath()) || !result.isEnabled()) {
                continue;
            }
            ZuulRoute zuulRoute = new ZuulRoute();
            BeanUtil.copyProperties(result, zuulRoute);
            routes.put(zuulRoute.getPath(), zuulRoute);
        }
        return routes;
    }
        
    private List<ZuulRouteEntity> getNacosConfig() {
        try {
            String content = nacosConfigProperties.configServiceInstance().getConfig(ZUUL_DATA_ID, ZUUL_GROUP_ID,5000);
            return getListByStr(content);
        } catch (NacosException e) {
            log.error("listenerNacos-error", e);
        }
        return new ArrayList<>(0);
    }

3.2.2. 增加 NacosListener 監聽路由數據變化

private void addListener() {
        try {
            nacosConfigProperties.configServiceInstance().addListener(ZUUL_DATA_ID, ZUUL_GROUP_ID, new Listener() {
                @Override
                public Executor getExecutor() {
                    return null;
                }

                @Override
                public void receiveConfigInfo(String configInfo) {
                    //賦值路由信息
                    locator.setZuulRouteEntities(getListByStr(configInfo));
                    RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(locator);
                    publisher.publishEvent(routesRefreshedEvent);
                }
            });
        } catch (NacosException e) {
            log.error("nacos-addListener-error", e);
        }
    }

注意路由數據變化後不需要自己手動刷新路由,只需要給 zuul 發送一個 RoutesRefreshedEvent 事件即可, zuul 自己有個 ZuulRefreshListener類 會監聽事件幫我們刷新路由

3.3. 配置類創建 NacosDynRouteLocator 的Bean

DynamicZuulRouteConfig可查看: NacosDynRouteLocator.java

@Configuration
@ConditionalOnProperty(prefix = "zlt.gateway.dynamicRoute", name = "enabled", havingValue = "true")
public class DynamicZuulRouteConfig {
    @Autowired
    private ZuulProperties zuulProperties;

    @Autowired
    private DispatcherServletPath dispatcherServletPath;

    /**
     * Nacos實現方式
     */
    @Configuration
    @ConditionalOnProperty(prefix = "zlt.gateway.dynamicRoute", name = "dataType", havingValue = "nacos", matchIfMissing = true)
    public class NacosZuulRoute {
        @Autowired
        private NacosConfigProperties nacosConfigProperties;

        @Autowired
        private ApplicationEventPublisher publisher;

        @Bean
        public NacosDynRouteLocator nacosDynRouteLocator() {
            return new NacosDynRouteLocator(nacosConfigProperties, publisher, dispatcherServletPath.getPrefix(), zuulProperties);
        }
    }
}

這裏通過自定義配置來控制是否開啓 動態路由功能

3.4. 添加 Nacos 路由配置

新增配置項:

  • Data Id:zuul-routes
  • Group:ZUUL_GATEWAY
  • 配置內容:
[
    {
        "enabled":true,
        "id":"csdn",
        "path":"/csdn/**",
        "retryable":false,
        "stripPrefix":true,
        "url":"https://www.csdn.net/"
    }, {
        "enabled":true,
        "id":"github",
        "path":"/github/**",
        "retryable":false,
        "stripPrefix":true,
        "url":"http://github.com/"
    }
]

添加兩條路由數據

四、測試

  • 啓動網關通過 /actuator/routes 端點查看當前路由信息

    可以看到靜態路由和 Nacos 裏配置的兩條路由信息並存顯示

  • 修改 Nacos 配置,關閉 csdn 路由
  • 刷新查看網關的路由信息

    csdn 的路由已經看不到了,實現了動態改變路由配置

推薦閱讀

請掃碼關注我的公衆號

相關文章