Spring cloud gateway 攔截請求404 等HTTP 狀態碼
Spring cloud gateway 修改response 截斷問題,亂碼問題解決
Spring cloud gateway 設置https 和http同時支持
Spring cloud Gateway 指定執行過濾器 (在配置文件中配置所需要過濾器)
目錄
spring cloud gateway 介紹
1. 網關是怎麼演化來的
單體應用拆分成多個服務後,對外需要一個統一入口,解耦客戶端與內部服務
注:圖片來自網絡
2. 網關的基本功能
-
網關核心功能是路由轉發,因此不要有耗時操作在網關上處理,讓請求快速轉發到後端服務上
-
網關還能做統一的熔斷、限流、認證、日誌監控等 注:圖片來自網絡
可以和服務註冊中心完美的整合,如:Eureka、Consul、Nacos
-
3.關於Spring Cloud Gateway
在SpringCloud微服務體系中,有個很重要的組件就是網關,在1.x版本中都是採用的Zuul網關;但在2.x版本中,zuul的升級一直跳票,SpringCloud最後自己研發了一個網關替代Zuul,那就是SpringCloud Gateway
網上很多地方都說Zuul是阻塞的,Gateway是非阻塞的,這麼說是不嚴謹的,準確的講Zuul1.x是阻塞的,而在2.x的版本中,Zuul也是基於Netty,也是非阻塞的,如果一定要說性能,其實這個真沒多大差距。
而官方出過一個測試項目,創建了一個benchmark的測試項目:spring-cloud-gateway-bench,其中對比了:
-
Spring Cloud Gateway
-
Zuul1.x
-
Linkerd
Proxy | Avg Latency | Avg Req/Sec/Thread |
---|---|---|
gateway | 6.61ms | 3.24k |
linkered | 7.62ms | 2.82k |
zuul | 12.56ms | 2.09k |
none | 2.09ms | 11.77k |
還有一點就是Gateway是基於WebFlux的。這裏引出了WebFlux名詞,那什麼是WebFlux?
WebFlux 介紹
注:圖片來自網絡
左側是傳統的基於Servlet的Spring Web MVC框架,
傳統的Web框架,比如說:struts2,springmvc等都是基於Servlet API與Servlet容器基礎之上運行的,在Servlet3.1之後纔有了異步非阻塞的支持。
右側是5.0版本新引入的基於Reactive Streams的Spring WebFlux框架,從上到下依次是Router Functions,WebFlux,Reactive Streams三個新組件。
Router Functions: 對標@Controller,@RequestMapping等標準的Spring MVC註解,提供一套函數式風格的API,用於創建Router,Handler和Filter。
WebFlux: 核心組件,協調上下游各個組件提供響應式編程支持。
Reactive Streams: 一種支持背壓(Backpressure)的異步數據流處理標準,主流實現有RxJava和Reactor,Spring WebFlux默認集成的是Reactor。
在Web容器的選擇上,Spring WebFlux既支持像Tomcat,Jetty這樣的的傳統容器(前提是支持Servlet 3.1 Non-Blocking IO API),又支持像Netty,Undertow那樣的異步容器。不管是何種容器,Spring WebFlux都會將其輸入輸出流適配成Flux<DataBuffer>格式,以便進行統一處理。
值得一提的是,除了新的Router Functions接口,Spring WebFlux同時支持使用老的Spring MVC註解聲明Reactive Controller。和傳統的MVC Controller不同,Reactive Controller操作的是非阻塞的ServerHttpRequest和ServerHttpResponse,而不再是Spring MVC裏的HttpServletRequest和HttpServletResponse。
根據官方的說法,webflux主要在如下兩方面體現出獨有的優勢:
1)非阻塞式
其實在servlet3.1提供了非阻塞的API,WebFlux提供了一種比其更完美的解決方案。使用非阻塞的方式可以利用較小的線程或硬件資源來處理併發進而提高其可伸縮性
2) 函數式編程端點
老生常談的編程方式了,Spring5必須讓你使用java8,那麼函數式編程就是java8重要的特點之一,而WebFlux支持函數式編程來定義路由端點處理請求。
IO模式和IO多路複用(阻塞IO、非阻塞IO、同步IO、異步IO等概念)這裏就不在闡述,因爲不在本分享範圍之內!
4.Spring Cloud Gateway 功能特徵
-
基於Spring Framework 5, Project Reactor 和 Spring Boot 2.0 進行構建;
-
動態路由:能夠匹配任何請求屬性;
-
集成 Spring Cloud 服務發現功能;
-
可以對路由指定 Predicate(斷言)和 Filter(過濾器);
-
易於編寫的 Predicate(斷言)和 Filter(過濾器);
-
集成Hystrix的斷路器功能;
-
請求限流功能;
-
支持路徑重寫。
注:圖片來自網絡
上圖中是核心的流程圖,最主要的就是Route、Predicates 和 Filters 作用於特定路由。
1)Route:**路由是網關的基本構件**。它由ID、目標URI、謂詞集合和過濾器集合定義。如果聚合謂詞爲真,則匹配路由。
2)Predicate:**參照Java8的新特性Predicate**。這允許開發人員匹配HTTP請求中的任何內容,比如頭或參數。
3)Filter:可以在發送下游請求之前或之後修改請求和響應。
我們爲什麼選擇Gateway?
一方面因爲Zuul已經進入了維護階段,而且Gateway是SpringCloud團隊研發的,是親兒子產品,值得信賴。而且很多功能Zuul都沒有;用起來也非常的簡單便捷。
Gateway是基於異步非阻塞模型上進行開發的,性能方面不需要擔心。雖然Netflix 早就發佈了最新的 Zuul 2.x,但 Spring Cloud 貌似沒有整合計劃。而且Netflix相關組件都宣佈進入維護期;不知前景如何?
多方面綜合考慮Gateway是很理想的網關選擇。
Spring Cloud Gateway 工作原理
注:該圖片來自官網
客戶端向 Spring Cloud Gateway 發出請求。然後在 Gateway Handler Mapping 中找到與請求相匹配的路由,將其發送到 Gateway Web Handler。Handler 再通過指 定的過濾器鏈來將請求發送到我們實際的服務執行業務邏輯,然後返回。過濾器之間用虛線分開是因爲過濾器可能會在發送代理請求之前(“pre”)或之後(“post”)執行業務邏輯。
Filter在“pre”類型的過濾器可以做參數校驗、權限校驗、流量監控、日誌輸出、協議轉換等,
在“post”類型的過濾器中可以做響應內容、響應頭的修改,日誌的輸出,流量監控等有着非常重要的作用。
核心邏輯就是路由轉發,執行過濾器鏈。
在上面的處理過程中,有一個重要的點就是講請求和路由進行匹配,這時候就需要用到predicate,它是決定了一個請求走哪一個路由。
5.predicate簡介
Predicate來自於java8的接口。Predicate接受一個輸入參數,返回一個布爾值結果。該接口包含多種默認方法來將Predicate組合成其他複雜的邏輯(比如:與,或,非)。可以用於接口請求參數校驗、判斷新老數據是否有變化需要進行更新操作。add--與、or--或、negate--非。
Spring Cloud Gateway內置了許多Predict,這些Predict的源碼在org.springframework.cloud.gateway.handler.predicate包中,有興趣可以閱讀一下。現在列舉各種Predicate如下圖:
注:圖片來自網絡
在上圖中,有很多類型的Predicate,比如說時間類型的Predicated(AfterRoutePredicateFactory BeforeRoutePredicateFactory BetweenRoutePredicateFactory),當只有滿足特定時間要求的請求會進入到此predicate中,並交由router處理;cookie類型的CookieRoutePredicateFactory,指定的cookie滿足正則匹配,纔會進入此router;以及host、method、path、querparam、remoteaddr類型的predicate,每一種predicate都會對當前的客戶端請求進行判斷,是否滿足當前的要求,如果滿足則交給當前請求處理。如果有很多個Predicate,並且一個請求滿足多個Predicate,則按照配置的順序第一個生效。
1. After Route Predicate Factory
After Route Predicate Factory使用的是時間作爲匹配規則,只要當前時間大於設定時間,路由纔會匹配請求。 application.yml:
spring:
cloud:
gateway:
routes:
- id: after_route
uri: http://www.google.com
predicates:
- After=2018-12-25T14:33:47.789+08:00
這個路由規則會在東8區的2018-12-25 14:33:47後,將請求都轉跳到google。
2. Before Route Predicate Factory
Before Route Predicate Factory也是使用時間作爲匹配規則,只要當前時間小於設定時間,路由纔會匹配請求。 application.yml:
spring:
cloud:
gateway:
routes:
- id: before_route
uri: http://www.google.com
predicates:
- Before=2018-12-25T14:33:47.789+08:00
這個路由規則會在東8區的2018-12-25 14:33:47前,將請求都轉跳到google。
3. Between Route Predicate Factory
Between Route Predicate Factory也是使用兩個時間作爲匹配規則,只要當前時間大於第一個設定時間,並小於第二個設定時間,路由纔會匹配請求。
application.yml:
spring:
cloud:
gateway:
routes:
- id: between_route
uri: http://www.google.com
predicates:
- Between=2018-12-25T14:33:47.789+08:00, 2018-12-26T14:33:47.789+08:00
這個路由規則會在東8區的2018-12-25 14:33:47到2018-12-26 14:33:47之間,將請求都轉跳到google。
4. Cookie Route Predicate Factory
Cookie Route Predicate Factory使用的是cookie名字和正則表達式的value作爲兩個輸入參數,請求的cookie需要匹配cookie名和符合其中value的正則。 application.yml:
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: http://www.google.com
predicates:
- Cookie=cookiename, cookievalue
路由匹配請求存在cookie名爲cookiename,cookie內容匹配cookievalue的,將請求轉發到google。
5. Header Route Predicate Factory
Header Route Predicate Factory,與Cookie Route Predicate Factory類似,也是兩個參數,一個header的name,一個是正則匹配的value。 application.yml:
spring:
cloud:
gateway:
routes:
- id: header_route
uri: http://www.google.com
predicates:
- Header=X-Request-Id, \d+
路由匹配存在名爲X-Request-Id
,內容爲數字的header的請求,將請求轉發到google。
6. Host Route Predicate Factory
Host Route Predicate Factory使用的是host的列表作爲參數,host使用Ant style匹配。 application.yml:
spring:
cloud:
gateway:
routes:
- id: host_route
uri: http://www.google.com
predicates:
- Host=**.somehost.org,**.anotherhost.org
路由會匹配Host諸如:www.somehost.org
或 beta.somehost.org
或www.anotherhost.org
等請求。
7. Method Route Predicate Factory
Method Route Predicate Factory是通過HTTP的method來匹配路由。 application.yml:
spring:
cloud:
gateway:
routes:
- id: method_route
uri: http://www.google.com
predicates:
- Method=GET
路由會匹配到所有GET方法的請求。
8. Path Route Predicate Factory
Path Route Predicate Factory使用的是path列表作爲參數,使用Spring的PathMatcher
匹配path,可以設置可選變量。 application.yml:
spring:
cloud:
gateway:
routes:
- id: host_route
uri: http://www.google.com
predicates:
- Path=/foo/{segment},/bar/{segment}
上面路由可以匹配諸如:/foo/1
或 /foo/bar
或 /bar/baz
等 其中的segment變量可以通過下面方式獲取:
PathMatchInfo variables = exchange.getAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE);
Map<String, String> uriVariables = variables.getUriVariables();
String segment = uriVariables.get("segment");
在後續的GatewayFilter Factories就可以做對應的操作了。
9. Query Route Predicate Factory
Query Route Predicate Factory可以通過一個或兩個參數來匹配路由,一個是查詢的name,一個是查詢的正則value。 application.yml:
spring:
cloud:
gateway:
routes:
- id: query_route
uri: http://www.google.com
predicates:
- Query=baz
路由會匹配所有包含baz
查詢參數的請求。 application.yml:
spring:
cloud:
gateway:
routes:
- id: query_route
uri: http://www.google.com
predicates:
- Query=foo, ba.
路由會匹配所有包含foo
,並且foo
的內容爲諸如:bar
或baz
等符合ba.
正則規則的請求。
10. RemoteAddr Route Predicate Factory
RemoteAddr Route Predicate Factory通過無類別域間路由(IPv4 or IPv6)列表匹配路由。 application.yml:
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: http://www.google.com
predicates:
- RemoteAddr=192.168.1.1/24
上面路由就會匹配RemoteAddr諸如192.168.1.10
等請求。
10.1 Modifying the way remote addresses are resolved
RemoteAddr Route Predicate Factory默認情況下,使用的是請求的remote address。但是如果Spring Cloud Gateway是部署在其他的代理後面的,如Nginx,則Spring Cloud Gateway獲取請求的remote address是其他代理的ip,而不是真實客戶端的ip。
考慮到這種情況,你可以自定義獲取remote address的處理器RemoteAddressResolver
。當然Spring Cloud Gateway也提供了基於X-Forwarded-For請求頭的XForwardedRemoteAddressResolver
。 熟悉Http代理協議的,都知道X-Forwarded-For頭信息做什麼的,不熟悉的可以自己谷歌瞭解一下。
XForwardedRemoteAddressResolver
提供了兩個靜態方法獲取它的實例: XForwardedRemoteAddressResolver::trustAll
得到的RemoteAddressResolver
總是獲取X-Forwarded-For的第一個ip地址作爲remote address,這種方式就比較容易被僞裝的請求欺騙,模擬請求很容易通過設置初始的X-Forwarded-For
頭信息,就可以欺騙到gateway。
XForwardedRemoteAddressResolver::maxTrustedIndex
得到的RemoteAddressResolver
則會在X-Forwarded-For
信息裏面,從右到左選擇信任最多maxTrustedIndex
個ip,因爲X-Forwarded-For
是越往右是越接近gateway的代理機器ip,所以是越往右的ip,信任度是越高的。 那麼如果前面只是擋了一層Nginx的話,如果只需要Nginx前面客戶端的ip,則maxTrustedIndex
取1,就可以比較安全地獲取真實客戶端ip。
使用java的配置: GatewayConfig.java:
RemoteAddressResolver resolver = XForwardedRemoteAddressResolver.maxTrustedIndex(1);
...
.route("direct-route", r -> r.remoteAddr("10.1.1.1", "10.10.1.1/24") .uri("http://www.google.com")
.route("proxied-route",r -> r.remoteAddr(resolver, "10.10.1.1", "10.10.1.1/24") .uri("http://www.google.com"))
6.GatewayFilter 工廠介紹
Route filters可以通過一些方式修改HTTP請求的輸入和輸出,針對某些特殊的場景,Spring Cloud Gateway已經內置了很多不同功能的GatewayFilter Factories。
下面就來通過例子逐一講解這些GatewayFilter Factories。
1. AddRequestHeader GatewayFilter Factory
AddRequestHeader GatewayFilter Factory通過配置name和value可以增加請求的header。 application.yml:
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: http://www.google.com
filters:
- AddRequestHeader=X-Request-Foo, Bar
對匹配的請求,會額外添加X-Request-Foo:Bar
的header。
2. AddRequestParameter GatewayFilter Factory
AddRequestParameter GatewayFilter Factory通過配置name和value可以增加請求的參數。 application.yml:
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: http://www.google.com
filters:
- AddRequestParameter=foo, bar
對匹配的請求,會額外添加foo=bar
的請求參數。
3. AddResponseHeader GatewayFilter Factory
AddResponseHeader GatewayFilter Factory通過配置name和value可以增加響應的header。 application.yml:
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: http://www.google.com
filters:
- AddResponseHeader=X-Response-Foo, Bar
對匹配的請求,響應返回時會額外添加X-Response-Foo:Bar
的header返回。
4. Hystrix GatewayFilter Factory
Hystrix是Netflix實現的斷路器模式工具包,The Hystrix GatewayFilter就是將斷路器使用在gateway的路由上,目的是保護你的服務避免級聯故障,以及在下游失敗時可以降級返回。
項目裏面引入spring-cloud-starter-netflix-hystrix
依賴,並提供HystrixCommand
的名字,即可生效Hystrix GatewayFilter。 application.yml:
spring:
cloud:
gateway:
routes:
- id: hystrix_route
uri: http://www.google.com
filters:
- Hystrix=myCommandName
那麼剩下的過濾器,就會包裝在名爲myCommandName
的HystrixCommand中運行。
Hystrix過濾器也是通過配置可以參數fallbackUri
,來支持路由熔斷後的降級處理,降級後,請求會跳過fallbackUri
配置的路徑,目前只支持forward:
的URI協議。 application.yml:
spring:
cloud:
gateway:
routes:
- id: hystrix_route
uri: lb://backing-service:8088
predicates:
- Path=/consumingserviceendpoint
filters:
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/incaseoffailureusethis
當Hystrix降級後就會將請求轉發到/incaseoffailureusethis
。
整個流程其實是用fallbackUri
將請求跳轉到gateway內部的controller或者handler,然而也可以通過以下的方式將請求轉發到外部的服務: application.yml:
spring:
cloud:
gateway:
routes:
- id: ingredients
uri: lb://ingredients
predicates:
- Path=//ingredients/**
filters:
- name: Hystrix
args:
name: fetchIngredients
fallbackUri: forward:/fallback
- id: ingredients-fallback
uri: http://localhost:9994
predicates:
- Path=/fallback
以上的例子,gateway降級後就會將請求轉發到http://localhost:9994
。
Hystrix Gateway filter在轉發降級請求時,會將造成降級的異常設置在ServerWebExchangeUtils.HYSTRIX_EXECUTION_EXCEPTION_ATTR
屬性中,在處理降級時也可以用到。
比如下一節講到的FallbackHeaders GatewayFilter Factory,就會通過上面的方式拿到異常信息,設置到降級轉發請求的header上,來告知降級下游異常信息。
通過下面配置可以設置Hystrix的全局超時信息: application.yml:
hystrix.command.fallbackcmd.execution.isolation.thread.timeoutInMilliseconds: 5000
5. FallbackHeaders GatewayFilter Factory
FallbackHeaders GatewayFilter Factory可以將Hystrix執行的異常信息添加到外部請求的fallbackUri
header上。 application.yml:
spring:
cloud:
gateway:
routes:
- id: ingredients
uri: lb://ingredients
predicates:
- Path=//ingredients/**
filters:
- name: Hystrix
args:
name: fetchIngredients
fallbackUri: forward:/fallback
- id: ingredients-fallback
uri: http://localhost:9994
predicates:
- Path=/fallback
filters:
- name: FallbackHeaders
args:
executionExceptionTypeHeaderName: Test-Header
在這個例子中,當請求lb://ingredients
降級後,FallbackHeaders
filter會將HystrixCommand
的異常信息,通過Test-Header
帶給http://localhost:9994
服務。
你也可以使用默認的header,也可以像上面一下配置修改header的名字:
-
executionExceptionTypeHeaderName
("Execution-Exception-Type"
) -
executionExceptionMessageHeaderName
("Execution-Exception-Message"
) -
rootCauseExceptionTypeHeaderName
("Root-Cause-Exception-Type"
) -
rootCauseExceptionMessageHeaderName
("Root-Cause-Exception-Message"
)
6. PrefixPath GatewayFilter Factory
The PrefixPath GatewayFilter Factor通過設置prefix
參數來路徑前綴。 application.yml:
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: http://www.google.com
filters:
- PrefixPath=/mypath
如果一個請求是/hello
,通過上面路由,就會將請求修改爲/mypath/hello
。
7. PreserveHostHeader GatewayFilter Factory
PreserveHostHeader GatewayFilter Factory會保留原始請求的host
頭信息,並原封不動的轉發出去,而不是被gateway的http客戶端重置。
application.yml:
spring:
cloud:
gateway:
routes:
- id: preserve_host_route
uri: http://www.google.com
filters:
- PreserveHostHeader
8. RequestRateLimiter GatewayFilter Factory
RequestRateLimiter GatewayFilter Factory使用RateLimiter
來決定當前請求是否允許通過,如果不允許,則默認返回狀態碼HTTP 429 - Too Many Requests
。
RequestRateLimiter GatewayFilter可以使用一個可選參數keyResolver
來做速率限制。
keyResolver
是KeyResolver
接口的一個實現bean,在配置裏面,通過SpEL表達式#{@myKeyResolver}
來管理bean的名字myKeyResolver
。
KeyResolver.java.
public interface KeyResolver {
Mono<String> resolve(ServerWebExchange exchange);
}
KeyResolver
接口允許你使用不同的策略來得出限制請求的key,未來,官方也會推出一些KeyResolver
的不同實現。
KeyResolver
默認實現是PrincipalNameKeyResolver
,通過ServerWebExchange
中獲取Principal
,並以Principal.getName()
作爲限流的key。
如果KeyResolver
拿不到key,請求默認都會被限制,你也可以自己配置spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key
:是否允許空key,spring.cloud.gateway.filter.request-rate-limiter.empty-key-status-code
:空key時返回的狀態碼。
RequestRateLimiter不支持捷徑配置,如下面的配置是非法的
application.properties.
# INVALID SHORTCUT CONFIGURATION
spring.cloud.gateway.routes[0].filters[0]=RequestRateLimiter=2, 2, #{@userkeyresolver}
8.1 Redis RateLimiter
基於 Stripe的redis實現方案,依賴spring-boot-starter-data-redis-reactive
Spring Boot starter,使用的是令牌桶算法。
redis-rate-limiter.replenishRate
配置的是每秒允許通過的請求數,其實就是令牌桶的填充速率。
redis-rate-limiter.burstCapacity
配置的是一秒內最大的請求數,其實就是令牌桶的最大容量,如果設置爲0,則會阻塞所有請求。
所以可以通過設置相同的replenishRate
和burstCapacity
來實現勻速的速率控制,通過設置burstCapacity
大於replenishRate
來允許系統流量瞬間突發,但是對於這種情況,突發週期爲burstCapacity / replenishRate
秒,如果週期內有兩次請求突發的情況,則第二次會有部分請求丟失,返回HTTP 429 - Too Many Requests
。
application.yml.
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: http://www.google.com
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
Config.java.
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}
上面定義了每個用戶每秒10個請求的速率限制,允許20的突發流量,突發完,下一秒只允許10個請求通過了,KeyResolver
定義了通過請求獲取請求參數user
作爲key。
你也可以實現RateLimiter
接口自定義自己的請求速率限制器,在配置文件中使用SpEL表達式配置對應的bean的名字即可。
application.yml.
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: http://www.google.com
filters:
- name: RequestRateLimiter
args:
rate-limiter: "#{@myRateLimiter}"
key-resolver: "#{@userKeyResolver}"
在最後有對請求限流具體介紹
9. RedirectTo GatewayFilter Factory
RedirectTo GatewayFilter Factory使用status
和url
兩個參數,其中status
必須是300系列的HTTP狀態碼,url
則是跳轉的地址,會放在響應的Location
的header中(http協議中轉跳的header)。
application.yml.
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: http://www.google.cn
filters:
- RedirectTo=302, http://www.edjdhbb.com
上面路由會執行302重定向到http://www.edjdhbb.com。
10. RemoveNonProxyHeaders GatewayFilter Factory
RemoveNonProxyHeaders GatewayFilter Factory轉發請求是會根據IETF的定義,默認會移除下列的http頭信息:
-
Connection
-
Keep-Alive
-
Proxy-Authenticate
-
Proxy-Authorization
-
TE
-
Trailer
-
Transfer-Encoding
-
Upgrade
你也可以通過配置spring.cloud.gateway.filter.remove-non-proxy-headers.headers
來更改需要移除的header列表。
11. RemoveRequestHeader GatewayFilter Factory
RemoveRequestHeader GatewayFilter Factory配置header的name,即可以移除請求的header。
application.yml.
spring:
cloud:
gateway:
routes:
- id: removerequestheader_route
uri: http://www.google.com
filters:
- RemoveRequestHeader=X-Request-Foo
上面路由在發送請求給下游時,會將請求中的X-Request-Foo
頭信息去掉。
12. RemoveResponseHeader GatewayFilter Factory
RemoveResponseHeader GatewayFilter Factory通過配置header的name,會在響應返回時移除header。
application.yml.
spring:
cloud:
gateway:
routes:
- id: removeresponseheader_route
uri: http://www.google.com
filters:
- RemoveResponseHeader=X-Response-Foo
上面路由會在響應返回給gateway的客戶端時,將X-Response-Foo
響應頭信息去掉。
13. RewritePath GatewayFilter Factory
RewritePath GatewayFilter Factory使用路徑regexp
和替換路徑replacement
兩個參數做路徑重寫,兩個都可以靈活地使用java的正則表達式。
application.yml.
spring:
cloud:
gateway:
routes:
- id: rewritepath_route
uri: http://www.google.com
predicates:
- Path=/foo/**
filters:
- RewritePath=/foo/(?<segment>.*), /$\{segment}
對於上面的例子,如果請求的路徑是/foo/bar
,則gateway會將請求路徑改爲/bar
發送給下游。
注:在YAML 的格式中使用
$\
來代替$
。
14. RewriteResponseHeader GatewayFilter Factory
RewriteResponseHeader GatewayFilter Factory的作用是修改響應返回的header內容,需要配置響應返回的header的name
,匹配規則regexp
和替換詞replacement
,也是支持java的正則表達式。
application.yml.
spring:
cloud:
gateway:
routes:
- id: rewriteresponseheader_route
uri: http://www.google.com
filters:
- RewriteResponseHeader=X-Response-Foo, , password=[^&]+, password=***
舉個例子,對於上面的filter,如果響應的headerX-Response-Foo
的內容是/42?user=ford&password=omg!what&flag=true
,這個內容會修改爲/42?user=ford&password=***&flag=true
。
15. SaveSession GatewayFilter Factory
SaveSession GatewayFilter Factory會在請求下游時強制執行WebSession::save
方法,用在那種像Spring Session
延遲數據存儲的,並在請求轉發前確保session狀態保存情況。
application.yml.
spring:
cloud:
gateway:
routes:
- id: save_session
uri: http://www.google.com
predicates:
- Path=/foo/**
filters:
- SaveSession
如果你將Spring Secutiry
於Spring Session
集成使用,並想確保安全信息都傳到下游機器,你就需要配置這個filter。
16. SecureHeaders GatewayFilter Factory
SecureHeaders GatewayFilter Factory會添加在返回響應中一系列安全作用的header,至於爲什麼,英文好的可以看一下這篇博客。
默認會添加這些頭信息和默認內容:
-
X-Xss-Protection:1; mode=block
-
Strict-Transport-Security:max-age=631138519
-
X-Frame-Options:DENY
-
X-Content-Type-Options:nosniff
-
Referrer-Policy:no-referrer
-
Content-Security-Policy:default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'
-
X-Download-Options:noopen
-
X-Permitted-Cross-Domain-Policies:none
如果你想修改這些頭信息的默認內容,可以在配置文件中添加下面的配置:
前綴:spring.cloud.gateway.filter.secure-headers
上面的header對應的後綴:
-
xss-protection-header
-
strict-transport-security
-
frame-options
-
content-type-options
-
referrer-policy
-
content-security-policy
-
download-options
-
permitted-cross-domain-policies
前後綴接起來即可,如:spring.cloud.gateway.filter.secure-headers.xss-protection-header
17. SetPath GatewayFilter Factory
SetPath GatewayFilter Factory採用路徑template
參數,通過請求路徑的片段的模板化,來達到操作修改路徑的母的,運行多個路徑片段模板化。
application.yml.
spring:
cloud:
gateway:
routes:
- id: setpath_route
uri: http://www.google.com
predicates:
- Path=/foo/{segment}
filters:
- SetPath=/{segment}
對於上面的例子,如果路徑是/foo/bar
,則對於下游的請求路徑會修改爲/bar
。
18. SetResponseHeader GatewayFilter Factory
SetResponseHeader GatewayFilter Factory通過設置name
和value
來替換響應對於的header。
application.yml.
spring:
cloud:
gateway:
routes:
- id: setresponseheader_route
uri: http://www.google.com
filters:
- SetResponseHeader=X-Response-Foo, Bar
對於上面的例子,如果下游的返回帶有頭信息爲X-Response-Foo:1234
,則會gateway會替換爲X-Response-Foo:Bar
,在返回給客戶端。
19. SetStatus GatewayFilter Factory
SetStatus GatewayFilter Factory通過配置有效的Spring HttpStatus
枚舉參數,可以是類似於404的這些數字,也可以是枚舉的name字符串,來修改響應的返回碼。
application.yml.
spring:
cloud:
gateway:
routes:
- id: setresponseheader_route
uri: http://www.google.com
filters:
- SetResponseHeader=X-Response-Foo, Bar
上面例子中,兩種路由都會將響應的狀態碼設置爲401。
20. StripPrefix GatewayFilter Factory
StripPrefix GatewayFilter Factory通過配置parts
來表示截斷路徑前綴的數量。
application.yml.
spring:
cloud:
gateway:
routes:
- id: nameRoot
uri: http://nameservice
predicates:
- Path=/name/**
filters:
- StripPrefix=2
如上面例子中,如果請求的路徑爲/name/bar/foo
,則路徑會修改爲/foo
,即將路徑的兩個前綴去掉了。
21. Retry GatewayFilter Factory
Retry GatewayFilter Factory可以配置針對不同的響應做請求重試,可以配置如下參數:
-
retries
: 重試次數 -
statuses
: 需要重試的狀態碼,需要根據枚舉org.springframework.http.HttpStatus
來配置 -
methods
: 需要重試的請求方法,需要根據枚舉org.springframework.http.HttpMethod
來配置 -
series
: HTTP狀態碼系列,詳情見枚舉org.springframework.http.HttpStatus.Series
application.yml.
spring:
cloud:
gateway:
routes:
- id: retry_test
uri: http://localhost:8080/flakey
predicates:
- Host=*.retry.com
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY
上面例子,當下遊服務返回502狀態碼時,gateway會重試3次。
22. RequestSize GatewayFilter Factory
RequestSize GatewayFilter Factory會限制客戶端請求包的大小,通過參數RequestSize
來配置最大上傳大小,單位字節。
application.yml.
spring:
cloud:
gateway:
routes:
- id: request_size_route
uri: http://localhost:8080/upload
predicates:
- Path=/upload
filters:
- name: RequestSize
args:
maxSize: 5000000
如果請求大小超過5000kb限制,則會返回狀態碼413 Payload Too Large
。
如果不設置這個filter,默認限制5M的請求大小。
23. Modify Request Body GatewayFilter Factory
官方說這個filter目前只是beta版本,API以後可能會修改。
Modify Request Body GatewayFilter Factory可以修改請求體內容,這個只能通過java來配置。
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rewrite_request_obj", r -> r.host("*.rewriterequestobj.org")
.filters(f -> f.prefixPath("/httpbin")
.modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE,
(exchange, s) -> return Mono.just(new Hello(s.toUpperCase())))).uri(uri))
.build();
}
static class Hello {
String message;
public Hello() { }
public Hello(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
24. Modify Response Body GatewayFilter Factory
官方說這個filter目前只是beta版本,API以後可能會修改。
Modify Response Body GatewayFilter Factory用於修改響應返回的內容,同樣只能通過java配置。
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rewrite_response_upper", r -> r.host("*.rewriteresponseupper.org")
.filters(f -> f.prefixPath("/httpbin")
.modifyResponseBody(String.class, String.class,
(exchange, s) -> Mono.just(s.toUpperCase()))).uri(uri)
.build();
}
7.集成Hystrix的斷路器功能
分佈式系統環境下,服務間類似依賴非常常見,一個業務調用通常依賴多個基礎服務。如下圖,對於同步調用,當庫存服務不可用時,商品服務請求線程被阻塞,當有大批量請求調用庫存服務時,最終可能導致整個商品服務資源耗盡,無法繼續對外提供服務。並且這種不可用可能沿請求調用鏈向上傳遞,這種現象被稱爲雪崩效應。
雪崩效應常見場景
-
硬件故障:如服務器宕機,機房斷電,光纖被挖斷等。
-
流量激增:如異常流量,重試加大流量等。
-
緩存穿透:一般發生在應用重啓,所有緩存失效時,以及短時間內大量緩存失效時。大量的緩存不命中,使請求直擊後端服務,造成服務提供者超負荷運行,引起服務不可用。
-
程序BUG:如程序邏輯導致內存泄漏,JVM長時間FullGC等。
-
同步等待:服務間採用同步調用模式,同步等待造成的資源耗盡。
雪崩效應應對策略
針對造成雪崩效應的不同場景,可以使用不同的應對策略,沒有一種通用所有場景的策略,參考如下:
-
硬件故障:多機房容災、異地多活等。
-
流量激增:服務自動擴容、流量控制(限流、關閉重試)等。
-
緩存穿透:緩存預加載、緩存異步加載等。
-
程序BUG:修改程序bug、及時釋放資源等。
-
同步等待:資源隔離、MQ解耦、不可用服務調用快速失敗等。資源隔離通常指不同服務調用採用不同的線程池;不可用服務調用快速失敗一般通過熔斷器模式結合超時機制實現。
綜上所述,如果一個應用不能對來自依賴的故障進行隔離,那該應用本身就處在被拖垮的風險中。 因此,爲了構建穩定、可靠的分佈式系統,我們的服務應當具有自我保護能力,當依賴服務不可用時,當前服務啓動自我保護功能,從而避免發生雪崩效應。本文將重點介紹使用Hystrix解決同步等待的雪崩問題。
斷路器工作原理
服務端的服務降級邏輯會因爲hystrix命令調用依賴服務超時而觸發,也就是說調用服務超時會進入斷路回調邏輯處理。但是即使這樣,受限於Hystrix超時時間的問題,調用依然會有可能產生堆積。
這個時候斷路器就會發揮作用。這裏涉及到斷路器的三個重要參數:
快照時間窗
斷路器確定是否打開需要統計一些請求和錯誤數據,而統計的時間範圍就是快照時間窗,默認爲最近的10秒。
請求總數下限
在快照時間窗內,必須滿足請求總數下限纔有資格熔斷。默認爲20,意味着在10秒內,如果該hystrix命令的調用次數不足20,即使所有的請求都超時或者其他原因失敗,斷路器都不會打開。
錯誤百分比下限
當請求總數在快照時間窗口內超過了下限,比如發生了30次調用,如果在這30次調用中有16次發生了超時異常,也就是超過了50%錯誤百分比,在默認設定50%下限情況下,這時候就會將斷路器打開。
因此,斷路器打開的條件是:在時間快照窗口期(默認爲10s)內,至少發生20次服務調用,並且服務調用錯誤率超過50%。
不滿足條件時斷路器並不會打開,服務調用錯誤只會觸發服務降級,也就是調用fallback函數,每個請求時間延遲就是近似hystrix的超時時間。如果將超時時間設置爲5秒,那麼每個請求都要延遲5每秒纔會返回。當斷路器在10秒內發現請求總數超過20並且錯誤率超過50%,這時候斷路器會打開。之後再有請求調用的時候,將不會調用主邏輯,而是直接調用降級邏輯,這個時候就不會等待5秒之後纔會返回fallback。通過斷路器實現自動發現錯誤並將降級邏輯切換爲主邏輯,減少響應延遲的效果。
在斷路器打開之後,處理邏輯並沒有結束,此時降級邏輯已經被切換爲主邏輯了,那麼原來的主邏輯要如何恢復呢?實際上hystrix也實現了這一點:當斷路器打開,對主邏輯進行熔斷之後,hystrix會啓動一個休眠時間窗,在這個時間窗內,降級邏輯是臨時的主邏輯,當休眠時間窗到期,斷路器將進入半開狀態,釋放一次請求到原來的主邏輯,如果此次請求正常返回,那麼斷路器將進行閉合,主邏輯恢復,如果這次請求依然有問題,斷路器繼續進入打開狀態,休眠時間窗重新計時。
換句話說,斷路器每隔一段時間進行一次重試,看看原來的主邏輯是否可用,可用就關閉,不可用就繼續打開。
通過上面的機制,hystrix的斷路器實現了對依賴資源故障的處理,對降級策略的自動切換以及對主邏輯的自動恢復。這使得我們的微服務在依賴外部服務或資源的時候得到了非常好的保護,同時對於一些具備降級邏輯的業務需求可以實現自動化的切換和恢復,相比於設置開關由監控和運維來進行切換的傳統實現方式顯得更爲智能和高效。
集成spring cloud gateway
maven依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
yml配置文件配置
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
default-filters:
- StripPrefix=1
- name: Hystrix
args:
name: fallback
fallbackUri: forward:/fallback
routes:
#基礎架構服務
- id: orgnization-wx
uri: lb:http://orgnization-wx
predicates:
- Path=/structure/**
fallback 實例
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 盪漾
* @title: PermissionFiler
* @projectName cmp
* @description: TODO
* @date 2019/10/25 10:24
*/
@RestController
@RequestMapping("/fallback")
public class FallbackController {
@RequestMapping("")
public ResponseEntity fallback(){
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("服務暫不可用!!!!");
}
}
8.請求限流
在高併發的系統中,往往需要在系統中做限流,一方面是爲了防止大量的請求使服務器過載,導致服務不可用,另一方面是爲了防止網絡攻擊。
一般開發高併發系統常見的限流有:限制總併發數(比如數據庫連接池、線程池)、限制瞬時併發數(如 nginx 的 limit_conn 模塊,用來限制瞬時併發連接數)、限制時間窗口內的平均速率(如 Guava 的 RateLimiter、nginx 的 limit_req 模塊,限制每秒的平均速率);其他還有如限制遠程接口調用速率、限制 MQ 的消費速率。另外還可以根據網絡連接數、網絡流量、CPU 或內存負載等來限流。
限流算法
計數器
簡單的做法是維護一個單位時間內的 計數器,每次請求計數器加1,當單位時間內計數器累加到大於設定的閾值,則之後的請求都被拒絕,直到單位時間已經過去,再將 計數器 重置爲零。此方式有個弊端:如果在單位時間1s內允許100個請求,在10ms已經通過了100個請求,那後面的990ms,只能眼巴巴的把請求拒絕,我們把這種現象稱爲“突刺現象”。
常用的更平滑的限流算法有兩種:漏桶算法 和 令牌桶算法。下面介紹下二者。
漏桶算法
漏桶算法思路很簡單,水(請求)先進入到漏桶裏,漏桶以一定的速度出水(接口有響應速率),當水流入速度過大會直接溢出(訪問頻率超過接口響應速率),然後就拒絕請求,可以看出漏桶算法能強行限制數據的傳輸速率。
可見這裏有兩個變量,一個是桶的大小,支持流量突發增多時可以存多少的水(burst),另一個是水桶漏洞的大小(rate)。因爲漏桶的漏出速率是固定的參數,所以,即使網絡中不存在資源衝突(沒有發生擁塞),漏桶算法也不能使流突發(burst)到端口速率。因此,漏桶算法對於存在突發特性的流量來說缺乏效率。
令牌桶算法
令牌桶算法 和漏桶算法 效果一樣但方向相反的算法,更加容易理解。隨着時間流逝,系統會按恆定 1/QPS 時間間隔(如果 QPS=100,則間隔是 10ms)往桶裏加入 Token(想象和漏洞漏水相反,有個水龍頭在不斷的加水),如果桶已經滿了就不再加了。新請求來臨時,會各自拿走一個 Token,如果沒有 Token 可拿了就阻塞或者拒絕服務。
令牌桶的另外一個好處是可以方便的改變速度。一旦需要提高速率,則按需提高放入桶中的令牌的速率。一般會定時(比如 100 毫秒)往桶中增加一定數量的令牌,有些變種算法則實時的計算應該增加的令牌的數量。
限流實現
在 Spring Cloud Gateway 上實現限流是個不錯的選擇,只需要編寫一個過濾器就可以了。有了前邊過濾器的基礎,寫起來很輕鬆。
Spring Cloud Gateway 已經內置了一個RequestRateLimiterGatewayFilterFactory,我們可以直接使用。
目前RequestRateLimiterGatewayFilterFactory的實現依賴於 Redis,所以我們還要引入spring-boot-starter-data-redis-reactive。
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifatId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
application.yml
server:
port: 8080
spring:
cloud:
gateway:
routes:
- id: limit_route
uri: http://httpbin.org:80/get
predicates:
- After=2019-02-26T00:00:00+08:00[Asia/Shanghai]
filters:
- name: RequestRateLimiter
args:
key-resolver: '#{@hostAddrKeyResolver}'
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 3
application:
name: gateway-limiter
redis:
host: localhost
port: 6379
database: 0
在上面的配置文件,配置了 redis的信息,並配置了RequestRateLimiter的限流過濾器,該過濾器需要配置三個參數:
-
burstCapacity:令牌桶總容量。
-
replenishRate:令牌桶每秒填充平均速率。
-
key-resolver:用於限流的鍵的解析器的 Bean 對象的名字。它使用 SpEL 表達式根據#{@beanName}從 Spring 容器中獲取 Bean 對象。
IP限流
獲取請求用戶ip作爲限流key。
@Bean
public KeyResolver hostAddrKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
用戶限流
獲取請求用戶id作爲限流key。
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
接口限流
獲取請求地址的uri作爲限流key。
@Bean
KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
完