springcloud zuul 网关 持久化 动态加载路由的思路分析

Easter79
• 阅读 820

在springcloud 最新的版本已经有了自己的gateway组件    

目前世面上都是基于netflix 出品 zuul 的 gateway       一般我们在生产上 都希望能将路由动态化 持久化  做动态管理

基本设想思路  通过后台页面来管理路由   然后 刷新配置    本文将探索一下如何进行 zuul 路由的数据库持久化  动态化 建议从github上下载spring-cloud-netflix源码 

根据一些教程很容易知道  配置路由映射  通过

zuul.routes.<key>.path=/foo/**
zuul.routes.<key>.service-id= 服务实例名称 
zuul.routes.<key>.url=http://xxxoo 

 来设置   由此我们可以找到一个 ZuulProperties 的zuul配置类 ,从中我们发现有个属性  

    /**
     * Map of route names to properties.
     */
    private Map<String, ZuulRoute> routes = new LinkedHashMap<>();

从名字上看 知道是路由集合 , 而且有对应的set方法 ,经过对配置元数据json的解读  确认 这就 是 装载路由的容器     我们可以在 ZuulProperties 初始化的时候 将路由装载到容器中   那么 ZuulRoute 又是个什么玩意儿呢:

public static class ZuulRoute {

        /**
         * 路由的唯一编号  同时也默认为 装载路由的容器的Key 用来标识映射的唯一性 重要.
         */
        private String id;

        /**
         *  路由的规则 /foo/**.
         */
        private String path;

        /**
         * 服务实例ID(如果有的话)来映射到此路由 你可以指定一个服务或者url 但是不能两者同时对于一个key来配置
         * 
         */
        private String serviceId;

        /**
         *  就是上面提到的url
         *  
         */
        private String url;

        /**
         * 路由前缀是否在转发开始前被删除 默认是删除
         * 举个例子 你实例的实际调用是http://localhost:8002/user/info  
         * 如果你路由设置该实例对应的path 为 /api/v1/**  那么 通过路由调用 
         *  http://ip:port/api/v1/user/info  
         *  当为true 转发到 http://localhost:8002/user/info  
         *  当为false 转发到 http://localhost:8002//api/v1user/info   
         */
        private boolean stripPrefix = true;

        /**
         * 是否支持重试如果支持的话  通常需要服务实例id 跟ribbon 
         * 
         */
        private Boolean retryable;

        /**
         * 不传递到下游请求的敏感标头列表。默认为“安全”的头集,通常包含用户凭证。如果下游服务是与代理相同的系统的一
         * 部分,那么将它们从列表中删除就可以了,因此它们共享身份验证数据。如果在自己的域之外使用物理URL,那么通常来
         * 说泄露用户凭证是一个坏主意
         */
        private Set<String> sensitiveHeaders = new LinkedHashSet<>();
        /**
         * 上述列表sensitiveHeaders  是否生效 默认不生效
         */  
        private boolean customSensitiveHeaders = false;

上面这些就是我们需要进行 持久化 的东西

你可以用你知道的持久化方式 来实现 当然 这些可以加入缓存来减少IO提高性能    这里只说一个思路具体自己可以实现

当持久化完成后 我们如何让网关来刷新这些配置呢  每一次的curd 能迅速生效呢

这个就要 路由加载的机制和原理   路由是由路由定位器来 从配置中 获取路由表   进行匹配的

package org.springframework.cloud.netflix.zuul.filters;

import java.util.Collection;
import java.util.List;

/**
 * @author Dave Syer
 */
public interface RouteLocator {

    /**
     * Ignored route paths (or patterns), if any.
     */
    Collection<String> getIgnoredPaths();

    /**
     * 获取路由表.
     */
    List<Route> getRoutes();

    /**
     * 将路径映射到具有完整元数据的实际路由.
     */
    Route getMatchingRoute(String path);

}

