[TOC]
Spring Cloud Gateway監(jiān)控端點(diǎn)
說到監(jiān)控萧朝,就應(yīng)該能想到Spring Boot Actuator白筹。而Spring Cloud Gateway基于Actuator提供了許多的監(jiān)控端點(diǎn)徒河。只需要在項(xiàng)目中添加spring-boot-starter-actuator
依賴,并將 gateway
端點(diǎn)暴露棒厘,即可獲得若干監(jiān)控端點(diǎn)。配置示例:
management:
endpoints:
web:
exposure:
include: gateway # 或者配置“*”暴露全部端點(diǎn)
Spring Cloud Gateway的監(jiān)控端點(diǎn)如下表:
端點(diǎn) | 請求方法 | 描述 |
---|---|---|
globalfilters |
GET | 展示所有的全局過濾器信息 |
routefilters |
GET | 展示所有的過濾器工廠信息 |
refresh |
POST(無消息體) | 清空路由緩存,即刷新路由信息 |
routes |
GET | 展示所有的路由信息列表 |
routes/{id} |
GET | 展示指定id的路由的信息 |
routes/{id} |
POST(有消息體) | 新增一個(gè)路由 |
routes/{id} |
DELETE(無消息體) | 刪除一個(gè)路由 |
Gateway所有的監(jiān)控端點(diǎn)都掛載在 /actuator/gateway
路徑下支救,例如globalfilters
端點(diǎn)的完整訪問路徑是 /actuator/gateway/globalfilters
各墨。該端點(diǎn)主要是查看Gateway啟用了哪些全局過濾器以及它們的執(zhí)行順序(數(shù)字越小越優(yōu)先執(zhí)行)贬堵。所以當(dāng)我們不知道Gateway啟用了哪些全局過濾器黎做,或者不知道這些全局過濾器的執(zhí)行順序蒸殿,就可以訪問該端點(diǎn)進(jìn)行查看:
同理酥艳,如果不知道Gateway啟用了哪些過濾器工廠,則可以訪問routefilters
端點(diǎn)查看:
若想知道Gateway里定義了哪些路由而又不想查看配置文件的話浓镜,那么就可以通過routes
端點(diǎn)查看所有的路由信息列表:
如果出現(xiàn)自定義的路由配置不生效或行為與預(yù)期不符,那么可以通過routes/{id}
端點(diǎn)查看該路由具體的詳細(xì)信息:
routes/{id}
端點(diǎn)還可以用于動態(tài)添加路由雅任,只需發(fā)送POST請求并定義一個(gè)消息體即可咨跌。消息體示例:
{
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/test"
}
}
],
"filters": [
{
"name": "AddRequestHeader",
"args": {
"_genkey_0": "X-Request-Foo",
"_genkey_1": "Bar"
}
},
{
"name": "PreLog",
"args": {
"_genkey_0": "a",
"_genkey_1": "b"
}
}
],
"uri": "https://www.example.com",
"order": 0
}
消息體其實(shí)是有規(guī)律的,你可以先在配置文件中配置一個(gè)路由規(guī)則禽车,然后訪問 routes
端點(diǎn),route_definition
字段里的內(nèi)容就是消息體殉摔,如下:
接下來我們實(shí)際測試一下,復(fù)制該消息體记焊,然后稍微修改一下并進(jìn)行發(fā)送逸月,如下:
路由添加成功后腐缤,訪問 routes
端點(diǎn)岭粤,就可以看到新添加的路由:
注:如果沒有實(shí)時(shí)生效剃浇,使用 refresh
端點(diǎn)刷新一下路由信息即可
官方文檔:
關(guān)于Gateway的調(diào)試、排錯(cuò)
1蔫磨、Gateway的監(jiān)控端點(diǎn):
上一小節(jié)介紹了Gateway的監(jiān)控端點(diǎn)淘讥,這些監(jiān)控端點(diǎn)可以幫助我們分析全局過濾器、過濾器工廠堤如、路由詳情等
2蒲列、日志:
設(shè)置一些相關(guān)包的日志級別,打印更詳細(xì)的日志信息搀罢,可按需將如下包的日志級別設(shè)置成 debug
或 trace
:
org.springframework.cloud.gateway
org.springframework.http.server.reactive
org.springframework.web.reactive
org.springframework.boot.autoconfigure.web
reactor.netty
redisratelimiter
配置示例:
logging:
level:
org.springframework.cloud.gateway: trace
3蝗岖、Wiretap Logger【需Greenwich SR3及更高版本才會支持】:
Reactor Netty的 HttpClient
以及 HttpServer
可啟用 Wiretap
。需將 reactor.netty
包設(shè)置成 debug
或 trace
榔至,然后在配置文件中添加如下配置:
-
spring.cloud.gateway.httpserver.wiretap=true
:開啟HttpServer
的Wiretap -
spring.cloud.gateway.httpclient.wiretap=true
:開啟HttpClient
的Wiretap
wiretap其實(shí)是Reactor Netty的概念抵赢,用于打印對端之間的流量詳情,相關(guān)文檔:
過濾器執(zhí)行順序
我們都知道全局過濾器使用@Order
注解或?qū)崿F(xiàn) Ordered
接口來配置一個(gè)決定執(zhí)行順序的數(shù)字唧取,該數(shù)字越小的過濾器越靠前執(zhí)行铅鲤。
但是在路由規(guī)則上所配置的過濾器工廠并沒有配置類似Order之類的東西,那么是如何決定執(zhí)行順序的呢枫弟?其實(shí)邢享,過濾器工廠默認(rèn)也會被設(shè)置一個(gè)Order,該Order按配置順序從1開始遞增媒区,也是Order越小越靠前執(zhí)行驼仪。如下:
routes:
- id: test-route
uri: lb://user-center
predicates:
- TimeBetween=上午9:00,下午5:00
filters:
# 按配置順序從1開始遞增
- AddRequestHeader=Y-Header, Bar # Order為1
- AddResponseHeader=X-Header, Bar # Order為2
- PreLog=testName,testValue # Order為3
使用default-filters
配置的默認(rèn)過濾器也是同理,但如果配置了默認(rèn)過濾器袜漩,則會先執(zhí)行相同Order的默認(rèn)過濾器:
default-filters:
- AddRequestHeader=Y-Foo, Bar # Order為1
- AddResponseHeader=X-Foo, Bar # Order為2
routes:
- id: test-route
uri: lb://user-center
predicates:
- TimeBetween=上午9:00,下午5:00
filters:
# 按配置順序從1開始遞增
- AddRequestHeader=Y-Header, Bar # Order為1
- AddResponseHeader=X-Header, Bar # Order為2
- PreLog=testName,testValue # Order為3
如需自行控制過濾器工廠的Order绪爸,可返回OrderedGatewayFilter
,如下示例:
@Slf4j
@Component
public class PreLogGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public OrderedGatewayFilter apply(NameValueConfig config) {
return new OrderedGatewayFilter((exchange, chain) -> {
...
return chain.filter(exchange);
}, -1);
}
}
核心代碼:
-
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#loadGatewayFilters
:為過濾器設(shè)置了Order 數(shù)值宙攻,從1開始 -
org.springframework.cloud.gateway.route. RouteDefinitionRouteLocator#getFilters
:加載默認(rèn)過濾器 & 路由過濾器奠货,并對過濾器做了排序 -
org.springframework.cloud.gateway.handler.FilteringWebH .andler#handle
:構(gòu)建過濾器鏈并執(zhí)行
Spring Cloud Gateway跨域配置
Gateway支持CORS相關(guān)配置,可以通過不同的URL規(guī)則匹配不同的CORS策略座掘。配置示例:
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "http://docs.spring.io"
allowedMethods:
- GET
除此之外递惋,還可以通過自定義過濾器來解決跨域問題柔滔,具體代碼如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
Spring Cloud Gateway限流相關(guān)
在高并發(fā)的系統(tǒng)中,限流往往是一個(gè)繞不開的話題萍虽,我們都知道網(wǎng)關(guān)是流量的入口睛廊,所以在網(wǎng)關(guān)上做限流也是理所當(dāng)然的。Spring Cloud Gateway內(nèi)置了一個(gè)過濾器工廠杉编,用于提供限流功能超全,這個(gè)過濾器工廠就是是RequestRateLimiterGatewayFilterFactory
,該過濾器工廠基于令牌桶算法實(shí)現(xiàn)限流功能邓馒。
目前嘶朱,該過濾器工廠默認(rèn)使用 RedisRateLimiter
作為限速器,需要依賴Redis來存儲限流配置光酣,以及統(tǒng)計(jì)數(shù)據(jù)等疏遏。當(dāng)然你也可以實(shí)現(xiàn)自己的RateLimiter
,只需實(shí)現(xiàn) org.springframework.cloud.gateway.filter.ratelimit.RateLimiter
接口救军,或者繼承 org.springframework.cloud.gateway.filter.ratelimit.AbstractRateLimiter
抽象類
Tips:
Redis Rate Limiter的實(shí)現(xiàn)基于這篇文章:Scaling your API with rate limiters
Spring官方引用的令牌桶算法文章:Token bucket
關(guān)于令牌桶之類的限流算法可以參考另一篇文章财异,這里就不過多贅述了:
動手實(shí)踐
1、添加Redis依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
2缤言、添加如下配置:
spring:
cloud:
gateway:
routes:
- id: user-center
uri: lb://user-center
predicates:
- Path=/user-center/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
# 令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
# 令牌桶的上限
redis-rate-limiter.burstCapacity: 2
# 使用SpEL表達(dá)式從Spring容器中獲取Bean對象
key-resolver: "#{@pathKeyResolver}"
# redis相關(guān)
redis:
host: 127.0.0.1
port: 6379
3宝当、編寫一個(gè)KeyResolver
,用于定義針對什么進(jìn)行限流胆萧。例如按照訪問路徑限流,就寫一個(gè)針對訪問路徑的KeyResolver
俐东;按照請求參數(shù)限流跌穗,那就寫一個(gè)針對請求參數(shù)的KeyResolver
,以此類推虏辫。這里我們按照訪問路徑限流蚌吸,具體實(shí)現(xiàn)代碼如下:
@Configuration
public class RaConfiguration {
/**
* 按照Path限流
*
* @return key
*/
@Bean
public KeyResolver pathKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest()
// 獲取path
.getPath()
.toString()
);
}
}
從代碼的實(shí)現(xiàn)不難看出,實(shí)際就只是返回了一個(gè)訪問路徑砌庄,這樣限流規(guī)則就會作用到訪問路徑上羹唠。例如訪問:http://${GATEWAY_URL}/users/1
,對于這個(gè)路徑娄昆,它的redis-rate-limiter.replenishRate = 1
佩微,redis-rate-limiter.burstCapacity = 2
。
訪問:http://${GATEWAY_URL}/shares/1
萌焰,對于這個(gè)路徑哺眯,它的redis-rate-limiter.replenishRate = 1
,redis-rate-limiter.burstCapacity = 2
扒俯;以此類推......
測試
接下來進(jìn)行一個(gè)簡單的測試奶卓,看看限流是否起作用了一疯。持續(xù)頻繁地訪問某個(gè)路徑,當(dāng)令牌桶的令牌被消耗完了夺姑,就會返回 429
這個(gè)HTTP狀態(tài)碼墩邀。如下:
然后迅速查看Redis中存儲的key,會發(fā)現(xiàn)其格式如下:
從key的格式可以看出來盏浙,實(shí)際上 KeyResolver
的目的就是用來獲取一個(gè)請求的唯一標(biāo)識(這個(gè)標(biāo)識可以是訪問路徑眉睹,可以是某個(gè)請求參數(shù),總之就是可以從這個(gè)請求里獲取出來的東西)只盹,并用其生成key以及解析key辣往,以此實(shí)現(xiàn)針對性的限流。
拓展
如果請求會攜帶一個(gè)名為user
的參數(shù)殖卑,其值為用戶名站削,那么我們就可以通過這個(gè)請求參數(shù)來實(shí)現(xiàn)針對用戶的限流。如下:
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest()
.getQueryParams()
.getFirst("user")
);
}
同理孵稽,我們還可以針對請求的來源IP進(jìn)行限流许起。如下:
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest()
.getHeaders()
.getFirst("X-Forwarded-For")
);
}