推薦:微服務彙總
Spring Cloud 之Zuul初使用
介紹
在微服務架構中,通常會有多個服務提供者。設想一個電商系統,可能會有商品、訂單、支付、使用者等多個類型的服務,而每個類型的服務數量也會随着整個系統體量的增大也會随之增長和變更。作為UI端,在展示頁面時可能需要從多個微服務中聚合資料,而且服務的劃分位置結構可能會有所改變。網關就可以對外暴露聚合API,屏蔽内部微服務的微小變動,保持整個系統的穩定性。
當然這隻是網關衆多功能中的一部分,它還可以做負載均衡,統一鑒權,協定轉換,監控監測等一系列功能。
Zuul是Spring Cloud全家桶中的微服務API網關。
所有從裝置或網站發出來的請求都會經過Zuul到達服務提供者。作為一個邊界性質的應用程式,Zuul提供了動态路由、監控、彈性負載和安全功能。Zuul底層利用各種Filter實作如下功能:
- 認證和安全: 識别每個需要認證的資源,拒絕不符合要求的請求。
- 性能監測: 在服務邊界追蹤并統計資料,提供精确的生産視圖。
- 動态路由: 根據需要将請求動态路由到後端叢集。
- 壓力測試: 逐漸增加對叢集的流量以了解其性能。
- 限流: 預先為每種類型的請求配置設定容量,當請求超過容量時自動丢棄。
- 靜态資源處理: 直接在邊界傳回某些響應。
Zuul提供了一個架構,可以對過濾器進行動态的加載,編譯,運作。
Zuul的過濾器之間沒有直接的互相通信,他們之間通過一個RequestContext的靜态類來進行資料傳遞。RequestContext類中有ThreadLocal變量來記錄每個Request所需要傳遞的資料。
Zuul的過濾器是由Groovy寫成,這些過濾器檔案被放在Zuul Server上的特定目錄下面,Zuul會定期輪詢這些目錄,修改過的過濾器會動态的加載到Zuul Server中,以便在相應情況下調用。
Zuul大部分功能都是通過過濾器來實作的。Zuul中定義了四種标準過濾器類型,這些過濾器類型對應于請求的典型生命周期。
-
:這種過濾器在請求被路由之前調用。我們可利用這種過濾器實作身份驗證、在叢集中選擇請求的微服務、記錄調試資訊等。PRE
-
:這種過濾器将請求路由到微服務。這種過濾器用于建構發送給微服務的請求,并使用Apache HttpClient或Netfilx Ribbon請求微服務。ROUTING
-
:這種過濾器在路由到微服務以後執行。這種過濾器可用來為響應添加标準的HTTP Header、收集統計資訊和名額、将響應從微服務發送給用戶端等。POST
-
:在其他階段發生錯誤時執行該過濾器。ERROR
Zuul是圍繞一系列Filter展開的,這些Filter在整個HTTP請求過程中執行一連串的操作。
Zuul Filter有以下幾個特征:
-
:用以表示路由過程中的階段(内置包含Type
、PRE
、ROUTING
和POST
)。ERROR
-
:表示相同Execution Order
的Filter的執行順序。Type
-
:執行條件。Criteria
-
:執行體。Action
Zuul提供了動态讀取、編譯和執行Filter的架構。各個Filter間沒有直接聯系,都通過RequestContext共享一些狀态資料。
盡管Zuul支援任何基于JVM的語言,但是過濾器目前是用Groovy編寫的。 每個過濾器的源代碼被寫入到Zuul伺服器上一組指定的目錄中,這些目錄将被定期輪詢檢查是否更新。Zuul會讀取已更新的過濾器,動态編譯到正在運作的伺服器中,并後續請求中調用。
代碼
首先需要建立服務注冊中心,這裡代碼就不貼了,不了解Eureka的話可以看一下下面這篇部落格。
Spring Cloud 之Eureka初使用
建立Zuul項目。
pom.xml
如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kaven</groupId>
<artifactId>zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zuul</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.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.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<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.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
如下:
spring:
application:
name: zuul
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
server:
port: 8084
啟動類:
package com.kaven.apigateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
@EnableDiscoveryClient
、
@EnableZuulProxy
注解要記得加上。
建立一個服務提供者。
pom.xml
如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.kaven</groupId>
<artifactId>server-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>server-provider</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR2</spring-cloud.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-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
如下:
spring:
application:
name: server-provider
server:
port: 8002
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
啟動類:
package com.kaven.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ServerProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServerProviderApplication.class, args);
}
}
@EnableDiscoveryClient
注解要記得加上。
接口:
package com.kaven.client.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MessageController{
@GetMapping("/getMessage")
public String getMessage(){
return "hello , Kaven";
}
}
通路
http://localhost:8761/
,可以看到服務已經全部注冊到Eureka上了。
通路
http://localhost:8084/server-provider/getMessage
,可以看到,通過Zuul已經成功請求到服務提供者的接口了。
由此得知,Zuul可以通過使用服務提供者注冊到Eureka的名稱,來路由到該服務提供者指定的接口。
Zuul也可以自定義路由路徑,需要修改配置如下:
spring:
application:
name: zuul
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
server:
port: 8084
zuul:
routes:
# 自定義一個名稱
myRoute:
# 自定義路由路徑,**是通配符
path: /route/**
# 服務提供者注冊到Eureka的名稱
serviceId: server-provider
通路
http://localhost:8084/route/getMessage
,也可以成功請求到服務提供者指定的接口。
還有一種方式來自定義路由路徑,直接服務提供者注冊到Eureka的名稱:路由路徑即可,如下所示:
zuul:
routes:
server-provider: /route/**
如果某個接口不想通過Zuul被請求到,可以添加如下配置:
zuul:
routes:
server-provider: /route/**
ignored-patterns:
- /**/getMessage
再通路
http://localhost:8084/route/getMessage
,可以看到已經
404
了。
該配置參數是
Set
類型。
下面是
ZuulProperties
的所有屬性,通過屬性名也很容易知道是什麼意思。
public static final List<String> SECURITY_HEADERS = Arrays.asList("Pragma", "Cache-Control", "X-Frame-Options", "X-Content-Type-Options", "X-XSS-Protection", "Expires");
private String prefix = "";
private boolean stripPrefix = true;
private Boolean retryable = false;
private Map<String, ZuulProperties.ZuulRoute> routes = new LinkedHashMap();
private boolean addProxyHeaders = true;
private boolean addHostHeader = false;
private Set<String> ignoredServices = new LinkedHashSet();
private Set<String> ignoredPatterns = new LinkedHashSet();
private Set<String> ignoredHeaders = new LinkedHashSet();
private boolean ignoreSecurityHeaders = true;
private boolean forceOriginalQueryStringEncoding = false;
private String servletPath = "/zuul";
private boolean ignoreLocalService = true;
private ZuulProperties.Host host = new ZuulProperties.Host();
private boolean traceRequestBody = true;
private boolean removeSemicolonContent = true;
private Set<String> sensitiveHeaders = new LinkedHashSet(Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
private boolean sslHostnameValidationEnabled = true;
private ExecutionIsolationStrategy ribbonIsolationStrategy;
private ZuulProperties.HystrixSemaphore semaphore;
private ZuulProperties.HystrixThreadPool threadPool;
private boolean setContentLength;
private boolean includeDebugHeader;
private int initialStreamBufferSize;
比如被請求的接口需要一些
Header
裡面敏感的資料,如
Cookie
、
Set-Cookie
、
Authorization
,可以直接把
sensitiveHeaders
置空:
zuul:
sensitive-headers:
接下來介紹Zuul的過濾器怎麼使用,原理在之前就介紹過了,這裡直接上代碼。
package com.kaven.apigateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.stereotype.Component;
import com.netflix.zuul.exception.ZuulException;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
@Component
public class MyFilter extends ZuulFilter {
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest httpServletRequest = requestContext.getRequest();
if("/route/getMessage".equals(httpServletRequest.getRequestURI())){
return true;
}
return false;
}
@Override
public Object run() throws ZuulException{
System.out.println("請求通過了");
return null;
}
}
需要繼承
ZuulFilter
,并且重載四個方法,就是之前說的:
-
:用以表示路由過程中的階段(内置包含Type
、PRE
、ROUTING
和POST
)。ERROR
-
:表示相同Execution Order
的Filter的執行順序。Type
-
:執行條件。Criteria
-
:執行體。Action
使用
PRE
類型的過濾器。
執行順序是在
PRE_DECORATION_FILTER_ORDER
之前(越小優先級越高)。
執行條件,通過代碼也很容易知道,就是通過判斷請求URI是否是
/route/getMessage
,是就通過,否則不通過。
執行體,就輸出請求通過了即可。
自定義過濾器是不是很簡單,接下來介紹兩種常見的自定義過濾器。
自定義限流過濾器:
package com.kaven.apigateway.filter;
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.*;
@Component
public class RateFilter extends ZuulFilter {
private static final RateLimiter RATE_LIMITER = RateLimiter.create(1000);
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return SERVLET_DETECTION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
if(!RATE_LIMITER.tryAcquire()){
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.BAD_REQUEST.value());
}
return null;
}
}
RateLimiter
不了解的自行百度。
處理響應資料過濾器:
package com.kaven.apigateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.*;
@Component
public class ResponseFilter extends ZuulFilter {
@Override
public String filterType() {
return POST_TYPE;
}
@Override
public int filterOrder() {
return SEND_RESPONSE_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run(){
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletResponse httpServletResponse = requestContext.getResponse();
httpServletResponse.setHeader("kaven","hello");
return null;
}
}
- ZUUL-API網關
- zuul入門(1)zuul 的概念和原理