spring-cloud-zuul动态路由的实现


声明:本文转载自https://my.oschina.net/dengfuwei/blog/1621627,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

先说明两个概念:路由配置和路由规则,路由配置是指配置某请求路径路由到指定的目的地址;路由规则是指匹配到路由配置之后,再进行自定义的规则判断,规则判断可以更改路由目的地址

zuul默认的路由都是在properties里配置的,如果需要动态路由,需要自己实现,由上面的源码分析可以看出,实现动态路由需要实现可刷新的路由定位器接口(RefreshableRouteLocator),并可以继承默认的实现(SimpleRouteLocator)再进行扩展

实现动态路由主要关注两个方法

  • protected Map<String, ZuulRoute> locateRoutes():此方法是加载路由配置的,父类中是获取properties中的路由配置,可以通过扩展此方法,达到动态获取配置的目的

  • public Route getMatchingRoute(String path):此方法是根据访问路径,获取匹配的路由配置,父类中已经匹配到路由,可以通过路由id查找自定义配置的路由规则,以达到根据自定义规则动态分流的效果

为了实现针对不同存储方式的动态路由,定义抽象类实现基本的功能,代码如下

package com.itopener.zuul.route.spring.boot.common; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator; import org.springframework.cloud.netflix.zuul.filters.Route; import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import com.alibaba.fastjson.JSON; import com.itopener.zuul.route.spring.boot.common.rule.IZuulRouteRule; import com.itopener.zuul.route.spring.boot.common.rule.IZuulRouteRuleMatcher;   public abstract class ZuulRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {     public final static Logger logger = LoggerFactory.getLogger(ZuulRouteLocator.class);           private ZuulProperties properties;           @Autowired     private IZuulRouteRuleMatcher zuulRouteRuleMatcher;           public ZuulRouteLocator(String servletPath, ZuulProperties properties) {         super(servletPath, properties);         this.properties = properties;         logger.info("servletPath:{}", servletPath);     }           /**      * @description 刷新路由配置      * @author fuwei.deng      * @date 2017年7月3日 下午6:04:42      * @version 1.0.0      * @return      */     @Override     public void refresh() {         doRefresh();     }           @Override     protected Map<String, ZuulRoute> locateRoutes() {         LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();         // 从application.properties中加载路由信息         // routesMap.putAll(super.locateRoutes());         // 加载路由配置         routesMap.putAll(loadLocateRoute());         // 优化一下配置         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;     }           /**      * @description 加载路由配置,由子类去实现      * @author fuwei.deng      * @date 2017年7月3日 下午6:04:42      * @version 1.0.0      * @return      */     public abstract Map<String, ZuulRoute> loadLocateRoute();           /**      * @description 获取路由规则,由子类去实现      * @author fuwei.deng      * @date 2017年7月3日 下午6:04:42      * @version 1.0.0      * @return      */     public abstract List<IZuulRouteRule> getRules(Route route);           /**      * @description 通过配置的规则改变路由目的地址      * @author fuwei.deng      * @date 2017年7月3日 下午6:04:42      * @version 1.0.0      * @return      */     @Override     public Route getMatchingRoute(String path) {         Route route = super.getMatchingRoute(path);         // 增加自定义路由规则判断         List<IZuulRouteRule> rules = getRules(route);         return zuulRouteRuleMatcher.matchingRule(route, rules);     }           /**      * @description 路由定位器的优先级      * @author fuwei.deng      * @date 2017年7月3日 下午6:04:42      * @version 1.0.0      * @return      */     @Override     public int getOrder() {         return -1;     }           /**      * @description 存储路由的entity转换为zuul需要的ZuulRoute      * @author fuwei.deng      * @date 2017年7月3日 下午6:19:40      * @version 1.0.0      * @param locateRouteList      * @return      */     public Map<String, ZuulRoute> handle(List<ZuulRouteEntity> locateRouteList){         if(CollectionUtils.isEmpty(locateRouteList)){             return null;         }         Map<String, ZuulRoute> routes = new LinkedHashMap<>();         for (ZuulRouteEntity locateRoute : locateRouteList) {             if (StringUtils.isEmpty(locateRoute.getPath())                     || !locateRoute.isEnable()                     || (StringUtils.isEmpty(locateRoute.getUrl()) && StringUtils.isEmpty(locateRoute.getServiceId()))) {                 continue;             }             ZuulRoute zuulRoute = new ZuulRoute();             try {                 zuulRoute.setCustomSensitiveHeaders(locateRoute.isCustomSensitiveHeaders());                 zuulRoute.setSensitiveHeaders(locateRoute.getSensitiveHeadersSet());                 zuulRoute.setId(locateRoute.getId()); //              zuulRoute.setLocation("");                 zuulRoute.setPath(locateRoute.getPath());                 zuulRoute.setRetryable(locateRoute.isRetryable());                 zuulRoute.setServiceId(locateRoute.getServiceId());                 zuulRoute.setStripPrefix(locateRoute.isStripPrefix());                 zuulRoute.setUrl(locateRoute.getUrl());                 logger.info("add zuul route: " + JSON.toJSONString(zuulRoute));             } catch (Exception e) {                 logger.error("=============load zuul route info with error==============", e);             }             routes.put(zuulRoute.getPath(), zuulRoute);         }         return routes;     } } 

然后定义子类实现路由配置和路由规则的获取,如存储在Zookeeper

package com.itopener.zuul.route.zk.spring.boot.autoconfigure; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.netflix.zuul.filters.Route; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import com.alibaba.fastjson.JSON; import com.itopener.zuul.route.spring.boot.common.ZuulRouteEntity; import com.itopener.zuul.route.spring.boot.common.ZuulRouteLocator; import com.itopener.zuul.route.spring.boot.common.ZuulRouteRuleEntity; import com.itopener.zuul.route.spring.boot.common.rule.IZuulRouteRule; /**  * @author fuwei.deng  * @date 2017年6月30日 上午11:11:19  * @version 1.0.0  */ public class ZuulRouteZookeeperLocator extends ZuulRouteLocator {     public final static Logger logger = LoggerFactory.getLogger(ZuulRouteZookeeperLocator.class);     @Autowired     private CuratorFrameworkClient curatorFrameworkClient;       private List<ZuulRouteEntity> locateRouteList;     public ZuulRouteZookeeperLocator(String servletPath, ZuulProperties properties) {         super(servletPath, properties);     }     @Override     public Map<String, ZuulRoute> loadLocateRoute() {         locateRouteList = new ArrayList<ZuulRouteEntity>();         try {             locateRouteList = new ArrayList<ZuulRouteEntity>();             //获取所有路由配置的id             List<String> keys = curatorFrameworkClient.getChildrenKeys("/");             //遍历获取所有路由配置             for(String item : keys){                 String value = curatorFrameworkClient.get("/" + item);                 if(!StringUtils.isEmpty(value)){                     ZuulRouteEntity route = JSON.parseObject(value, ZuulRouteEntity.class);                     //只需要启用的路由配置                     if(!route.isEnable()){                         continue;                     }                     route.setRuleList(new ArrayList<IZuulRouteRule>());                     //获取路由配置对应的所有路由规则的ID                     List<String> ruleKeys = curatorFrameworkClient.getChildrenKeys("/" + item);                     //遍历获取所有的路由规则                     for(String ruleKey : ruleKeys){                         String ruleStr = curatorFrameworkClient.get("/" + item + "/" + ruleKey);                         if(!StringUtils.isEmpty(ruleStr)){                             ZuulRouteRuleEntity rule = JSON.parseObject(ruleStr, ZuulRouteRuleEntity.class);                             //只保留可用的路由规则                             if(!rule.isEnable()){                                 continue;                             }                             route.getRuleList().add(rule);                         }                     }                     locateRouteList.add(route);                 }             }         } catch (Exception e) {             logger.error("load zuul route from zk exception", e);         }         logger.info("load zuul route from zk : " + JSON.toJSONString(locateRouteList));         return handle(locateRouteList);     }     @Override     public List<IZuulRouteRule> getRules(Route route) {         if(CollectionUtils.isEmpty(locateRouteList)){             return null;         }         for(ZuulRouteEntity item : locateRouteList){             if(item.getId().equals(route.getId())){                 return item.getRuleList();             }         }         return null;     } } 

以上为封装zuul动态路由的主要代码,完整代码见附件,附件代码包含使用db、zk、redis存储路由配置和路由规则,同时也包含管理页面、示例代码

使用方法和达到的效果

在配置路由的应用里引入对应的starter(引入一个即可)

<!-- zookeeper --> <dependency>     <groupId>com.itopener</groupId>     <artifactId>zuul-route-zk-starter</artifactId>     <version>1.0.0-SNAPSHOT</version>     <type>pom</type> </dependency> <!-- db--> <dependency>     <groupId>com.itopener</groupId>     <artifactId>zuul-route-db-starter</artifactId>     <version>1.0.0-SNAPSHOT</version>     <type>pom</type> </dependency> <!-- redis--> <dependency>     <groupId>com.itopener</groupId>     <artifactId>zuul-route-redis-starter</artifactId>     <version>1.0.0-SNAPSHOT</version>     <type>pom</type> </dependency> 

配置对应的数据源

  • zk:spring.zuul.route.zk.serverLists
  • db:正常配置数据源DataSource
  • redis:正常配置redis(RedisTemplate)

启动zuul-route-admin,进入管理页面,配置路由和路由规则(可以只配置路由,如果对应的路由规则为空则不进行规则判断),路由规则是js语法,内置obj对象,可以直接通过obj取request里的参数,比如

zuul路由规则配置

路由配置页面如下,配置的字段与properties配置的一致

zuul路由配置

路由和规则可以禁用、启用、删除等,也可以切换数据源查看

路由规则列表

切换路由规则数据源

路由配置和规则配置的刷新

  • 路由配置和规则配置后,本机可以直接调用刷新方法,但是考虑到路由网关一般也会多节点部署,所以没有直接调用刷新方法

  • zk可以监听数据变化,如果是使用zk存储,修改数据之后,各节点会自动刷新

  • redis和db没有监听的方法,所以需要配置自动刷新的时间,spring.zuul.route.refreshCron,默认值是:0/30 * * * * ?(每30秒刷新一次)

达到的效果

  • 如需根据时间配置分流(如之前的66活动根据上下午时间分流到不同的应用),可以配置对应的规则,规则内容为:new Date().getHours()>12?'true':'false',规则期望结果:true,规则匹配后即可路由到对应的路由目的地址(蓝色文字为admin管理页面对应的字段)

  • 如需根据参数name分流,可以配置对应的规则:obj.name == 'honey'?'1':'2',然后配置对应的期望结果和路由目的地址

  • 如果所有的规则都没有匹配,会返回404.所以使用时尽量让至少一个规则匹配,避免给用户带来不好的体验

目前已知的问题

  • 通过配置规则目前只能达到分流到指定服务,但不能细化到指定服务的某些节点

  • 由于spring容易监听了HeartbeatEvent事件会触发刷新加载路由配置,并且多次触发就会多次刷新,所以实际刷新的时间可能与自己配置的刷新时间不一致,并且会导致多次重复刷新

本文发表于2018年02月13日 08:31
(c)注:本文转载自https://my.oschina.net/dengfuwei/blog/1621627,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2469 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1