栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

springcloud Zuul动态路由的实现

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

springcloud Zuul动态路由的实现

前言

Zuul 是Netflix 提供的一个开源组件,致力于在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。也有很多公司使用它来作为网关的重要组成部分,碰巧今年公司的架构组决定自研一个网关产品,集动态路由,动态权限,限流配额等功能为一体,为其他部门的项目提供统一的外网调用管理,最终形成产品(这方面阿里其实已经有成熟的网关产品了,但是不太适用于个性化的配置,也没有集成权限和限流降级)。

不过这里并不想介绍整个网关的架构,而是想着重于讨论其中的一个关键点,并且也是经常在交流群中听人说起的:动态路由怎么做?

再阐释什么是动态路由之前,需要介绍一下架构的设计。

传统互联网架构图

 

上图是没有网关参与的一个最典型的互联网架构(本文中统一使用book代表应用实例,即真正提供服务的一个业务系统)

加入eureka的架构图

 

book注册到eureka注册中心中,zuul本身也连接着同一个eureka,可以拉取book众多实例的列表。服务中心的注册发现一直是值得推崇的一种方式,但是不适用与网关产品。因为我们的网关是面向众多的其他部门的已有或是异构架构的系统,不应该强求其他系统都使用eureka,这样是有侵入性的设计。

最终架构图

 

要强调的一点是,gateway最终也会部署多个实例,达到分布式的效果,在架构图中没有画出,请大家自行脑补。

本博客的示例使用最后一章架构图为例,带来动态路由的实现方式,会有具体的代码。

动态路由

动态路由需要达到可持久化配置,动态刷新的效果。如架构图所示,不仅要能满足从spring的配置文件properties加载路由信息,还需要从数据库加载我们的配置。另外一点是,路由信息在容器启动时就已经加载进入了内存,我们希望配置完成后,实施发布,动态刷新内存中的路由信息,达到不停机维护路由信息的效果。

zuul–HelloWorldDemo

项目结构

 com.sinosoft
 zuul-gateway-demo
 pom
 1.0

 
  org.springframework.boot
  spring-boot-starter-parent
  1.5.2.RELEASE
 

 
  gateway
  book
 

 
  
   
    org.springframework.cloud
    spring-cloud-dependencies
    Camden.SR6
    pom
    import
   
  
 

tip:springboot-1.5.2对应的springcloud的版本需要使用Camden.SR6,一开始想专门写这个demo时,只替换了springboot的版本1.4.0->1.5.2,结果启动就报错了,最后发现是版本不兼容的锅。

gateway项目:

启动类:GatewayApplication.java

@EnableZuulProxy
@SpringBootApplication
public class GatewayApplication {

 public static void main(String[] args) {
  SpringApplication.run(GatewayApplication.class, args);
 }

}

配置:application.properties

#配置在配置文件中的路由信息
zuul.routes.books.url=http://localhost:8090
zuul.routes.books.path=/books
 protected Map locateRoutes() {
  linkedHashMap routesMap = new linkedHashMap();
  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=" + path);
  return adjustedPath;
 }

}

重写过后的自定义路由定位器如下:

public class CustomRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator{

 public final static Logger logger = LoggerFactory.getLogger(CustomRouteLocator.class);

 private JdbcTemplate jdbcTemplate;

 private ZuulProperties properties;

 public void setJdbcTemplate(JdbcTemplate jdbcTemplate){
  this.jdbcTemplate = jdbcTemplate;
 }

 public CustomRouteLocator(String servletPath, ZuulProperties properties) {
  super(servletPath, properties);
  this.properties = properties;
  logger.info("servletPath:{}",servletPath);
 }

 //父类已经提供了这个方法,这里写出来只是为了说明这一个方法很重要!!!
// @Override
// protected void doRefresh() {
//  super.doRefresh();
// }


 @Override
 public void refresh() {
  doRefresh();
 }

