1. Zuul是什么
Zuul是所有從設備和web站點到Netflix流媒體應用程序后端請求的前門。作為一個邊緣服務應用程序,Zuul被構建來支持動態路由、監視、彈性和安全性。它還可以根據需要將請求路由到多個Amazon自動伸縮組。
2. 為什么用Zuul
Zuul使用了一系列不同類型的過濾器,使我們能夠快速靈活地將功能應用到服務中。這些過濾器幫助我們執行以下功能:
- 身份驗證和安全性 : 識別每個資源的身份驗證需求,並拒絕不滿足它們的請求
- 監控 : 在邊緣跟蹤有意義的數據和統計數據,以便給我們一個准確的生產視圖
- 動態路由 : 動態路由請求到不同的后端集群
- 壓力測試 : 逐漸增加集群的流量,以評估性能
- 限流 : 為每種請求類型分配容量,並丟棄超過限制的請求
- 靜態響應處理 : 直接在邊緣構建一些響應,而不是將它們轉發到內部集群
3. 在Netflix上是如何使用Zuul的
4. Zuul是如何工作的
4.1. Zuul 2.0架構
Zuul的核心業務邏輯是過濾器。它們能夠執行非常大范圍的操作,並且可以在請求-響應生命周期的不同階段運行。如下圖所示:
- Inbound Filters : 路由到 Origin 之前執行,可以用於身份驗證、路由和裝飾請求
- Endpoint Filters : 可用於返回靜態響應,否則內置的ProxyEndpoint過濾器將請求路由到Origin
- Outbound Filters : 從Origin那里獲取響應后執行,可以用於度量、裝飾用戶的響應或添加自定義header
有兩種類型的過濾器:sync 和 async。因為Zuul是運行在一個事件循環之上的,因此從來不要在過濾中阻塞。如果你非要阻塞,可以在一個異步過濾器中這樣做,並且在一個單獨的線程池上運行,否則可以使用同步過濾器。
4.2. Zuul 1.0 請求生命周期
5. 過濾器
過濾器是Zuul的核心功能。它們負責應用程序的業務邏輯,可以執行各種任務。
- Type : 通常定義過濾器應用在哪個階段
- Async : 定義過濾器是同步還是異步
- Execution Order : 執行順序
- Criteria : 過濾器執行的條件
- Action : 如果條件滿足,過濾器執行的動作
Zuul提供了一個動態讀取、編譯和運行這些過濾器的框架。過濾器之間不直接通信,而是通過每個請求特有的RequestContext共享狀態。
5.1. Incoming
Incoming過濾器在請求被代理到Origin之前執行。這通常是執行大部分業務邏輯的地方。例如:認證、動態路由、速率限制、DDoS保護、指標。
5.2. Endpoint
Endpoint過濾器負責基於incoming過濾器的執行來處理請求。Zuul有一個內置的過濾器(ProxyEndpoint),用於將請求代理到后端服務器,因此這些過濾器的典型用途是用於靜態端點。例如:健康檢查響應,靜態錯誤響應,404響應。
5.3. Outgoing
Outgoing過濾器在從后端接收到響應以后執行處理操作。通常情況下,它們更多地用於形成響應和添加指標,而不是用於任何繁重的工作。例如:存儲統計信息、添加/剝離標准標題、向實時流發送事件、gziping響應。
5.4. 過濾器類型
下面是與一個請求典型的生命周期對應的標准的過濾器類型:
- PRE : 路由到Origin之前執行
- ROUTING : 路由到Origin期間執行
- POST : 請求被路由到Origin之后執行
- ERROR : 發生錯誤的時候執行
6. 示例
6.1. 2.0示例
https://github.com/Netflix/zuul/wiki/Filters
https://github.com/Netflix/zuul/tree/2.1/zuul-sample
6.1. 1.0示例
6.1.1. 父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.cjs.example</groupId> <artifactId>cjs-springcloud-example</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>route-book</module> <module>route-gateway</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</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> <spring-cloud.version>Finchley.SR1</spring-cloud.version> <lombok.version>1.18.2</lombok.version> </properties> <dependencyManagement> <dependencies> <!-- Spring Cloud --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>provided</scope> </dependency> </dependencies> </dependencyManagement> </project>
6.1.2. book工程
<?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"> <parent> <artifactId>cjs-springcloud-example</artifactId> <groupId>com.cjs.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>route-book</artifactId> <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.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
RouteBookApplication.java
package com.cjs.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @SpringBootApplication public class RouteBookApplication { @RequestMapping("/hello") public String hello() { return "hello"; } @RequestMapping("/hi") public String hi() { return "hi"; } public static void main(String[] args) { SpringApplication.run(RouteBookApplication.class, args); } }
application.properties
spring.application.name=book server.port=8090
6.1.3. gateway工程
<?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"> <parent> <artifactId>cjs-springcloud-example</artifactId> <groupId>com.cjs.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>route-gateway</artifactId> <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.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
RouteGatewayApplication.java
package com.cjs.example; import com.cjs.example.filter.SimpleFilter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.context.annotation.Bean; /** * http://localhost:8080/books/hello * http://localhost:8080/books/hi */ @EnableZuulProxy @SpringBootApplication public class RouteGatewayApplication { public static void main(String[] args) { SpringApplication.run(RouteGatewayApplication.class, args); } @Bean public SimpleFilter simpleFilter() { return new SimpleFilter(); } }
SimpleFilter.java
package com.cjs.example.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import lombok.extern.slf4j.Slf4j; import javax.servlet.http.HttpServletRequest; /** * Zuul有4中標准的過濾器類型: * pre 過濾器,在請求被路由之前執行 * route 過濾器,在路由請求的時候執行 * post 過濾器,請求被路由以后執行 * error 過濾器,如果在處理請求的過程中發生錯誤,則執行 * * * 繼承com.netflix.zuul.ZuulFilter的Bean就是一個filter */ @Slf4j public class SimpleFilter extends ZuulFilter { /** * 過濾器類型,在這種情況下可選值是:pre 或者 route */ @Override public String filterType() { return "pre"; } /** * 過濾器順序 */ @Override public int filterOrder() { return 1; } /** * 包含一段邏輯,以決定什么時候應該執行這個過濾器 */ @Override public boolean shouldFilter() { return true; } /** * 過濾器的功能,即過濾器真正要做的事情 */ @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info("Method: {}, URL: {}", request.getMethod(), request.getRequestURL()); return null; } }
application.properties
server.port=8080 #Spring Cloud Zuul將自動設置路徑到應用 #在本例中,因為我們設置zuul.routes.books.url=http://localhost:8090,所以Zuul將代理/books的請求到http://localhost:8090 zuul.routes.books.url=http://localhost:8090 ribbon.eureka.enabled=false
7. 參考
https://github.com/Netflix/zuul/wiki