Zuul網關存在的問題:
性能問題
Zuul1x 版本本質上就是一個同步Servlet,采用多線程阻塞模型進行請求轉發。簡單講,每來一個請求,Servlet容器要為該請求分配一個線程專門負責處理這個請求,直到響應返回客戶端這個線程才會被釋放返回容器線程池。如果后台服務調用比較耗時,那么這個線程就會被阻塞,阻塞期間線程資源被占用,不能干其它事情。我們知道Servlet容器線程池的大小是有限制的,當前端請求量大,而后台慢服務比較多時,很容易耗盡容器線程池內的線程,造成容器無法接受新的請求。
不支持任何長連接,如 websocket
Zuul網關的替換方案:
Zuul2.x版本
SpringCloud Gateway
Gateway簡介:
Spring Cloud Gateway 是 Spring 官方基於 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發的網關,旨在為微服務架構提供一種簡單而有效的統一的 API 路由管理方式,統一訪問接口。Spring Cloud Gateway 作為 Spring Cloud 生態系中的網關,目標是替代 Netflix ZUUL,其不僅提供統一的路由方式,並且基於 Filter 鏈的方式提供了網關基本的功能,例如:安全,監控/埋點,和限流等。它是基於Nttey的響應式開發模式。
核心概念:
1. 路由(route) 路由是網關最基礎的部分,路由信息由一個ID、一個目的URL、一組斷言工廠和一組Filter組成。如果斷言為真,則說明請求URL和配置的路由匹配。
2. 斷言(predicates) Java8中的斷言函數,Spring Cloud Gateway中的斷言函數輸入類型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的斷言函數允許開發者去定義匹配來自Http Request中的任何信息,比如請求頭和參數等。
3. 過濾器(filter) 一個標准的Spring webFilter,Spring Cloud Gateway中的Filter分為兩種類型,分別是Gateway Filter和Global Filter。過濾器Filter可以對請求和響應進行處理。
入門案例:
1.創建新模塊gateway_server導入依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
注意 SpringCloud Gateway使用的web框架為webflux,和SpringMVC不兼容。引入的限流組件是hystrix。redis底層不再使用jedis,而是lettuce。
2.配置啟動類
@SpringBootApplication public class GatewayServerApplication { public static void main(String[] args) { SpringApplication.run(GatewayServerApplication.class, args); } }
3.創建 application.yml 配置文件
server:
port: 8080
spring:
application:
name: gateway-server #服務名稱
##路由配置
cloud:
gateway:
routes:
- id: service-product #自定義的路由 ID,保持唯一
uri: http://127.0.0.1:9011 #目標服務地址
#order: 1 #路由優先級,數字越小優先級越高
#路由條件(斷言),Predicate 接收一個輸入參數,返回一個布爾值結果。
#該接口包含多種默認方法來將 Predicate 組合成其他復雜的邏輯(比如:與,或,非)。
predicates:
- Path=/product/**
4.啟動測試,訪問 http://localhost:8080/product/1
路由規則:
Spring Cloud Gateway 的功能很強大,前面我們只是使用了 predicates 進行了簡單的條件匹配,其實Spring Cloud Gataway 幫我們內置了很多 Predicates 功能。在 Spring Cloud Gateway 中 Spring 利用Predicate 的特性實現了各種路由匹配規則,有通過 Header、請求參數等不同的條件來進行作為條件匹配到對應的路由。

#路由斷言之后匹配
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://xxxx.com
predicates:
- After=xxxxx
#路由斷言之前匹配
spring:
cloud:
gateway:
routes:
- id: before_route
uri: https://xxxxxx.com
predicates:
- Before=xxxxxxx
#路由斷言之間
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://xxxx.com
predicates:
- Between=xxxx,xxxx
#路由斷言Cookie匹配,此predicate匹配給定名稱(chocolate)和正則表達式(ch.p)
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://xxxx.com
predicates:
- Cookie=chocolate, ch.p
#路由斷言Header匹配,header名稱匹配X-Request-Id,且正則表達式匹配\d+
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://xxxx.com
predicates:
- Header=X-Request-Id, \d+
#路由斷言匹配Host匹配,匹配下面Host主機列表,**代表可變參數
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://xxxx.com
predicates:
- Host=**.somehost.org,**.anotherhost.org
#路由斷言Method匹配,匹配的是請求的HTTP方法
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://xxxx.com
predicates:
- Method=GET
#路由斷言匹配,{segment}為可變參數
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://xxxx.com
predicates:
- Path=/foo/{segment},/bar/{segment}
#路由斷言Query匹配,將請求的參數param(baz)進行匹配,也可以進行regexp正則表達式匹配 (參數包含foo,並且foo的值匹配ba.)
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://xxxx.com
predicates:
- Query=baz 或 Query=foo,ba.
#路由斷言RemoteAddr匹配,將匹配192.168.1.1~192.168.1.254之間的ip地址,其中24為子網掩碼位
數即255.255.255.0
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
動態路由(面向服務的路由):
和zuul網關類似,在SpringCloud GateWay中也支持動態路由:即自動的從注冊中心中獲取服務列表並訪問。
1.添加注冊中心依賴
在工程的pom文件中添加注冊中心的客戶端依賴(這里以Eureka為例)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
2.配置動態路由
修改 application.yml 配置文件,添加eureka注冊中心的相關配置,並修改訪問映射的URL為服務名稱
##配置eureka eureka: client: serviceUrl: defaultZone: http://127.0.0.1:9000/eureka/ instance: preferIpAddress: true
##路由配置 cloud: gateway: routes: - id: service-product #自定義的路由 ID,保持唯一 #uri: http://127.0.0.1:9011 #目標服務地址 uri: lb://service-product predicates: - Path=/product/**
uri : uri以 lb://開頭(lb代表從注冊中心獲取服務),后面接的就是你需要轉發到的服務名稱
3.重寫轉發路徑:
在SpringCloud Gateway中,路由轉發不同於 Zuul 截取 /** 來拼接URL,而是直接將匹配的路由 path 直接拼接到映射路徑 URI 之后(http://127.0.0.1:8080/product/1 --> http://127.0.0.1:9011/product/1),上面例子直接拼接上去即可訪問是刻意為之,在微服務開發中往往沒有那么便利,就需要通過RewritePath機制來進行路徑重寫。
(1) 案例改造:
修改 application.yml ,將匹配路徑改為 /service-product /**
(2) 添加RewritePath重寫轉發路徑
##路由配置 cloud: gateway: routes: - id: service-product #自定義的路由 ID,保持唯一 #uri: http://127.0.0.1:9011 #目標服務地址 uri: lb://service-product predicates: - Path=/service-product/** filters: #- RewritePath=/service-product/(?<segment>.*), /$\{segment} #方式1 - StripPrefix=1 #方式2:在請求轉發之前去掉一層路徑
4.配置自動根據服務名稱進行路由轉發:此規則和 Zuul 網關默認規則一樣
##路由配置 cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true #微服務名稱以小寫形式呈現
過濾器:
Spring Cloud Gateway除了具備請求路由功能之外,也支持對請求的過濾。和Zuul網關類似,也是通過過濾器的形式來實現的。
過濾器的生命周期:
Spring Cloud Gateway 的 Filter 的生命周期不像 Zuul 的那么豐富,它只有兩個:“pre” 和 “post”。
PRE : 這種過濾器在請求被路由之前調用。我們可利用這種過濾器實現身份驗證、在集群中選擇請求的微服務、記錄調試信息等。
POST :這種過濾器在路由到微服務以后執行。這種過濾器可用來為響應添加標准的 HTTPHeader、收集統計信息和指標、將響應從微服務發送給客戶端等。
局部過濾器:
局部過濾器(GatewayFilter),是針對單個路由的過濾器。可以對訪問的URL過濾,進行切面處理。在Spring Cloud Gateway中通過GatewayFilter的形式內置了很多不同類型的局部過濾器。
gateway內置了很多局部過濾器,使用方式:
全局過濾器:
全局過濾器(GlobalFilter)作用於所有路由,Spring Cloud Gateway 定義了Global Filter接口,用戶可以自定義實現自己的Global Filter。通過全局過濾器可以實現對權限的統一校驗,安全性驗證等功能,並且全局過濾器也是使用比較多的過濾器。
內置的過濾器已經可以完成大部分的功能,但是對於企業開發的一些業務功能處理,還是需要我們自己編寫過濾器來實現的
自定義一個GlobalFilter,去校驗所有請求的請求參數中是否包含“access-token”,如果不包含請求參數“access-token”則不轉發路由,否則執行正常的邏輯。
/** * 自定義一個全局過濾器 * 實現 globalfilter , ordered接口 */ @Component public class LoginFilter implements GlobalFilter,Ordered { /** * 執行過濾器中的業務邏輯 * 對請求參數中的access-token進行判斷 * 如果存在此參數:代表已經認證成功 * 如果不存在此參數 : 認證失敗. * ServerWebExchange : 相當於請求和響應的上下文(zuul中的RequestContext) */ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("執行了自定義的全局過濾器"); // 1.獲取請求參數access-token String token = exchange.getRequest().getQueryParams().getFirst("access-token"); // 2.判斷是否存在 if(token == null) { //3.如果不存在 : 認證失敗 System.out.println("沒有登錄"); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); // 請求結束 } //4.如果存在,繼續執行 return chain.filter(exchange); //繼續向下執行 } /** * 指定過濾器的執行順序 , 返回值越小,執行優先級越高 */ public int getOrder() { return 0; } }
網關限流:
常見的限流算法:
1.計數器
計數器限流算法是最簡單的一種限流實現方式。其本質是通過維護一個單位時間內的計數器,每次請求計數器加1,當單位時間內計數器累加到大於設定的閾值,則之后的請求都被拒絕,直到單位時間已經過去,再將計數器重置為零
2.漏桶算法(保護別人)
漏桶算法可以很好地限制容量池的大小,從而防止流量暴增。漏桶可以看作是一個帶有常量服務時間的單服務器隊列,如果漏桶(包緩存)溢出,那么數據包會被丟棄。 在網絡中,漏桶算法可以控制端口的流量輸出速率,平滑網絡上的突發流量,實現流量整形,從而為網絡提供一個穩定的流量。
為了更好的控制流量,漏桶算法需要通過兩個變量進行控制:一個是桶的大小,支持流量突發增多時可以存多少的水(burst),另一個是水桶漏洞的大小(rate)。
3.令牌桶算法(保護自己)
令牌桶算法是對漏桶算法的一種改進,桶算法能夠限制請求調用的速率,而令牌桶算法能夠在限制調用的平均速率的同時還允許一定程度的突發調用。在令牌桶算法中,存在一個桶,用來存放固定數量的令牌。算法中存在一種機制,以一定的速率往桶中放令牌。每次請求調用需要先獲取令牌,只有拿到令牌,才有機會繼續執行,否則選擇選擇等待可用的令牌、或者直接拒絕。放令牌這個動作是持續不斷的進行,如果桶中令牌數達到上限,就丟棄令牌,所以就存在這種情況,桶中一直有大量的可用令牌,這時進來的請求就可以直接拿到令牌執行,比如設置qps為100,那么限流器初始化完成一秒后,桶中就已經有100個令牌了,這時服務還沒完全啟動好,等啟動完成對外提供服務時,該限流器可以抵擋瞬時的100個請求。所以,只有桶中沒有令牌時,請求才會進行等待,最后相當於以一定的速率執行。
基於Filter的限流:
SpringCloudGateway官方就提供了基於令牌桶的限流支持。基於其內置的過濾器工廠RequestRateLimiterGatewayFilterFactory 實現。在過濾器工廠中是通過Redis和lua腳本結合的方式進行流量控制。
環境搭建:
1.導入 redis的依賴(redis的reactive依賴)和監控依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-actuator</artifactId> </dependency>
2. 修改application.yml配置文件,加入限流的配置
spring:
...省略
##路由配置 cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true #微服務名稱以小寫形式呈現 filters: - name: RequestRateLimiter args: #使用SpEL從容器中獲取對象 key-resolver: '#{@pathKeyResolver}' #令牌桶每秒填充平均速率 redis-rate-limiter.replenishRate: 1 #令牌桶的總容量 redis-rate-limiter.burstCapacity: 3
key-resolver ,用於限流的鍵的解析器的 Bean 對象名字。它使用 SpEL 表達式根據#{@beanName}從 Spring 容器中獲取 Bean 對象。
添加redis的信息:
spring: ...省略 ##配置redis redis: host: localhost port: 6379 database: 0 password: root
3.配置KeyResolver
為了達到不同的限流效果和規則,可以通過實現 KeyResolver 接口,定義不同請求類型的限流鍵。
@Configuration public class KeyResolverConfiguration { /** * 基於請求路徑的限流 */ @Bean public KeyResolver pathKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getPath().toString() ); } /** * 基於請求ip地址的限流 */ @Bean public KeyResolver ipKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getHeaders().getFirst("X-Forwarded-For") ); } /** * 基於用戶的限流 */ @Bean public KeyResolver userKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getQueryParams().getFirst("user") ); } }
Spring Cloud Gateway目前提供的限流還是相對比較簡單的,在實際中我們的限流策略會有很多種情況,比如:
對不同接口的限流
被限流后的友好提示
基於Sentinel的限流:
Sentinel 支持對 Spring Cloud Gateway、Zuul 等主流的 API Gateway 進行限流。
從 1.6.0 版本開始,Sentinel 提供了 Spring Cloud Gateway 的適配模塊,可以提供兩種資源維度的限流:
route 維度:即在 Spring 配置文件中配置的路由條目,資源名為對應的 routeId
自定義 API 維度:用戶可以利用 Sentinel 提供的 API 來自定義一些 API 分組
另外也可以使用 Sentinel 控制台圖形界面方式定義限流規則,更加方便。