从零开始搭建spring-cloud(5) ----zuul
擼了今年阿里、頭條和美團的面試,我有一個重要發(fā)現(xiàn).......>>>
zuul是什么
zuul 是netflix開源的一個API Gateway 服務(wù)器, 本質(zhì)上是一個web servlet應(yīng)用。
zuul 在云平臺上提供動態(tài)路由,監(jiān)控,彈性,安全等邊緣服務(wù)的框架。Zuul 相當(dāng)于是設(shè)備和 Netflix 流應(yīng)用的 Web 網(wǎng)站后端所有請求的前門。
zuul的例子可以參考 netflix 在github上的 simple webapp,可以按照netflix 在github wiki 上文檔說明來進行使用(https://github.com/Netflix/zuul/wiki)。
zuul的工作原理
過濾器機制
zuul的核心是一系列的filters, 其作用可以類比Servlet框架的Filter,或者AOP。
zuul把Request route到 用戶處理邏輯 的過程中,這些filter參與一些過濾處理,比如Authentication,Load Shedding等。
Zuul提供了一個框架,可以對過濾器進行動態(tài)的加載,編譯,運行。
Zuul的過濾器之間沒有直接的相互通信,他們之間通過一個RequestContext的靜態(tài)類來進行數(shù)據(jù)傳遞的。RequestContext類中有ThreadLocal變量來記錄每個Request所需要傳遞的數(shù)據(jù)。
Zuul的過濾器是由Groovy寫成,這些過濾器文件被放在Zuul Server上的特定目錄下面。Zuul會定期輪詢這些目錄,修改過的過濾器會動態(tài)的加載到Zuul Server中以便過濾請求使用。
下面有幾種標(biāo)準(zhǔn)的過濾器類型:
Zuul大部分功能都是通過過濾器來實現(xiàn)的。Zuul中定義了四種標(biāo)準(zhǔn)過濾器類型,這些過濾器類型對應(yīng)于請求的典型生命周期。
?內(nèi)置的特殊過濾器
zuul還提供了一類特殊的過濾器,分別為:StaticResponseFilter和SurgicalDebugFilter
StaticResponseFilter:StaticResponseFilter允許從Zuul本身生成響應(yīng),而不是將請求轉(zhuǎn)發(fā)到源。
SurgicalDebugFilter:SurgicalDebugFilter允許將特定請求路由到分隔的調(diào)試集群或主機。
自定義的過濾器
除了默認的過濾器類型,Zuul還允許我們創(chuàng)建自定義的過濾器類型。
例如,我們可以定制一種STATIC類型的過濾器,直接在Zuul中生成響應(yīng),而不將請求轉(zhuǎn)發(fā)到后端的微服務(wù)。
過濾器的生命周期
Zuul請求的生命周期如圖,該圖詳細描述了各種類型的過濾器的執(zhí)行順序。
zuul 能做什么
Zuul可以通過加載動態(tài)過濾機制,從而實現(xiàn)以下各項功能:
- 驗證與安全保障: 識別面向各類資源的驗證要求并拒絕那些與要求不符的請求。
- 審查與監(jiān)控: 在邊緣位置追蹤有意義數(shù)據(jù)及統(tǒng)計結(jié)果,從而為我們帶來準(zhǔn)確的生產(chǎn)狀態(tài)結(jié)論。
- 動態(tài)路由: 以動態(tài)方式根據(jù)需要將請求路由至不同后端集群處。
- 壓力測試: 逐漸增加指向集群的負載流量,從而計算性能水平。
- 負載分配: 為每一種負載類型分配對應(yīng)容量,并棄用超出限定值的請求。
- 靜態(tài)響應(yīng)處理: 在邊緣位置直接建立部分響應(yīng),從而避免其流入內(nèi)部集群。
- 多區(qū)域彈性: 跨越AWS區(qū)域進行請求路由,旨在實現(xiàn)ELB使用多樣化并保證邊緣位置與使用者盡可能接近。
?搭建zuul服務(wù)
首先搭建Eureka server和一個Eureka-Provider
在上一節(jié)從零開始搭建spring-cloud(1) ----eureka中拷貝spring-cloud-eureka-server到這個項目中。
在上一節(jié)從零開始搭建spring-cloud(1) ----eureka中拷貝spring-cloud-eureka-provider-A-1到這個項目中。
搭建zuul服務(wù)
新建項目spring-cloud-zuul,pom.xml內(nèi)容如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.vincent</groupId><artifactId>spring-cloud-zuul</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Greenwich.RELEASE</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-zuul</artifactId></dependency></dependencies></project>我們都知道,在zuul過濾器里PRE_TYPE類型是在路由前執(zhí)行的,所以我要給大家演示配置三個PRE_TYPE類型的過濾器,按照順序依次處理不同的業(yè)務(wù)。以及,三個PRE_TYPE類型過濾器中任意一個出現(xiàn)異常時他的下游業(yè)務(wù)應(yīng)該怎么處理。
我們首先看一下項目的目錄結(jié)構(gòu):
各個Filter內(nèi)容如下:
ErrorFilter.java?
package com.vincent.filter;import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component;import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ERROR_TYPE;/*** @author vincent* @time 2019-06-23 16:15*/ @Component public class ErrorFilter extends ZuulFilter {@Overridepublic String filterType() {return ERROR_TYPE;}@Overridepublic int filterOrder() {return -1;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() throws ZuulException {RequestContext ctx = RequestContext.getCurrentContext();System.out.println("這是ErrorFilter");return null;} }FirstPreFilter.java
package com.vincent.filter;import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component;import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;/*** @author vincent* @time 2019-06-23 16:17*/ @Component public class FirstPreFilter extends ZuulFilter {@Overridepublic String filterType() {return PRE_TYPE;}@Overridepublic int filterOrder() {return 0;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() throws ZuulException {System.out.println("這是第一個自定義Zuul Filter!");return null;} }SecondPreFilter.java
package com.vincent.filter;import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;/*** @author vincent* @time 2019-06-23 16:18*/ @Component public class SecondPreFilter extends ZuulFilter {@Overridepublic String filterType() {return PRE_TYPE;}@Overridepublic int filterOrder() {return 2;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() throws ZuulException {System.out.println("這是SecondPreFilter!");//從RequestContext獲取上下文RequestContext ctx = RequestContext.getCurrentContext();//從上下文獲取HttpServletRequestHttpServletRequest request = ctx.getRequest();//從request嘗試獲取a參數(shù)值String a = request.getParameter("a");//如果a參數(shù)值為空則進入此邏輯if (null == a) {//對該請求禁止路由,也就是禁止訪問下游服務(wù)ctx.setSendZuulResponse(false);//設(shè)定responseBody供PostFilter使用ctx.setResponseBody("{\"status\":500,\"message\":\"a param is null\"}");//logic-is-success保存于上下文,作為同類型下游Filter的執(zhí)行開關(guān)ctx.set("logic-is-success", false);//到這里此Filter邏輯結(jié)束return null;}//設(shè)置避免報空ctx.set("logic-is-success", true);return null;} }ThirdPreFilter.java
package com.vincent.filter;import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;/*** @author vincent* @time 2019-06-23 16:19*/ @Component public class ThirdPreFilter extends ZuulFilter {@Overridepublic String filterType() {return PRE_TYPE;}@Overridepublic int filterOrder() {return 3;}@Overridepublic boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();//從上下文獲取logic-is-success值,用于判斷此Filter是否執(zhí)行return (boolean)ctx.get("logic-is-success");}@Overridepublic Object run() throws ZuulException {System.out.println("這是ThirdPreFilter!");//從RequestContext獲取上下文RequestContext ctx = RequestContext.getCurrentContext();//從上下文獲取HttpServletRequestHttpServletRequest request = ctx.getRequest();//從request嘗試獲取b參數(shù)值String b = request.getParameter("b");//如果b參數(shù)值為空則進入此邏輯if (null == b) {//對該請求禁止路由,也就是禁止訪問下游服務(wù)ctx.setSendZuulResponse(false);//設(shè)定responseBody供PostFilter使用ctx.setResponseBody("{\"status\":500,\"message\":\"b param is null\"}");//logic-is-success保存于上下文,作為同類型下游Filter的執(zhí)行開關(guān),假定后續(xù)還有自定義Filter當(dāng)設(shè)置此值ctx.set("logic-is-success", false);//到這里此Filter邏輯結(jié)束return null;}return null;} }PostFilter.java
package com.vincent.filter;import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException;import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.POST_TYPE;/*** @author vincent* @time 2019-06-23 16:20*/ public class PostFilter extends ZuulFilter {@Overridepublic String filterType() {return POST_TYPE;}@Overridepublic int filterOrder() {return 0;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() throws ZuulException {System.out.println("這是PostFilter!");//從RequestContext獲取上下文RequestContext ctx = RequestContext.getCurrentContext();//處理返回中文亂碼ctx.getResponse().setCharacterEncoding("GBK");//獲取上下文中保存的responseBodyString responseBody = ctx.getResponseBody();//如果responseBody不為空,則說明流程有異常發(fā)生if (null != responseBody) {//設(shè)定返回狀態(tài)碼ctx.setResponseStatusCode(500);//替換響應(yīng)報文ctx.setResponseBody(responseBody);}return null;} }新建App.java,內(nèi)容如下:
@SpringBootApplication @EnableZuulProxy public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);} }我們開始配置application.properties,內(nèi)容如下:
eureka.client.service-url.defaultZone=http://127.0.0.1:8080/eureka/ spring.application.name=service-zuulzuul.routes.users.url=http://localhost:8081/ zuul.routes.users.path=/** zuul.ignored-headers=Access-Controller-Allow-Credentials, Access-Control-Allow-Origin zuul.host.connect-timeout-millis=10000000 zuul.host.socket-timeout-millis=10000000server.port=8082這里zuul的匹配規(guī)則是通過url進行匹配。
zuul.host.connect-timeout-millis=10000000
zuul.host.socket-timeout-millis=10000000
這兩句的作用是防止服務(wù)提供方返回響應(yīng)的時間過長
先添加參數(shù)訪問微服務(wù)
控制臺輸出結(jié)果如下:
這是第一個自定義Zuul Filter! 這是SecondPreFilter! 這是PostFilter!添加參數(shù)a訪問,
控制臺輸出結(jié)果如下:
這是第一個自定義Zuul Filter! 這是SecondPreFilter! 這是ThirdPreFilter! 這是PostFilter!添加參數(shù)a和參數(shù)b訪問
控制臺輸出結(jié)果如下:
2019-06-23 17:29:44.348 INFO 3363 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration 這是第一個自定義Zuul Filter! 這是SecondPreFilter! 這是ThirdPreFilter! 這是PostFilter!zuul轉(zhuǎn)發(fā)有兩種配置
根據(jù) eureka server 的serviceId 轉(zhuǎn)發(fā)
zuul.routes.serviceName.path=/exampleService/** zuul.routes.serviceName.serviceId=serviceId注:
zuul.routes 是固定的
serviceName 是可以隨便寫的,但最好根據(jù)要路由的服務(wù)取
serviceId 是 eureka 服務(wù)注冊時的名稱
exampleService 是前端請求某個微服務(wù)的一個公共的路徑名,如/users
而微服務(wù)在 Controller層的 RequestMapping 注解中可以不包含/users
例如本項目中的配置如下:
zuul.routes.myservice.path=/** zuul.routes.myservice.service-id=service-provider-A根據(jù)具體 URL 轉(zhuǎn)發(fā):
zuul.routes.serviceName.path=/exampleService/** zuul.routes.serviceName.url=http://127.0.0.1:8080/如果項目尚未使用eureka,可以采用了第二種轉(zhuǎn)發(fā)規(guī)則。這種轉(zhuǎn)發(fā)有很多好處,最大的好處就是可以很好地過渡到Spring Cloud,使用Zuul可以直接HTTP調(diào)用,方便很多。
例如本項目中的URL規(guī)則轉(zhuǎn)發(fā)如下:
zuul.routes.users.url=http://localhost:8081/ zuul.routes.users.path=/**?
總結(jié)
以上是生活随笔為你收集整理的从零开始搭建spring-cloud(5) ----zuul的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot 使用interce
- 下一篇: SpringBoot Controlle