注意:本文的前提是基于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只有結(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如下:
-
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ù)
-
post類型:
- SendResponseFilter:將下游服務(wù)對(duì)請(qǐng)求的響應(yīng)寫(xiě)入當(dāng)前response中。
-
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ò)濾器的核心類。

能不能有更好的方式解決這個(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)箱即用的框架或接口。