1. 程式人生 > >zuul源碼(2)

zuul源碼(2)

ice service miss 暴露 ima array manually hset 所有

路由

路由是網關的核心功能,既然在spring的框架下,那就要按Spring的規矩來。

路由規則類:org.springframework.cloud.netflix.zuul.filters.Route 維護這以下信息:

private String id;

private String fullPath;

private String path;

private String location;

private String prefix;

private Boolean retryable;

private Set<String> sensitiveHeaders = new LinkedHashSet<>();

private boolean customSensitiveHeaders;

private boolean prefixStripped = true;

路由規則維護:RouteLocator

public interface RouteLocator {

   /**
    * Ignored route paths (or patterns), if any.
    */
   Collection<String> getIgnoredPaths();

   /**
    * A map of route path (pattern) to location (e.g. service id or URL).
    */
   List<Route> getRoutes();

   /**
    * Maps a path to an actual route with full metadata.
    */
   Route getMatchingRoute(String path);

}

類結構如下:
技術分享圖片

自動配置代碼:

@Bean
@ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
public DiscoveryClientRouteLocator discoveryRouteLocator() {
   return new DiscoveryClientRouteLocator(this.server.getServletPrefix(),
         this.discovery, this.zuulProperties, this.serviceRouteMapper, this.registration);
}

@Bean
@Primary
public CompositeRouteLocator primaryRouteLocator(
      Collection<RouteLocator> routeLocators) {
   return new CompositeRouteLocator(routeLocators);
}

@Bean
@ConditionalOnMissingBean(SimpleRouteLocator.class)
public SimpleRouteLocator simpleRouteLocator() {
   return new SimpleRouteLocator(this.server.getServletPrefix(),
         this.zuulProperties);
}

這裏會使用DiscoveryClientRouteLocator,它做了一個事就是利用DiscoveryClient把註冊中心的信息撈過來直接做映射成為路由規則列表。具體代碼寫的也有差點意思的,比如下圖:
技術分享圖片

首先有ZuulController和ZuulHandlerMapping,請求進來先在ZuulHandlerMapping裏的列表上找有沒有,如果有就把請求丟給ZuulController處理。所以裏面一定維護這個一個<path,ZuulController>這麽個map。這個匹配規則哪裏來呢,一般我們認為是配置,但這裏用了spring cloud,它加了註冊的微服務動態加入匹配規則的邏輯,也就是下面的DiscoveryClientRouteLocator。
ZuulHandlerMapping的lookupHandler方法:

protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
   if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
      return null;
   }
   if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null;
   RequestContext ctx = RequestContext.getCurrentContext();
   if (ctx.containsKey("forward.to")) {
      return null;
   }
   if (this.dirty) {
      synchronized (this) {
         if (this.dirty) {
            registerHandlers();
            this.dirty = false;
         }
      }
   }
   return super.lookupHandler(urlPath, request);
}

private void registerHandlers() {
   Collection<Route> routes = this.routeLocator.getRoutes();
   if (routes.isEmpty()) {
      this.logger.warn("No routes found from RouteLocator");
   }
   else {
      for (Route route : routes) {
         registerHandler(route.getFullPath(), this.zuul);
      }
   }
}

這裏看到this.dirty來控制是不是重新調用registerHandlers,看代碼是執行一遍後,列表被存下來後面進來就用這個列表就行了。這裏有一個點,就是每次心跳事件,就是應用和註冊中心保持的心跳的時候會把這個重新改成true,從而再執行到locateRoutes方法,就可以重新在內存裏拿註冊中心可能同步過來的新的信息。

public void onApplicationEvent(ApplicationEvent event) {
   if (event instanceof InstanceRegisteredEvent) {
      reset();
   }
   else if (event instanceof ParentHeartbeatEvent) {
      ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
      resetIfNeeded(e.getValue());
   }
   else if (event instanceof HeartbeatEvent) {
      HeartbeatEvent e = (HeartbeatEvent) event;
      resetIfNeeded(e.getValue());
   }

}

private void resetIfNeeded(Object value) {
   if (this.monitor.update(value)) {
      reset();
   }
}

private void reset() {
   this.zuulHandlerMapping.setDirty(true);
}

locateRoutes方法就是從註冊中心同步過來的所有註冊的應用,轉成一個個路由規則,如果自己在配置上配置了的路由規則,則按配置的來,沒配置的就補上。這裏註意了,也就是說不管你配置沒配置,只要註冊上來的,這個網關的路由規則上就有!問題是,很多微服務提供的接口並不想給網關用,甚至從分層的角度上來說某些應用屬於基礎服務應用,只想給上層的業務應用調用,並不想直接由網關暴露出去。那麽是不是有問題呢,這個也不算是問題,人家提供的框架本來就是要你在這個基礎上改造的。這個後續再講。
locateRoutes方法:

protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
   LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
   routesMap.putAll(super.locateRoutes());
   if (this.discovery != null) {
      Map<String, ZuulRoute> staticServices = new LinkedHashMap<String, ZuulRoute>();
      for (ZuulRoute route : routesMap.values()) {
         String serviceId = route.getServiceId();
         if (serviceId == null) {
            serviceId = route.getId();
         }
         if (serviceId != null) {
            staticServices.put(serviceId, route);
         }
      }
      // Add routes for discovery services by default
      List<String> services = this.discovery.getServices();
      String[] ignored = this.properties.getIgnoredServices()
            .toArray(new String[0]);
      for (String serviceId : services) {
         // Ignore specifically ignored services and those that were manually
         // configured
         String key = "/" + mapRouteToService(serviceId) + "/**";
         if (staticServices.containsKey(serviceId)
               && staticServices.get(serviceId).getUrl() == null) {
            // Explicitly configured with no URL, cannot be ignored
            // all static routes are already in routesMap
            // Update location using serviceId if location is null
            ZuulRoute staticRoute = staticServices.get(serviceId);
            if (!StringUtils.hasText(staticRoute.getLocation())) {
               staticRoute.setLocation(serviceId);
            }
         }
         if (!PatternMatchUtils.simpleMatch(ignored, serviceId)
               && !routesMap.containsKey(key)) {
            // Not ignored
            routesMap.put(key, new ZuulRoute(key, serviceId));
         }
      }
   }
   if (routesMap.get(DEFAULT_ROUTE) != null) {
      ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE);
      // Move the defaultServiceId to the end
      routesMap.remove(DEFAULT_ROUTE);
      routesMap.put(DEFAULT_ROUTE, defaultRoute);
   }
   LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
   for (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;
}

zuul源碼(2)