[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") ); }