
入門執行個體
項目依賴
org.springframework.cloud spring-cloud-starter-netflix-zuul org.springframework.cloud spring-cloud-starter-netflix-eureka-client
驅動網關工作
@[email protected](scanBasePackages ="com.spring.cloud.zuul.filter")public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }}
在網關應用入口處使用@EnableZuulProxy注解驅動Zuul網關工作。
@EnableZuulProxy注解
@EnableCircuitBreaker//驅動Hystrix熔斷器工作@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Import({ZuulProxyMarkerConfiguration.class})public @interface EnableZuulProxy {}
之是以使用@EnableZuulProxy驅動Zuul網關工作後,需要驅動Hystrix熔斷器開啟工作,因為服務網關可以在高并發場景下請求流量過大時保護服務網關,使服務網關應用不至于癱瘓。預設情況下,Zuul會使用信号量隔離的方式使用Hystrix熔斷器,并非使用線程池的方式進行隔離。
網關配置
application.properties
# Zuul應用名稱spring.application.name=zuulDemo#Zuul應用端口server.port=8040#将服務網關應用注冊至服務注冊中心,并從服務注冊中心擷取服務執行個體清單(包含服務執行個體名稱,服務執行個體IP,服務執行個體端口号)eureka.client.service-url.defaultZone=http://server1:8701/eureka/,http://server2:8702/eureka/#配置通過path方式進行服務路由zuul.routes.user-router.path=/user/**#配置通過url方式進行服務路由zuul.routes.user-router.url=http://localhost:8100/#配置服務路由service-idzuul.router.user-router.service-id=user-service-provider
上述配置檔案中,zuul.routes.*用來配置路由資訊,本身是一個Map對象,後續的配置是為了配置路由資訊。zuul.routes.user-router.path用來配置使用者微服務比對路徑;zuul.routes.user-router.url用來配置使用者微服務實際路徑;如果使用者微服務存在多個服務執行個體,通過配置微服務執行個體Id的方式進行路由配置(zuul.router.user-router.service-id=服務執行個體Id)。
Zuul原理——過濾器
Zuul的本質是一套Servlet的API。其中ZuulServlet是核心Servlet,接收各類服務請求。此外,Zuul還提供了ZuulServletFilter攔截器,可以攔截各類請求。為了更加友善和快捷的增加和删除攔截邏輯,在ZuulServlet和ZuulServletFilter基礎上,定義了自己的過濾器ZuulFilter。
過濾器設計
ZuulFilter是一個抽象類,實作了接口IZuulFilter。Netflix Zuul提供了許多ZuulFilter實作類。在Spring Cloud Zuul中實作的過濾器必須包含4個特征:過濾類型、執行順序、執行條件、具體操作。在ZuulFilter抽象類中對4個過濾器特性進行了定義:
public abstract class ZuulFilter implements IZuulFilter, Comparable { public abstract String filterType(); public abstract int filterOrder(); boolean shouldFilter(); Object run() throws ZuulException;}
方法的含義和功能總結如下:
方法名 | 描述 |
filterType | 傳回過濾器類型字元串(pre,routing,post,error) |
filterOrder | 傳回該過濾器執行順序,數值越小執行順序越靠前 |
shouldFilter | 傳回布爾值決定該過濾器是否需要執行 |
run | 過濾器執行的具體邏輯,可在該方法中實作自定義的業務邏輯,傳回null則進行後續的正常邏輯 |
執行原理
在ZuulFilter抽象類中,定義的4個方法均沒有參數,如何執行過濾器相關操作?
過濾器攔截的均為Http請求,在Zuul中提供了以下核心類來完成過濾器相關工作。
核心類 | 描述 |
ZuulServlet | 處理http請求入口,用于處理服務網關Http請求 |
ZuulRunner | 用于對不同類型的過濾器進行串聯 |
FilterProcessor | 執行具體的過濾器相關操作 |
RequestContext | 請求上下文,用于擷取請求參數和設定響應資訊 |
RequestContext
每一個新的http請求都是由一個獨立的線程進行處理(tomcat線程),本次請求的所有參數均在一個獨立的線程中進行處理,故将每次的請求上下文存儲于ThreadLocal(線程本地變量,每次處理過程中存儲RequestContex執行個體副本,避免參數誤操作)中。在initialValue方法中,每個Http請求隻能執行個體化自己的請求上下文(RequestContext)執行個體。
public class RequestContext extends ConcurrentHashMap { private static final Logger LOG = LoggerFactory.getLogger(RequestContext.class); protected static Class extends RequestContext> contextClass = RequestContext.class; private static RequestContext testContext = null; protected static final ThreadLocal extends RequestContext> threadLocal = new ThreadLocal() { protected RequestContext initialValue() { try { return (RequestContext)RequestContext.contextClass.newInstance(); } catch (Throwable var2) { throw new RuntimeException(var2); } } }; public static RequestContext getCurrentContext() { if (testContext != null) { return testContext; } else { RequestContext context = (RequestContext)threadLocal.get(); return context; } } public boolean getBoolean(String key) { return this.getBoolean(key, false); } public boolean getZuulEngineRan() { return this.getBoolean("zuulEngineRan"); } public void setZuulEngineRan() { this.put("zuulEngineRan", true); } public HttpServletRequest getRequest() { return (HttpServletRequest)this.get("request"); } public void setRequest(HttpServletRequest request) { this.put("request", request); } public HttpServletResponse getResponse() { return (HttpServletResponse)this.get("response"); } public void setResponse(HttpServletResponse response) { this.set("response", response); } public void unset() { threadLocal.remove(); }}
方法 | 描述 |
getCurrentContext | 擷取目前請求上下文(RequestContext) |
get(set)Request | 擷取(設定)請求資訊 |
get(set)Response | 擷取(設定)響應資訊 |
get(set)ZuulEngineRan | 設定Zuul引擎(ZuulRunner) |
get(set)Boolean | 擷取(設定)Boolean值 |
unset | 從ThreadLocal中移除目前請求上下文副本 |
ZuulServlet
package com.netflix.zuul.http;public class ZuulServlet extends HttpServlet { private static final long serialVersionUID = -3374242278843351500L; private ZuulRunner zuulRunner; public ZuulServlet() { } public void init(ServletConfig config) throws ServletException { super.init(config); //buffer-requests:初始化Zuul引擎(ZuulRunner)必要參數(預設為false) String bufferReqsStr = config.getInitParameter("buffer-requests"); boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true"); this.zuulRunner = new ZuulRunner(bufferReqs); } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { try { //初始化Zuul引擎(ZuulRunner) this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse); RequestContext context = RequestContext.getCurrentContext(); //設定buffer-requests參數為true context.setZuulEngineRan(); try { this.preRoute(); } catch (ZuulException var13) { this.error(var13); this.postRoute(); return; } try { this.route(); } catch (ZuulException var12) { this.error(var12); this.postRoute(); return; } try { this.postRoute(); } catch (ZuulException var11) { this.error(var11); } } catch (Throwable var14) { this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); } } void postRoute() throws ZuulException { this.zuulRunner.postRoute(); } void route() throws ZuulException { this.zuulRunner.route(); } void preRoute() throws ZuulException { this.zuulRunner.preRoute(); } void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) { this.zuulRunner.init(servletRequest, servletResponse); } void error(ZuulException e) { RequestContext.getCurrentContext().setThrowable(e); this.zuulRunner.error(); }}
方法 | 描述 |
init(ServletConfig) | 初始化Zuul執行引擎(ZuulRunner) |
init(HttpServletRequst,HttpServletResponse) | 設定Zuul執行引擎(ZuulRunner)請求(request)響應(response)資訊和參數 |
service() | 執行過濾器相關操作 |
preRoute() | 使用Zuul執行引擎執行前置過濾器(過濾器類型為pre) |
route() | 使用Zuul執行引擎執行路由過濾器(過濾器類型為routing) |
postRoute() | 使用Zuul執行引擎執行Post過濾器(過濾器類型為post) |
error() | 使用Zuul執行引擎執行異常過濾器(過濾器類型為error) |
通過service方法,我們可以獲知Zuul過濾器執行順序如下:
- 前置(pre)、路由(route)、post類型過濾器執行過程中均未發生異常,執行順序為pre->route->post,error類型不執行
- 前置(pre)類型過濾器執行過程中發生異常,執行順序為pre->error->post
- 路由(route)類型過濾器執行過程中發生異常,執行順序為pre->route->error->post
- post類型過濾器執行過程中發生異常,執行順序為pre->route->post->error
service方法執行的過程中一旦發生異常,将抛出的Throwable執行個體設定到目前執行請求上下文的Throwable屬性内。執行過程中無論是否有異常發生,從線程本地變量中移除請求上下文(ReqeustContext)副本。
ZuulRunner
public class ZuulRunner { private boolean bufferRequests; public ZuulRunner() { this.bufferRequests = true; } public ZuulRunner(boolean bufferRequests) { this.bufferRequests = bufferRequests; } public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) { RequestContext ctx = RequestContext.getCurrentContext(); if (this.bufferRequests) { ctx.setRequest(new HttpServletRequestWrapper(servletRequest)); } else { ctx.setRequest(servletRequest); } ctx.setResponse(new HttpServletResponseWrapper(servletResponse)); } public void postRoute() throws ZuulException { FilterProcessor.getInstance().postRoute(); } public void route() throws ZuulException { FilterProcessor.getInstance().route(); } public void preRoute() throws ZuulException { FilterProcessor.getInstance().preRoute(); } public void error() { FilterProcessor.getInstance().error(); }}
public class HttpServletResponseWrapper extends javax.servlet.http.HttpServletResponseWrapper { private int status = 0; public HttpServletResponseWrapper(HttpServletResponse response) { super(response); } public void setStatus(int sc) { this.status = sc; super.setStatus(sc); } public void setStatus(int sc, String sm) { this.status = sc; super.setStatus(sc, sm); } public int getStatus() { return this.status; }}
ZuulRunner作為Zuul執行引擎,初始化時将請求上下文(ReqeustContext)中的響應屬性設定為HttpServletResponseWrapper執行個體,該執行個體主要通過status屬性設定http響應狀态碼。如果參數bufferRequests為true,将請求上下文中的請求屬性設定為HttpServletRequestWrapper執行個體,HttpServletRequestWrapper類主要用于把請求的表單參數和請求體緩存(設定)在對應的屬性中,無特殊要求傳遞bufferRequests預設值(false)。
方法 | 描述 |
init | 初始化Zuul執行引擎 |
postRoute | 使用FilterProcessor執行個體執行post類型過濾器 |
route | 使用FilterProcessor執行個體執行路由(類型為route)過濾器 |
preRoute | 使用FilterProcessor執行個體執行前置(類型為pre)過濾器 |
error | 使用FilterProcessor執行個體執行錯誤(類型為error)過濾器 |
FilterProcessor
public class FilterProcessor { static FilterProcessor INSTANCE = new FilterProcessor(); protected static final Logger logger = LoggerFactory.getLogger(FilterProcessor.class); private FilterUsageNotifier usageNotifier = new FilterProcessor.BasicFilterUsageNotifier(); public FilterProcessor() { } public static FilterProcessor getInstance() { return INSTANCE; } public static void setProcessor(FilterProcessor processor) { INSTANCE = processor; } public void setFilterUsageNotifier(FilterUsageNotifier notifier) { this.usageNotifier = notifier; } public void postRoute() throws ZuulException { try { this.runFilters("post"); } catch (ZuulException var2) { throw var2; } catch (Throwable var3) { throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + var3.getClass().getName()); } } public void error() { try { this.runFilters("error"); } catch (Throwable var2) { logger.error(var2.getMessage(), var2); } } public void route() throws ZuulException { try { this.runFilters("route"); } catch (ZuulException var2) { throw var2; } catch (Throwable var3) { throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + var3.getClass().getName()); } } public void preRoute() throws ZuulException { try { this.runFilters("pre"); } catch (ZuulException var2) { throw var2; } catch (Throwable var3) { throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + var3.getClass().getName()); } } public Object runFilters(String sType) throws Throwable { if (RequestContext.getCurrentContext().debugRouting()) { Debug.addRoutingDebug("Invoking {" + sType + "} type filters"); } boolean bResult = false; List list = FilterLoader.getInstance().getFiltersByType(sType); if (list != null) { for(int i = 0; i < list.size(); ++i) { ZuulFilter zuulFilter = (ZuulFilter)list.get(i); Object result = this.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(); String metricPrefix = "zuul.filter-"; long execTime = 0L; 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); } } if (t != null) { throw t; } else { //計數器統計 this.usageNotifier.notify(filter, s); return o; } } catch (Throwable var15) { if (bDebug) { Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + var15.getMessage()); } //計數器統計 this.usageNotifier.notify(filter, ExecutionStatus.FAILED); if (var15 instanceof ZuulException) { throw (ZuulException)var15; } else { ZuulException ex = new ZuulException(var15, "Filter threw Exception", 500, filter.filterType() + ":" + filterName); ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime); throw ex; } } }}
在FilterProcessor類中,提供屬性usageNotifier(類型為FilterUsageNotifier接口),預設實作類為FilterProcessor靜态内部類BasicFilterUsageNotifier,該類實作接口FilterUsageNotifier的notify方法(方法參數為ZuulFilter和ExecutionStatus枚舉),ExecutionStatus(處理狀态)枚舉中主要提供如下可選值
public enum ExecutionStatus { SUCCESS(1), SKIPPED(-1), DISABLED(-2), FAILED(-3); private int status; private ExecutionStatus(int status) { this.status = status; }}
可選值 | 描述 |
SUCCESS(1) | 該過濾器處理成功 |
SKIPPED(-1) | 該過濾器跳過處理 |
DISABLED(-2) | 該過濾器為禁用狀态 |
FAILED(-3) | 該過濾器處理失敗 |
在FilterProcesser類中,主要通過runFilter方法(參數為過濾器類型字元串)和processZuulFilter(參數為ZuulFilter對象)對過濾器進行執行并傳回執行結果。runFilter方法中傳回值為布爾類型(過濾器執行成功或失敗),該傳回值無實際意義,執行成功或失敗都通過請求上下文(ReqeustContext).addFilterExecutionSummary(參數分别為過濾器名稱,執行狀态枚舉值,執行時間)對過濾器執行結果進行記錄。
方法 | 描述 |
postRoute | 執行post類型過濾器(字首為post) |
route | 執行路由類型過濾器(字首為route) |
preRoute | 執行前置類型過濾器(字首為pre) |
error | 執行錯誤類型過濾器(字首為error) |
runFilter | 通過參數擷取需要執行的過濾器類型,傳回執行結果 |
processZuulFilter | 對具體的過濾器執行操作,并将執行結果設定在請求上下文(RequestContext)中 |
自動裝配的過濾器
過濾器類型 | 過濾器名 | 過濾器描述 | 順序 |
pre | ServletDetectionFilter | 标記處理Servlet的類型 | -3 |
Servlet30WrapperFilter | 包裝HttpServletRequest請求 | -2 | |
FromBodyWrapperFilter | 包裝請求體 | -1 | |
DebugFilter | 标記調試狀态 | 1 | |
PreDecorationFilter | 處理請求上下文供後續使用 | 5 | |
route | RibbonRoutingFilter | serviceId請求轉發 | 10 |
SimpleHostRoutingFilter | url請求轉發 | 100 | |
SendForwardFilter | forward請求轉發 | 500 | |
post | SendErrorFilter | 處理有錯誤的請求響應 | |
SendResponseFilter | 處理正常處理的請求響應 | 1000 |
開發過濾器
在本例中,我們将通過過濾器對http請求過程中的使用者名、密碼、token參數資訊進行校驗,校驗成功通過過濾器為響應資訊設定新的響應頭(response-header)資訊。
使用者名校驗過濾器(UserNameFilter)
import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;/** * 根據使用者名進行校驗 */@Componentpublic class UserNameFilter extends ZuulFilter { @Override //設定過濾器類型 public String filterType() { return FilterConstants.PRE_TYPE; } //使用者名校驗最先執行 @Override public int filterOrder() { return FilterConstants.SERVLET_DETECTION_FILTER_ORDER-3; } @Override public boolean shouldFilter() { return true; } //執行具體的校驗邏輯 @Override public Object run() throws ZuulException { RequestContext requestContext=RequestContext.getCurrentContext(); HttpServletRequest request=requestContext.getRequest(); if(null!=request.getParameter("userName")) { requestContext.setSendZuulResponse(true); requestContext.setResponseStatusCode(200); requestContext.set("isSuccess",true); } else{ requestContext.setSendZuulResponse(false); throw new ZuulRuntimeException(new ZuulException(this.filterType()+":"+this.getClass().getSimpleName(), HttpStatus.UNAUTHORIZED.value(),"使用者名不能為空")); } return null; }}
使用者密碼校驗(PassWordFilter)
import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;@Componentpublic class PassWordFilter extends ZuulFilter { //設定過濾器類型 @Override public String filterType() { return FilterConstants.PRE_TYPE; } //設定過濾器執行順序 @Override public int filterOrder() { return FilterConstants.SERVLET_DETECTION_FILTER_ORDER-2; } @Override public boolean shouldFilter() { return true; } //執行具體的校驗邏輯 @Override public Object run() throws ZuulException { RequestContext requestContext=RequestContext.getCurrentContext(); HttpServletRequest request=requestContext.getRequest(); if(null!=request.getParameter("passWord")) { requestContext.setSendZuulResponse(true); requestContext.setResponseStatusCode(200); requestContext.set("isSuccess",true); } else { requestContext.setSendZuulResponse(false); throw new ZuulRuntimeException(new ZuulException(this.filterType()+":"+this.getClass().getSimpleName(), HttpStatus.UNAUTHORIZED.value(),"密碼不能為空")); } return null; }}
Token校驗器(AccessTokenFilter)
import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;@Componentpublic class AccessTokenFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return FilterConstants.SERVLET_DETECTION_FILTER_ORDER-1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext requestContext=RequestContext.getCurrentContext(); HttpServletRequest request=requestContext.getRequest(); if(null!=request.getParameter("accessToken")) { requestContext.setResponseStatusCode(200); requestContext.setSendZuulResponse(true); requestContext.set("isSuccess",true); } else{ requestContext.setSendZuulResponse(false); throw new ZuulRuntimeException(new ZuulException(this.filterType()+":"+this.getClass().getSimpleName(), HttpStatus.UNAUTHORIZED.value(),"token不能為空")); } return null; }}
設定響應頭過濾器(ResponseHeaderFilter)
import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletResponse;@Componentpublic class ResponseHeaderFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.POST_TYPE; } @Override public int filterOrder() { return FilterConstants.SEND_RESPONSE_FILTER_ORDER-1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext requestContext=RequestContext.getCurrentContext(); HttpServletResponse response=requestContext.getResponse(); response.addHeader("response-header","response-header"); return null; }}
使用者名、密碼、token資訊參數均在前置類型過濾器,在ServletDetectionFilter前執行校驗操作,校驗通過後交由Zuul内置過濾器在前置類型過濾器進行處理。前置類型過濾器處理完成,通過路由類型過濾器路由到post過濾器,設定響應頭過濾器為post類型過濾器,在正常處理響應請求(SendResponseFilter)前執行。
路由配置規則
預設路由通路規則
http://服務網關應用名稱:服務網關應用端口/服務提供者執行個體名稱
自定義通路URL
zuul.routes.服務執行個體ID=URL
如zuul.routes.user-service=/user/**就會将user-service微服務映射到/user/**通路路徑。
忽略指定微服務
zuul.ignored-services=服務提供者執行個體名稱
通過上述配置,可以配置需要忽略的服務執行個體,多個服務執行個體間用逗号(,)間隔
忽略所有微服務,隻路由指定微服務
zuul.ignored-services=*zuul.routes.user-service=/user/**
通過上述配置,可以配置忽略所有服務執行個體,隻路由到user-service執行個體
定義服務執行個體ID和對應路徑
zuul.routes.user-router.service-id=user-service-providerzuul.routes.user-router.path=/user/**
通過上述配置,同時指定使用者微服務執行個體ID為user-service-provider,對應路徑為/user/**,user-router隻是給路由一個有意義的名稱,可以任意起名。
同時指定path和URL
zuul.routes.user-route.url=http://localhost:8100/user-servicezuul.routes.user-route.path=/user/**
通過上述配置,可以将通路位址為/user/**的路由到使用者服務執行個體(localhost:8100為服務注冊中心位址,user-service為服務提供者配置的服務執行個體名稱)。
使用上述配置方式,不會使HystrixCommand執行,也不會使Ribbon負載均衡多個URL,破壞了Zuul的服務熔斷和負載均衡特性。
同時指定path和URL并且不破壞服務熔斷(Hystrix)負載均衡(Ribbon)特性
zuul.routes.user-route.service-id=user-service-providerzuul.routes.user-route.path=/user/**ribbon.eureka.enabled=false #為Ribbon禁用Eurekauser-service-provider.ribbon.listOfServers=localhost:8100,localhost:8101
通過上述配置,即可以指定path和URL,又保證Zuul的服務熔斷和負載均衡特性可用。配置負載均衡的服務執行個體名須與服務執行個體ID(service-id)配置内容一緻。
路由字首
zuul.prefix=/apizuul.routes.user-router=/user/**#zuul.routes.user-router.strip-prefix=true#zuul.strip-prefix=false
通過上述配置,路由轉發至使用者微服務執行個體時去掉/api字首;如果需要路由轉發時帶上路由字首,通過zuul.routes.user-service-provider.strip-prefix=true進行指定路由移除代理前置配置即可(user-router隻是為服務路由起了一個有意義的名字,可以任意起名),也可以通過zuul.strip-prefix=false對所有服務路由。
忽略某些路徑
在前面的路由配置規則中,提到了如何忽略微服務,有時我們還需要更細粒度的路由控制,例如想讓zuul路由到使用者微服務執行個體,又想保護使用者微服務的使用者角色配置相關服務不被通路,可以進行如下配置:
zuul.ignoredPatterns:/**/RoleConfig/**zuul.routes.user-service-provider=/user/**
通過上述配置,可以講使用者微服務路由到/userRoleConfig/**)。
本地轉發
在Zuul實作的API網關路由功能中,還支援forward形式的服務端轉發配置。通過配置path和url相關資訊就能夠實作本地轉發。
zuul.routes.user-route.url=http://localhost:8100/zuul.routes.user-route.path=/user/**zuul.routes.order-route.url=forward:/order-servicezuul.routes.user-route.path=/order/**
上述配置中,/user/**請求被轉發到http://localhost:8100/,/order/**請求被轉發到/order-service進行處理。在配置本地轉發後,需要在服務提供者應用中提供請求路徑為/order-service對應的接口進行服務調用處理,否則會因為本地轉發無法找到對應處理接口而傳回404錯誤。