Spring Cloud 網關服務 zuul 三 動態路由

  • 2019 年 10 月 31 日
  • 筆記

zuul動態路由

網關服務是流量的唯一入口。不能隨便停服務。所以動態路由就顯得尤為必要。

資料庫動態路由基於事件刷新機制熱修改zuul的路由屬性。

DiscoveryClientRouteLocator

file

可以看到DiscoveryClientRouteLocator 是默認的刷新的核心處理類。

  //重新載入路由資訊方法 protected方法。需要子方法重新方法。  protected LinkedHashMap<String, ZuulRoute> locateRoutes()    //觸發刷新的方法  RefreshableRouteLocator 介面   public void refresh() {              this.doRefresh();      }  

而這倆個方法都是繼承與SimpleRouteLocator 類,並進行了重新操作。其實官方的方法注釋說明了。如果需要動態讀取載入映射關係。則需要子類重寫這倆個方法。
進行具體的實現

首先pom jar包導入 需要連接mysql 資料庫

        <dependency>              <groupId>mysql</groupId>              <artifactId>mysql-connector-java</artifactId>          </dependency>            <!-- jdbc -->          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-jdbc</artifactId>          </dependency>

路由實體 ZuulRouteEntity

package com.xian.cloud.entity;    import lombok.Data;    import java.io.Serializable;  import java.util.Date;    /**   * <Description> 路由實體類   *   * @author [email protected]   * @version 1.0   * @createDate 2019/10/30 15:00   */  @Data  public class ZuulRouteEntity implements Serializable {      private static final long serialVersionUID = 1L;        /**       * router Id       */      private Integer id;      /**       * 路由路徑       */      private String path;      /**       * 服務名稱       */      private String serviceId;      /**       * url代理       */      private String url;      /**       * 轉發去掉前綴       */      private String stripPrefix;      /**       * 是否重試       */      private String retryable;      /**       * 是否啟用       */      private String enabled;      /**       * 敏感請求頭       */      private String sensitiveheadersList;      /**       * 創建時間       */      private Date createTime;      /**       * 更新時間       */      private Date updateTime;      /**       * 刪除標識(0-正常,1-刪除)       */      private String delFlag;  }  

