微服務(wù)實(shí)戰(zhàn)SpringCloud之Zuul

注意:本文的前提是基于zuul的1.3.X版本來(lái)解析的,2.0版本采用了netty作為底層框架重新設(shè)計(jì)了整個(gè)zuul的架構(gòu),將在后面進(jìn)行分析。

zuul是什么

zuul是Netflix設(shè)計(jì)用來(lái)為所有面向設(shè)備、web網(wǎng)站提供服務(wù)的所有應(yīng)用的門(mén)面,zuul可以提供動(dòng)態(tài)路由、監(jiān)控、彈性擴(kuò)展、安全認(rèn)證等服務(wù),他還可以根據(jù)需求將請(qǐng)求路由到多個(gè)應(yīng)用中。

zuul是用來(lái)解決什么問(wèn)題的

在使用網(wǎng)關(guān)之前,動(dòng)態(tài)的路由是通過(guò)Nginx的配置來(lái)做的,但是一旦發(fā)生改變,比如IP地址發(fā)生改變,加入其它路由,就要重新配置Nginx,重啟Nginx。安全認(rèn)證是放在每一個(gè)應(yīng)用中,應(yīng)用中包含了非業(yè)務(wù)強(qiáng)相關(guān)的內(nèi)容,看起來(lái)也是不夠優(yōu)雅。

在目前的應(yīng)用中,zuul主要用來(lái)做如下幾件事情:

  • 動(dòng)態(tài)路由:APP、web網(wǎng)站通過(guò)zuul來(lái)訪問(wèn)不同的服務(wù)提供方,且與ribbon結(jié)合,還可以負(fù)載均衡的路由到同一個(gè)應(yīng)用不同的實(shí)例中。

  • 安全認(rèn)證:zuul作為互聯(lián)網(wǎng)服務(wù)架構(gòu)中的網(wǎng)關(guān),可以用來(lái)校驗(yàn)非法訪問(wèn)、授予token、校驗(yàn)token等。

  • 限流:zuul通過(guò)記錄每種請(qǐng)求的類型來(lái)達(dá)到限制訪問(wèn)過(guò)多導(dǎo)致服務(wù)down掉的目的。

  • 靜態(tài)響應(yīng)處理:直接在zuul就處理一些請(qǐng)求,返回響應(yīng)內(nèi)容,不轉(zhuǎn)發(fā)到微服務(wù)內(nèi)部。

  • 區(qū)域彈性:主要是針對(duì)AWS上的應(yīng)用做一些彈性擴(kuò)展。

zuul的最佳實(shí)踐是怎樣的

Netflix是如何使用zuul的?

[站外圖片上傳中...(image-f9d654-1547796647001)]

可以看到,在Netflix的架構(gòu)中,亞馬遜的彈性負(fù)載均衡作為第一層,zuul作為第二層,為所有應(yīng)用的網(wǎng)關(guān),請(qǐng)求經(jīng)過(guò)AWS的負(fù)載均衡先發(fā)送到zuul,zuul再將流量轉(zhuǎn)發(fā)到各個(gè)API、website等等,然后各個(gè)API、website應(yīng)用再調(diào)用各個(gè)微服務(wù)。

當(dāng)然在Netflix的使用中,zuul也結(jié)合了Netflix的其他微服務(wù)組件一起使用。

  • Hystrix:用來(lái)服務(wù)降級(jí)及熔斷

  • Ribbon:用來(lái)作為軟件的負(fù)載均衡,微服務(wù)之間相互調(diào)用是通過(guò)ribbon作為軟件負(fù)載均衡使用負(fù)載到微服務(wù)集群內(nèi)的不同的實(shí)例

  • Turbin:監(jiān)控服務(wù)的運(yùn)行狀況

  • Feign:用作微服務(wù)之間發(fā)送rest請(qǐng)求的組件,可以將rest調(diào)用類似spring的其他bean一樣直接注入使用功能

  • Eureka:服務(wù)注冊(cè)中心

入門(mén)示例

引入zuul

dependencies {
    //spring-cloud-starter-netflix-zuul模塊
    implementation('org.springframework.cloud:spring-cloud-starter-netflix-zuul')
    //test模塊
    testImplementation('org.springframework.boot:spring-boot-starter-test')
    //引入eureka-client模塊
    implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
}

這里引入eureka-client是為了將zuul也注冊(cè)到eureka中,作為微服務(wù)中的一個(gè)應(yīng)用,那么zuul就能將eureka中注冊(cè)的所有微服務(wù)應(yīng)用的注冊(cè)信息都拿到,從而做到動(dòng)態(tài)路由。

啟動(dòng)類

@EnableDiscoveryClient
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }
}

zuul的路由配置

zuul的配置類為ZuulProperties,配置前綴為zuul。

下面為路由的一些示例配置(未結(jié)合eureka):

zuul:
  routes:
    product: http://localhost:8082/product/**
    cart: http://localhost:8081/cart/**

zuul服務(wù)引用了eureka-client后配置可以改成如下:

zuul:
  routes:
    product: /product/**
    cart: /cart/**

或者更細(xì)致的配置:

zuul:
  routes:
    cart:
      path: /cart/**
      serviceId: cart

原訪問(wèn)地址:http://localhost:8081/cart/1

經(jīng)過(guò)zuul后的訪問(wèn)地址:http://localhost:8080/cart/cart/1

訪問(wèn)效果:

zuul-訪問(wèn)效果1.png

注意:zuul只有結(jié)合了eureka,才會(huì)有ribbon作為軟件負(fù)載均衡,直接配置邏輯URL,不會(huì)起到負(fù)載均衡的效果,也不會(huì)有hystrix作為熔斷器使用。

如下:

zuul:
  routes:
  users:
  path: /product/**
  url: http://localhost:8082/product

如果想指定多個(gè)服務(wù)的列表且需要通過(guò)ribbon實(shí)現(xiàn)負(fù)載均衡,配置可參考如下:

zuul:
  routes:
    users:
      path: /product/**
      serviceId: product

ribbon:
  eureka:
    enabled: false

product:
  ribbon:
    listOfServers: example.com,google.com

當(dāng)整合eureka時(shí),配置簡(jiǎn)單,服務(wù)太多,不想一個(gè)個(gè)配置的時(shí)候,可以通過(guò)定義PatternServiceRouteMapper來(lái)全局定義匹配規(guī)則。

個(gè)人推薦這種方式,簡(jiǎn)單。

示例如下:

@Configuration
public class ZuuPatternServiceRouteMapperConfiguration {

    //注意?。?!這兩者保留一個(gè)即可。
    /**
     * 沒(méi)有版本號(hào)的路由匹配規(guī)則bean
     * @retu沒(méi)rn 路由匹配規(guī)則bean
     */
    @Bean
    public PatternServiceRouteMapper patternServiceRouteMapper(){

        return new PatternServiceRouteMapper("(?<version>v.*$)","${name}");
    }

    /**
     * 有版本號(hào)的路由匹配規(guī)則bean
     * @return 路由匹配規(guī)則bean
     */
    @Bean
    public PatternServiceRouteMapper patternServiceRouteMapperWithVersion(){
        return new PatternServiceRouteMapper("(?<name>.*)-(?<version>v.*$)","${version}/${name}");
    }

}

全局添加映射前綴

通過(guò)zuul.prefix可以指定全局映射前綴,可以避免服務(wù)URL直接暴露出去,如前綴為/api(注意,這個(gè)”/“要加上,否則可能會(huì)出現(xiàn)404),默認(rèn)情況下,代理前綴會(huì)在請(qǐng)求轉(zhuǎn)發(fā)前從請(qǐng)求中刪除前綴,可以通過(guò)zuul.stripPrefix來(lái)配置,默認(rèn)是true。

zuul:
  prefix: /api
  strip-prefix: true

這樣原來(lái)直接訪問(wèn)比如說(shuō)cart服務(wù)的地址為http://localhost:8081/cart/1,通過(guò)zuul網(wǎng)關(guān),且添加了全局映射前綴/api后的訪問(wèn)路徑變?yōu)?a href="http://localhost:8080/api/cart/cart/1" target="_blank" rel="nofollow">http://localhost:8080/api/cart/cart/1。

如果在局部路由想關(guān)閉這個(gè)刪除前綴映射,可以通過(guò)以下配置指定:

zuul:
   routes:
     cart:
       path: /cart/**
       stripPrefix: false

不走路由的服務(wù)或者映射配置

ZuulProperties配置中,有兩個(gè)屬性ignoredServices,ignoredPatterns,屬性類型均為L(zhǎng)inkedHashSet。

ignoredServices:指定忽略某些服務(wù)的路由

ignoredPatterns:配置不走路由的某些路由匹配規(guī)則

zuul的filter

1.X版本的zuul實(shí)現(xiàn)是依賴于servlet的,所以對(duì)過(guò)濾器支持較好。zuul本身提供了較多的filter,如SendForwardFilter、DebugFilter、SendResponseFilter、SendErrorFilter等。

zuul本身也提供了抽象類ZuulFilter,供自定義filter。

  • pre:在zuul按照規(guī)則路由到下游服務(wù)之前執(zhí)行,如果需要對(duì)請(qǐng)求進(jìn)行預(yù)處理,比如鑒權(quán)、限流等,應(yīng)在此類型的過(guò)濾器實(shí)現(xiàn)。
  • route:這類filter是zuul路由的具體執(zhí)行者,是HTTPClient、OKhttp、或ribbon發(fā)送原始http請(qǐng)求的地方。
  • post:這類filter是下游服務(wù)返回響應(yīng)信息后執(zhí)行的地方,如果需要對(duì)response做一些處理,可以考慮在此類過(guò)濾器實(shí)現(xiàn)。
  • error:在整個(gè)生命周期類如果出現(xiàn)異常,則會(huì)進(jìn)入此類過(guò)濾器,可作為全局異常處理。

自定義ZuulFilter

自定義ZuulFilter,需要實(shí)現(xiàn)幾個(gè)方法,下面為前置過(guò)濾器的簡(jiǎn)單示例。

public class AuthenticationFilter extends ZuulFilter {
    @Override
    public String filterType() {
        //fiterType,有pre、route、post、error四種類型,分別代表路由前、路由時(shí)
        //路由后以及異常時(shí)的過(guò)濾器
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        //排序,指定相同filterType時(shí)的執(zhí)行順序,越小越優(yōu)先執(zhí)行,這里指定順序?yàn)槁酚蛇^(guò)濾器的順序-1,即在路由前執(zhí)行
        return return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        //是否應(yīng)該調(diào)用run()方法
        return false;
    }

    @Override
    public Object run() throws ZuulException {
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        String token = request.getHeader("X-Authentication");
        if (StringUtils.isBlank(token)) {
            throw new TokenNotValidException("token不存在!");
        }
        //校驗(yàn)token正確性
        return null;
    }
}

多個(gè)過(guò)濾器之間可能想要傳輸內(nèi)容,那么可以通過(guò)zuul提供的RequestContext來(lái)完成,RequestContext本身也是依賴于ThreadLocal來(lái)完成的。RequestContext本身也攜帶了HttpServletRequest和HttpServletResponse,可以獲取請(qǐng)求或響應(yīng)中的內(nèi)容。

zuul的過(guò)濾器也可以配置為不啟用

通過(guò)zuul.<SimpleClassName>.<filterType>.disable=false來(lái)關(guān)閉指定過(guò)濾器。

例如:

zuul.SendResponseFilter.post.disable=true

@EnableZuulServer與@EnableZuulProxy的區(qū)別

在zuul的@EnableZuulProxy注解的源代碼中,是這么說(shuō)的,@EnableZuulProxy提供了一些基本的反向代理過(guò)濾器,@EnableZuulServer只是將zuul指定為一個(gè)zuul的server,并不提供任何反向代理的過(guò)濾器。一般我們推薦使用@EnableZuulProxy,如果不想用zuul自帶的過(guò)濾器,可以通過(guò)上面的方式關(guān)閉指定的過(guò)濾器。

@EnableZuulProxy自帶的filter如下:

  1. pre類型:

    • ServletDetectionFilter

    • FormBodyWrapperFilter:解析請(qǐng)求form表單并在轉(zhuǎn)發(fā)到下游請(qǐng)求前重新編碼

    • DebugFilter:會(huì)打印執(zhí)行每個(gè)過(guò)濾器的執(zhí)行日志,但僅僅是打印”Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);“

    • SendForwardFilter:將請(qǐng)求轉(zhuǎn)發(fā)(路由)到下游服務(wù)

  2. post類型:

    • SendResponseFilter:將下游服務(wù)對(duì)請(qǐng)求的響應(yīng)寫(xiě)入當(dāng)前response中。
  3. error類型:

    • SendErrorFilter:處理異常情況的過(guò)濾器

zuul的http客戶端

zuul實(shí)現(xiàn)路由,是通過(guò)http方式轉(zhuǎn)發(fā)到其他服務(wù)的,那么就需要http客戶端。默認(rèn)情況下是通過(guò)Apache HTTP Client來(lái)發(fā)送http請(qǐng)求的,如果想使用restClient或者OKhttp可以通過(guò)配置指定。

ribbon:
  okhttp:
    enabled: true

或者

ribbon:
  restclient:
    enabled: true

當(dāng)然可以自定義Apache HTTP Client和OkHttpClient并聲明為Bean。

敏感header內(nèi)容

zuul支持將一些敏感的header內(nèi)容不轉(zhuǎn)發(fā)到下游的其他服務(wù)中,通過(guò)zuul.sensitiveHeaders將這些header內(nèi)容對(duì)下游服務(wù)屏蔽。默認(rèn)情況下,header中的Cookie、Set-Cookie、Authorization將不被傳遞到下游服務(wù)器中,可以通過(guò)zuul.sensitiveHeaders指定全局的敏感header內(nèi)容。所以在下游服務(wù)器想要獲取到cookie內(nèi)容時(shí),這里需要重新配置下。個(gè)人建議,cookie、Authorization內(nèi)容應(yīng)該在zuul內(nèi)將其轉(zhuǎn)換成下游服務(wù)需要的userId、user等,再傳遞到下游服務(wù)器。

超時(shí)配置

zuul支持配置超時(shí)時(shí)間,如果使用的是eureka作為服務(wù)注冊(cè)中心,那么只要指定ribbon的超時(shí)時(shí)間即可。即ribbon.ReadTimeout和ribbon.SocketTimeout。如果使用的是具體的URL路由,那么通過(guò)zuul.host.connect-timeout-millis和zuul.host.socket-timeout-millis指定。zuulProperties內(nèi)部類Host中還有一些諸如maxTotalConnections最大連接數(shù)等的一些配置。

CORS配置

默認(rèn)情況下,zuul是允許所有cors請(qǐng)求的,如果要自定義cors,可以通過(guò)自定義WebMvcConfigurer來(lái)完成。

如:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/*")
                .allowedOrigins("http://baidu.com");
    }
}

其他配置

重試

zuul.retryable可以配置zuul是否使用ribbon的重試機(jī)制。

代理請(qǐng)求header

zuul.addProxyHeaders可以配置是否將X-Forwarded-Host放入轉(zhuǎn)發(fā)的請(qǐng)求中

zuul的實(shí)現(xiàn)原理及設(shè)計(jì)是怎樣的

了解了zuul的使用方式,我們要開(kāi)始了解下zuul的源碼以及zuul的整個(gè)架構(gòu)設(shè)計(jì)。

先從啟動(dòng)類ZuulApplication入手。

@EnableDiscoveryClient
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }
}

可以看到啟動(dòng)類上加了注解@EnableZuulProxy,我們看下這個(gè)注解的源碼。

@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}

這個(gè)注解導(dǎo)入了一個(gè)配置類ZuulProxyMarkerConfiguration。

/**
 * Responsible for adding in a marker bean to trigger activation of 
 * {@link ZuulProxyAutoConfiguration}
 *
 * @author Biju Kunjummen
 */

