在微服務場景下,每個微服務都對外暴露了一組細粒度的defuwu服務??蛻舳说恼埱罂赡軙婕暗揭淮姆照{(diào)用,如果將這些服務都暴露客戶端,那么客戶端需要請求不同的微服務才能完成一次業(yè)務處理,增加客戶端的代碼復雜度,除此之外,對于微服務我們可能需要服務調(diào)用進行統(tǒng)一的認證和校驗。
zuul是基于設(shè)計模式中的facade模式(外觀模式), 將細粒度的服務組合起來提供一個粗粒度的服務,所有服務的入口都統(tǒng)一到一個入口,那么整個服務只需暴露一個API, 對外端屏蔽服務的細節(jié),也減少了客戶端與服務器的網(wǎng)絡(luò)調(diào)用次數(shù)。就是API服務網(wǎng)關(guān)(api-Gateway)服務。
spring cloud netflix的zuul組件可以做反向代理功能,通過路由尋址將求轉(zhuǎn)發(fā)到后端的粒度服務上,并做統(tǒng)一的邏輯處理
一、zuul介紹
zuul中定義的四中不同生命的過濾器類型:
1. pre:路由之前
2. routing:路由之時
3. post:路由之后
4. error:發(fā)送錯誤調(diào)用
二、zuul請求路由
zuul的請求路由有點類似于nginx的反向代理,通過指定代理的請求路徑和配置過濾條件來組件一個請求的路由規(guī)則,將符合路由規(guī)則的請求轉(zhuǎn)發(fā)到真正請求的服務上面去。
zuul.routes.record-serve.path=/api/record-server/**
zuul.routes.record-serve.url=http://192.168.1.126:10007/
三、zuul服務名稱路由
zuul通過服務名稱到enurek注冊中心查找到服務對應的IP在路由到指定的服務上面(如何有多個相同名稱的服務,ribbon會進行負載均衡到其中的一個ip上返回)。在路由到對應的服務上面去完成請求操作
zuul:
routes:
api-a:
path: /api-a/**
serviceId: service-ribbon
api-b:
path: /api-b/**
serviceId: service-feign
四、@EnableZuulServer注解
1.pre類型過濾器
(1)ServletDetectionFilter: 主要是檢測是否是否通過DispatcherServlet控制器進行進行請求,設(shè)置isDispatcherServletRequest參數(shù)
#部分源碼展示
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (!(request instanceof HttpServletRequestWrapper) && this.isDispatcherServletRequest(request)) {
ctx.set("isDispatcherServletRequest", true);
} else {
ctx.set("isDispatcherServletRequest", false);
}
return null;
}
private boolean isDispatcherServletRequest(HttpServletRequest request) {
return request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null;
}
(2)FormBodyWrapperFilter: 解析表單數(shù)據(jù),并為請求重寫編碼
(3)DebugFilter: 調(diào)試用的過濾器
2.route類型過濾器
SendForwardFilter:該過濾器使用ServletRequestDispatcher轉(zhuǎn)發(fā)請求,轉(zhuǎn)發(fā)位置存儲在RequestContext.getCurrentContext().get("forward.to")中??梢詫⒙酚稍O(shè)置成:
zuul.routes.record-serve.path=/api/record-server/**
zuul.routes.record-serve.url=http://192.168.1.126:10007/
SendForwardFilter部分源碼
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return ctx.containsKey("forward.to") && !ctx.getBoolean("sendForwardFilter.ran", false);
}
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
String path = (String)ctx.get("forward.to");
RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);
if (dispatcher != null) {
ctx.set("sendForwardFilter.ran", true);
if (!ctx.getResponse().isCommitted()) {
dispatcher.forward(ctx.getRequest(), ctx.getResponse());
ctx.getResponse().flushBuffer();
}
}
} catch (Exception var4) {
ReflectionUtils.rethrowRuntimeException(var4);
}
return null;
}
3.post類型過濾器
SendResponseFilter:將Zuul所代理的微服務的響應寫入當前響應
4.error類型過濾器
SendErrorFilter: 如果RequestContext.getThrowable()不為null,那么默認就會轉(zhuǎn)發(fā)到/error,也可以設(shè)置error.path屬性修改默認的轉(zhuǎn)發(fā)路徑。
五、@EnableZuulProxy注解
一、pre類型過濾器
PreDecorationFilter:該過濾器根據(jù)提供的RouteLocator確定路由到的地址,以及怎樣去路由。該路由器也可為后端請求設(shè)置各種代理相關(guān)的header。
二、route類型過濾器
(1) RibbonRoutingFilter:該過濾器使用Ribbon,Hystrix和可插拔的HTTP客戶端發(fā)送請求。serviceId在RequestContext.getCurrentContext().get("serviceId")中。該過濾器可使用不同的HTTP客戶端,例如:
1.Apache HttpClient:默認的HTTP客戶端
2.SquareupOkHttpClient v3:如需使用該客戶端,需保證com.squareup.okhttp3的依賴在classpath中,并設(shè)置如下:
ribbon.okhttp.enabled = true
3.Netflix Ribbon HTTP client:設(shè)置如下即可啟用該HTTP客戶端。需要注意的是,該客戶端有一定限制,例如不支持PATCH方法,另外,它有內(nèi)置的重試機制。
ribbon.restclient.enabled = true
(2) SimpleHostRoutingFilter:該過濾器通過Apache HttpClient向指定的URL發(fā)送請求。URL在RequestContext.getRouteHost()中。
六、使用zuul所遇到的問題
(1)注意zuul路由的請求路徑問題(按請求路由)
查看下面請求路由配置
# 在微服務中有個個record的子服務,需要將他的所有符合匹配規(guī)則的請求通過zuul轉(zhuǎn)發(fā)
# record 微服務部署ip 為192.168.1.126 端口為10007
# zuul 微服務部署ip 192.168.1.126 端口10009
zuul.routes.record-serve.path=/api/record-server/**
zuul.routes.record-serve.url=http://192.168.1.126:10007/
你在未使用zuul網(wǎng)關(guān)訪問的路徑是http://192.168.1.126:10007/api/record-server/hello,此時訪問 http://192.168.1.126:10009/api/record-server/hello,結(jié)果是404,由于你strpPrefix設(shè)置的是true,匹配訪問時是會忽略/api/record-server/段的,你正真匹配發(fā)送到record服務的請求是http://192.168.1.126:10007/hello,所以會導致404.
那是由于zuul 默認設(shè)置的stripPrefix是ture,轉(zhuǎn)發(fā)時默認去掉請求前綴之后再進行轉(zhuǎn)發(fā)
zuul.routes.record-serve.path=/api/record-server/**
zuul.routes.record-serve.stripPrefix=false
zuul.routes.record-serve.url=http://192.168.1.126:10007/
通過查看zuul配置文件的部分源碼可以看到各項的默認值,
@ConfigurationProperties("zuul")
public class ZuulProperties {
public static final List<String> SECURITY_HEADERS = Arrays.asList("Pragma", "Cache-Control", "X-Frame-Options", "X-Content-Type-Options", "X-XSS-Protection", "Expires");
private String prefix = "";
private boolean stripPrefix = true;
private Boolean retryable = false;
private boolean addProxyHeaders = true;
private boolean addHostHeader = false;
private boolean ignoreSecurityHeaders = true;
private boolean forceOriginalQueryStringEncoding = false;
private String servletPath = "/zuul";
private boolean ignoreLocalService = true;
private ZuulProperties.Host host = new ZuulProperties.Host();
private boolean traceRequestBody = true;
private boolean removeSemicolonContent = true;
private Set<String> sensitiveHeaders = new LinkedHashSet(Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
private boolean sslHostnameValidationEnabled = true;
private ExecutionIsolationStrategy ribbonIsolationStrategy;
private ZuulProperties.HystrixSemaphore semaphore;
(2)zuul不能通過服務名稱路由的問題
第一,注意是否整合了eureka注冊中心和是否在設(shè)置項目啟動配置時使用的是@EnableZuulProxy和@EnableEurekaClient的組合,只有將zuul注冊到eureka中和使用EnableZuulServer的升級版EnableZuulProxy才能夠通過服務名稱找到對應的服務服務地址。
第二, 確認使用的是netfilix ribbon HttpClient client。如果沒有使用下面代碼配置開啟,如果沒有設(shè)置,zuul默認使用apache httpclient.
ribbon.restclient.enabled = true