 @Override
 protected Map locateRoutes() {
  linkedHashMap routesMap = new linkedHashMap();
  //从application.properties中加载路由信息
  routesMap.putAll(super.locateRoutes());
  //从db中加载路由信息
  routesMap.putAll(locateRoutesFromDB());
  //优化一下配置
  linkedHashMap values = new linkedHashMap<>();
  for (Map.Entry 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;
 }

 private Map locateRoutesFromDB(){
  Map routes = new linkedHashMap<>();
  List results = jdbcTemplate.query("select * from gateway_api_define where enabled = true ",new BeanPropertyRowMapper<>(ZuulRouteVO.class));
  for (ZuulRouteVO result : results) {
   if(org.apache.commons.lang3.StringUtils.isBlank(result.getPath()) || org.apache.commons.lang3.StringUtils.isBlank(result.getUrl()) ){
    continue;
   }
   ZuulRoute zuulRoute = new ZuulRoute();
   try {
    org.springframework.beans.BeanUtils.copyProperties(result,zuulRoute);
   } catch (Exception e) {
    logger.error("=============load zuul route info from db with error==============",e);
   }
   routes.put(zuulRoute.getPath(),zuulRoute);
  }
  return routes;
 }

 public static class ZuulRouteVO {

  
  private String id;

  
  private String path;

  
  private String serviceId;

  
  private String url;

  
  private boolean stripPrefix = true;

  
  private Boolean retryable;

  private Boolean enabled;

  public String getId() {
   return id;
  }

  public void setId(String id) {
   this.id = id;
  }

  public String getPath() {
   return path;
  }

  public void setPath(String path) {
   this.path = path;
  }

  public String getServiceId() {
   return serviceId;
  }

  public void setServiceId(String serviceId) {
   this.serviceId = serviceId;
  }

  public String getUrl() {
   return url;
  }

  public void setUrl(String url) {
   this.url = url;
  }

  public boolean isStripPrefix() {
   return stripPrefix;
  }

  public void setStripPrefix(boolean stripPrefix) {
   this.stripPrefix = stripPrefix;
  }

  public Boolean getRetryable() {
   return retryable;
  }

  public void setRetryable(Boolean retryable) {
   this.retryable = retryable;
  }

  public Boolean getEnabled() {
   return enabled;
  }

  public void setEnabled(Boolean enabled) {
   this.enabled = enabled;
  }
 }
}

配置这个自定义的路由定位器:

@Configuration
public class CustomZuulConfig {

 @Autowired
 ZuulProperties zuulProperties;
 @Autowired
 ServerProperties server;
 @Autowired
 JdbcTemplate jdbcTemplate;

 @Bean
 public CustomRouteLocator routeLocator() {
  CustomRouteLocator routeLocator = new CustomRouteLocator(this.server.getServletPrefix(), this.zuulProperties);
  routeLocator.setJdbcTemplate(jdbcTemplate);
  return routeLocator;
 }

}

现在容器启动时,就可以从数据库和配置文件中一起加载路由信息了,离动态路由还差最后一步,就是实时刷新,前面已经说过了,默认的ZuulConfigure已经配置了事件监听器,我们只需要发送一个事件就可以实现刷新了。

public class RefreshRouteService {

 @Autowired
 ApplicationEventPublisher publisher;

 @Autowired
 RouteLocator routeLocator;

 public void refreshRoute() {
  RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
  publisher.publishEvent(routesRefreshedEvent);
 }

}

具体的刷新流程其实就是从数据库重新加载了一遍,有人可能会问,为什么不自己是手动重新加载Locator.dorefresh?非要用事件去刷新。这牵扯到内部的zuul内部组件的工作流程,不仅仅是Locator本身的一个变量,具体想要了解的还得去看源码。

到这儿我们就实现了动态路由了,所以的实例代码和建表语句我会放到github上,下载的时候记得给我star QAQ !!!

链接:https://github.com/lexburner/zuul-gateway-demo

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/139736.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号