javascript
Spring Cloud Zuul网关 Filter、熔断、重试、高可用的使用方式
時(shí)間過的很快,寫springcloud(十):服務(wù)網(wǎng)關(guān)zuul初級(jí)篇還在半年前,現(xiàn)在已經(jīng)是2018年了,我們繼續(xù)探討Zuul更高級(jí)的使用方式。
上篇文章主要介紹了Zuul網(wǎng)關(guān)使用模式,以及自動(dòng)轉(zhuǎn)發(fā)機(jī)制,但其實(shí)Zuul還有更多的應(yīng)用場景,比如:鑒權(quán)、流量轉(zhuǎn)發(fā)、請(qǐng)求統(tǒng)計(jì)等等,這些功能都可以使用Zuul來實(shí)現(xiàn)。
Zuul的核心
Filter是Zuul的核心,用來實(shí)現(xiàn)對(duì)外服務(wù)的控制。Filter的生命周期有4個(gè),分別是“PRE”、“ROUTING”、“POST”、“ERROR”,整個(gè)生命周期可以用下圖來表示。
Zuul大部分功能都是通過過濾器來實(shí)現(xiàn)的,這些過濾器類型對(duì)應(yīng)于請(qǐng)求的典型生命周期。
- PRE:?這種過濾器在請(qǐng)求被路由之前調(diào)用。我們可利用這種過濾器實(shí)現(xiàn)身份驗(yàn)證、在集群中選擇請(qǐng)求的微服務(wù)、記錄調(diào)試信息等。
- ROUTING:這種過濾器將請(qǐng)求路由到微服務(wù)。這種過濾器用于構(gòu)建發(fā)送給微服務(wù)的請(qǐng)求,并使用Apache HttpClient或Netfilx Ribbon請(qǐng)求微服務(wù)。
- POST:這種過濾器在路由到微服務(wù)以后執(zhí)行。這種過濾器可用來為響應(yīng)添加標(biāo)準(zhǔn)的HTTP Header、收集統(tǒng)計(jì)信息和指標(biāo)、將響應(yīng)從微服務(wù)發(fā)送給客戶端等。
- ERROR:在其他階段發(fā)生錯(cuò)誤時(shí)執(zhí)行該過濾器。?
除了默認(rèn)的過濾器類型,Zuul還允許我們創(chuàng)建自定義的過濾器類型。例如,我們可以定制一種STATIC類型的過濾器,直接在Zuul中生成響應(yīng),而不將請(qǐng)求轉(zhuǎn)發(fā)到后端的微服務(wù)。
Zuul中默認(rèn)實(shí)現(xiàn)的Filter
| pre | -3 | ServletDetectionFilter | 標(biāo)記處理Servlet的類型 |
| pre | -2 | Servlet30WrapperFilter | 包裝HttpServletRequest請(qǐng)求 |
| pre | -1 | FormBodyWrapperFilter | 包裝請(qǐng)求體 |
| route | 1 | DebugFilter | 標(biāo)記調(diào)試標(biāo)志 |
| route | 5 | PreDecorationFilter | 處理請(qǐng)求上下文供后續(xù)使用 |
| route | 10 | RibbonRoutingFilter | serviceId請(qǐng)求轉(zhuǎn)發(fā) |
| route | 100 | SimpleHostRoutingFilter | url請(qǐng)求轉(zhuǎn)發(fā) |
| route | 500 | SendForwardFilter | forward請(qǐng)求轉(zhuǎn)發(fā) |
| post | 0 | SendErrorFilter | 處理有錯(cuò)誤的請(qǐng)求響應(yīng) |
| post | 1000 | SendResponseFilter | 處理正常的請(qǐng)求響應(yīng) |
禁用指定的Filter
可以在application.yml中配置需要禁用的filter,格式:
zuul:FormBodyWrapperFilter:pre:disable: true- 1
- 2
- 3
- 4
自定義Filter
實(shí)現(xiàn)自定義Filter,需要繼承ZuulFilter的類,并覆蓋其中的4個(gè)方法。
public class MyFilter extends ZuulFilter {@OverrideString filterType() {return "pre"; //定義filter的類型,有pre、route、post、error四種}@Overrideint filterOrder() {return 10; //定義filter的順序,數(shù)字越小表示順序越高,越先執(zhí)行}@Overrideboolean shouldFilter() {return true; //表示是否需要執(zhí)行該filter,true表示執(zhí)行,false表示不執(zhí)行}@OverrideObject run() {return null; //filter需要執(zhí)行的具體操作} }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
自定義Filter示例
我們假設(shè)有這樣一個(gè)場景,因?yàn)榉?wù)網(wǎng)關(guān)應(yīng)對(duì)的是外部的所有請(qǐng)求,為了避免產(chǎn)生安全隱患,我們需要對(duì)請(qǐng)求做一定的限制,比如請(qǐng)求中含有Token便讓請(qǐng)求繼續(xù)往下走,如果請(qǐng)求不帶Token就直接返回并給出提示。
首先自定義一個(gè)Filter,在run()方法中驗(yàn)證參數(shù)是否含有Token。
public class TokenFilter extends ZuulFilter {private final Logger logger = LoggerFactory.getLogger(TokenFilter.class);@Overridepublic String filterType() {return "pre"; // 可以在請(qǐng)求被路由之前調(diào)用}@Overridepublic int filterOrder() {return 0; // filter執(zhí)行順序,通過數(shù)字指定 ,優(yōu)先級(jí)為0,數(shù)字越大,優(yōu)先級(jí)越低}@Overridepublic boolean shouldFilter() {return true;// 是否執(zhí)行該過濾器,此處為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");// 獲取請(qǐng)求的參數(shù)if (StringUtils.isNotBlank(token)) {ctx.setSendZuulResponse(true); //對(duì)請(qǐng)求進(jìn)行路由ctx.setResponseStatusCode(200);ctx.set("isSuccess", true);return null;} else {ctx.setSendZuulResponse(false); //不對(duì)其進(jìn)行路由ctx.setResponseStatusCode(400);ctx.setResponseBody("token is empty");ctx.set("isSuccess", false);return null;}}}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
將TokenFilter加入到請(qǐng)求攔截隊(duì)列,在啟動(dòng)類中添加以下代碼:
@Bean public TokenFilter tokenFilter() {return new TokenFilter(); }- 1
- 2
- 3
- 4
這樣就將我們自定義好的Filter加入到了請(qǐng)求攔截中。
測試
我們依次啟動(dòng)示例項(xiàng)目:spring-cloud-eureka、spring-cloud-producer、spring-cloud-zuul,這個(gè)三個(gè)項(xiàng)目均為上一篇示例項(xiàng)目,spring-cloud-zuul稍微進(jìn)行改造。
訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo,返回:token is empty ,請(qǐng)求被攔截返回。?
訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx,返回:hello neo,this is first messge,說明請(qǐng)求正常響應(yīng)。
通過上面這例子我們可以看出,我們可以使用“PRE”類型的Filter做很多的驗(yàn)證工作,在實(shí)際使用中我們可以結(jié)合shiro、oauth2.0等技術(shù)去做鑒權(quán)、驗(yàn)證。
路由熔斷
當(dāng)我們的后端服務(wù)出現(xiàn)異常的時(shí)候,我們不希望將異常拋出給最外層,期望服務(wù)可以自動(dòng)進(jìn)行一降級(jí)。Zuul給我們提供了這樣的支持。當(dāng)某個(gè)服務(wù)出現(xiàn)異常時(shí),直接返回我們預(yù)設(shè)的信息。
我們通過自定義的fallback方法,并且將其指定給某個(gè)route來實(shí)現(xiàn)該route訪問出問題的熔斷處理。主要繼承ZuulFallbackProvider接口來實(shí)現(xiàn),ZuulFallbackProvider默認(rèn)有兩個(gè)方法,一個(gè)用來指明熔斷攔截哪個(gè)服務(wù),一個(gè)定制返回內(nèi)容。
public interface ZuulFallbackProvider {/*** The route this fallback will be used for.* @return The route the fallback will be used for.*/public String getRoute();/*** Provides a fallback response.* @return The fallback response.*/public ClientHttpResponse fallbackResponse(); }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
實(shí)現(xiàn)類通過實(shí)現(xiàn)getRoute方法,告訴Zuul它是負(fù)責(zé)哪個(gè)route定義的熔斷。而fallbackResponse方法則是告訴 Zuul 斷路出現(xiàn)時(shí),它會(huì)提供一個(gè)什么返回值來處理請(qǐng)求。
后來Spring又?jǐn)U展了此類,豐富了返回方式,在返回的內(nèi)容中添加了異常信息,因此最新版本建議直接繼承類FallbackProvider?。
我們以上面的spring-cloud-producer服務(wù)為例,定制它的熔斷返回內(nèi)容。
@Component public class ProducerFallback implements FallbackProvider {private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class);//指定要處理的 service。@Overridepublic String getRoute() {return "spring-cloud-producer";}public ClientHttpResponse fallbackResponse() {return new ClientHttpResponse() {@Overridepublic HttpStatus getStatusCode() throws IOException {return HttpStatus.OK;}@Overridepublic int getRawStatusCode() throws IOException {return 200;}@Overridepublic String getStatusText() throws IOException {return "OK";}@Overridepublic void close() {}@Overridepublic InputStream getBody() throws IOException {return new ByteArrayInputStream("The service is unavailable.".getBytes());}@Overridepublic HttpHeaders getHeaders() {HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);return headers;}};}@Overridepublic ClientHttpResponse fallbackResponse(Throwable cause) {if (cause != null && cause.getCause() != null) {String reason = cause.getCause().getMessage();logger.info("Excption {}",reason);}return fallbackResponse();} }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
當(dāng)服務(wù)出現(xiàn)異常時(shí),打印相關(guān)異常信息,并返回”The service is unavailable.”。
啟動(dòng)項(xiàng)目spring-cloud-producer-2,這時(shí)候服務(wù)中心會(huì)有兩個(gè)spring-cloud-producer項(xiàng)目,我們重啟Zuul項(xiàng)目。再手動(dòng)關(guān)閉spring-cloud-producer-2項(xiàng)目,多次訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx,會(huì)交替返回:
hello neo,this is first messge The service is unavailable. ...- 1
- 2
- 3
根據(jù)返回結(jié)果可以看出:spring-cloud-producer-2項(xiàng)目已經(jīng)啟用了熔斷,返回:The service is unavailable.
Zuul 目前只支持服務(wù)級(jí)別的熔斷,不支持具體到某個(gè)URL進(jìn)行熔斷。
路由重試
有時(shí)候因?yàn)榫W(wǎng)絡(luò)或者其它原因,服務(wù)可能會(huì)暫時(shí)的不可用,這個(gè)時(shí)候我們希望可以再次對(duì)服務(wù)進(jìn)行重試,Zuul也幫我們實(shí)現(xiàn)了此功能,需要結(jié)合Spring Retry 一起來實(shí)現(xiàn)。下面我們以上面的項(xiàng)目為例做演示。
添加Spring Retry依賴
首先在spring-cloud-zuul項(xiàng)目中添加Spring Retry依賴。
<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId> </dependency>- 1
- 2
- 3
- 4
開啟Zuul Retry
再配置文件中配置啟用Zuul Retry
#是否開啟重試功能 zuul.retryable=true #對(duì)當(dāng)前服務(wù)的重試次數(shù) ribbon.MaxAutoRetries=2 #切換相同Server的次數(shù) ribbon.MaxAutoRetriesNextServer=0- 1
- 2
- 3
- 4
- 5
- 6
這樣我們就開啟了Zuul的重試功能。
測試
我們對(duì)spring-cloud-producer-2進(jìn)行改造,在hello方法中添加定時(shí),并且在請(qǐng)求的一開始打印參數(shù)。
@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"; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
重啟 spring-cloud-producer-2和spring-cloud-zuul項(xiàng)目。
訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx,當(dāng)頁面返回:The service is unavailable.時(shí)查看項(xiàng)目spring-cloud-producer-2后臺(tái)日志如下:
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- 1
- 2
- 3
說明進(jìn)行了三次的請(qǐng)求,也就是進(jìn)行了兩次的重試。這樣也就驗(yàn)證了我們的配置信息,完成了Zuul的重試功能。
注意
開啟重試在某些情況下是有問題的,比如當(dāng)壓力過大,一個(gè)實(shí)例停止響應(yīng)時(shí),路由將流量轉(zhuǎn)到另一個(gè)實(shí)例,很有可能導(dǎo)致最終所有的實(shí)例全被壓垮。說到底,斷路器的其中一個(gè)作用就是防止故障或者壓力擴(kuò)散。用了retry,斷路器就只有在該服務(wù)的所有實(shí)例都無法運(yùn)作的情況下才能起作用。這種時(shí)候,斷路器的形式更像是提供一種友好的錯(cuò)誤信息,或者假裝服務(wù)正常運(yùn)行的假象給使用者。
不用retry,僅使用負(fù)載均衡和熔斷,就必須考慮到是否能夠接受單個(gè)服務(wù)實(shí)例關(guān)閉和eureka刷新服務(wù)列表之間帶來的短時(shí)間的熔斷。如果可以接受,就無需使用retry。
Zuul高可用
我們實(shí)際使用Zuul的方式如上圖,不同的客戶端使用不同的負(fù)載將請(qǐng)求分發(fā)到后端的Zuul,Zuul在通過Eureka調(diào)用后端服務(wù),最后對(duì)外輸出。因此為了保證Zuul的高可用性,前端可以同時(shí)啟動(dòng)多個(gè)Zuul實(shí)例進(jìn)行負(fù)載,在Zuul的前端使用Nginx或者F5進(jìn)行負(fù)載轉(zhuǎn)發(fā)以達(dá)到高可用性。
示例代碼-github
示例代碼-碼云
參考:
Spring Cloud(七)服務(wù)網(wǎng)關(guān) Zuul Filter 使用?
Spring Cloud技術(shù)分析(4)- spring cloud zuul?
Zuul 路由使用
總結(jié)
以上是生活随笔為你收集整理的Spring Cloud Zuul网关 Filter、熔断、重试、高可用的使用方式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JasperReports是一个开源的j
- 下一篇: Dubbo和Spring Cloud微服