@Configuration
public class ZuulProxyMarkerConfiguration {
   @Bean
   public Marker zuulProxyMarkerBean() {
      return new Marker();
   }

   class Marker {
   }
}

可以看到這個(gè)類什么都沒(méi)做,但是注釋上說(shuō)明了這個(gè)類的作用是觸發(fā)另一個(gè)配置類ZuulProxyAutoConfiguration。

@Configuration
//導(dǎo)入了發(fā)送http請(qǐng)求的配置類,分別是對(duì)restClient、httpClient、OKhttp等封裝的配置類
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
      RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
      RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
      HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {

   @SuppressWarnings("rawtypes")
   @Autowired(required = false)
   private List<RibbonRequestCustomizer> requestCustomizers = Collections.emptyList();

   @Autowired(required = false)
   private Registration registration;

   @Autowired
   private DiscoveryClient discovery;

   @Autowired
   private ServiceRouteMapper serviceRouteMapper;

   @Override
   public HasFeatures zuulFeature() {
      return HasFeatures.namedFeature("Zuul (Discovery)",
            ZuulProxyAutoConfiguration.class);
   }
   //依賴服務(wù)發(fā)現(xiàn)的路由定向類
   @Bean
   @ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
   public DiscoveryClientRouteLocator discoveryRouteLocator() {
      return new DiscoveryClientRouteLocator(this.server.getServlet().getContextPath(), this.discovery, this.zuulProperties,
            this.serviceRouteMapper, this.registration);
   }

   // pre filters 前置過(guò)濾器,基于RouteLocator決定如何路由以及路由到哪個(gè)服務(wù)
   @Bean
   @ConditionalOnMissingBean(PreDecorationFilter.class)
   public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) {
      return new PreDecorationFilter(routeLocator, this.server.getServlet().getContextPath(), this.zuulProperties,
            proxyRequestHelper);
   }

   // route filters 路由過(guò)濾器,和ribbon結(jié)合的路由過(guò)濾器,決定路由到服務(wù)的具體哪個(gè)實(shí)例
   @Bean
   @ConditionalOnMissingBean(RibbonRoutingFilter.class)
   public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
         RibbonCommandFactory<?> ribbonCommandFactory) {
      RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
            this.requestCustomizers);
      return filter;
   }
   //route filter 路由過(guò)濾器,針對(duì)預(yù)先決定的(配置了URL的)服務(wù),執(zhí)行簡(jiǎn)單的路由功能,并且此時(shí)也沒(méi)有配置  CloseableHttpClient的bean
   @Bean
   @ConditionalOnMissingBean({SimpleHostRoutingFilter.class, CloseableHttpClient.class})
   public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,
         ZuulProperties zuulProperties,
         ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
         ApacheHttpClientFactory httpClientFactory) {
      return new SimpleHostRoutingFilter(helper, zuulProperties,
            connectionManagerFactory, httpClientFactory);
   }
//route filter 路由過(guò)濾器,針對(duì)預(yù)先決定的(配置了URL的)服務(wù),執(zhí)行簡(jiǎn)單的路由功能,并且配置  CloseableHttpClient的bean
   @Bean
   @ConditionalOnMissingBean({SimpleHostRoutingFilter.class})
   public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper,
                                             ZuulProperties zuulProperties,
                                             CloseableHttpClient httpClient) {
      return new SimpleHostRoutingFilter(helper, zuulProperties,
            httpClient);
   }

   @Bean
   @ConditionalOnMissingBean(ServiceRouteMapper.class)
   public ServiceRouteMapper serviceRouteMapper() {
      return new SimpleServiceRouteMapper();
   }

   @Configuration
   @ConditionalOnMissingClass("org.springframework.boot.actuate.health.Health")
   protected static class NoActuatorConfiguration {

      //請(qǐng)求代理工具類,處理請(qǐng)求的URL轉(zhuǎn)換、參數(shù)、請(qǐng)求頭等等
      @Bean
      public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
         ProxyRequestHelper helper = new ProxyRequestHelper(zuulProperties);
         return helper;
      }

   }

   @Configuration
   @ConditionalOnClass(Health.class)
   protected static class EndpointConfiguration {

      @Autowired(required = false)
      private HttpTraceRepository traces;

      //列出所有路由信息的endpoint 
      @Bean
      @ConditionalOnEnabledEndpoint
      public RoutesEndpoint routesEndpoint(RouteLocator routeLocator) {
         return new RoutesEndpoint(routeLocator);
      }
       
      //列出所有zuul的過(guò)濾器的endpoint 
      @ConditionalOnEnabledEndpoint
      @Bean
      public FiltersEndpoint filtersEndpoint() {
         FilterRegistry filterRegistry = FilterRegistry.instance();
         return new FiltersEndpoint(filterRegistry);
      }

      //增強(qiáng)了log的請(qǐng)求代理工具類
      @Bean
      public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
         TraceProxyRequestHelper helper = new TraceProxyRequestHelper(zuulProperties);
         if (this.traces != null) {
            helper.setTraces(this.traces);
         }
         return helper;
      }
   }
}

