zoukankan      html  css  js  c++  java
  • zuul源码(2)

    路由

    路由是网关的核心功能,既然在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方法,就可以重新在内存里拿注册中心可能同步过来的新的信息。

    注意ZuulProxyAutoConfiguration中ZuulDiscoveryRefreshListener的onApplicationEvent方法:

    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;
    }
    
  • 相关阅读:
    Mongodb-SpringData
    Node-Vue
    SpringBoot-SpringCloud
    WebSocket-WebService
    Scala_学习
    GO_学习
    页面分页
    页面分页
    如何将域名部署到Tomcat中,用域名访问服务器
    如何将域名部署到Tomcat中,用域名访问服务器
  • 原文地址:https://www.cnblogs.com/killbug/p/9978491.html
Copyright © 2011-2022 走看看