1. 程式人生 > 程式設計 >Spring Cloud 閘道器服務 zuul 動態路由的實現方法

Spring Cloud 閘道器服務 zuul 動態路由的實現方法

zuul動態路由

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

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

DiscoveryClientRouteLocator

可以看到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='動態路由配置表'

將配置檔案client 消費者服務 路由配置註釋掉。設定資料來源。從資料庫中讀取

啟動服務列印日誌

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 介面 看看是否能轉發成功

基於zuul 動態閘道器路由完成。

總結

以上所述是小編給大家介紹的Spring Cloud 閘道器服務 zuul 動態路由的實現方法,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對我們網站的支援!
如果你覺得本文對你有幫助,歡迎轉載,煩請註明出處,謝謝!