 实现有这么几个:

springcloud  zuul 网关 持久化 动态加载路由的思路分析

第一个 复合定位器 CompositeRouteLocator

public class CompositeRouteLocator implements RefreshableRouteLocator {
    private final Collection<? extends RouteLocator> routeLocators;
    private ArrayList<RouteLocator> rl;

    public CompositeRouteLocator(Collection<? extends RouteLocator> routeLocators) {
        Assert.notNull(routeLocators, "'routeLocators' must not be null");
        rl = new ArrayList<>(routeLocators);
        AnnotationAwareOrderComparator.sort(rl);
        this.routeLocators = rl;
    }

    @Override
    public Collection<String> getIgnoredPaths() {
        List<String> ignoredPaths = new ArrayList<>();
        for (RouteLocator locator : routeLocators) {
            ignoredPaths.addAll(locator.getIgnoredPaths());
        }
        return ignoredPaths;
    }

    @Override
    public List<Route> getRoutes() {
        List<Route> route = new ArrayList<>();
        for (RouteLocator locator : routeLocators) {
            route.addAll(locator.getRoutes());
        }
        return route;
    }

    @Override
    public Route getMatchingRoute(String path) {
        for (RouteLocator locator : routeLocators) {
            Route route = locator.getMatchingRoute(path);
            if (route != null) {
                return route;
            }
        }
        return null;
    }

    @Override
    public void refresh() {
        for (RouteLocator locator : routeLocators) {
            if (locator instanceof RefreshableRouteLocator) {
                ((RefreshableRouteLocator) locator).refresh();
            }
        }
    }
}

这是一个既可以刷新 又可以定位的定位器   作用 可以将一个到多个定位器转换成 可刷新的定位器

看构造   传入路由定位器集合  然后   进行了排序 赋值  同时实现了  路由定位器的方法   跟刷新方法

我们刷新 可以根据将定位器  放入这个容器进行转换   

第二个  DiscoveryClientRouteLocator    是组合  静态  以及配置好的路由 跟一个服务发现实例  而且有优先权

第三个 **RefreshableRouteLocator   **实现 即可实现  动态刷新逻辑

第四个 **Simple****RouteLocator **   可以发现 第二个 继承了此定位器  说明 这个是一个基础的实现   基于所有的配置

public class SimpleRouteLocator implements RouteLocator, Ordered {

    private static final Log log = LogFactory.getLog(SimpleRouteLocator.class);

    private static final int DEFAULT_ORDER = 0;

    private ZuulProperties properties;

    private PathMatcher pathMatcher = new AntPathMatcher();

    private String dispatcherServletPath = "/";
    private String zuulServletPath;

    private AtomicReference<Map<String, ZuulRoute>> routes = new AtomicReference<>();
    private int order = DEFAULT_ORDER;

    public SimpleRouteLocator(String servletPath, ZuulProperties properties) {
        this.properties = properties;
        if (StringUtils.hasText(servletPath)) {
            this.dispatcherServletPath = servletPath;
        }

        this.zuulServletPath = properties.getServletPath();
    }

    @Override
    public List<Route> getRoutes() {
        List<Route> values = new ArrayList<>();
        for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {
            ZuulRoute route = entry.getValue();
            String path = route.getPath();
            values.add(getRoute(route, path));
        }
        return values;
    }

    @Override
    public Collection<String> getIgnoredPaths() {
        return this.properties.getIgnoredPatterns();
    }

    @Override
    public Route getMatchingRoute(final String path) {

        return getSimpleMatchingRoute(path);

    }

    protected Map<String, ZuulRoute> getRoutesMap() {
        if (this.routes.get() == null) {
            this.routes.set(locateRoutes());
        }
        return this.routes.get();
    }

