SpringCloud 08 - Hystrix 熔斷器
1. 概述簡介
1.1 官網
上一代 zuul 1.x: https://github.com/Netflix/zuul/wiki
目前 gateway:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
1.2 簡介
① 概述
Cloud 全家桶中有個很重要的元件就是網關,在 1.x 版本中都是采用的 Zool 網關;但在 2.x 版本中,zool 的更新一直跳票,SpringCloud 最後自己研發了一個網關代替 Zool,那就是 SpringCloud Gateway。就是說 Gateway 是原 zoo1.x 的替代。

Gateway 是在 Spring 生态系統之上建構的 API 網關服務,基于 Spring 5,Spring Boot2 和 Project Reactor 等技術。Gateway 旨在提供一種簡單而有效的方式來對 API 進行路由,以及提供一些強大的過濾器功能,例如:熔斷、限流、重試等。
SpringCloud Gateway 是 Spring Cloud 的一個全新項目,基于 Spring 5.0+ Spring Boot 2.0 和 Project Reactor 等技術開發的網關,它旨在為微服務架構提供一種簡單有效的統一的 API 路由管理方式。
SpringCloud Gateway 作為 Spring Cloud 生态系統中的網關,目标是替代 Zuul,在 Spring Cloud 2.0 以上版本中,沒有對新版本的Zuul 2.0 以上最新高性能版本進行內建,仍然還是使用的 Zuul 1.x 非 Reactor 模式的老版本。而為了提升網關的性能,SpringCloud Gateway 是基于 WebFlux 架構實作的,而 WebFlux 架構底層則使用了高性能的 Reactor 模式通信架構 Netty。
Spring Cloud Gateway 的目标提供統一的路由方式且基于 Filter 鍊的方式提供了網關基本的功能,例如:安全,監控/名額和限流。
② 一句話
SpringCloud Gateway 使用的是 Webflux 中的 reactor-netty 響應式程式設計元件,底層使用了 Netty 通訊架構。
源碼架構:
1.3 能幹嘛
- 反向代理
- 鑒權
- 流量控制
- 熔斷
- 日志監控
- .....
1.4 微服務架構中網關在哪裡
1.5 有Zuull了怎麼又出來gateway
① 我們為什麼選擇Gateway?
- netflix 不太靠譜,zuul2.0 一直跳票,遲遲不釋出
- SpringCloud Gateway 具有如下特性
- SpringCloud Gateway 與 Zuul 的差別
② Zuul1.x 模型
Springcloud 中所內建的 Zuul 版本,采用的是 Tomcat 容器,使用的是傳統的 Servlet I0 處理模型。
Servlet的生命周期:
servlet 由 servlet container 進行生命周期管理。container 啟動時構造 servlet 對象并調用 servlet init() 進行初始化;
container 運作時接受請求,并為每個請求配置設定一個線程(一般從線程池中擷取空閑線程)然後調用 service()。container 關閉時調用servlet destory() 銷毀servlet;
上述模式的缺點:
servlet 是一個簡單的網絡 I0 模型, 當請求進入 servlet container 時,servlet container 就會為其綁定一個線程, 在并發不高的場景下這種模型是适用的。但是一旦高并發(此如抽風用 jemeter 壓),線程數量就會上漲,而線程資源代價是昂貴的(上線文切換,記憶體消耗大)嚴重影響請求的處理時間。在一些簡單業務場景下,不希望為每個 request 配置設定一個線程, 隻需要 1 個或幾個線程就能應對極大并發的請求,這種業務場景下 servlet 模型沒有優勢。
是以 Zuul1.X 是基于 servlet 之上的一個阻塞式處理模型,即 spring 實作了處理所有 request 請求的一個 servlet (DispatcherServlet) 并由該 servlet 阻塞式處理。是以 Springcloud Zuul 無法擺脫 servlet 模型的弊端。
③ Gateway 模型
WebFlux 是什麼:https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#spring-webflux
說明:
傳統的 Web 架構,比如說:struts2,springmvc 等都是基于 Servlet API 與 Servlet 容器基礎之上運作的。
但是,在 Servlet 3.1 之後有了異步非阻塞的支援。而 WebFlux 是一個典型非阻塞異步的架構,它的核心是基于 Reactor 的相關 API 實作的。 相對于傳統的 web 架構來說,它可以運作在諸如 Netty, Undertow 及支援 Servlet3.1 的容器上。非阻塞式+函數式程式設計(Spring5 必須讓你使用 Java8)
Spring WebFlux 是 Spring 5.0 引入的新的響應式架構,差別于 Spring MVC,它不需要依賴 Servlet API,它是完全異步非阻塞的,并且基于 Reactor 來實作響應式流規範。
2. 三個核心概念
2.1 Route(路由):路由是建構網關的基本子產品,它由 ID,目标URI,一系列的斷言和過濾器組成,如斷言為 true 則比對該路由。
2.2 Predicate(斷言):參考的是 Java8 的 java.util.function.Predicate 開發人員可以比對 HTTP 請求中的所有内容(例如請求頭或請求參數),如果請求與斷言相比對則進行路由。
2.3 Filter(過濾):指的是 Spring 架構中 GatewayFilter 的執行個體,使用過濾器,可以在請求被路由前或者之後對請求進行修改。
2.4 總結
web 請求,通過一些比對條件,定位到真正的服務節點。并在這個轉發過程的前後,進行一些精細化控制,predicate 就是我們的比對條件;
而 filter,就可以了解為一個無所不能的攔截器,有了這兩個元素,再加上目标 url,就可以實作一個具體的路由了。
3. Gateway 工作流程
3.1 官網總結
用戶端向 Spring Cloud Gateway 送出請求。然後在 Gateway Handler Mapping 中找到與請求相比對的路由,将其發送到 Gateway Web Handler。
Handler 再通過指定的過濾器鍊來将請求發送到我們實際的服務執行業務邏輯,然後傳回。過濾器之間用虛線分開是因為過濾器可能會在發送代理請求之前( "pre" )或之後( "post" )執行業務邏輯。
Filter 在"pre" 類型的過濾器可以做參數校驗、權限校驗、流量監控、日志輸出、協定轉換等,在"post"類型的過濾器中可以做響應内容、響應頭的修改,日志的輸出,流量監控等有着非常重要的作用。
3.2 核心邏輯
路由轉發 + 執行過濾器鍊
4. 入門配置
4.1 建立Module:cloud-gateway-gateway9527
4.2 POM
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.janet.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--一般基礎配置類-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
4.3 YML
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
instance:
hostname: cloud-gateway-service
client: #服務提供者provider注冊進eureka服務清單内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
4.4 主啟動類
package com.janet.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @Date 2020/5/11
* @Author Janet
*/
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class, args);
}
}
4.5 業務類: 無
4.6 9527網關如何做路由映射?
cloud-provider-payment8001看看 controller 的通路位址:get、lb
我們目前不想暴露 8001 端口,希望在 8001 外面套一層 9527
4.7 YML 新增網關配置
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
uri: http://localhost:8001 #比對後提供服務的路由位址
predicates:
- Path=/payment/get/** # 斷言,路徑相比對的進行路由
- id: payment_routh2 #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
uri: http://localhost:8001 #比對後提供服務的路由位址
predicates:
- Path=/payment/lb/** # 斷言,路徑相比對的進行路由
eureka:
instance:
hostname: cloud-gateway-service
client: #服務提供者provider注冊進eureka服務清單内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
4.8 測試
啟動7001,啟動8001(cloud-provider-payment8001),啟動9527
通路說明:
- 添加網關前:http://localhost:8001/payment/get/1
- 添加網關後:http://localhost:9527/payment/get/1
4.9 YML配置說明
Gateway網關路由有兩種配置方式:
① 在配置檔案yaml中配置:見前面的步驟
② 代碼中注入 RouteLocator 的 Bean
- 官網案例
- 百度國内新聞網站,需要外網:https://news.baidu.com/guonei
- 自己寫一個 百度新聞
業務需求:通過9527網關通路到外網的百度新聞網址
編碼:cloud-gateway-gateway9527
業務實作:config
package com.janet.springcloud.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description 這個類是自己用代碼配置路由的 config 類
* @Date 2020/5/11
* @Author Janet
*/
@Configuration
public class GateWayConfig {
/*
* 配置了一個 id 為 rout-name 的路由規則,
* 當通路位址 http://localhost:9527/guonei 時會自動轉發到位址:https://news.baidu.com/guonei
*
* */
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//
routes.route("path_rout_janet", r -> r.path("/guonei").uri("https://news.baidu.com/guonei")).build();
return routes.build();
}
}
5. 通過服務名實作動态
預設情況下 Gatway 會根據注冊中心注冊的服務清單, 以注冊中心上微服務名為路徑建立動态路由進行轉發,進而實作動态路由的功能
① 啟動: 一個 eureka7001 + 兩個服務提供者 8001/8002
② POM(之前都加過了)
③ YML
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #開啟從注冊中心動态建立路由的功能,利用微服務名進行路由
routes:
- id: payment_routh #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
#uri: http://localhost:8001 #比對後提供服務的路由位址
uri: lb://cloud-payment-service #比對後提供服務的路由位址
predicates:
- Path=/payment/get/** # 斷言,路徑相比對的進行路由
- id: payment_routh2 #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
#uri: http://localhost:8001 #比對後提供服務的路由位址
uri: lb://cloud-payment-service #比對後提供服務的路由位址
predicates:
- Path=/payment/lb/** # 斷言,路徑相比對的進行路由
eureka:
instance:
hostname: cloud-gateway-service
client: #服務提供者provider注冊進eureka服務清單内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
需要注意的是 uri 的協定 lb,表示啟用 Gateway 的負載均衡功能
lb://serverName 是spring cloud gatway在微服務中自動為我們建立的負載均衡 url
④ 測試:http://localhost:9527/payment/lb
8001/8002兩個端口切換:
6. Predicate
6.1 簡介
啟動微服務 gateway9527:
6.2 Route Predicate Factories
6.3 常用的Route Predicate
① After Route Predicate
② Before Route Predicate
③ Between Route Predicate
④ Cookie Route Predicate
Cookie Route Predicate 需要兩個參數,一個是 Cookie name,一個是正規表達式。路由規則會通過擷取對應的 Cookie name 值和正規表達式去比對,如果比對上就會執行路由,如果沒有比對上則不執行。
不帶cookies通路:
帶上cookies通路:
⑤ Header Route Predicate
兩個參數:一個屬性名稱和一個正規表達式,這個屬性值和正規表達式比對則執行。
curl http://localhost:9527/payment/lb -H "X-Request-Id:123"
⑥ Host Route Predicate
⑦ Method Route Predicate
⑧ Path Route Predicate
⑨ Query Route Predicate
⑩ RemoteAddr Route Predicate
⑪ Weight Route Predicate
說白了,Predicate 就是為了實作一組比對規則, 讓請求過來找到對應的 Route 進行處理。
7. Filter 的使用
7.1 是什麼
7.2 Spring Cloud Gateway 的 filter
① 生命周期
pre
post
② 種類
- GatewayFilter:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories
- GlobalFilter:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#global-filters
7.3 常用的GatewayFilter
AddRequestParameter:YML
7.4 自定義過濾器
自定義全局GlobalFilter
① 兩個主要接口介紹:implements GlobalFilter, Ordered
② 能幹嘛
- 全局日志記錄
- 統一網關鑒權
- .....
③ 案例代碼
package com.janet.springcloud.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;
/**
* @author Janet
* @date 2020/5/12
*/
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("--------------------come in MyLogGateWayFilter: "+new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if(uname == null) {
log.info("------------使用者名為null,非法使用者,o(╥﹏╥)o");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0; //資料越小,優先級越高
}
}
④ 測試
啟動 7001,8001,8002,9527
正确: http://localhost:9527/payment/lb?uname=z3
錯誤:
SpringCloud 10 - SpringCloud config 分布式配置中心