注意:本文的前提是基于zuul的1.3.X版本來解析的炸茧,2.0版本采用了netty作為底層框架重新設(shè)計了整個zuul的架構(gòu)乌逐,將在后面進行分析擎椰。
zuul是什么
zuul是Netflix設(shè)計用來為所有面向設(shè)備逻住、web網(wǎng)站提供服務的所有應用的門面钟哥,zuul可以提供動態(tài)路由、監(jiān)控鄙信、彈性擴展瞪醋、安全認證等服務,他還可以根據(jù)需求將請求路由到多個應用中装诡。
zuul是用來解決什么問題的
在使用網(wǎng)關(guān)之前银受,動態(tài)的路由是通過Nginx的配置來做的,但是一旦發(fā)生改變鸦采,比如IP地址發(fā)生改變宾巍,加入其它路由,就要重新配置Nginx渔伯,重啟Nginx顶霞。安全認證是放在每一個應用中,應用中包含了非業(yè)務強相關(guān)的內(nèi)容锣吼,看起來也是不夠優(yōu)雅选浑。
在目前的應用中,zuul主要用來做如下幾件事情:
動態(tài)路由:APP玄叠、web網(wǎng)站通過zuul來訪問不同的服務提供方古徒,且與ribbon結(jié)合,還可以負載均衡的路由到同一個應用不同的實例中读恃。
安全認證:zuul作為互聯(lián)網(wǎng)服務架構(gòu)中的網(wǎng)關(guān)隧膘,可以用來校驗非法訪問、授予token寺惫、校驗token等疹吃。
限流:zuul通過記錄每種請求的類型來達到限制訪問過多導致服務down掉的目的。
靜態(tài)響應處理:直接在zuul就處理一些請求西雀,返回響應內(nèi)容萨驶,不轉(zhuǎn)發(fā)到微服務內(nèi)部。
區(qū)域彈性:主要是針對AWS上的應用做一些彈性擴展艇肴。
zuul的最佳實踐是怎樣的
Netflix是如何使用zuul的篡撵?
[站外圖片上傳中...(image-f9d654-1547796647001)]
可以看到判莉,在Netflix的架構(gòu)中,亞馬遜的彈性負載均衡作為第一層育谬,zuul作為第二層券盅,為所有應用的網(wǎng)關(guān),請求經(jīng)過AWS的負載均衡先發(fā)送到zuul膛檀,zuul再將流量轉(zhuǎn)發(fā)到各個API锰镀、website等等,然后各個API咖刃、website應用再調(diào)用各個微服務泳炉。
當然在Netflix的使用中,zuul也結(jié)合了Netflix的其他微服務組件一起使用嚎杨。
Hystrix:用來服務降級及熔斷
Ribbon:用來作為軟件的負載均衡花鹅,微服務之間相互調(diào)用是通過ribbon作為軟件負載均衡使用負載到微服務集群內(nèi)的不同的實例
Turbin:監(jiān)控服務的運行狀況
Feign:用作微服務之間發(fā)送rest請求的組件,可以將rest調(diào)用類似spring的其他bean一樣直接注入使用功能
Eureka:服務注冊中心
入門示例
引入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也注冊到eureka中枫浙,作為微服務中的一個應用刨肃,那么zuul就能將eureka中注冊的所有微服務應用的注冊信息都拿到,從而做到動態(tài)路由箩帚。
啟動類
@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服務引用了eureka-client后配置可以改成如下:
zuul:
routes:
product: /product/**
cart: /cart/**
或者更細致的配置:
zuul:
routes:
cart:
path: /cart/**
serviceId: cart
原訪問地址:http://localhost:8081/cart/1
經(jīng)過zuul后的訪問地址:http://localhost:8080/cart/cart/1
訪問效果:
注意:zuul只有結(jié)合了eureka紧帕,才會有ribbon作為軟件負載均衡盔然,直接配置邏輯URL,不會起到負載均衡的效果是嗜,也不會有hystrix作為熔斷器使用愈案。
如下:
zuul:
routes:
users:
path: /product/**
url: http://localhost:8082/product
如果想指定多個服務的列表且需要通過ribbon實現(xiàn)負載均衡,配置可參考如下:
zuul:
routes:
users:
path: /product/**
serviceId: product
ribbon:
eureka:
enabled: false
product:
ribbon:
listOfServers: example.com,google.com
當整合eureka時鹅搪,配置簡單站绪,服務太多,不想一個個配置的時候涩嚣,可以通過定義PatternServiceRouteMapper來全局定義匹配規(guī)則崇众。
個人推薦這種方式掂僵,簡單航厚。
示例如下:
@Configuration
public class ZuuPatternServiceRouteMapperConfiguration {
//注意!C膛睢幔睬!這兩者保留一個即可。
/**
* 沒有版本號的路由匹配規(guī)則bean
* @retu沒rn 路由匹配規(guī)則bean
*/
@Bean
public PatternServiceRouteMapper patternServiceRouteMapper(){
return new PatternServiceRouteMapper("(?<version>v.*$)","${name}");
}
/**
* 有版本號的路由匹配規(guī)則bean
* @return 路由匹配規(guī)則bean
*/
@Bean
public PatternServiceRouteMapper patternServiceRouteMapperWithVersion(){
return new PatternServiceRouteMapper("(?<name>.*)-(?<version>v.*$)","${version}/${name}");
}
}
全局添加映射前綴
通過zuul.prefix可以指定全局映射前綴芹扭,可以避免服務URL直接暴露出去麻顶,如前綴為/api(注意赦抖,這個”/“要加上,否則可能會出現(xiàn)404)辅肾,默認情況下队萤,代理前綴會在請求轉(zhuǎn)發(fā)前從請求中刪除前綴,可以通過zuul.stripPrefix來配置矫钓,默認是true要尔。
zuul:
prefix: /api
strip-prefix: true
這樣原來直接訪問比如說cart服務的地址為http://localhost:8081/cart/1,通過zuul網(wǎng)關(guān)新娜,且添加了全局映射前綴/api后的訪問路徑變?yōu)?a href="http://localhost:8080/api/cart/cart/1" target="_blank" rel="nofollow">http://localhost:8080/api/cart/cart/1赵辕。
如果在局部路由想關(guān)閉這個刪除前綴映射,可以通過以下配置指定:
zuul:
routes:
cart:
path: /cart/**
stripPrefix: false
不走路由的服務或者映射配置
ZuulProperties配置中概龄,有兩個屬性ignoredServices还惠,ignoredPatterns,屬性類型均為LinkedHashSet私杜。
ignoredServices:指定忽略某些服務的路由
ignoredPatterns:配置不走路由的某些路由匹配規(guī)則
zuul的filter
1.X版本的zuul實現(xiàn)是依賴于servlet的蚕键,所以對過濾器支持較好。zuul本身提供了較多的filter歪今,如SendForwardFilter嚎幸、DebugFilter、SendResponseFilter寄猩、SendErrorFilter等嫉晶。
zuul本身也提供了抽象類ZuulFilter,供自定義filter田篇。
- pre:在zuul按照規(guī)則路由到下游服務之前執(zhí)行替废,如果需要對請求進行預處理,比如鑒權(quán)泊柬、限流等椎镣,應在此類型的過濾器實現(xiàn)。
- route:這類filter是zuul路由的具體執(zhí)行者兽赁,是HTTPClient状答、OKhttp、或ribbon發(fā)送原始http請求的地方刀崖。
- post:這類filter是下游服務返回響應信息后執(zhí)行的地方惊科,如果需要對response做一些處理,可以考慮在此類過濾器實現(xiàn)亮钦。
- error:在整個生命周期類如果出現(xiàn)異常馆截,則會進入此類過濾器,可作為全局異常處理。
自定義ZuulFilter
自定義ZuulFilter蜡娶,需要實現(xiàn)幾個方法混卵,下面為前置過濾器的簡單示例。
public class AuthenticationFilter extends ZuulFilter {
@Override
public String filterType() {
//fiterType,有pre窖张、route幕随、post、error四種類型宿接,分別代表路由前合陵、路由時
//路由后以及異常時的過濾器
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
//排序,指定相同filterType時的執(zhí)行順序澄阳,越小越優(yōu)先執(zhí)行拥知,這里指定順序為路由過濾器的順序-1,即在路由前執(zhí)行
return return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
//是否應該調(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不存在碎赢!");
}
//校驗token正確性
return null;
}
}
多個過濾器之間可能想要傳輸內(nèi)容低剔,那么可以通過zuul提供的RequestContext來完成,RequestContext本身也是依賴于ThreadLocal來完成的肮塞。RequestContext本身也攜帶了HttpServletRequest和HttpServletResponse襟齿,可以獲取請求或響應中的內(nèi)容。
zuul的過濾器也可以配置為不啟用
通過zuul.<SimpleClassName>.<filterType>.disable=false來關(guān)閉指定過濾器枕赵。
例如:
zuul.SendResponseFilter.post.disable=true
@EnableZuulServer與@EnableZuulProxy的區(qū)別
在zuul的@EnableZuulProxy注解的源代碼中猜欺,是這么說的,@EnableZuulProxy提供了一些基本的反向代理過濾器拷窜,@EnableZuulServer只是將zuul指定為一個zuul的server开皿,并不提供任何反向代理的過濾器。一般我們推薦使用@EnableZuulProxy篮昧,如果不想用zuul自帶的過濾器赋荆,可以通過上面的方式關(guān)閉指定的過濾器。
@EnableZuulProxy自帶的filter如下:
-
pre類型:
ServletDetectionFilter
FormBodyWrapperFilter:解析請求form表單并在轉(zhuǎn)發(fā)到下游請求前重新編碼
DebugFilter:會打印執(zhí)行每個過濾器的執(zhí)行日志懊昨,但僅僅是打印”Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);“
SendForwardFilter:將請求轉(zhuǎn)發(fā)(路由)到下游服務
-
post類型:
- SendResponseFilter:將下游服務對請求的響應寫入當前response中窄潭。
-
error類型:
- SendErrorFilter:處理異常情況的過濾器
zuul的http客戶端
zuul實現(xiàn)路由,是通過http方式轉(zhuǎn)發(fā)到其他服務的酵颁,那么就需要http客戶端嫉你。默認情況下是通過Apache HTTP Client來發(fā)送http請求的,如果想使用restClient或者OKhttp可以通過配置指定躏惋。
ribbon:
okhttp:
enabled: true
或者
ribbon:
restclient:
enabled: true
當然可以自定義Apache HTTP Client和OkHttpClient并聲明為Bean幽污。
敏感header內(nèi)容
zuul支持將一些敏感的header內(nèi)容不轉(zhuǎn)發(fā)到下游的其他服務中,通過zuul.sensitiveHeaders將這些header內(nèi)容對下游服務屏蔽其掂。默認情況下油挥,header中的Cookie潦蝇、Set-Cookie款熬、Authorization將不被傳遞到下游服務器中深寥,可以通過zuul.sensitiveHeaders指定全局的敏感header內(nèi)容。所以在下游服務器想要獲取到cookie內(nèi)容時贤牛,這里需要重新配置下惋鹅。個人建議,cookie殉簸、Authorization內(nèi)容應該在zuul內(nèi)將其轉(zhuǎn)換成下游服務需要的userId闰集、user等,再傳遞到下游服務器般卑。
超時配置
zuul支持配置超時時間武鲁,如果使用的是eureka作為服務注冊中心,那么只要指定ribbon的超時時間即可蝠检。即ribbon.ReadTimeout和ribbon.SocketTimeout沐鼠。如果使用的是具體的URL路由,那么通過zuul.host.connect-timeout-millis和zuul.host.socket-timeout-millis指定叹谁。zuulProperties內(nèi)部類Host中還有一些諸如maxTotalConnections最大連接數(shù)等的一些配置饲梭。
CORS配置
默認情況下,zuul是允許所有cors請求的焰檩,如果要自定義cors憔涉,可以通過自定義WebMvcConfigurer來完成。
如:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/*")
.allowedOrigins("http://baidu.com");
}
}
其他配置
重試
zuul.retryable可以配置zuul是否使用ribbon的重試機制析苫。
代理請求header
zuul.addProxyHeaders可以配置是否將X-Forwarded-Host放入轉(zhuǎn)發(fā)的請求中
zuul的實現(xiàn)原理及設(shè)計是怎樣的
了解了zuul的使用方式兜叨,我們要開始了解下zuul的源碼以及zuul的整個架構(gòu)設(shè)計。
先從啟動類ZuulApplication入手衩侥。
@EnableDiscoveryClient
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
可以看到啟動類上加了注解@EnableZuulProxy浪腐,我們看下這個注解的源碼。
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
這個注解導入了一個配置類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 {
}
}
可以看到這個類什么都沒做议街,但是注釋上說明了這個類的作用是觸發(fā)另一個配置類ZuulProxyAutoConfiguration。
@Configuration
//導入了發(fā)送http請求的配置類璧榄,分別是對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);
}
//依賴服務發(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 前置過濾器骨杂,基于RouteLocator決定如何路由以及路由到哪個服務
@Bean
@ConditionalOnMissingBean(PreDecorationFilter.class)
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) {
return new PreDecorationFilter(routeLocator, this.server.getServlet().getContextPath(), this.zuulProperties,
proxyRequestHelper);
}
// route filters 路由過濾器涂身,和ribbon結(jié)合的路由過濾器,決定路由到服務的具體哪個實例
@Bean
@ConditionalOnMissingBean(RibbonRoutingFilter.class)
public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
RibbonCommandFactory<?> ribbonCommandFactory) {
RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
this.requestCustomizers);
return filter;
}
//route filter 路由過濾器搓蚪,針對預先決定的(配置了URL的)服務蛤售,執(zhí)行簡單的路由功能,并且此時也沒有配置 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 路由過濾器,針對預先決定的(配置了URL的)服務悴能,執(zhí)行簡單的路由功能揣钦,并且配置 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 {
//請求代理工具類,處理請求的URL轉(zhuǎn)換漠酿、參數(shù)冯凹、請求頭等等
@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的過濾器的endpoint
@ConditionalOnEnabledEndpoint
@Bean
public FiltersEndpoint filtersEndpoint() {
FilterRegistry filterRegistry = FilterRegistry.instance();
return new FiltersEndpoint(filterRegistry);
}
//增強了log的請求代理工具類
@Bean
public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
TraceProxyRequestHelper helper = new TraceProxyRequestHelper(zuulProperties);
if (this.traces != null) {
helper.setTraces(this.traces);
}
return helper;
}
}
}
從源碼中可以看到,ZuulProxyAutoConfiguration配置了一些諸如DiscoveryClientRouteLocator(依賴服務發(fā)現(xiàn)的路由定向類)炒嘲、ProxyRequestHelper(請求代理工具類)宇姚、zuul的幾個pre、route夫凸、post過濾器等bean浑劳。
ZuulProxyAutoConfiguration還導入了兼容了幾種兼容發(fā)送http請求的配置類,如HTTPClient夭拌、OKhttp呀洲、restClient等。
除此之外啼止,ZuulProxyAutoConfiguration還繼承了ZuulProxyAutoConfiguration類道逗。我們看下這個類做了哪些配置操作。
@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等都是配置在這個類中
@Autowired
protected ZuulProperties zuulProperties;
//server的配置類,主要是獲取contextPath
@Autowired
protected ServerProperties server;
//錯誤異常處理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);
}
//簡單的路由定向類吏夯,基于ZuulProperties
@Bean
@ConditionalOnMissingBean(SimpleRouteLocator.class)
public SimpleRouteLocator simpleRouteLocator() {
return new SimpleRouteLocator(this.server.getServlet().getContextPath(),
this.zuulProperties);
}
//聲明ZuulController,將ZuulServlet交給spring管理即横,springMvc mapping到路由噪生,會路由到此controller
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
//配置zuul handlerMapping,將請求的地址與遠程服務匹配
@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的應用監(jiān)聽器东囚,用于監(jiān)聽下游服務有沒有刷新applicationContext
@Bean
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
}
//注冊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跺嗽,決定請求是走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的包裝過濾器页藻,對請求進行包裝
@Bean
public Servlet30WrapperFilter servlet30WrapperFilter() {
return new Servlet30WrapperFilter();
}
// post filters桨嫁,后置過濾器,將代理請求中的響應寫入response中
@Bean
public SendResponseFilter sendResponseFilter(ZuulProperties properties) {
return new SendResponseFilter(zuulProperties);
}
//處理error的filter份帐,執(zhí)行順序為0
@Bean
public SendErrorFilter sendErrorFilter() {
return new SendErrorFilter();
}
//重定向的filter璃吧,執(zhí)行順序為500
@Bean
public SendForwardFilter sendForwardFilter() {
return new SendForwardFilter();
}
//饑餓加載時,從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提供的過濾器執(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ù)中畜挨,獲取是否緩存請求的配置
String bufferReqsStr = config.getInitParameter("buffer-requests");
boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;
//構(gòu)造ZuulRunner筒繁,初始化request & response,也是執(zhí)行filter的門面
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í)行前置過濾器
preRoute();
} catch (ZuulException e) {
//發(fā)生異常執(zhí)行error過濾器
error(e);
//執(zhí)行后置過濾器
postRoute();
return;
}
try {
//2.執(zhí)行路由過濾器
route();
} catch (ZuulException e) {
//發(fā)生異常執(zhí)行error過濾器
error(e);
//執(zhí)行后置過濾器
postRoute();
return;
}
try {
//3.執(zhí)行后置過濾器
postRoute();
} catch (ZuulException e) {
//發(fā)生異常執(zhí)行error過濾器
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 {
//通過ZuulRunner執(zhí)行后置過濾器
zuulRunner.postRoute();
}
/**
* executes "route" filters
*
* @throws ZuulException
*/
void route() throws ZuulException {
//通過ZuulRunner執(zhí)行路由過濾器
zuulRunner.route();
}
/**
* executes "pre" filters
*
* @throws ZuulException
*/
void preRoute() throws ZuulException {
//通過ZuulRunner執(zhí)行前綴過濾器
zuulRunner.preRoute();
}
/**
* initializes request
*
* @param servletRequest
* @param servletResponse
*/
void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
//通過ZuulRunner執(zhí)行init方法毡咏,初始化request & response
zuulRunner.init(servletRequest, servletResponse);
}
/**
* sets error context info and executes "error" filters
*
* @param e
*/
void error(ZuulException e) {
//通過ZuulRunner執(zhí)行error過濾器
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) {
//獲取請求上下文
RequestContext ctx = RequestContext.getCurrentContext();
if (bufferRequests) {
//如果緩存請求內(nèi)容,包裝下request幻赚,再將包裝后的request放入請求上下文中禀忆,供其他類使用,例如過濾器
ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
} else {
//將request放入請求上下文中落恼,供其他類使用箩退,例如過濾器
ctx.setRequest(servletRequest);
}
//將response放入請求上下文中,供其他類使用佳谦,例如過濾器
ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
}
再看下preRoute()方法戴涝。
/**
* executes "pre" filters
*
* @throws ZuulException
*/
void preRoute() throws ZuulException {
//通過ZuulRunner執(zhí)行前綴過濾器
zuulRunner.preRoute();
}
ZuulRunner的preRoute()方法。
/**
* executes "pre" filterType ZuulFilters
*
* @throws ZuulException
*/
public void preRoute() throws ZuulException {
//通過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ù)過濾器類型獲取該類型的所有zuulFilters
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
//逐個獲取過濾器實例
ZuulFilter zuulFilter = list.get(i);
//執(zhí)行過濾器
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
//獲取請求上下文
RequestContext ctx = RequestContext.getCurrentContext();
long execTime = 0;
try {
//記錄執(zhí)行過濾器時開始時間
long ltime = System.currentTimeMillis();
Object o = null;
Throwable t = null;
//執(zhí)行過濾器
ZuulFilterResult result = filter.runFilter();
//過濾器執(zhí)行結(jié)果狀態(tài)
ExecutionStatus s = result.getStatus();
//記錄執(zhí)行過濾器所耗時間
execTime = System.currentTimeMillis() - ltime;
switch (s) {
case FAILED:
//如果過濾器執(zhí)行結(jié)果狀態(tài)是FAILED啥刻,獲取執(zhí)行時異常,向請求上下文中塞入執(zhí)行結(jié)果概要
t = result.getException();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
break;
case SUCCESS:
//如果過濾器執(zhí)行結(jié)果狀態(tài)是SUCCESS咪笑,獲取執(zhí)行結(jié)果可帽,向請求上下文中塞入執(zhí)行結(jié)果概要
o = result.getResult();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
break;
default:
break;
}
//如果執(zhí)行過濾器失敗,有異常窗怒,這里再拋出異常
if (t != null) throw t;
return o;
}
//...
}
接下來看如何執(zhí)行過濾器映跟,追蹤ZuulFilter的runFilter()方法。
public ZuulFilterResult runFilter() {
//構(gòu)造zuul 過濾器執(zhí)行結(jié)果類
ZuulFilterResult zr = new ZuulFilterResult();
//先判斷過濾器是否關(guān)閉了
if (!isFilterDisabled()) {
//判斷是否要執(zhí)行過濾器扬虚,通過調(diào)用該過濾器的shouldFilter()方法
if (shouldFilter()) {
//....
try {
//調(diào)用該過濾器的run()方法
Object res = run();
//封裝run()方法執(zhí)行結(jié)果到過濾器執(zhí)行結(jié)果類中
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable e) {
//如果執(zhí)行run()方法異常了努隙,封裝過濾器執(zhí)行結(jié)果類
zr = new ZuulFilterResult(ExecutionStatus.FAILED);
zr.setException(e);
}
//....
} else {
//不執(zhí)行過濾器,封裝過濾器執(zhí)行結(jié)果類
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
在FilterProcessor的runFilters()方法中辜昵,有一行代碼值得說一下荸镊。
? List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
這句代碼的意思是根據(jù)zuul的過濾器類型,"pre"堪置、"route"贷洲、"post"、"error"等獲取過濾器列表晋柱。
追蹤下FilterLoader的getFiltersByType()方法优构。
/**
* Returns a list of filters by the filterType specified
*
* @param filterType
* @return a List<ZuulFilter>
*/
public List<ZuulFilter> getFiltersByType(String filterType) {
//這里hashFiltersByType是一個ConcurrentHashMap,key為filterType雁竞,value為zuul的所有filterType類型的filters
//先從內(nèi)存緩存中獲取該過濾器類型的過濾器列表
List<ZuulFilter> list = hashFiltersByType.get(filterType);
if (list != null) return list;
list = new ArrayList<ZuulFilter>();
//如果內(nèi)存緩存中沒有這些過濾器list钦椭,再從FilterRegistry中獲取所有過濾器拧额,遍歷,獲取所有該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);
}
}
//按照filterOrder排序彪腔,從小到大
Collections.sort(list); // sort by priority
//從FilterRegistry中獲取的所有過濾器再放入內(nèi)存緩存中
hashFiltersByType.putIfAbsent(filterType, list);
return list;
}
FilterRegistr整個類比較簡單侥锦,這里直接列出這個類。
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()方法就是直接將這個類中的ConcurrentHashMap緩存的值返回出去了德挣。
return this.filters.values();
}
}
既然FilterRegistry中的這個ConcurrentHashMap有所有過濾器的數(shù)據(jù)恭垦,那么這個數(shù)據(jù)是什么時候放進去的呢?
我們看下FilterRegistry的put()方法在什么時候被調(diào)用格嗅。
追蹤到了ZuulFilterInitializer的contextInitialized()方法中番挺。前面說過,這個ZuulFilterInitializer會在zuul啟動時初始化屯掖,那么就在啟動的時候就已經(jīng)將FilterRegistry緩存的所有過濾器數(shù)據(jù)塞進去了玄柏。
@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等都是映射到這個類中
ZuulProxyMarkerConfiguration:配置類映砖,引入了ZuulProxyAutoConfiguration配置。
ZuulProxyAutoConfiguration:引入了restClient灾挨、HTTPClient邑退、OKhttp等配置類,以及PreDecorationFilter劳澄、RibbonRoutingFilter等內(nèi)置過濾器地技,還有ProxyRequestHelper請求代理類等。
ZuulServerAutoConfiguration:配置了CompositeRouteLocator(組合的路由定向類)秒拔、SimpleRouteLocator(簡單的路由定向類)莫矗、聲明了ZuulController、ZuulHandlerMapping等砂缩,注冊了ZuulRefreshListener作谚、ServletRegistrationBean等,配置了ServletDetectionFilter庵芭、FormBodyWrapperFilter妹懒、SendResponseFilter、SendErrorFilter双吆、SendForwardFilter等filter眨唬。
ZuulFilterInitializer:監(jiān)聽整個zuul Filter的生命周期会前,初始化zuul的各種組件,包括過濾器等匾竿,并初始化了FilterRegistry、FilterLoader临庇,保存了各個類型的過濾器的列表昵慌。以及生命周期結(jié)束時時清除FilterRegistry假夺、FilterLoader的過濾器緩存數(shù)據(jù)。
ZuulServlet:zuul的1.X版本是依賴于servlet的侄泽,是zuul的所有訪問的入口蜻韭,包括對請求的過濾器執(zhí)行。
ZuulRunner:初始化request & response柿扣,以及將zuulServlet的調(diào)用轉(zhuǎn)發(fā)到FilterProcessor中去肖方。
FilterProcessor:執(zhí)行過濾器的核心類未状。
能不能有更好的方式解決這個問題
待完善司草。
zuul存在的一些問題是什么
- 基于多線程+阻塞IO的網(wǎng)絡(luò)模型,注定在面對大并發(fā)時處理能力相對較弱猜憎,不過2.X版本已經(jīng)重新設(shè)計了架構(gòu)搔课,采用netty作為底層網(wǎng)絡(luò)模型,并發(fā)連接數(shù)有了大幅度提升柬讨,性能也有了一定提升袍啡。
- 權(quán)限認證、限流等需要自己從頭開發(fā)卖鲤,無開箱即用的框架或接口。