從源碼中可以看到,ZuulProxyAutoConfiguration配置了一些諸如DiscoveryClientRouteLocator(依賴服務(wù)發(fā)現(xiàn)的路由定向類)、ProxyRequestHelper(請(qǐng)求代理工具類)、zuul的幾個(gè)pre、route、post過(guò)濾器等bean。

ZuulProxyAutoConfiguration還導(dǎo)入了兼容了幾種兼容發(fā)送http請(qǐng)求的配置類,如HTTPClient、OKhttp、restClient等。

除此之外,ZuulProxyAutoConfiguration還繼承了ZuulProxyAutoConfiguration類。我們看下這個(gè)類做了哪些配置操作。

@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)//基于ZuulServlet的配置
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
// Make sure to get the ServerProperties from the same place as a normal web app would
// FIXME @Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {

   //zuul的配置類,之前配置的zuul的prefix、routes等都是配置在這個(gè)類中
   @Autowired
   protected ZuulProperties zuulProperties;

   //server的配置類,主要是獲取contextPath 
   @Autowired
   protected ServerProperties server;

   //錯(cuò)誤異常處理controller,springboot自帶BasicErrorController 
   @Autowired(required = false)
   private ErrorController errorController;

   private Map<String, CorsConfiguration> corsConfigurations;

   @Autowired(required = false)
   private List<WebMvcConfigurer> configurers = emptyList();

   @Bean
   public HasFeatures zuulFeature() {
      return HasFeatures.namedFeature("Zuul (Simple)", ZuulServerAutoConfiguration.class);
   }

   //組合的路由定向類
   @Bean
   @Primary
   public CompositeRouteLocator primaryRouteLocator(
         Collection<RouteLocator> routeLocators) {
      return new CompositeRouteLocator(routeLocators);
   }

   //簡(jiǎn)單的路由定向類,基于ZuulProperties 
   @Bean
   @ConditionalOnMissingBean(SimpleRouteLocator.class)
   public SimpleRouteLocator simpleRouteLocator() {
      return new SimpleRouteLocator(this.server.getServlet().getContextPath(),
            this.zuulProperties);
   }

   //聲明ZuulController,將ZuulServlet交給spring管理,springMvc mapping到路由,會(huì)路由到此controller 
   @Bean
   public ZuulController zuulController() {
      return new ZuulController();
   }

   //配置zuul handlerMapping,將請(qǐng)求的地址與遠(yuǎn)程服務(wù)匹配 
   @Bean
   public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
      ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
      mapping.setErrorController(this.errorController);
      mapping.setCorsConfigurations(getCorsConfigurations());
      return mapping;
   }

   protected final Map<String, CorsConfiguration> getCorsConfigurations() {
      if (this.corsConfigurations == null) {
         ZuulCorsRegistry registry = new ZuulCorsRegistry();
         this.configurers
               .forEach(configurer -> configurer.addCorsMappings(registry));
         this.corsConfigurations = registry.getCorsConfigurations();
      }
      return this.corsConfigurations;
   }

   //zuul的應(yīng)用監(jiān)聽(tīng)器,用于監(jiān)聽(tīng)下游服務(wù)有沒(méi)有刷新applicationContext 
   @Bean
   public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
      return new ZuulRefreshListener();
   }

   //注冊(cè)zuulServlet到spring中 
   @Bean
   @ConditionalOnMissingBean(name = "zuulServlet")
   public ServletRegistrationBean zuulServlet() {
      ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean<>(new ZuulServlet(),
            this.zuulProperties.getServletPattern());
      // The whole point of exposing this servlet is to provide a route that doesn't
      // buffer requests.
      servlet.addInitParameter("buffer-requests", "false");
      return servlet;
   }

   // pre filters,決定請(qǐng)求是走DispatcherServlet還是ZuulServlet
   @Bean
   public ServletDetectionFilter servletDetectionFilter() {
      return new ServletDetectionFilter();
   }
   //解析form表單數(shù)據(jù),并重新編碼       
   @Bean
   public FormBodyWrapperFilter formBodyWrapperFilter() {
      return new FormBodyWrapperFilter();
   }

   @Bean
   public DebugFilter debugFilter() {
      return new DebugFilter();
   }

   //支持servlet 3.0的包裝過(guò)濾器,對(duì)請(qǐng)求進(jìn)行包裝 
   @Bean
   public Servlet30WrapperFilter servlet30WrapperFilter() {
      return new Servlet30WrapperFilter();
   }

   // post filters,后置過(guò)濾器,將代理請(qǐng)求中的響應(yīng)寫(xiě)入response中
   @Bean
   public SendResponseFilter sendResponseFilter(ZuulProperties properties) {
      return new SendResponseFilter(zuulProperties);
   }

   //處理error的filter,執(zhí)行順序?yàn)? 
   @Bean
   public SendErrorFilter sendErrorFilter() {
      return new SendErrorFilter();
   }

   //重定向的filter,執(zhí)行順序?yàn)?00 
   @Bean
   public SendForwardFilter sendForwardFilter() {
      return new SendForwardFilter();
   }

   //饑餓加載時(shí),從zuulPropertis中獲取所有路由信息 
   @Bean
   @ConditionalOnProperty(value = "zuul.ribbon.eager-load.enabled")
   public ZuulRouteApplicationContextInitializer zuulRoutesApplicationContextInitiazer(
         SpringClientFactory springClientFactory) {
      return new ZuulRouteApplicationContextInitializer(springClientFactory,
            zuulProperties);
   }

   @Configuration
   protected static class ZuulFilterConfiguration {

      @Autowired
      private Map<String, ZuulFilter> filters;

      //初始化zuul filter各種各樣組件
      @Bean
      public ZuulFilterInitializer zuulFilterInitializer(
            CounterFactory counterFactory, TracerFactory tracerFactory) {
         FilterLoader filterLoader = FilterLoader.getInstance();
         FilterRegistry filterRegistry = FilterRegistry.instance();
         return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
      }

   }