    protected Route getSimpleMatchingRoute(final String path) {
        if (log.isDebugEnabled()) {
            log.debug("Finding route for path: " + path);
        }

        // This is called for the initialization done in getRoutesMap()
        getRoutesMap();

        if (log.isDebugEnabled()) {
            log.debug("servletPath=" + this.dispatcherServletPath);
            log.debug("zuulServletPath=" + this.zuulServletPath);
            log.debug("RequestUtils.isDispatcherServletRequest()="
                    + RequestUtils.isDispatcherServletRequest());
            log.debug("RequestUtils.isZuulServletRequest()="
                    + RequestUtils.isZuulServletRequest());
        }

        String adjustedPath = adjustPath(path);

        ZuulRoute route = getZuulRoute(adjustedPath);

        return getRoute(route, adjustedPath);
    }

    protected ZuulRoute getZuulRoute(String adjustedPath) {
        if (!matchesIgnoredPatterns(adjustedPath)) {
            for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {
                String pattern = entry.getKey();
                log.debug("Matching pattern:" + pattern);
                if (this.pathMatcher.match(pattern, adjustedPath)) {
                    return entry.getValue();
                }
            }
        }
        return null;
    }

    protected Route getRoute(ZuulRoute route, String path) {
        if (route == null) {
            return null;
        }
        if (log.isDebugEnabled()) {
            log.debug("route matched=" + route);
        }
        String targetPath = path;
        String prefix = this.properties.getPrefix();
        if (path.startsWith(prefix) && this.properties.isStripPrefix()) {
            targetPath = path.substring(prefix.length());
        }
        if (route.isStripPrefix()) {
            int index = route.getPath().indexOf("*") - 1;
            if (index > 0) {
                String routePrefix = route.getPath().substring(0, index);
                targetPath = targetPath.replaceFirst(routePrefix, "");
                prefix = prefix + routePrefix;
            }
        }
        Boolean retryable = this.properties.getRetryable();
        if (route.getRetryable() != null) {
            retryable = route.getRetryable();
        }
        return new Route(route.getId(), targetPath, route.getLocation(), prefix,
                retryable,
                route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null, 
                route.isStripPrefix());
    }

    /**
     * Calculate all the routes and set up a cache for the values. Subclasses can call
     * this method if they need to implement {@link RefreshableRouteLocator}.
     */
    protected void doRefresh() {
        this.routes.set(locateRoutes());
    }

    /**
     * Compute a map of path pattern to route. The default is just a static map from the
     * {@link ZuulProperties}, but subclasses can add dynamic calculations.
     */
    protected Map<String, ZuulRoute> locateRoutes() {
        LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
        for (ZuulRoute route : this.properties.getRoutes().values()) {
            routesMap.put(route.getPath(), route);
        }
        return routesMap;
    }

    protected boolean matchesIgnoredPatterns(String path) {
        for (String pattern : this.properties.getIgnoredPatterns()) {
            log.debug("Matching ignored pattern:" + pattern);
            if (this.pathMatcher.match(pattern, path)) {
                log.debug("Path " + path + " matches ignored pattern " + pattern);
                return true;
            }
        }
        return false;
    }

    private String adjustPath(final String path) {
        String adjustedPath = path;

        if (RequestUtils.isDispatcherServletRequest()
                && StringUtils.hasText(this.dispatcherServletPath)) {
            if (!this.dispatcherServletPath.equals("/")) {
                adjustedPath = path.substring(this.dispatcherServletPath.length());
                log.debug("Stripped dispatcherServletPath");
            }
        }
        else if (RequestUtils.isZuulServletRequest()) {
            if (StringUtils.hasText(this.zuulServletPath)
                    && !this.zuulServletPath.equals("/")) {
                adjustedPath = path.substring(this.zuulServletPath.length());
                log.debug("Stripped zuulServletPath");
            }
        }
        else {
            // do nothing
        }

        log.debug("adjustedPath=" + adjustedPath);
        return adjustedPath;
    }

    @Override
    public int getOrder() {
        return order;
    }
    