新建DiscoveryRouteLocator 類 父類 介面 都不變化

  package com.xian.cloud.router;      import com.google.common.base.Strings;  import com.google.common.collect.Sets;  import com.xian.cloud.entity.ZuulRoute;  import lombok.extern.slf4j.Slf4j;  import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator;  import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;  import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;  import org.springframework.jdbc.core.BeanPropertyRowMapper;  import org.springframework.jdbc.core.JdbcTemplate;  import org.springframework.util.StringUtils;    import java.util.*;    /**   * <Description>   *   * @author [email protected]   * @version 1.0   * @createDate 2019/10/30 18:57   */  @Slf4j  public class DiscoveryRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {        private ZuulProperties properties;        private JdbcTemplate jdbcTemplate;        public DiscoveryRouteLocator(String servletPath, ZuulProperties properties, JdbcTemplate jdbcTemplate) {          super(servletPath, properties);          this.properties = properties;          this.jdbcTemplate = jdbcTemplate;          log.info("servletPath:{}",servletPath);      }        @Override      public void refresh() {          doRefresh();      }        @Override      protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {          LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap<String, ZuulProperties.ZuulRoute>();          //從配置文件中載入路由資訊          routesMap.putAll(super.locateRoutes());          //自定義載入路由資訊          routesMap.putAll(getRouteList());          //優化一下配置          LinkedHashMap<String, ZuulProperties.ZuulRoute> values = new LinkedHashMap<>();          for (Map.Entry<String, ZuulProperties.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;      }        /**       * 從資料庫讀取zuul路由規則       * @return       */     private LinkedHashMap<String, ZuulProperties.ZuulRoute> getRouteList() {          LinkedHashMap<String, ZuulProperties.ZuulRoute> zuulRoutes = new LinkedHashMap<>();          List<ZuulRoute> sysZuulRoutes = jdbcTemplate.query("select * from sys_zuul_route where del_flag = 0", new BeanPropertyRowMapper<>(ZuulRoute.class));           for (ZuulRoute route: sysZuulRoutes) {               // 為空跳過             if (Strings.isNullOrEmpty(route.getPath()) && Strings.isNullOrEmpty(route.getUrl())) {                 continue;             }               ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();             try {                 zuulRoute.setId(route.getServiceId());                 zuulRoute.setPath(route.getPath());                 zuulRoute.setServiceId(route.getServiceId());                 zuulRoute.setRetryable(Objects.equals("0", route.getRetryable()) ? Boolean.FALSE : Boolean.TRUE);                 zuulRoute.setStripPrefix(Objects.equals("0", route.getStripPrefix()) ? Boolean.FALSE : Boolean.TRUE);                 zuulRoute.setUrl(route.getUrl());                 List<String> sensitiveHeadersList = Arrays.asList(route.getSensitiveheadersList().split(","));                 if (sensitiveHeadersList != null) {                     Set<String> sensitiveHeaderSet = Sets.newHashSet();                     sensitiveHeadersList.forEach(sensitiveHeader -> sensitiveHeaderSet.add(sensitiveHeader));                     zuulRoute.setSensitiveHeaders(sensitiveHeaderSet);                     zuulRoute.setCustomSensitiveHeaders(true);                 }             } catch (Exception e) {                 log.error("資料庫載入配置異常", e);             }             log.info("自定義的路由配置,path:{},serviceId:{}", zuulRoute.getPath(), zuulRoute.getServiceId());             zuulRoutes.put(zuulRoute.getPath(), zuulRoute);           }          return zuulRoutes;     }  }  

我們還需要一個事件的生產者 和 消費者 直接圖方便 集成到一個類中

package com.xian.cloud.event;    import com.xian.cloud.router.DiscoveryRouteLocator;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.cloud.client.discovery.event.HeartbeatEvent;  import org.springframework.cloud.client.discovery.event.HeartbeatMonitor;  import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;  import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent;  import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;  import org.springframework.context.ApplicationEvent;  import org.springframework.context.ApplicationEventPublisher;  import org.springframework.context.ApplicationListener;  import org.springframework.context.event.ContextRefreshedEvent;  import org.springframework.stereotype.Service;    /**   * <Description> 路由刷新事件發布,與事件監聽者   *   * @author [email protected]   * @version 1.0   * @createDate 2019/10/30 15:27   */  @Service  public class RefreshRouteService implements ApplicationListener<ApplicationEvent> {        @Autowired      private ZuulHandlerMapping zuulHandlerMapping;        private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();        @Autowired      ApplicationEventPublisher publisher;        @Autowired      private DiscoveryRouteLocator dynamicRouteLocator;        /**       * 動態路由實現 調用refreshRoute() 發布刷新路由事件       */      public void refreshRoute() {          RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(dynamicRouteLocator);          publisher.publishEvent(routesRefreshedEvent);      }        /**       * 事件監聽者。監控檢測事件刷新       * @param event       */      @Override      public void onApplicationEvent(ApplicationEvent event) {          if(event instanceof ContextRefreshedEvent || event instanceof RefreshScopeRefreshedEvent || event instanceof RoutesRefreshedEvent){              //主動手動刷新。上下文刷新,配置屬性刷新              zuulHandlerMapping.setDirty(true);          }else if(event instanceof HeartbeatEvent){              //心跳觸發,將本地映射關係。關聯到遠程服務上              HeartbeatEvent heartbeatEvent = (HeartbeatEvent)event;              if(heartbeatMonitor.update(heartbeatEvent.getValue())){                  zuulHandlerMapping.setDirty(true);              }          }      }  }

對外提供觸發介面

package com.xian.cloud.controller;    import com.xian.cloud.event.RefreshRouteService;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.web.bind.annotation.GetMapping;  import org.springframework.web.bind.annotation.RestController;    /**   * <Description> 手動刷新對外介面   *   * @author [email protected]   * @version 1.0   * @createDate 2019/10/30 20:23   */  @RestController  public class RefreshController {        @Autowired      private RefreshRouteService refreshRouteService;        @GetMapping("/refresh")      public String refresh() {          refreshRouteService.refreshRoute();          return "refresh";      }    }  

資料庫表結構

CREATE TABLE `sys_zuul_route` (    `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'router Id',    `path` varchar(255) NOT NULL COMMENT '路由路徑',    `service_id` varchar(255) NOT NULL COMMENT '服務名稱',    `url` varchar(255) DEFAULT NULL COMMENT 'url代理',    `strip_prefix` char(1) DEFAULT '1' COMMENT '轉發去掉前綴',    `retryable` char(1) DEFAULT '1' COMMENT '是否重試',    `enabled` char(1) DEFAULT '1' COMMENT '是否啟用',    `sensitiveHeaders_list` varchar(255) DEFAULT NULL COMMENT '敏感請求頭',    `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',    `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',    `del_flag` char(1) DEFAULT '0' COMMENT '刪除標識(0-正常,1-刪除)',    PRIMARY KEY (`id`)  ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='動態路由配置表'

file

將配置文件client 消費者服務 路由配置注釋掉。設置數據源。從資料庫中讀取

file

啟動服務列印日誌

2019-10-30 20:49:39.946  INFO 63449 --- [TaskScheduler-1] c.xian.cloud.router.DynamicRouteLocator  : 添加資料庫自定義的路由配置,path:/client/**,serviceId:cloud-discovery-client  2019-10-30 20:49:40.397  INFO 63449 --- [TaskScheduler-1] c.xian.cloud.router.DynamicRouteLocator  : 添加資料庫自定義的路由配置,path:/client/**,serviceId:cloud-discovery-client

postman 請求client 介面 看看是否能轉發成功
file

基於zuul 動態網關路由完成。

後續還會更新網關的灰度方案、swagger2 整合的調試源服務。敬請期待!

摘自參考 spring cloud 官方文檔

參考書籍 重新定義spring cloud實戰

示例程式碼地址

伺服器nacos 地址 http://47.99.209.72:8848/nacos

往期地址 spring cloud alibaba 地址

spring cloud alibaba 簡介

Spring Cloud Alibaba (nacos 註冊中心搭建)

Spring Cloud Alibaba 使用nacos 註冊中心

Spring Cloud Alibaba nacos 配置中心使用

spring cloud 網關服務

Spring Cloud zuul網關服務 一

Spring Cloud 網關服務 zuul 二

如何喜歡可以關注分享本公眾號。
file

版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。轉載請附帶公眾號二維碼

Tags: