參考github wiki:https://github.com/Netflix/zuul/wiki
一、什么是Zuul
Zuul是從設備和網(wǎng)站到Netflix流應用程序后端的所有請求的前門。作為邊緣服務應用程序,Zuul旨在實現(xiàn)動態(tài)路由,監(jiān)控,彈性和安全性。
特點
- 身份驗證和安全性
- 觀察與監(jiān)控
- 動態(tài)路由
- 壓力測試
- 負載
- 靜態(tài)響應處理
- 多區(qū)域彈性
二、架構原理
-
工作原理
Zuul的中心是一系列的過濾器,能夠在請求和響應的路由過程中執(zhí)行一系列操作。
Zuul提供了一個支持動態(tài)讀取、編譯和運行過濾器的框架。且過濾器之間不互相通信,而是通過RequestContext共享狀態(tài),且RequestContext 對于每個請求都是唯一的(ThreadLocal)。
-
核心流程

-
核心功能
-
服務發(fā)現(xiàn)
- 支持與 Eureka 無縫集成
- 支持靜態(tài)服務類別以發(fā)現(xiàn)服務
-
負載均衡
-
連接池
-
狀態(tài)類別
-
重試
-
申請護照
-
申請重試
-
原始并發(fā)保護
-
相互TLS
-
代理協(xié)議
-
GZip壓縮
-
-
內(nèi)置Filter說明
Zuul內(nèi)置了四種不同生命周期的過濾器類型,且過濾器之間不“直接”互相通信,而是通過RequestContext共享狀態(tài)。開發(fā)人員可以通過使用zuul來創(chuàng)建各種校驗規(guī)則的過濾器。
Zuul RequestContext
? 為了在過濾器之間傳遞信息,Zuul使用了
RequestContext。其數(shù)據(jù)保存在ThreadLocal每個請求的特定數(shù)據(jù)中過濾器類型如下
- pre-filter(s):在請求被路由之前調用
- route-filter(s):在路由請求時調用
- error-filter(s):在處理請求發(fā)生錯誤時調用
- post-filter(s):在route和error過濾器被調用之后調用
Zuul請求的一個生命周期如下圖

二、Zuul-Instance-Demo說明
1. 簡介
Demo只有服務注冊中心、網(wǎng)關服務、服務提供方

網(wǎng)關的配置
spring:
application:
name: zuul-api-gateway
redis:
cluster:
nodes: 192.168.0.201:7000
max-redirects: 3
password: 123456
jedis:
pool:
max-idle: 10
max-active: 500
max-wait: 1000
server:
port: 1001
eureka:
client:
service-url:
defaultZone: http://localhost:1001/eureka/
# 單實例配置:zuul.routes.<route>.path與zuul.routes.<route>.serviceId 參數(shù)對的方式配置
zuul:
# # 過濾客戶端附帶的headers
# sensitive-headers: Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Credentials,Access-Control-Allow-Headers,Access-Control-Expose-Headers,Access-Control-Max-Age
# # 過濾網(wǎng)關內(nèi)服務之間通信所附帶的headers
# ignored-headers: Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Credentials,Access-Control-Allow-Headers,Access-Control-Expose-Headers,Access-Control-Max-Age
# 限流設置
ratelimit:
enabled: true
# 配置60秒內(nèi)請求超3次,網(wǎng)關則拋異常,且60s后可恢復正常請求
default-policy-list:
- limit: 3
quota: 2
refresh-interval: 60
repository: Redis
# 路由設置
routes:
# 將對符合/api/** 規(guī)則的請求路徑轉發(fā)到服務名為eureka-provider的服務實例上
service:
path: /api/**
serviceId: eureka-provider
# 當訪問格式如:http://localhost:port/服務名/請求路徑 時,若遇到服務名太長,可如下做修改
eureka-provider: /p/**
# 前綴,所有服務調用需在方法路徑前加/api
# prefix:/api
# 排除服務
ignored-services: eureka-provider1
# 設置超時時間,ribbon和hystrix能夠同時生效,且取兩者的最小值
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
ribbon:
# 該參數(shù)用來設置路由轉發(fā)請求的超時時間
ReadTimeout: 4000
# 該參數(shù)用來設置路由轉發(fā)請求的時候,創(chuàng)建請求連接的超時時間
ConnectTimeout: 4000
# 最大自動重試次數(shù)
MaxAutoRetries: 1
# 最大自動重試下一個服務的次數(shù)
MaxAutoRetriesNextServer: 1
eureka:
enabled: true
2. 準備
-
服務端口及參數(shù)定義
啟動服務 端口 運行參數(shù) eureka-server 1000 無 zuul-api-gateway 1001 無 eureka-provider 1101 server.port=1101<br />thread.sleep-ms=500 eureka-provider 1102 server.port=1102<br />thread.sleep-ms=1500 eureka-provider 1103 server.port=1103<br />thread.sleep-ms=10000<br />eureka.instance.metadata-map.publish=gray 注意:1103的實例配置的休眠時間是10秒,網(wǎng)關配置的超時時間是4秒必定會超時
-
配置服務提供方
以IDEA為例,打開Maven面板,依次打開
eureka-provider——Plugins——spring-boot,右擊選擇spring-boot:run項maven_config.jpg
依次添加三個provider實例,按以上表格的運行參數(shù)分別配置

3. 運行
- 啟動注冊中心
- 啟動網(wǎng)關
- 分別運行第二步配置的三個provider實例
- 運行網(wǎng)關下的測試類TestZuul.java
三、使用開發(fā)
1. 引入依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<properties>
<project.version>1.0.0</project.version>
<cloud.version>2.1.2.RELEASE</cloud.version>
<springboot.version>2.1.5.RELEASE</springboot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>${cloud.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>${cloud.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>${cloud.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>${cloud.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
2. zuul配置
-
步驟一:創(chuàng)建微服務網(wǎng)關:zuul-api-gateway
這里默認已有eureka注冊中心
eureka-server和服務提供方eureka-provider。服務提供方的某個controller
@RestController @RequestMapping("/test/") public class TestController { @GetMapping("hello/{name}") public String hello(@PathVariable String name) throws InterruptedException { Thread.sleep(5* 1000); return "hello " + name; } } -
步驟二:配置文件
spring: application: name: zuul-api-gateway server: port: 1000 eureka: client: service-url: defaultZone: http://localhost:1001/eureka/ # 單實例配置:zuul.routes.<route>.path與zuul.routes.<route>.serviceId 參數(shù)對的方式配置 zuul: routes: # 將對符合/api/** 規(guī)則的請求路徑轉發(fā)到服務名為eureka-provider的服務實例上 service: path: /api/** serviceId: eureka-provider # 當訪問格式如:http://localhost:port/服務名/請求路徑 時,若遇到服務名太長,可如下做修改 eureka-provider: /p/** # 前綴,所有服務調用需在方法路徑前加/api # prefix:/api # 排除服務 ignored-services: eureka-provider1 -
步驟三:添加啟動類
@SpringCloudApplication @EnableZuulProxy public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } }這里解釋以下
@EnableZuulServer和@EnableZuulProxy的區(qū)別-
@EnableZuulServer普通版Zuul Server, 支持基本的router和filter功能
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({ZuulServerMarkerConfiguration.class}) public @interface EnableZuulServer { } -
@EnableZuulProxy增強版Zuul Server,在普通版的基礎上,結合eureka+ribbon+增加服務發(fā)現(xiàn)與熔斷等功能
@EnableCircuitBreaker @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Import({ZuulProxyMarkerConfiguration.class}) public @interface EnableZuulProxy { }詳細可查看ZuulProxyConfiguration
-
-
步驟四:啟動
訪問eureka注冊中心后臺:http://localhost:1001/,可以看到eureka上面注冊了服務提供方和zuul網(wǎng)關
eureka.jpg
訪問方式:
正常即可轉發(fā)到服務提供方eureka-provider。
3. 設置超時時間
Zuul 內(nèi)部使用了 Ribbon 做負載均衡,它的默認超時時間是1s,當執(zhí)行一些比較長的請求,會被當做超時處理,返回504

在配置文件內(nèi)添加如下配置
# 設置超時時間,ribbon和hystrix能夠同時生效,且取兩者的最小值
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000
ribbon:
ReadTimeout: 30000
ConnectTimeout: 30000
4. 自定義熔斷降級策略
當Zuul中給定的路徑發(fā)生錯誤時,可以通過創(chuàng)建自定義FallbackProvider來提供熔斷響應,而Zuul默認的響應不太友好(比如上個point的超時錯誤返回504)。
-
自定義熔斷處理類
@Component public class TestFallbackProvider implements FallbackProvider { /** * 指定為哪個微服務提供回退功能,*表示所有微服務 * @return */ @Override public String getRoute() { return "eureka-provider"; } /** * 返回體 * @param route * @param cause * @return */ @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { if (cause instanceof HystrixTimeoutException) { return response(HttpStatus.GATEWAY_TIMEOUT); } else { return response(HttpStatus.INTERNAL_SERVER_ERROR); } } private ClientHttpResponse response(final HttpStatus status) { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return status; } @Override public int getRawStatusCode() throws IOException { return status.value(); } @Override public String getStatusText() throws IOException { return status.getReasonPhrase(); } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("服務暫時不可用,要不等一會再試試?".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }
5. 自定義Filter
通過繼承ZuulFilter,即可實現(xiàn)過濾器機制。
以下自定義過濾器用于校驗接口是否傳遞了token
@Slf4j
@Component
public class AccessFilter extends ZuulFilter {
/**
* 四種不同生命周期的過濾器類型
* 1. pre:在請求被路由之前調用
* 2. route:在路由請求時被調用
* 3. post:在route和error過濾器之后被調用
* 4. error:處理請求時發(fā)生錯誤時被調用·
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 過濾的優(yōu)先級,數(shù)字越大,優(yōu)先級越低。
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* @return 該過濾器是否需要被執(zhí)行
*/
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());
String token = request.getParameter("token");
if(Objects.isNull(token)) {
log.warn("token is empty");
// 讓zuul過濾該請求,不對其進行路由
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
log.info("token is ok");
return null;
}
}
訪問方式:
- http://localhost:1000/eureka-provider/test/hello/jerry?token=111
- http://localhost:1000/api/test/hello/jerry?token=111
正常即可轉發(fā)到服務提供方eureka-provider。
6. 限流
-
引入依賴包
spring-cloud-zuul-ratelimit,支持與zuul整合提供分布式限流策略、github地址:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
<dependency> <groupId>com.marcosbarbero.cloud</groupId> <artifactId>spring-cloud-zuul-ratelimit</artifactId> <version>2.2.4.RELEASE</version> </dependency>-
限流粒度:
粗粒度:
- 網(wǎng)關限流
- 單個服務限流
細粒度:
- url:對請求的目標url進行限流
- origin:對請求來源ip進行限流
- user:對特定用戶(比如系統(tǒng)的非vip用戶)進行限流
- serviceId:對特定服務id進行限流
-
限流統(tǒng)計數(shù)據(jù)存儲
ConsulRateLimiter:
ConsulRedisRateLimiter:
Redis<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>SpringDataRateLimiter:
Spring DataBucket4jJCacheRateLimiter:
Bucket4jBucket4jHazelcastRateLimiter:
Bucket4jBucket4jIgniteRateLimiter:
Bucket4jBucket4jInfinispanRateLimiter:
Bucket4j -
限流配置模板
zuul: ratelimit: key-prefix: your-prefix # 開啟限流 enabled: true # 存儲類型,用于存儲統(tǒng)計信息(對于不同的存儲類型,需要在pom添加不同的依賴) repository: REDIS behind-proxy: true add-response-headers: true default-policy-list: #optional 全局配置 - limit: 10 #optional - 單位時間內(nèi)窗口的請求數(shù)限制 quota: 1000 #optional - 單位時間內(nèi)窗口的請求總時間限制 refresh-interval: 60 # 單位時間設置 type: #optional - user # 通過登錄用戶區(qū)分 - origin # 通過請求ip區(qū)分 - url # 通過請求路徑區(qū)分 - httpmethod # 通過請求類型區(qū)分 ################################################################# policy-list: # 局部配置(對特定的服務id進行限流) myServiceId: - limit: 10 #optional - request number limit per refresh interval window quota: 1000 #optional - request time limit per refresh interval window (in seconds) refresh-interval: 60 #default value (in seconds) type: #optional - user - origin - url - type: #optional value for each type - user=anonymous - origin=somemachine.com - url=/api #url prefix - role=user - httpmethod=get #case insensitive
-
-
測試
這里選用redis作為網(wǎng)關的數(shù)據(jù)存儲,需要引入redis的依賴包,并且配置redis和ratelimit
spring: application: name: zuul-api-gateway redis: cluster: nodes: 192.168.0.201:7000 max-redirects: 3 password: 123456 jedis: pool: max-idle: 10 max-active: 500 max-wait: 1000 zuul: # 開啟全局配置限流 ratelimit: enabled: true # 配置60秒內(nèi)請求超3次,網(wǎng)關則拋異常,且60s后可恢復正常請求 default-policy-list: - limit: 3 quota: 2 refresh-interval: 60 repository: Redis結果如下:
ratelimit.png
4. 負載均衡
5. 路由重試
引入依賴
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
配置 zuul.retryable=true
zuul-demo-provider: #指定服務
ribbon:
MaxAutoRetries: 0 #本服務重試次數(shù)
MaxAutoRetriesNextServer: 1 #重試下一個服務個數(shù)
ReadTimeout: 1000
ConnectTimeout: 3000
6. 權限集成
參考自定義Filter的實現(xiàn)方式。
7. 灰度發(fā)布
8. 開啟跨域
注意:當網(wǎng)關配置了跨域處理后,內(nèi)部服務則不需要配置
配置過濾請求頭
zuul:
# 過濾客戶端附帶的headers
sensitive-headers: Access-Control-Allow-Origin
# 過濾網(wǎng)關內(nèi)服務之間通信所附帶的headers
ignored-headers: Access-Control-Allow-Origin
配置解決跨域訪問問題
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 允許cookies跨域
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.setMaxAge(18000L);
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
感謝閱讀,Ending...