    public void setOrder(int order) {
        this.order = order;
    }

}

我们可以从   第一跟第四个下功夫

将配置从DB读取 放入 Simple****RouteLocator **    再注入到CompositeRouteLocator**

刷新的核心类 :

package org.springframework.cloud.netflix.zuul;

import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.context.ApplicationEvent;

/**
 * @author Dave Syer
 */
@SuppressWarnings("serial")
public class RoutesRefreshedEvent extends ApplicationEvent {

    private RouteLocator locator;

    public RoutesRefreshedEvent(RouteLocator locator) {
        super(locator);
        this.locator = locator;
    }

    public RouteLocator getLocator() {
        return this.locator;
    }

}

基于 事件    我们只要写一个监听器 来监听 就OK了  具体  自行实现 

这是我自己实现的  目前多实例情况下还不清楚是否会广播   如果不能广播 可参考config 刷新的思路来解决

@Configuration
public class ZuulConfig {
    @Resource
    private IRouterService routerService;
    @Resource
    private ServerProperties serverProperties;

    /**
     * 将数据库的网关配置数据 写入配置
     *
     * @return the zuul properties
     */
    @Bean
    public ZuulProperties zuulProperties() {
        ZuulProperties zuulProperties = new ZuulProperties();
        zuulProperties.setRoutes(routerService.initRoutersFromDB());
        return zuulProperties;
    }


    /**
     * 将配置写入可刷新的路由定位器.
     *
     * @param zuulProperties the zuul properties
     * @return the composite route locator
     */
    @Bean
    @ConditionalOnBean(ZuulProperties.class)
    public CompositeRouteLocator compositeRouteLocator(@Qualifier("zuulProperties") ZuulProperties zuulProperties) {
        List<RouteLocator> routeLocators = new ArrayList<>();
        RouteLocator simpleRouteLocator = new SimpleRouteLocator(serverProperties.getServletPrefix(), zuulProperties);
        routeLocators.add(simpleRouteLocator);
        return new CompositeRouteLocator(routeLocators);
    }

}

路由刷新器:

@Service
public class ZuulRefresherImpl implements ZuulRefresher {

    private static final Logger log = LoggerFactory.getLogger(ZuulRefresher.class);
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @Resource
    private IRouterService iRouterService;
    @Resource
    private ServerProperties serverProperties;
    @Resource
    private ZuulProperties zuulProperties;
    @Resource
    private CompositeRouteLocator compositeRouteLocator;

    @Override
    public void refreshRoutes() {

        zuulProperties.setRoutes(iRouterService.initRoutersFromDB());

        List<RouteLocator> routeLocators = new ArrayList<>();
        RouteLocator simpleRouteLocator = new SimpleRouteLocator(serverProperties.getServletPrefix(), zuulProperties);
        routeLocators.add(simpleRouteLocator);

        compositeRouteLocator = new CompositeRouteLocator(routeLocators);
        RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(compositeRouteLocator);
        applicationEventPublisher.publishEvent(routesRefreshedEvent);
        log.info("zuul 路由已刷新");
    }

}

springcloud  zuul 网关 持久化 动态加载路由的思路分析

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
3年前
Srping cloud gateway 实现动态路由(MySQL持久化+redis分布式缓存) 最新
摘要本文讲解在SpringCloud中如何通过MySQL和redis实现动态路由配置,以及路由信息持久化在MySQL中,同时使用Redis作为分布式路由信息缓存。无广告原文链接:Srpingcloudgateway实现动态路由(MySQL持久化redis分布式缓存)(https://www.oschina.net
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Easter79 Easter79
3年前
SpringCloud微服务(05):Zuul组件,实现路由网关控制
一、Zuul组件简介1、基础概念Zuul网关主要提供动态路由,监控,弹性,安全管控等功能。在分布式的微服务系统中,系统被拆为了多个微服务模块,通过zuul网关对用户的请求进行路由,转发到具体的后微服务模块中。2、Zuul的作用1)按照不同策略,将请求转发到不同的服务上去;
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k