Zuul簡介html
全部微服務之間的調用,都應該經過服務網關進行路由,服務網關充當服務與服務之間的中介。服務網關像交通警察同樣指揮交通,將用戶引導到目標微服務實例。服務網關還充當着應用程序內全部微服務調用的入站流量的守門人。有了服務網關,服務客戶端永遠不會直接調用單個服務的URL,而是將全部調用都放到服務網關上。java
構建一個Zuul Spring boot項目python
首先,在pom.xml中添加依賴spring-cloud-starter-netflix-zuul。git
<!-- Spring cloud starter: netflix-zuul --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>
其次,在啓動類Application中加入@EnableZuulProxy註解。github
@SpringBootApplication @EnableZuulProxy public class ServerZuulApplication { public static void main(String[] args) { SpringApplication.run(ServerZuulApplication.class, args); } }
此外,它仍是一個Eureka Client和Config Client,如何配置Eureka Client和Config Client請看前面章節。web
在Zuul中配置路由spring
Zuul的核心是一個反向代理,即一箇中間服務器,它位於客戶端服務器與資源服務器之間,客戶端服務器只需訪問反向代理服務器,而反向代理服務器負責捕獲客戶端請求,而後表明客戶端調用遠程資源。配置Zuul有3種方式:sql
(1)經過服務發現自動映射路由,此時不須要任何配置。api
好比咱們正常訪問一個在Eureka Server註冊的服務(Eureka的服務ID爲app-sql):安全
http://localhost:10200/app-sql/sql-sp-search/list(格式爲http://[host]:[port]/[context-path]/[path])
若是使用Zuul訪問,則爲:
http://localhost:10030/server-zuul/app-sql/app-sql/sql-sp-search/list(格式爲http://[host]:[port]/[context-path]/[app service-id]/[app context-path]/[path])
(2)經過服務發現手動映射路由,Zuul使用了Hystrix和Ribbon庫,來幫助方式長時間運行服務調用而影響服務網關的性能。
zuul: # 排除全部的基於Eureka的服務ID註冊的路由 ignored-services: '*' # 添加前綴 prefix: /api # Eureka的服務ID routes: app-sql: /s1/** app-one: /s2/** app-anther-one: /s3/** # 設置Hystrix超時(default能夠替換成具體的某個服務ID) hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 18000 # 設置Ribbon超時(若是是具體的某個服務ID,能夠用[service-id].ribbon) ribbon: ConnectTimeout: 1000 ReadTimeout: 8000
# Zuul不會將敏感HTTP首部(如Cookie,Set-Cookie,Authorization)轉發到下游服務。這裏排除了Authorization爲後面的OAuth2服務
sensitiveHeaders: Cookie,Set-Cookie
此時url爲:http://localhost:10030/server-zuul/api/s1/app-sql/sql-sp-search/list(格式爲http://[host]:[port]/[context-path]/[prefix]/[app routes.app-sql]/[app context-path]/[path])
[注1] 通常來講,hystrixTimeout >= ribbonTimeout(ReadTimeout + ConnectTimeout)。若是小於,則會出現警告(參考AbstractRibbonCommand.getHystrixTimeout())。其中ribbonTimeout的計算公式能夠參考AbstractRibbonCommand.getRibbonTimeout()。
這裏計算公式是ribbonTimeout = (ReadTimeout + ConnectTimeout)*(MaxAutoRetries+ 1)*(MaxAutoRetriesNextServer + 1) = (8000 + 1000)* 1 * 2 = 18000ms,因此hystrixTimeout要設置>=18000。
[注2] 這裏配置的sensitiveHeaders會在Spring Cloud Security OAuth2中用到。
(3)使用靜態URL手動映射路由。
有些服務沒有向Eureka Server註冊,並無受到Eureka Server的管理,好比一個用python寫的服務,這時仍然能夠創建Zuul直接路由到靜態URL,而且能夠手動配置Hystrix和Ribbon作到熔斷和負載均衡。
zuul: routes: python-service: path: /ps1/** # 定義一個服務ID serviceId: python-service hystrix: command: python-service:
execution: isolation: thread: timeoutInMilliseconds: 18000 python-service: ribbon: NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList # 若是python-service服務有多個實例,則能夠負載均衡映射到多個路由 listOfServers: http://localhost:9221,http://localhost:9222 # 設置ribbon的timeout ConnectTimeout: 1000 ReadTimeout: 8000 MaxTotalHttpConnections: 500 MaxConnectionsPerHost: 100
過濾器
當咱們經過網關自定義邏輯時(如安全性,日誌,服務跟蹤等),咱們可使用Zuul過濾器
(1)前置過濾器(PRE Filters):在Zuul將請求發送到目的地前調用,能夠檢查request header,驗證用戶信息,log記錄等。
(2)路由過濾器(ROUTING Filters):調用目標服務前調用。好比它能夠將服務調用重定向到另外一個地方,這裏的重定向並非HTTP重定向,而是會終止傳入的HTTP請求,而後再表明原始調用者發送新的請求。
(3)後置過濾器(POST Filters):在目標服務被調用並返回響應後調用。好比在response header中添加一些信息。
(4)Error過濾器(ERROR Filters):發生error時調用。
它們之間的關係以下圖:
[注] 參考https://github.com/Netflix/zuul/wiki/How-it-Works
下面是3個過濾器的代碼示例:
package com.mytools.filter; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; /** * 前置過濾器<br> */ @Component public class PreFilter extends ZuulFilter { private static final Logger logger = LoggerFactory.getLogger(PreFilter.class); private static final String PRE_FILTER_TYPE = "pre"; private static final int FILTER_ORDER = 1; private static final boolean SHOULD_FILTER = true; /* Filter type: PRE Filter * @see com.netflix.zuul.ZuulFilter#filterType() */ @Override public String filterType() { return PRE_FILTER_TYPE; } /* 過濾器的執行順序 * @see com.netflix.zuul.ZuulFilter#filterOrder() */ @Override public int filterOrder() { return FILTER_ORDER; } /* 是否執行過濾器 * @see com.netflix.zuul.IZuulFilter#shouldFilter() */ @Override public boolean shouldFilter() { return SHOULD_FILTER; } /* run()是每次服務經過過濾器時執行的代碼 * @see com.netflix.zuul.IZuulFilter#run() */ @Override public Object run() { logger.debug("<<<<< PreFilter start >>>>>"); RequestContext ctx = RequestContext.getCurrentContext(); printReqHeader(ctx); printZuulReqHeader(ctx); logger.debug("<<<<< PreFilter end >>>>>"); return null; } private void printReqHeader(RequestContext ctx) { HttpServletRequest req = ctx.getRequest(); List<String> headerNameList = new ArrayList<>(); if (ctx.getRequest() != null) { Enumeration<String> headerNames = req.getHeaderNames(); while (headerNames.hasMoreElements()) { headerNameList.add(headerNames.nextElement()); } } if (headerNameList.isEmpty()) { logger.info("----- Original Request Header is NULL. -----"); } else { logger.info("----- Original Request Header: -----"); for (String headerName : headerNameList) { logger.info(String.format("%s: %s", headerName, req.getHeader(headerName))); } } } private void printZuulReqHeader(RequestContext ctx) { Map<String, String> reqMap = ctx.getZuulRequestHeaders(); if (reqMap == null || reqMap.isEmpty()) { logger.info("----- Zuul Request Header is NULL. -----"); } else { logger.info("----- Zuul Request Header: -----"); reqMap.forEach((p, q) -> { logger.info(String.format("%s: %s", p, q)); }); } } }
package com.mytools.filter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import com.netflix.zuul.ZuulFilter; /** * 路由過濾器<br> */ @Component public class RoutingFilter extends ZuulFilter { private static final Logger logger = LoggerFactory.getLogger(PostFilter.class); public static final String ROUTE_FILTER_TYPE = "route"; private static final int FILTER_ORDER = 1; private static final boolean SHOULD_FILTER = true; /* Filter type: ROUTING Filter * @see com.netflix.zuul.ZuulFilter#filterType() */ @Override public String filterType() { return ROUTE_FILTER_TYPE; } /* 過濾器的執行順序 * @see com.netflix.zuul.ZuulFilter#filterOrder() */ @Override public int filterOrder() { return FILTER_ORDER; } /* 是否執行過濾器 * @see com.netflix.zuul.IZuulFilter#shouldFilter() */ @Override public boolean shouldFilter() { return SHOULD_FILTER; } /* run()是每次服務經過過濾器時執行的代碼 * @see com.netflix.zuul.IZuulFilter#run() */ @Override public Object run() { logger.debug("<<<<< RoutingFilter start >>>>>"); logger.info("This is Routing Filter."); logger.debug("<<<<< RoutingFilter end >>>>>"); return null; } }
package com.mytools.filter; import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import com.netflix.util.Pair; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; /** * 後置過濾器<br> */ @Component public class PostFilter extends ZuulFilter { private static final Logger logger = LoggerFactory.getLogger(PostFilter.class); private static final String POST_FILTER_TYPE = "post"; private static final int FILTER_ORDER = 1; private static final boolean SHOULD_FILTER = true; /* Filter type: POST Filter * @see com.netflix.zuul.ZuulFilter#filterType() */ @Override public String filterType() { return POST_FILTER_TYPE; } /* 過濾器的執行順序 * @see com.netflix.zuul.ZuulFilter#filterOrder() */ @Override public int filterOrder() { return FILTER_ORDER; } /* 是否執行過濾器 * @see com.netflix.zuul.IZuulFilter#shouldFilter() */ @Override public boolean shouldFilter() { return SHOULD_FILTER; } /* run()是每次服務經過過濾器時執行的代碼 * @see com.netflix.zuul.IZuulFilter#run() */ @Override public Object run() { logger.debug("<<<<< PostFilter start >>>>>"); RequestContext ctx = RequestContext.getCurrentContext(); printResHeader(ctx); printZuulResHeader(ctx); logger.debug("<<<<< PostFilter end >>>>>"); return null; } private void printResHeader(RequestContext ctx) { HttpServletResponse res = ctx.getResponse(); List<String> headerNameList = new ArrayList<>(); if (ctx.getRequest() != null) { headerNameList.addAll(res.getHeaderNames()); } if (headerNameList.isEmpty()) { logger.info("----- Original Response Header is NULL. -----"); } else { logger.info("----- Original Response Header: -----"); for (String headerName : headerNameList) { logger.info(String.format("%s: %s", headerName, res.getHeader(headerName))); } } } private void printZuulResHeader(RequestContext ctx) { List<Pair<String, String>> resList = ctx.getZuulResponseHeaders(); if (resList == null || resList.isEmpty()) { logger.info("----- Zuul Response Header is NULL. -----"); } else { logger.info("----- Zuul Response Header: -----"); resList.forEach(elem -> { logger.info(String.format("%s: %s", elem.first(), elem.second())); }); } } }
使用Actuator查詢路由和過濾器信息
Zuul新添加了兩個Endpoints用於查看路由和過濾器信息,只需做如下配置便可。
## Actuator info (need add '/actuator' prefix) management: endpoints: web: exposure: # routes: 查看全部路由 | filters: 查看全部過濾器 include: routes,filters,info,health