//....
}

zuul提供的過(guò)濾器執(zhí)行順序如下:

[圖片上傳失敗...(image-71beae-1547796647002)]

zuul的關(guān)鍵邏輯在ZuulServlet中。

public class ZuulServlet extends HttpServlet {

    private ZuulRunner zuulRunner;

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        //從初始化方法參數(shù)中,獲取是否緩存請(qǐng)求的配置
        String bufferReqsStr = config.getInitParameter("buffer-requests");
        boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;
        //構(gòu)造ZuulRunner,初始化request & response,也是執(zhí)行filter的門(mén)面
        zuulRunner = new ZuulRunner(bufferReqs);
    }

    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            //初始化request、response
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

            // Marks this request as having passed through the "Zuul engine", as opposed to servlets
            // explicitly bound in web.xml, for which requests will not have the same data attached
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                //1.先執(zhí)行前置過(guò)濾器
                preRoute();
            } catch (ZuulException e) {
                //發(fā)生異常執(zhí)行error過(guò)濾器
                error(e);
                //執(zhí)行后置過(guò)濾器
                postRoute();
                return;
            }
            try {
                //2.執(zhí)行路由過(guò)濾器
                route();
            } catch (ZuulException e) {
                //發(fā)生異常執(zhí)行error過(guò)濾器
                error(e);
                //執(zhí)行后置過(guò)濾器
                postRoute();
                return;
            }
            try {
                //3.執(zhí)行后置過(guò)濾器
                postRoute();
            } catch (ZuulException e) {
                //發(fā)生異常執(zhí)行error過(guò)濾器
                error(e);
                return;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }

    /**
     * executes "post" ZuulFilters
     *
     * @throws ZuulException
     */
    void postRoute() throws ZuulException {
        //通過(guò)ZuulRunner執(zhí)行后置過(guò)濾器
        zuulRunner.postRoute();
    }

    /**
     * executes "route" filters
     *
     * @throws ZuulException
     */
    void route() throws ZuulException {
        //通過(guò)ZuulRunner執(zhí)行路由過(guò)濾器
        zuulRunner.route();
    }

    /**
     * executes "pre" filters
     *
     * @throws ZuulException
     */
    void preRoute() throws ZuulException {
        //通過(guò)ZuulRunner執(zhí)行前綴過(guò)濾器
        zuulRunner.preRoute();
    }

    /**
     * initializes request
     *
     * @param servletRequest
     * @param servletResponse
     */
    void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        //通過(guò)ZuulRunner執(zhí)行init方法,初始化request & response
        zuulRunner.init(servletRequest, servletResponse);
    }

    /**
     * sets error context info and executes "error" filters
     *
     * @param e
     */
    void error(ZuulException e) {
        //通過(guò)ZuulRunner執(zhí)行error過(guò)濾器
        RequestContext.getCurrentContext().setThrowable(e);
        zuulRunner.error();
    }
}    

先看下init()方法。

void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
    //直接調(diào)用了ZuulRunner的init()方法
    zuulRunner.init(servletRequest, servletResponse);
}

ZuulRunner的init()方法。

public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
    //獲取請(qǐng)求上下文
    RequestContext ctx = RequestContext.getCurrentContext();
    if (bufferRequests) {
        //如果緩存請(qǐng)求內(nèi)容,包裝下request,再將包裝后的request放入請(qǐng)求上下文中,供其他類使用,例如過(guò)濾器
        ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
    } else {
        //將request放入請(qǐng)求上下文中,供其他類使用,例如過(guò)濾器
        ctx.setRequest(servletRequest);
    }
     //將response放入請(qǐng)求上下文中,供其他類使用,例如過(guò)濾器
    ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
}

再看下preRoute()方法。

/**
 * executes "pre" filters
 *
 * @throws ZuulException
 */
void preRoute() throws ZuulException {
    //通過(guò)ZuulRunner執(zhí)行前綴過(guò)濾器
    zuulRunner.preRoute();
}

ZuulRunner的preRoute()方法。

/**
 * executes "pre" filterType  ZuulFilters
 *
 * @throws ZuulException
 */
public void preRoute() throws ZuulException {
    //通過(guò)FilterProcessor執(zhí)行preRoute方法
    FilterProcessor.getInstance().preRoute();
}

FilterProcessor的preRoute()方法。

/**
 * runs all "pre" filters. These filters are run before routing to the orgin.
 *
 * @throws ZuulException
 */
public void preRoute() throws ZuulException {
    //...
    runFilters("pre");
    //...
}

public Object runFilters(String sType) throws Throwable {
        //...
        //定義全局結(jié)果布爾值
        boolean bResult = false;
        //從FilterLoader中根據(jù)過(guò)濾器類型獲取該類型的所有zuulFilters
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for (int i = 0; i < list.size(); i++) {
                //逐個(gè)獲取過(guò)濾器實(shí)例
                ZuulFilter zuulFilter = list.get(i);
                //執(zhí)行過(guò)濾器
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        return bResult;
}

public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
        //獲取請(qǐng)求上下文
        RequestContext ctx = RequestContext.getCurrentContext();
        long execTime = 0;
        try {
            //記錄執(zhí)行過(guò)濾器時(shí)開(kāi)始時(shí)間
            long ltime = System.currentTimeMillis();
            Object o = null;
            Throwable t = null;
            //執(zhí)行過(guò)濾器
            ZuulFilterResult result = filter.runFilter();
            //過(guò)濾器執(zhí)行結(jié)果狀態(tài)
            ExecutionStatus s = result.getStatus();
            //記錄執(zhí)行過(guò)濾器所耗時(shí)間
            execTime = System.currentTimeMillis() - ltime;

            switch (s) {
                case FAILED:
                    //如果過(guò)濾器執(zhí)行結(jié)果狀態(tài)是FAILED,獲取執(zhí)行時(shí)異常,向請(qǐng)求上下文中塞入執(zhí)行結(jié)果概要
                    t = result.getException();
                    ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                    break;
                case SUCCESS:
                    //如果過(guò)濾器執(zhí)行結(jié)果狀態(tài)是SUCCESS,獲取執(zhí)行結(jié)果,向請(qǐng)求上下文中塞入執(zhí)行結(jié)果概要
                    o = result.getResult();
                    ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
                    
                    break;
                default:
                    break;
            }
            //如果執(zhí)行過(guò)濾器失敗,有異常,這里再拋出異常
            if (t != null) throw t;
            return o;
        } 
    //...
    }

接下來(lái)看如何執(zhí)行過(guò)濾器,追蹤ZuulFilter的runFilter()方法。

public ZuulFilterResult runFilter() {
    //構(gòu)造zuul 過(guò)濾器執(zhí)行結(jié)果類
    ZuulFilterResult zr = new ZuulFilterResult();
    //先判斷過(guò)濾器是否關(guān)閉了
    if (!isFilterDisabled()) {
        //判斷是否要執(zhí)行過(guò)濾器,通過(guò)調(diào)用該過(guò)濾器的shouldFilter()方法
        if (shouldFilter()) {
            //....
            try {
                //調(diào)用該過(guò)濾器的run()方法
                Object res = run();
                //封裝run()方法執(zhí)行結(jié)果到過(guò)濾器執(zhí)行結(jié)果類中
                zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
            } catch (Throwable e) {
                //如果執(zhí)行run()方法異常了,封裝過(guò)濾器執(zhí)行結(jié)果類
                zr = new ZuulFilterResult(ExecutionStatus.FAILED);
                zr.setException(e);
            }
            //....
        } else {
            //不執(zhí)行過(guò)濾器,封裝過(guò)濾器執(zhí)行結(jié)果類
            zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
        }
    }
    return zr;
}

在FilterProcessor的runFilters()方法中,有一行代碼值得說(shuō)一下。

? List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);

這句代碼的意思是根據(jù)zuul的過(guò)濾器類型,"pre"、"route"、"post"、"error"等獲取過(guò)濾器列表。

追蹤下FilterLoader的getFiltersByType()方法。

/**
 * Returns a list of filters by the filterType specified
 *
 * @param filterType
 * @return a List<ZuulFilter>
 */
public List<ZuulFilter> getFiltersByType(String filterType) {
    //這里hashFiltersByType是一個(gè)ConcurrentHashMap,key為filterType,value為zuul的所有filterType類型的filters
    //先從內(nèi)存緩存中獲取該過(guò)濾器類型的過(guò)濾器列表
    List<ZuulFilter> list = hashFiltersByType.get(filterType);
    if (list != null) return list;

    list = new ArrayList<ZuulFilter>();
    //如果內(nèi)存緩存中沒(méi)有這些過(guò)濾器list,再?gòu)腇ilterRegistry中獲取所有過(guò)濾器,遍歷,獲取所有該filterType的filter list
    Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
    for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
        ZuulFilter filter = iterator.next();
        if (filter.filterType().equals(filterType)) {
            list.add(filter);
        }
    }
    //按照f(shuō)ilterOrder排序,從小到大
    Collections.sort(list); // sort by priority
    //從FilterRegistry中獲取的所有過(guò)濾器再放入內(nèi)存緩存中
    hashFiltersByType.putIfAbsent(filterType, list);
    return list;
}

