springcloud zuul 網關 持久化 動態加載路由的思路分析

在springcloud 最新的版本已經有了本身的gateway組件    java

目前世面上都是基於netflix 出品 zuul 的 gateway       通常咱們在生產上 都但願能將路由動態化 持久化  作動態管理git

基本設想思路  經過後臺頁面來管理路由   而後 刷新配置    本文將探索一下如何進行 zuul 路由的數據庫持久化  動態化 建議從github上下載spring-cloud-netflix源碼 github

根據一些教程很容易知道  配置路由映射  經過spring

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 又是個什麼玩意兒呢:json

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;

上面這些就是咱們須要進行 持久化 的東西api

你能夠用你知道的持久化方式 來實現 固然 這些能夠加入緩存來減小IO提升性能    這裏只說一個思路具體本身能夠實現緩存

 

 

當持久化完成後 咱們如何讓網關來刷新這些配置呢  每一次的curd 能迅速生效呢安全

這個就要 路由加載的機制和原理   路由是由路由定位器來 從配置中 獲取路由表   進行匹配的app

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);

}

 實現有這麼幾個:

 

第一個 複合定位器 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   實現 便可實現  動態刷新邏輯

第四個 SimpleRouteLocator    能夠發現 第二個 繼承了此定位器  說明 這個是一個基礎的實現   基於全部的配置

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讀取 放入 SimpleRouteLocator     再注入到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 路由已刷新");
    }

}

 

 

 

                   

相關文章
相關標籤/搜索