第三章?網關zuul搭建
前言:
1、主要功能
? ? ? ? zuul主要提供動態路由(內置ribbon實現)和過濾(可以做統一鑒權過濾器、灰度發布過濾器、黑白名單IP過濾器、服務限流過濾器(可以配合Sentinel實現))功能;
2、和Spring Cloud GateWay的區別
? ? ? ? 屬于兩個不同開源組織提供的網關解決方案。spring cloud GateWay使用非阻塞API,內置限流過濾器,支持長連接(比如?websockets),在高并發和后端服務響應慢的場景下比Zuul1的表現要好;zuul無內置限流過濾器,但是可以與Sentinel(是阿里開源的一款高性能的限流框架)集成使用。
一、創建zuul服務module
參考第二章eureka的創建
二、配置文件
1、pom文件配置
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><modelVersion>4.0.0</modelVersion><parent><groupId>com.hq</groupId><artifactId>taxi-online</artifactId><version>0.0.1-SNAPSHOT</version></parent><artifactId>taxi-online-zuul</artifactId><name>taxi-online-zuul</name><description>personal test for zuul</description><url>org.apache</url><packaging>jar</packaging><dependencies><!-- zuul --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-zuul</artifactId></dependency><!-- eureka-client --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency></dependencies>
</project>
2、application.yml文件配置
#服務端口
server:port: 9100
#服務名
spring:application:name: taxi-online-zuul
#服務注冊
eureka:client:service-url:defaultZone: http://localhost:9001/eureka/,http://localhost:9002/eureka/enabled: true # 是否啟用Eureka clientinstance:hostname: localhostinstance-id: taxi-online-zuul # 實例id
3、啟動類
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class StartSpringCloudZuul {public static void main(String[] args) {SpringApplication.run(StartSpringCloudZuul.class,args);}
}
三、zuul功能
1、動態路由
2、統一鑒權
3、過濾器
4、灰度發布
5、限流
四、底層源碼
1、啟動原理
類似注冊中心:
1)boot啟動類
boot啟動類上添加@EnableZuulProxy注解;
2)EnableZuulProxy注解
EnableZuulProxy注解中導入ZuulProxyMarkerConfiguration配置類
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {}
3)ZuulProxyMarkerConfiguration配置類
ZuulProxyMarkerConfiguration配置類往容器添加了一個空的Marker對象,這個對象是作為容器是否添加ZuulProxyAutoConfiguration類的條件,因為ZuulProxyAutoConfiguration類上面加了@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)的注解
@Configuration(proxyBeanMethods = false)
public class ZuulProxyMarkerConfiguration {@Beanpublic Marker zuulProxyMarkerBean() {return new Marker();}class Marker {}}
4)spring.factories文件
zuul包下面的spring.factories文件指定了兩個引入類,只要導包就會被引進去(由SpringFactoriesLoader?進行讀取)--見下方截圖。如果沒有這個文件,boot的自動掃描不會掃描這些外部包的類。這兩個引入類被注入容器的條件分別是:
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
,分別可以通過在啟動類上添加下面注解來引入相應的Marker類:
@EnableZuulProxy
@EnableZuulServer。
@EnableZuulServer、@EnableZuulProxy兩個注解的區別:
@EnableZuulServer - 普通Zuul Server,只支持基本的route與filter功能.
@EnableZuulProxy - 普通Zuul Server+服務發現與熔斷等功能的增強版,具有反向代理功能。
@EnableZuulProxy簡單理解為@EnableZuulServer的增強版,當Zuul與Eureka、Ribbon等組件配合使用時,我們使用@EnableZuulProxy。所以我們做SpringCloud微服務系統一般都是使用@EnableZuulProxy。
5)ZuulServerAutoConfiguration配置類
ZuulServerAutoConfiguration配置類(源碼見下方)主要做的事情有:
1、往容器注入了ZuulServlet,該類是zuul的核心類。
2、往容器注入了 一些前置過濾器和后置過濾器。
3、注入ZuulFilterInitializer類并初始化,初始化邏輯中將所有過濾器交給FilterRegistry類進行管理。FilterRegistry類源碼如下:
/*** @author mhawthorne*/
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() {return this.filters.values();}}
ZuulServerAutoConfiguration源碼如下(有刪減):
/*** @author Spencer Gibb* @author Dave Syer* @author Biju Kunjummen*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
@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 {@Bean@ConditionalOnMissingBean(name = "zuulServlet")@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false",matchIfMissing = true)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@Beanpublic ServletDetectionFilter servletDetectionFilter() {return new ServletDetectionFilter();}@Bean@ConditionalOnMissingBeanpublic FormBodyWrapperFilter formBodyWrapperFilter() {return new FormBodyWrapperFilter();}@Bean@ConditionalOnMissingBeanpublic DebugFilter debugFilter() {return new DebugFilter();}@Bean@ConditionalOnMissingBeanpublic Servlet30WrapperFilter servlet30WrapperFilter() {return new Servlet30WrapperFilter();}// post filters@Beanpublic SendResponseFilter sendResponseFilter(ZuulProperties properties) {return new SendResponseFilter(zuulProperties);}@Beanpublic SendErrorFilter sendErrorFilter() {return new SendErrorFilter();}@Beanpublic SendForwardFilter sendForwardFilter() {return new SendForwardFilter();}//@Configuration(proxyBeanMethods = false)protected static class ZuulFilterConfiguration {@Autowiredprivate Map<String, ZuulFilter> filters;@Beanpublic ZuulFilterInitializer zuulFilterInitializer(CounterFactory counterFactory,TracerFactory tracerFactory) {FilterLoader filterLoader = FilterLoader.getInstance();FilterRegistry filterRegistry = FilterRegistry.instance();return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory,filterLoader, filterRegistry);}}
}
6)ZuulProxyAutoConfiguration配置類
繼承了ZuulServerAutoConfiguration類,是ZuulServerAutoConfiguration的增強版,在ZuulServerAutoConfiguration的基礎上增加eureka、ribbon、hystrix等功能。源碼如下:
/*** @author Spencer Gibb* @author Dave Syer* @author Biju Kunjummen*/
@Configuration(proxyBeanMethods = false)
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {// pre filters@Bean@ConditionalOnMissingBean(PreDecorationFilter.class)public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,ProxyRequestHelper proxyRequestHelper) {return new PreDecorationFilter(routeLocator,this.server.getServlet().getContextPath(), this.zuulProperties,proxyRequestHelper);}// route filters@Bean@ConditionalOnMissingBean(RibbonRoutingFilter.class)public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,RibbonCommandFactory<?> ribbonCommandFactory) {RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,this.requestCustomizers);return filter;}@Bean@ConditionalOnMissingBean({ SimpleHostRoutingFilter.class,CloseableHttpClient.class })public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,ZuulProperties zuulProperties,ApacheHttpClientConnectionManagerFactory connectionManagerFactory,ApacheHttpClientFactory httpClientFactory) {return new SimpleHostRoutingFilter(helper, zuulProperties,connectionManagerFactory, httpClientFactory);}@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(proxyBeanMethods = false)@ConditionalOnMissingClass("org.springframework.boot.actuate.health.Health")protected static class NoActuatorConfiguration {@Beanpublic ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {ProxyRequestHelper helper = new ProxyRequestHelper(zuulProperties);return helper;}}@Configuration(proxyBeanMethods = false)@ConditionalOnClass(Health.class)protected static class EndpointConfiguration {@Autowired(required = false)private HttpTraceRepository traces;@Bean@ConditionalOnAvailableEndpointpublic RoutesEndpoint routesEndpoint(RouteLocator routeLocator) {return new RoutesEndpoint(routeLocator);}@ConditionalOnAvailableEndpoint@Beanpublic FiltersEndpoint filtersEndpoint() {FilterRegistry filterRegistry = FilterRegistry.instance();return new FiltersEndpoint(filterRegistry);}@Beanpublic ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {TraceProxyRequestHelper helper = new TraceProxyRequestHelper(zuulProperties);if (this.traces != null) {helper.setTraces(this.traces);}return helper;}}}
2、核心類ZuulServlet
1)作用
調度不同階段的filters,處理異常。所有的Request都要經過ZuulServlet的處理。三個核心的方法preRoute(),route(), postRoute(),zuul對request處理邏輯都在這三個方法里。源碼如下:
public class ZuulServlet extends HttpServlet {private static final long serialVersionUID = -3374242278843351500L;private ZuulRunner zuulRunner;@Overridepublic void init(ServletConfig config) throws ServletException {super.init(config);String bufferReqsStr = config.getInitParameter("buffer-requests");boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;zuulRunner = new ZuulRunner(bufferReqs);}@Overridepublic void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {try {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 attachedRequestContext context = RequestContext.getCurrentContext();context.setZuulEngineRan();try {preRoute();} catch (ZuulException e) {error(e);postRoute();return;}try {route();} catch (ZuulException e) {error(e);postRoute();return;}try {postRoute();} catch (ZuulException e) {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.postRoute();}/*** executes "route" filters** @throws ZuulException*/void route() throws ZuulException {zuulRunner.route();}/*** executes "pre" filters** @throws ZuulException*/void preRoute() throws ZuulException {zuulRunner.preRoute();}/*** initializes request** @param servletRequest* @param servletResponse*/void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {zuulRunner.init(servletRequest, servletResponse);}/*** sets error context info and executes "error" filters** @param e*/void error(ZuulException e) {RequestContext.getCurrentContext().setThrowable(e);zuulRunner.error();}
}
2)調用鏈路:
以preRoute()為例:
1、ZuulServlet——》ZuulRunner,源碼見四-2-1);
2、ZuulRunner——》FilterProcessor,源碼如下:
public void preRoute() throws ZuulException {FilterProcessor.getInstance().preRoute();}
3、FilterProcessor調自己的runFilters方法,涉及調用其他類:
FilterProcessor——》FilterLoader,源碼見下方4
實際執行過濾器邏輯是在調用自己processZuulFilter方法中的下方的代碼邏輯。
ZuulFilterResult result = filter.runFilter();(runFilter里面調用了子類的run方法)
public void preRoute() throws ZuulException {try {runFilters("pre");} catch (ZuulException e) {throw e;} catch (Throwable e) {throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());}}/*** runs all filters of the filterType sType/ Use this method within filters to run custom filters by type** @param sType the filterType.* @return* @throws Throwable throws up an arbitrary exception*/public Object runFilters(String sType) throws Throwable {if (RequestContext.getCurrentContext().debugRouting()) {Debug.addRoutingDebug("Invoking {" + sType + "} type filters");}boolean bResult = false;List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);if (list != null) {for (int i = 0; i < list.size(); i++) {ZuulFilter zuulFilter = list.get(i);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();boolean bDebug = ctx.debugRouting();final String metricPrefix = "zuul.filter-";long execTime = 0;String filterName = "";try {long ltime = System.currentTimeMillis();filterName = filter.getClass().getSimpleName();RequestContext copy = null;Object o = null;Throwable t = null;if (bDebug) {Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);copy = ctx.copy();}ZuulFilterResult result = filter.runFilter();ExecutionStatus s = result.getStatus();execTime = System.currentTimeMillis() - ltime;switch (s) {case FAILED:t = result.getException();ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);break;case SUCCESS:o = result.getResult();ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);if (bDebug) {Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");Debug.compareContextState(filterName, copy);}break;default:break;}if (t != null) throw t;usageNotifier.notify(filter, s);return o;} catch (Throwable e) {if (bDebug) {Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage());}usageNotifier.notify(filter, ExecutionStatus.FAILED);if (e instanceof ZuulException) {throw (ZuulException) e;} else {ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);throw ex;}}}
4、FilterLoader——》filterRegistry,通過filterRegistry拿到所有的過濾器并篩選返回符合要求類型的集合并按優先級排序,放入緩存。
/*** Returns a list of filters by the filterType specified** @param filterType* @return a List<ZuulFilter>*/public List<ZuulFilter> getFiltersByType(String filterType) {List<ZuulFilter> list = hashFiltersByType.get(filterType);if (list != null) return list;list = new ArrayList<ZuulFilter>();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);}}Collections.sort(list); // sort by priorityhashFiltersByType.putIfAbsent(filterType, list);return list;}
5、FilterRegistry調用自己的getAllFilters方法返回所有的過濾器
private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>();
public Collection<ZuulFilter> getAllFilters() {return this.filters.values();}
3、頂級抽象父類ZuulFilter
所有的過濾器都應繼承該抽象類,重寫shouldFilter、filterType、filterOrder、run方法。
shouldFilter:該過濾器是否生效 ?返回true:有效;false:無效
filterType:指定過濾器類型,FilterConstants包含下面圖中幾種類型:filterOrder:在過濾器鏈中的順序,返回值越小越靠前;
run:過濾器的邏輯。
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {abstract boolean shouldFilter();abstract Object run() throws ZuulException;abstract public String filterType();/*** filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not* important for a filter. filterOrders do not need to be sequential.** @return the int order of a filter*/abstract public int filterOrder();public int compareTo(ZuulFilter filter) {return Integer.compare(this.filterOrder(), filter.filterOrder());}
}
4、過濾器熱加載
參考下方博客:
zuul 1.x 源碼解析 (結合本人過去2年工作經歷整理,超詳細)_zuulproxyautoconfiguration.predecorationfilter-CSDN博客文章瀏覽閱讀428次,點贊6次,收藏4次。zuul 1.x 源碼解析,全程斷點調試,超詳細(結合本人過去2年工作經歷,花了1周時間整理并記錄)_zuulproxyautoconfiguration.predecorationfilterhttps://blog.csdn.net/qq_22270363/article/details/132052278