FilterRegistr整個(gè)類比較簡(jiǎn)單,這里直接列出這個(gè)類。

public class FilterRegistry {

    private static final FilterRegistry INSTANCE = new FilterRegistry();

    public static final FilterRegistry instance() {
        return INSTANCE;
    }

    private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>();

    private FilterRegistry() {
    }

    public ZuulFilter remove(String key) {
        return this.filters.remove(key);
    }

    public ZuulFilter get(String key) {
        return this.filters.get(key);
    }

    public void put(String key, ZuulFilter filter) {
        this.filters.putIfAbsent(key, filter);
    }

    public int size() {
        return this.filters.size();
    }

    public Collection<ZuulFilter> getAllFilters() {
        //可以看到getAllFilters()方法就是直接將這個(gè)類中的ConcurrentHashMap緩存的值返回出去了。
        return this.filters.values();
    }

}

既然FilterRegistry中的這個(gè)ConcurrentHashMap有所有過(guò)濾器的數(shù)據(jù),那么這個(gè)數(shù)據(jù)是什么時(shí)候放進(jìn)去的呢?

我們看下FilterRegistry的put()方法在什么時(shí)候被調(diào)用。

追蹤到了ZuulFilterInitializer的contextInitialized()方法中。前面說(shuō)過(guò),這個(gè)ZuulFilterInitializer會(huì)在zuul啟動(dòng)時(shí)初始化,那么就在啟動(dòng)的時(shí)候就已經(jīng)將FilterRegistry緩存的所有過(guò)濾器數(shù)據(jù)塞進(jìn)去了。

@PostConstruct
public void contextInitialized() {
   log.info("Starting filter initializer");

   TracerFactory.initialize(tracerFactory);
   CounterFactory.initialize(counterFactory);

   for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
      filterRegistry.put(entry.getKey(), entry.getValue());
   }
}

FilterProcessor的route()、postRoute()、error()方法和preRoute()方法類似,都是調(diào)用runFilters()方法,傳入不同的filterType,這里不再贅述。

總結(jié)一下:

  • ZuulProperties:zuul的配置類,yaml文件中配置的zuul的prefix、routes等都是映射到這個(gè)類中

  • ZuulProxyMarkerConfiguration:配置類,引入了ZuulProxyAutoConfiguration配置。

  • ZuulProxyAutoConfiguration:引入了restClient、HTTPClient、OKhttp等配置類,以及PreDecorationFilter、RibbonRoutingFilter等內(nèi)置過(guò)濾器,還有ProxyRequestHelper請(qǐng)求代理類等。

  • ZuulServerAutoConfiguration:配置了CompositeRouteLocator(組合的路由定向類)、SimpleRouteLocator(簡(jiǎn)單的路由定向類)、聲明了ZuulController、ZuulHandlerMapping等,注冊(cè)了ZuulRefreshListener、ServletRegistrationBean等,配置了ServletDetectionFilter、FormBodyWrapperFilter、SendResponseFilter、SendErrorFilter、SendForwardFilter等f(wàn)ilter。

  • ZuulFilterInitializer:監(jiān)聽(tīng)整個(gè)zuul Filter的生命周期,初始化zuul的各種組件,包括過(guò)濾器等,并初始化了FilterRegistry、FilterLoader,保存了各個(gè)類型的過(guò)濾器的列表。以及生命周期結(jié)束時(shí)時(shí)清除FilterRegistry、FilterLoader的過(guò)濾器緩存數(shù)據(jù)。

  • ZuulServlet:zuul的1.X版本是依賴于servlet的,是zuul的所有訪問(wèn)的入口,包括對(duì)請(qǐng)求的過(guò)濾器執(zhí)行。

  • ZuulRunner:初始化request & response,以及將zuulServlet的調(diào)用轉(zhuǎn)發(fā)到FilterProcessor中去。

  • FilterProcessor:執(zhí)行過(guò)濾器的核心類。

zuul 幾種過(guò)濾器類型執(zhí)行順序.png

能不能有更好的方式解決這個(gè)問(wèn)題

待完善。

zuul存在的一些問(wèn)題是什么

  • 基于多線程+阻塞IO的網(wǎng)絡(luò)模型,注定在面對(duì)大并發(fā)時(shí)處理能力相對(duì)較弱,不過(guò)2.X版本已經(jīng)重新設(shè)計(jì)了架構(gòu),采用netty作為底層網(wǎng)絡(luò)模型,并發(fā)連接數(shù)有了大幅度提升,性能也有了一定提升。
  • 權(quán)限認(rèn)證、限流等需要自己從頭開(kāi)發(fā),無(wú)開(kāi)箱即用的框架或接口。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容