Zuul做爲微服務系統的網關組件,用於構建邊界服務,致力於動態路由、過濾、監控、彈性伸縮和安全。html
爲何須要Zuuljava
Zuul、Ribbon以及Eureka結合能夠實現智能路由和負載均衡的功能;網關將全部服務的API接口統一聚合,統一對外暴露。外界調用API接口時,不須要知道微服務系統中各服務相互調用的複雜性,保護了內部微服務單元的API接口;網關能夠作用戶身份認證和權限認證,防止非法請求操做API接口;網關能夠實現監控功能,實時日誌輸出,對請求進行記錄;網關能夠實現流量監控,在高流量的狀況下,對服務降級;API接口從內部服務分離出來,方便作測試。git
Zuul經過Servlet來實現,經過自定義的ZuulServlet來對請求進行控制。核心是一系列過濾器,能夠在Http請求的發起和響應返回期間執行一系列過濾器。Zuul採起了動態讀取、編譯和運行這些過濾器。過濾器之間不能直接通訊,而是經過RequestContext對象來共享數據,每一個請求都會建立一個RequestContext對象。github
Zuul生命週期以下圖。 當一個客戶端Request請求進入Zuul網關服務時,網關先進入」pre filter「,進行一系列的驗證、操做或者判斷。而後交給」routing filter「進行路由轉發,轉發到具體的服務實例進行邏輯處理、返回數據。當具體的服務處理完成後,最後由」post filter「進行處理,該類型的處理器處理完成以後,將Request信息返回客戶端。 web
搭建Zuul服務spring
須要配置好eureka,ribbon,可參考:apache
spring boot 2.0.3+spring cloud (Finchley)一、搭建Eureka 以及構建高可用Eureka Server集羣 api
spring boot 2.0.3+spring cloud (Finchley)二、搭建負載均衡Ribbon (Eureka+Ribbon+RestTemplate)瀏覽器
spring boot 2.0.3+spring cloud (Finchley)三、聲明式調用Feign安全
新建工程eureka-zuul-client,pom文件引入相關依賴,包括主maven工程的pom文件,eureka client起步依賴,Zuul的起步依賴,web的起步依賴(已在主maven工程pom中配置)。
<?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.cralor</groupId> <artifactId>eureka-zuul-client</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>eureka-zuul-client</name> <description>Demo project for Spring Boot</description> <parent> <groupId>com.cralor</groupId> <artifactId>chap9-zuul</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
在啓動類加上註解@EnableZuulProxy
@EnableZuulProxy @SpringBootApplication public class EurekaZuulClientApplication { public static void main(String[] args) { SpringApplication.run(EurekaZuulClientApplication.class, args); } }
在配置文件作相關配置,包括端口號5000,服務註冊中心地址http://localhost:8761/eureka/,程序名service-zuul,其中zuul路由配置:zuul.routes.hiapi爲」/hiapi/**「,zuul.routes.serviceId爲」eureka-client「,這兩個配置就能夠將以」/hiapi「開頭的Url路由到eureka-client服務,zuul.routes.hiapi中的」hiapi「是本身定義的須要指定它的path和serviceId,二者配合使用,就能夠將指定類型的請求Url路由到指定的serviceId。同理,知足」/ribbonapi「開頭的請求Url都會被分發到eureka-ribbon-client,知足」/feignapi「開頭的請求Url都會被分發到eureka-feign-client服務。若是服務存在多個實例,zuul會結合ribbon作負載均衡。
server:
port: 5000
spring:
application:
name: service-zuul
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
zuul:
routes:
hiapi:
path: /hiapi/**
serviceId: eureka-client
ribbonapi:
path: /ribbonapi/**
serviceId: eureka-ribbon-client
feignapi:
path: /feignapi/**
serviceId: eureka-feign-client
依次啓動工程eureka-server、eureka-client(啓動兩個實例端口爲876二、8763)、eureka-ribbon-client、eureka-feign-client和eureka-zuul-client。瀏覽器屢次訪問http://localhost:5000/hiapi/hi?name=cralor,會交替顯示
可見zuul在路由轉發作了負載均衡。同理屢次訪問http://localhost:5000/ribbonapi/hi?name=cralor和http://localhost:5000/feignapi/hi?name=cralor也能夠看到相似內容。
在Zuul上配置API接口的版本號
若是想給每一個服務的API接口加前綴,可以使用zuul.prefix配置,例如http://localhost:5000/v1/hiapi/hi?name=cralor,即在全部的API接口上加一個v1做爲版本號。
zuul:
prefix: /v1
routes:
hiapi:
path: /hiapi/**
serviceId: eureka-client
ribbonapi:
path: /ribbonapi/**
serviceId: eureka-ribbon-client
feignapi:
path: /feignapi/**
serviceId: eureka-feign-client
重啓eureka-zuul-client,訪問http://localhost:5000/v1/hiapi/hi?name=cralor,結果如上。
在Zuul上配置熔斷器
Zuul做爲Netflix組件,能夠與Ribbon、Eureka和Hystrix等組件相結合,實現負載均衡、熔斷器的功能。默認狀況下Zuul和Ribbon相結合,實現了負載均衡。實現熔斷器功能須要實現FallbackProvider接口,實現該接口的兩個方法,一個是getRoute(),用於指定熔斷器功能應用於哪些路由的服務;另外一個方法fallbackResponse()爲進入熔斷器功能時執行的邏輯。
@Component public class MyFallbackProvider implements FallbackProvider { @Override public String getRoute() { return "eureka-client"; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { System.out.println("route:"+route); System.out.println("exception:"+cause.getMessage()); return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return 200; } @Override public String getStatusText() throws IOException { return "ok"; } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("oooops!error,i'm the fallback.".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }
重啓eureka-zuul-client,關閉eureka-client的全部實例,訪問http://localhost:5000/v1/hiapi/hi?name=cralor,瀏覽器顯示
若是須要全部的路由服務都加熔斷功能,須要在getRoute()方法上返回」*「的匹配符
@Override public String getRoute() { return "*"; }
在Zuul使用過濾器
Zuul包括如下4中過濾器
PRE過濾器:是在請求路由到具體服務以前執行的,能夠作安全驗證,如身份驗證,參數驗證。
ROUTING過濾器:它用於將請求 路由到具體的微服務實例。默認使用Http Client進行網絡請求。
POST過濾器:在請求已被路由到微服務後執行的。可用做收集統計信息、指標,以及將響應傳輸到客戶端。
ERROR過濾器:在其餘過濾器發生錯誤時執行。
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,格式爲zuul.<SimpleClassName>.<filterType>.disable=true
。
好比要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter
就設置
zuul: SendResponseFilter: post: disable: true
實現自定義濾器須要繼承ZuulFilter,實現ZuulFilter中的抽象方法,包括filterType(),filterOrder()以及IZuulFilter的shouldFilter()和run()方法。filterType()爲過濾器類型,有4中類型:pre、post、routing和error。filterOrder()是過濾順序,它爲一個int類型的值,值越小,越早執行該過濾器。shouldFilter()表示是否須要執行該過濾器邏輯,true表示執行,false表示不執行,若是true則執行run()方法。run()方法是具體的過濾的邏輯。本例中檢查請求的參數中是否傳了token這個參數,若是沒有傳,則請求不被路由到具體的服務實例,直接返回 響應,狀態碼爲401。
@Component public class MyFilter extends ZuulFilter { private static Logger log=LoggerFactory.getLogger(MyFilter.class); @Override public String filterType() { return "pre"; //定義filter的類型,有pre、route、post、error四種 } @Override public int filterOrder() { return 0;//定義filter的順序,數字越小表示順序越高,越先執行 } @Override public boolean shouldFilter() { return true;//表示是否須要執行該filter,true表示執行,false表示不執行 } @Override public Object run() throws ZuulException { //filter須要執行的具體操做 RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); String token = request.getParameter("token"); System.out.println(token); if(token==null){ log.warn("token is empty"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); try { ctx.getResponse().getWriter().write("token is empty"); } catch (IOException e) { e.printStackTrace(); } return null; } log.info("ok"); return null; } }
重啓服務,訪問http://localhost:5000/v1/hiapi/hi?name=cralor,顯示
地址欄輸入http://localhost:5000/v1/hiapi/hi?name=cralor&token=ccc,顯示
可見,MyFilter這個Bean注入IOC容器後,對請求進行了過濾,並在請求路由轉發以前進行了邏輯判斷。
案例代碼地址:https://github.com/cralor7/springcloud