springcloud(十一):服务网关Zuul高级篇
2019獨角獸企業重金招聘Python工程師標準>>>
Zuul的核心
Filter是Zuul的核心,用來實現對外服務的控制。Filter的生命周期有4個,分別是“PRE”、“ROUTING”、“POST”、“ERROR”,整個生命周期可以用下圖來表示。
Zuul大部分功能都是通過過濾器來實現的,這些過濾器類型對應于請求的典型生命周期。
PRE:?這種過濾器在請求被路由之前調用。我們可利用這種過濾器實現身份驗證、在集群中選擇請求的微服務、記錄調試信息等。
ROUTING:這種過濾器將請求路由到微服務。這種過濾器用于構建發送給微服務的請求,并使用Apache HttpClient或Netfilx Ribbon請求微服務。
POST:這種過濾器在路由到微服務以后執行。這種過濾器可用來為響應添加標準的HTTP Header、收集統計信息和指標、將響應從微服務發送給客戶端等。
ERROR:在其他階段發生錯誤時執行該過濾器。 除了默認的過濾器類型,Zuul還允許我們創建自定義的過濾器類型。例如,我們可以定制一種STATIC類型的過濾器,直接在Zuul中生成響應,而不將請求轉發到后端的微服務。
Zuul中默認實現的Filter
類型?? ?順序?? ?過濾器?? ?功能
pre?? ?-3?? ?ServletDetectionFilter?? ?標記處理Servlet的類型
pre?? ?-2?? ?Servlet30WrapperFilter?? ?包裝HttpServletRequest請求
pre?? ?-1?? ?FormBodyWrapperFilter?? ?包裝請求體
route?? ?1?? ?DebugFilter?? ?標記調試標志
route?? ?5?? ?PreDecorationFilter?? ?處理請求上下文供后續使用
route?? ?10?? ?RibbonRoutingFilter?? ?serviceId請求轉發
route?? ?100?? ?SimpleHostRoutingFilter?? ?url請求轉發
route?? ?500?? ?SendForwardFilter?? ?forward請求轉發
post?? ?0?? ?SendErrorFilter?? ?處理有錯誤的請求響應
post?? ?1000?? ?SendResponseFilter?? ?處理正常的請求響應
禁用指定的Filter
可以在application.yml中配置需要禁用的filter,格式:
?
自定義Filter
實現自定義Filter,需要繼承ZuulFilter的類,并覆蓋其中的4個方法。
public class MyFilter extends ZuulFilter {@OverrideString filterType() {return "pre"; //定義filter的類型,有pre、route、post、error四種}@Overrideint filterOrder() {return 10; //定義filter的順序,數字越小表示順序越高,越先執行}@Overrideboolean shouldFilter() {return true; //表示是否需要執行該filter,true表示執行,false表示不執行}@OverrideObject run() {return null; //filter需要執行的具體操作} }自定義Filter示例
我們假設有這樣一個場景,因為服務網關應對的是外部的所有請求,為了避免產生安全隱患,我們需要對請求做一定的限制,比如請求中含有Token便讓請求繼續往下走,如果請求不帶Token就直接返回并給出提示。
首先自定義一個Filter,在run()方法中驗證參數是否含有Token。
public class TokenFilter extends ZuulFilter {private final Logger logger = LoggerFactory.getLogger(TokenFilter.class);@Overridepublic String filterType() {return "pre"; // 可以在請求被路由之前調用}@Overridepublic int filterOrder() {return 0; // filter執行順序,通過數字指定 ,優先級為0,數字越大,優先級越低}@Overridepublic boolean shouldFilter() {return true;// 是否執行該過濾器,此處為true,說明需要過濾}@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();logger.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString());String token = request.getParameter("token");// 獲取請求的參數if (StringUtils.isNotBlank(token)) {ctx.setSendZuulResponse(true); //對請求進行路由ctx.setResponseStatusCode(200);ctx.set("isSuccess", true);return null;} else {ctx.setSendZuulResponse(false); //不對其進行路由ctx.setResponseStatusCode(400);ctx.setResponseBody("token is empty");ctx.set("isSuccess", false);return null;}}}將TokenFilter加入到請求攔截隊列,在啟動類中添加以下代碼:
@Bean public TokenFilter tokenFilter() {return new TokenFilter(); }這樣就將我們自定義好的Filter加入到了請求攔截中。
測試
我們依次啟動示例項目:spring-cloud-eureka、spring-cloud-producer、spring-cloud-zuul,這個三個項目均為上一篇示例項目,spring-cloud-zuul稍微進行改造。
訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo,返回:token is empty ,請求被攔截返回。
訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx,返回:hello neo,this is first messge,說明請求正常響應。
通過上面這例子我們可以看出,我們可以使用“PRE”類型的Filter做很多的驗證工作,在實際使用中我們可以結合shiro、oauth2.0等技術去做鑒權、驗證。
路由熔斷
當我們的后端服務出現異常的時候,我們不希望將異常拋出給最外層,期望服務可以自動進行一降級。Zuul給我們提供了這樣的支持。當某個服務出現異常時,直接返回我們預設的信息。
我們通過自定義的fallback方法,并且將其指定給某個route來實現該route訪問出問題的熔斷處理。主要繼承ZuulFallbackProvider接口來實現,ZuulFallbackProvider默認有兩個方法,一個用來指明熔斷攔截哪個服務,一個定制返回內容。
?
實現類通過實現getRoute方法,告訴Zuul它是負責哪個route定義的熔斷。而fallbackResponse方法則是告訴 Zuul 斷路出現時,它會提供一個什么返回值來處理請求。
后來Spring又擴展了此類,豐富了返回方式,在返回的內容中添加了異常信息,因此最新版本建議直接繼承類FallbackProvider?。
我們以上面的spring-cloud-producer服務為例,定制它的熔斷返回內容。
?
當服務出現異常時,打印相關異常信息,并返回”The service is unavailable.”。
啟動項目spring-cloud-producer-2,這時候服務中心會有兩個spring-cloud-producer項目,我們重啟Zuul項目。再手動關閉spring-cloud-producer-2項目,多次訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx,會交替返回:
hello neo,this is first messge The service is unavailable. ...根據返回結果可以看出:spring-cloud-producer-2項目已經啟用了熔斷,返回:The service is unavailable.
Zuul 目前只支持服務級別的熔斷,不支持具體到某個URL進行熔斷。
路由重試
有時候因為網絡或者其它原因,服務可能會暫時的不可用,這個時候我們希望可以再次對服務進行重試,Zuul也幫我們實現了此功能,需要結合Spring Retry 一起來實現。下面我們以上面的項目為例做演示。
添加Spring Retry依賴
首先在spring-cloud-zuul項目中添加Spring Retry依賴。
<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId> </dependency>開啟Zuul Retry
再配置文件中配置啟用Zuul Retry
#是否開啟重試功能 zuul.retryable=true #對當前服務的重試次數 ribbon.MaxAutoRetries=2 #切換相同Server的次數 ribbon.MaxAutoRetriesNextServer=0這樣我們就開啟了Zuul的重試功能。
測試
我們對spring-cloud-producer-2進行改造,在hello方法中添加定時,并且在請求的一開始打印參數。
@RequestMapping("/hello") public String index(@RequestParam String name) {logger.info("request two name is "+name);try{Thread.sleep(1000000);}catch ( Exception e){logger.error(" hello two error",e);}return "hello "+name+",this is two messge"; }重啟 spring-cloud-producer-2和spring-cloud-zuul項目。
訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx,當頁面返回:The service is unavailable.時查看項目spring-cloud-producer-2后臺日志如下:
2018-01-22 19:50:32.401 INFO 19488 --- [io-9001-exec-14] o.s.c.n.z.f.route.FallbackProvider : request two name is neo 2018-01-22 19:50:33.402 INFO 19488 --- [io-9001-exec-15] o.s.c.n.z.f.route.FallbackProvider : request two name is neo 2018-01-22 19:50:34.404 INFO 19488 --- [io-9001-exec-16] o.s.c.n.z.f.route.FallbackProvider : request two name is neo說明進行了三次的請求,也就是進行了兩次的重試。這樣也就驗證了我們的配置信息,完成了Zuul的重試功能。
注意
開啟重試在某些情況下是有問題的,比如當壓力過大,一個實例停止響應時,路由將流量轉到另一個實例,很有可能導致最終所有的實例全被壓垮。說到底,斷路器的其中一個作用就是防止故障或者壓力擴散。用了retry,斷路器就只有在該服務的所有實例都無法運作的情況下才能起作用。這種時候,斷路器的形式更像是提供一種友好的錯誤信息,或者假裝服務正常運行的假象給使用者。
不用retry,僅使用負載均衡和熔斷,就必須考慮到是否能夠接受單個服務實例關閉和eureka刷新服務列表之間帶來的短時間的熔斷。如果可以接受,就無需使用retry。
Zuul高可用
我們實際使用Zuul的方式如上圖,不同的客戶端使用不同的負載將請求分發到后端的Zuul,Zuul在通過Eureka調用后端服務,最后對外輸出。因此為了保證Zuul的高可用性,前端可以同時啟動多個Zuul實例進行負載,在Zuul的前端使用Nginx或者F5進行負載轉發以達到高可用性。
整體架構如下:
愿意了解框架技術或者源碼的朋友直接求求交流分享技術:?貳一四七七七五六叁叁
?
轉載于:https://my.oschina.net/u/3826344/blog/2963814
總結
以上是生活随笔為你收集整理的springcloud(十一):服务网关Zuul高级篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: sniffer 和 debug flow
- 下一篇: mysql备份与还原-mysqldump