[TOC]html
說到監控,就應該能想到Spring Boot Actuator。而Spring Cloud Gateway基於Actuator提供了許多的監控端點。只須要在項目中添加spring-boot-starter-actuator
依賴,並將 gateway
端點暴露,便可得到若干監控端點。配置示例:java
management: endpoints: web: exposure: include: gateway # 或者配置「*」暴露所有端點
Spring Cloud Gateway的監控端點以下表:react
端點 | 請求方法 | 描述 |
---|---|---|
globalfilters |
GET | 展現全部的全局過濾器信息 |
routefilters |
GET | 展現全部的過濾器工廠信息 |
refresh |
POST(無消息體) | 清空路由緩存,即刷新路由信息 |
routes |
GET | 展現全部的路由信息列表 |
routes/{id} |
GET | 展現指定id的路由的信息 |
routes/{id} |
POST(有消息體) | 新增一個路由 |
routes/{id} |
DELETE(無消息體) | 刪除一個路由 |
Gateway全部的監控端點都掛載在 /actuator/gateway
路徑下,例如globalfilters
端點的完整訪問路徑是 /actuator/gateway/globalfilters
。該端點主要是查看Gateway啓用了哪些全局過濾器以及它們的執行順序(數字越小越優先執行)。因此當咱們不知道Gateway啓用了哪些全局過濾器,或者不知道這些全局過濾器的執行順序,就能夠訪問該端點進行查看:web
同理,若是不知道Gateway啓用了哪些過濾器工廠,則能夠訪問routefilters
端點查看:redis
若想知道Gateway裏定義了哪些路由又不想查看配置文件的話,那麼就能夠經過routes
端點查看全部的路由信息列表:算法
若是出現自定義的路由配置不生效或行爲與預期不符,那麼能夠經過routes/{id}
端點查看該路由具體的詳細信息:spring
routes/{id}
端點還能夠用於動態添加路由,只需發送POST請求並定義一個消息體便可。消息體示例:json
{ "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 }
消息體實際上是有規律的,你能夠先在配置文件中配置一個路由規則,而後訪問 routes
端點,route_definition
字段裏的內容就是消息體,以下:api
接下來咱們實際測試一下,複製該消息體,而後稍微修改一下並進行發送,以下:跨域
路由添加成功後,訪問 routes
端點,就能夠看到新添加的路由:
注:若是沒有實時生效,使用 refresh
端點刷新一下路由信息便可
官方文檔:
一、Gateway的監控端點:
上一小節介紹了Gateway的監控端點,這些監控端點能夠幫助咱們分析全局過濾器、過濾器工廠、路由詳情等
二、日誌:
設置一些相關包的日誌級別,打印更詳細的日誌信息,可按需將以下包的日誌級別設置成 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
三、Wiretap Logger【需Greenwich SR3及更高版本纔會支持】:
Reactor Netty的 HttpClient
以及 HttpServer
可啓用 Wiretap
。需將 reactor.netty
包設置成 debug
或 trace
,而後在配置文件中添加以下配置:
spring.cloud.gateway.httpserver.wiretap=true
:開啓 HttpServer
的Wiretapspring.cloud.gateway.httpclient.wiretap=true
:開啓 HttpClient
的Wiretapwiretap實際上是Reactor Netty的概念,用於打印對端之間的流量詳情,相關文檔:
咱們都知道全局過濾器使用@Order
註解或實現 Ordered
接口來配置一個決定執行順序的數字,該數字越小的過濾器越靠前執行。
可是在路由規則上所配置的過濾器工廠並無配置相似Order之類的東西,那麼是如何決定執行順序的呢?其實,過濾器工廠默認也會被設置一個Order,該Order按配置順序從1開始遞增,也是Order越小越靠前執行。以下:
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
配置的默認過濾器也是同理,但若是配置了默認過濾器,則會先執行相同Order的默認過濾器:
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
:爲過濾器設置了Order 數值,從1開始org.springframework.cloud.gateway.route. RouteDefinitionRouteLocator#getFilters
:加載默認過濾器 & 路由過濾器,並對過濾器作了排序org.springframework.cloud.gateway.handler.FilteringWebH .andler#handle
:構建過濾器鏈並執行Gateway支持CORS相關配置,能夠經過不一樣的URL規則匹配不一樣的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內置了一個過濾器工廠,用於提供限流功能,這個過濾器工廠就是是RequestRateLimiterGatewayFilterFactory
,該過濾器工廠基於令牌桶算法實現限流功能。
目前,該過濾器工廠默認使用 RedisRateLimiter
做爲限速器,須要依賴Redis來存儲限流配置,以及統計數據等。固然你也能夠實現本身的RateLimiter
,只需實現 org.springframework.cloud.gateway.filter.ratelimit.RateLimiter
接口,或者繼承 org.springframework.cloud.gateway.filter.ratelimit.AbstractRateLimiter
抽象類
Tips:
Redis Rate Limiter的實現基於這篇文章:Scaling your API with rate limiters
Spring官方引用的令牌桶算法文章:Token bucket
關於令牌桶之類的限流算法能夠參考另外一篇文章,這裏就不過多贅述了:
一、添加Redis依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
二、添加以下配置:
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表達式從Spring容器中獲取Bean對象 key-resolver: "#{@pathKeyResolver}" # redis相關 redis: host: 127.0.0.1 port: 6379
三、編寫一個KeyResolver
,用於定義針對什麼進行限流。例如按照訪問路徑限流,就寫一個針對訪問路徑的KeyResolver
;按照請求參數限流,那就寫一個針對請求參數的KeyResolver
,以此類推。這裏咱們按照訪問路徑限流,具體實現代碼以下:
@Configuration public class RaConfiguration { /** * 按照Path限流 * * @return key */ @Bean public KeyResolver pathKeyResolver() { return exchange -> Mono.just( exchange.getRequest() // 獲取path .getPath() .toString() ); } }
從代碼的實現不難看出,實際就只是返回了一個訪問路徑,這樣限流規則就會做用到訪問路徑上。例如訪問:http://${GATEWAY_URL}/users/1
,對於這個路徑,它的redis-rate-limiter.replenishRate = 1
,redis-rate-limiter.burstCapacity = 2
。
訪問:http://${GATEWAY_URL}/shares/1
,對於這個路徑,它的redis-rate-limiter.replenishRate = 1
,redis-rate-limiter.burstCapacity = 2
;以此類推......
接下來進行一個簡單的測試,看看限流是否起做用了。持續頻繁訪問某個路徑,當令牌桶的令牌被消耗完了,就會返回 429
這個HTTP狀態碼。以下:
而後迅速查看Redis中存儲的key,會發現其格式以下:
從key的格式能夠看出來,實際上 KeyResolver
的目的就是用來獲取一個請求的惟一標識(這個標識能夠是訪問路徑,能夠是某個請求參數,總之就是能夠從這個請求裏獲取出來的東西),並用其生成key以及解析key,以此實現針對性的限流。
若是請求會攜帶一個名爲user
的參數,其值爲用戶名,那麼咱們就能夠經過這個請求參數來實現針對用戶的限流。以下:
@Bean public KeyResolver userKeyResolver() { return exchange -> Mono.just( exchange.getRequest() .getQueryParams() .getFirst("user") ); }
同理,咱們還能夠針對請求的來源IP進行限流。以下:
@Bean public KeyResolver ipKeyResolver() { return exchange -> Mono.just( exchange.getRequest() .getHeaders() .getFirst("X-Forwarded-For") ); }