一、简介
Gateway网关是我们服务的守门神,所有微服务的统一入口。Spring Cloud Gateway 是 Spring Cloud的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。在Gateway之前,SpringCloud并不自己开发网关,可能是觉得Netflflix公司的Zuul不行吧,然后自己就写了一个,也是替代Netflflix Zuul。其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
本身也是一个微服务,需要注册到Eureka。
https://spring.io/projects/spring-cloud-gateway
1、功能特点:
- 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
- 动态路由
- Predicates 和 Filters 作用于特定路由
- 集成 Hystrix 断路器
- 集成 Spring Cloud DiscoveryClient
- 简单好用的 Predicates 和 Filters
- 限流
- 路径重写
- 不管是来自客户端的请求,还是服务内部调用。一切对服务的请求都可经过网关。
- 网关实现鉴权、动态路由等等操作。
- Gateway是我们服务的统一入口。
- 鉴权,安全控制,⽇志统⼀处理,易于监控的相关功能。
2、术语解释
Route(路由):这是网关的基本模块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。
Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
Filter(过滤器):这是org.springframework.cloud.gateway.fifilter.GatewayFilter
的实例,我们可以使用它修改请求和响应。
3、网关技术
nginx Nginx (engine x)
:是⼀个⾼性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。zuul
:是 Netflix 出品的⼀个基于 JVM 路由和服务端的负载均衡器。spring-cloud-gateway
: 是spring 出品的 基于spring 的⽹关项⽬,集成断路器,路径重写,性能⽐Zuul好。
二、快速开始
1、创建Spring boot工程
2、启动引导类开启注册中心Eureka客户端发现
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient // 开启Eureka客户端发现服务 public class DemoGatewayApplication { public static void main(String[] args) { SpringApplication.run(DemoGatewayApplication.class, args); } }
3、配置文件appliation.yml
# 端口号 server.port: 10010 # 应用名 spring.application.name: api-gateway # 注册中心地址 eureka.client.service-url.defaultZone: http://8.131.239.157:10086/eureka/
4、编写路由规则
spring: cloud: gateway: # 路由si(集合) routes: # id(唯一标识) - id: websocket_test # 路由服务地址(转发后地址) uri: http://127.0.0.1:9091 order: 9000 # 断言(判断哪些请求要被转发) predicates: - Path=/user/**
- 将符合 path 规则的请求,路由到 uri 参数指定地址。
- 举例
http://localhost:10010/user/fifindById?id=1
路由转发到http://localhost:9091/user/fifindById?id=1
- 访问路径中,必须包含路由规则的映射路径
/user
才会被路由
三、路由配置(转发)(predicates)
谓词(predicates):当满足条件在进行路由转发
在 Spring Cloud Gateway 中谓词实现 GatewayPredicate 接口。其中类名符合: XXXRoutepredicateFactory
,其中 XXX 就是在配置文件中谓词名称。在上面示例中 Path=/demo/**
实际上使用的就是PathRoutePredicateFactory
。
所有的谓词都设置在 predicates 属性中,当设置多个谓词时取逻辑与条件,且一个谓 词只能设置一组条件,如果需要有个多条件,添加多个相同谓词。
1、Query属性
表示路径满足/system/**同时包含参数 abc。
predicates: - Path=/system/** - Query=abc
支持正则表达式
jqk.
支持指定某个字段值
abc,jqk.
abc=jqka 或 abc=jqkw 能满足谓词条件。
predicates: - Path=/system/** - Query=abc,jqk.
测试:
http://localhost:9000/system/one?abc=jqk
完整版:
spring: cloud: routes: - id: system uri: lb://system predicates: - Path=/system/** - Query=abc filters: - StripPrefix= 1
2、Header
表示请求头中必须包含的内容。
- 参数名和参数值之间依然使用逗号。
- 参数值要使用正则表达式。
- 支持选择多个Header头信息。
如果 Header 只有一个值表示请求头中必须包含的参数。如果有两个值,第一个表示请求头必须包含的参数名,第二个表示请求头参数对应值。
predicates: - Header=Connection,keep-alive - Header=Cache-Control,max-age=0
完整版:
spring: cloud: routes: - id: system uri: lb://system predicates: - Path=/system/** - Query=abc - Header=Connection,keep-alive - Header=Cache-Control,max-age=0
3、Method
Method 表示请求方式。支持多个值,使用逗号分隔,多个值之间为 or 条件。
spring: cloud: routes: - id: system uri: lb://system predicates: - Path=/system/** - Query=abc - Header=Connection,keep-alive - Header=Cache-Control,max-age=0 - Method=GET,POST
4、RemoteAddr
允许访问的客户端地址
- 要使用127.0.0.1,不要使用localhost。
spring: cloud: routes: - id: system uri: lb://system predicates: - Path=/system/** - Query=abc - Header=Connection,keep-alive - Header=Cache-Control,max-age=0 - Method=GET,POST - RemoteAddr=127.0.0.1
5、Host
匹配请求中的Host信息。满足Ant模式。
?:匹配一个字符
*:匹配0个或多个字符
**:匹配0个或多个目录
spring: cloud: routes: - id: system uri: lb://system predicates: - Path=/system/** - Query=abc - Header=Connection,keep-alive - Header=Cache-Control,max-age=0 - Method=GET,POST - RemoteAddr=127.0.0.1 - Host=127.0.0.1:9000
6、Cookie
要求请求中包含指定的Cookie名和满足特定正则要求的值。
- 必须要有2个值,第一个包含的是参数名,第2个表示参数对应的值(正则表达式)。
spring: cloud: routes: - id: system uri: lb://system predicates: - Path=/system/** - Query=abc - Header=Connection,keep-alive - Header=Cache-Control,max-age=0 - Method=GET,POST - RemoteAddr=127.0.0.1 - Host=127.0.0.1:9000 - Cookie=age,.*
7、Before
在指定时间点之前
spring: cloud: routes: - id: system uri: lb://system predicates: - Path=/system/** - Query=abc - Header=Connection,keep-alive - Header=Cache-Control,max-age=0 - Method=GET,POST - RemoteAddr=127.0.0.1 - Host=127.0.0.1:9000 - Cookie=age,.* - Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
8、After
在指定时间点之后
spring: cloud: routes: - id: system uri: lb://system predicates: - Path=/system/** - Query=abc - Header=Connection,keep-alive - Header=Cache-Control,max-age=0 - Method=GET,POST - RemoteAddr=127.0.0.1 - Host=127.0.0.1:9000 - Cookie=age,.* - Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai] - After=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
9、Between
必须在设定的范围时间内,才能进行路由转发
spring: cloud: routes: - id: system uri: lb://system predicates: - Path=/system/** - Query=abc - Header=Connection,keep-alive - Header=Cache-Control,max-age=0 - Method=GET,POST - RemoteAddr=127.0.0.1 - Host=127.0.0.1:9000 - Cookie=age,.* - Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai] - After=2020-01-31T18:00:00.000+08:00[Asia/Shanghai] - Between=2020-01-31T18:00:00.000+08:00[Asia/Shanghai], 2020-02-01T00:00:00.000+08:00[Asia/Shanghai]
10、Weight
设置服务转发的权重,用于限制某个的请求占比
语法:Weight=组名,负载均衡权重
suiyi:权重:20%
suiyi2:权重:80%
spring: cloud: routes: - id: suiyi uri: lb://demo-one predicates: - Path=/demo/** - Weight=group,2 filters: - StripPrefix=1 - id: suiyi2 uri: lb://demo-two predicates: - Path=/demo/** - Weight=group,8 filters: - StripPrefix=1
四、过滤器(拦截)(Filter)
1、简介
过滤器作为网关的其中一个重要功能,就是实现请求的鉴权。
执行顺序:
Spring Cloud Gateway 的 Filter 的执行顺序有两个:“pre” 和 “post”。“pre”和 “post” 分别会在请求被执行前调用和被执行后调用。
Gateway自带过滤器有几十个,常见自带过滤器有:
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1.RELEASE/single/spring-cloud-gateway.html#_gatewayfilter_factories
过滤器名称 | 说明 |
AddRequestHeader | 对匹配上的请求加上Header |
AddRequestParameters | 对匹配上的请求路由 |
AddResponseHeader | 对从网关返回的响应添加Header |
StripPrefix | 对匹配上的请求路径去除前缀 |
PrefixPath | 对匹配上的请求路径添加前缀 |
使用场景:
- 请求鉴权:如果没有访问权限,直接进行拦截
- 异常处理:记录异常日志
- 服务调用时长统计
2、系统内置过滤器使用
**过滤器类型:**Gateway有两种过滤器
- 局部过滤器:只作用在当前配置的路由上。
- 全局过滤器:作用在所有路由上。
2.1 添加响应头(AddResponseHeader)
对输出的响应设置其头部属性名称为i-love,值为itheima。
配置文件更新:
# 往响应过滤器中加入信息 - AddResponseHeader: i-love,itheima
全部配置:
spring: cloud: gateway: # 全局过滤器配置 default-filters: # 往响应过滤器中加入信息 - AddResponseHeader: i-love,itheima - AddRequestParameters: - AddRequestHeader: - StripPrefix: - PrefixPath:
结果展示:
2.2 增加路由配置(PrefixPath)
在gateway中可以通过配置路由的过滤器PrefifixPath
实现映射路径中的前缀添加。可以起到隐藏接口地址的作用,避免接口地址暴露。
# 请求地址添加路径前缀过滤器 filters: - PrefixPath=/user
完整配置:
spring: cloud: gateway: routes: - id: user-service-route # 路由id,可以随意写 # 代理服务地址;lb表示从Eureka中获取具体服务 uri: lb://user-service # 路由断言,配置映射路径 predicates: - Path=/** # 请求地址添加路径前缀过滤器 filters: - PrefixPath=/user
结果展示:
配置 | 访问地址 | 路由地址 |
PrefifixPath=/user |
localhost:10010/findById?id=1 | localhost:9091/user/findById?id=1 |
PrefifixPath=/user/abc |
localhost:10010/findById?id=1 | localhost:9091/user/abc/findById?id=1 |
2.3 删除路由配置(StripPrefix)
在gateway中通过配置路由过滤器StripPrefifix
,实现映射路径中地址的去除。通过StripPrefifix=1
来指定路由要去掉的前缀个数。
如:路径/api/user/1
将会被路由到/user/1
。
filters: # 去除路径前缀过滤器 - StripPrefix=1
完整配置文件
spring: cloud: gateway: routes: - id: user-service-route # 路由id,可以随意写 # 代理服务地址;lb表示从Eureka中获取具体服务 uri: lb://user-service # 路由断言,配置映射路径 predicates: - Path=/** # 请求地址添加路径前缀过滤器 filters: # 去除路径前缀过滤器 - StripPrefix=1
访问效果:
配置 | 访问地址 | 路由地址 |
StripPrefix=1 |
localhost:10010/api/user/findById?id=1 | localhost:9091/user/findById? id=1 |
StripPrefix=2 |
localhost:10010/aa/api/user/findById? id=1 | localhost:9091/user/findById? id=1 |
2.4 AddRequestHeader
添加请求头参数,参数和值之间使用逗号分隔
filters: - StripPrefix= 1 - AddRequestHeader=MyHeader,jqk
2.5 AddRequestParameter
添加请求表单参数,多个参数需要有多个过滤器。
filters: - AddRequestParameter=name,bjsxt - AddRequestParameter=age,123
2.6 DedupeResponseHeader
对指定响应头去重复。
语法:DedupeResponseHeader=响应头参数 响应头参数,strategy
可选参数 strategy 可取值:
RETAIN_FIRST
:默认值,保留第一个RETAIN_LAST
:保留最后一个。RETAIN_UNIQUE
:保留唯一的,出现重复的属性值,会保留一个。例如有两个 My:bbb 的属性,最后会只留一个。
filters: - StripPrefix= 1 - DedupeResponseHeader=My Content-Type,RETAIN_UNIQUE
2.7 CircuitBreaker
实现熔断时使用,支持 CircuitBreaker 和 Hystrix 两种
2.8 FallbackHeaders
可以添加降级时的异常信息
2.9 RequestRateLimiter
限流过滤器
2.10 RedirectTo
重定向。
有两个参数,status 和 url。其中 status 应该 300 系列重定向状态码
2.11 RemoveRequestHeader
删除请求头参数
2.12 RemoveResponseHeader
删除响应头参数
2.13 RemoveRequestParameter
删除请求参数
2.14 RewritePath
重写请求路径
2.15 RewriteResponseHeader
重写响应头参数
2.16 SaveSession
如果项目中使用Spring Security和Spring Session整合时,会使用到此属性。
2.17 SecureHeaders
具有权限验证时,建议的头信息内容。
2.18 Retry
设置重试次数
功能和StripPrefix类似。
2.19 RequestSize
请求的最大大小。包含maxSize参数,可以有单位'KB'或'MB'默认为B。
2.20 ModifyRequestBody
修改请求体内容
2.21 ModifyResponseBody
修改响应体内容
2.22 SetPath
当请求路径为
/red/blue
时,会将/blue
发送给下游。
spring: cloud: routes: - id: system uri: lb://system predicates: - Path=/red/{segment} filters: - SetPath=/{segment}
2.23 SetRequestHeader
替换请求参数头。
2.24 SetResponseHeader
替换相应头参数
2.25 SetStatus
设置相应状态码
3、自定义全局过滤器
放行:
//12. 放⾏ return chain.filter(exchange);
拦截:
//7. 响应中放⼊返回的状态吗, 没有权限访问 response.setStatusCode(HttpStatus.UNAUTHORIZED); //8. 返回 return response.setComplete()
3.1 假如token为null,则拦截
import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Configuration public class MyGlobalFileter implements GlobalFilter, Ordered { /** * 自定义过滤器规则 */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("-----------------全局过滤器MyGlobalFilter---------------------"); String token = exchange.getRequest().getQueryParams().getFirst("token"); if (StringUtils.isBlank(token)) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } /** * 定义过滤器执行顺序 * 返回值越小,越靠前执行 * @return */ @Override public int getOrder() { return 0; } }
3.2 IP过滤器
import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.net.InetSocketAddress; @Component public class IPFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("经过IP过滤器"); ServerHttpRequest request = exchange.getRequest(); InetSocketAddress remoteAddress = request.getRemoteAddress(); System.out.println("请求的IP地址为:" + remoteAddress.getHostName()); return chain.filter(exchange); } @Override public int getOrder() { return 1; } }
3.3 请求url过滤器
import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Component public class UrlFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("经过url过滤器"); ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); System.out.println("请求的url为:" + path); return chain.filter(exchange); } @Override public int getOrder() { return 2; } }
4、自定义可配置过滤器(添加到配置文建)
import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.cloud.gateway.support.GatewayToStringStyler; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.Arrays; import java.util.List; @Component public class MyRouteGatewayFilterFactory extends AbstractGatewayFilterFactory<MyRouteGatewayFilterFactory.Config> { public MyRouteGatewayFilterFactory() { super(MyRouteGatewayFilterFactory.Config.class); } @Override public GatewayFilter apply(Config config) { return new GatewayFilter() { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("在这个位置写点东西传递进来的 name: " + config.getName() + ",age:" + config.getAge()); return chain.filter(exchange); } @Override public String toString() { return GatewayToStringStyler.filterToStringCreator(MyRouteGatewayFilterFactory.this).append("name", config.getName()).toString(); } }; } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("name"); } public static class Config { private String name; private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Config() { } public String getName() { return name; } public void setName(String name) { this.name = name; } } }
添加到配置文件中(application.yaml)
spring: application: name: sysgateway cloud: routes: # 自定义配置 - id: myFilter uri: lb:/jqk predicates: - Path=/project/** filters: - StripPrefix=1 - name: MyRoute args: # 传递的参数(name、age) name: hello age: 12
五、其它配置
1、网关自动映射处理
- 只要请求地址符合规则:http://gatewayIp:gatewayPort/微服务名称/微服务请求地址
- 网关自动映射。把请求地址转发到 http://微服务名称/微服务请求地址
- 如:有微服务,命名为ribbon-app-service
- 商业开发中:enabled一般不设置,默认为false。避免不必要的自动转发机制。
案例:
请求地址:http://localhost:9999/ribbon-app-service/getArgs?name=admin&age=20 自动转发到:http://ribbon-app-service/getArgs?name=admin&age=20
配置文件
spring: application: name: sysgateway cloud: gateway: discovery: # 配置网关发现机制 locator: # 配置处理机制 enabled: true # 开启网关自动映射处理机制 lower-case-service-id: true # 开启微服务名称小写转换。Eureka对服务名管理默认全大写。
2、动态路由
# 路由到执行IP uri: http://127.0.0.1:9091 # 根据服务名称进行路由(从配置中心获取指定IP) uri: lb://user-service
- 路由配置中uri所用的协议为lb时,gateway将把user-service解析为实际的主机和端口,并通过Ribbon进行负载均衡。
- 应该是根据服务名称,去Eureka注册中心查找服务对应的所有实例列表,然后进行动态路由!
测试日志:
这次gateway进行路由时,会利用Ribbon进行负载均衡访问。日志中可以看到使用了负载均衡器。
3、跨域配置
spring: cloud: gateway: globalcors: cors-configurations: '[/**]': # 匹配所有请求 allowedOrigins: "*" #跨域处理 允许所有的域 allowedMethods: # ⽀持的⽅法 - GET - POST - PUT - DELETE
4、记录执行耗时
// 将开始时间放入请求中 exchange.getAttributes().put("startTime", System.currentTimeMillis()); // 计算执行耗时 return chain.filter(exchange).then(Mono.fromRunnable(() -> { Long startTime = exchange.getAttribute("startTime"); Long executeTime = System.currentTimeMillis() - startTime; }));
完整版:
import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Slf4j @Component public class UrlFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("经过url过滤器"); ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); System.out.println("请求的url为:" + path); exchange.getAttributes().put("startTime", System.currentTimeMillis()); return chain.filter(exchange).then(Mono.fromRunnable(() -> { Long startTime = exchange.getAttribute("startTime"); if (startTime != null) { long executeTime = System.currentTimeMillis() - startTime; log.info("请求时间:{}", exchange.getRequest().getURI().getRawPath() + "d" + executeTime + "ms"); } })); } @Override public int getOrder() { return 2; } }
5、exchange
代码信息
// 获取请求头信息 HttpHeaders headers = exchange.getRequest().getHeaders(); String first = exchange.getRequest().getHeaders().getFirst("Content-type"); ServerHttpRequest build = exchange.getRequest().mutate().header("Content-type","json").build(); // 获取请求方式 String name = exchange.getRequest().getMethod().name(); // 获取请求URL地址信息 String host = exchange.getRequest().getURI().getHost(); String path1 = exchange.getRequest().getURI().getPath(); String rawPath = exchange.getRequest().getURI().getRawPath(); // 获取请求属性信息 exchange.getAttributes().put("startTime",123123); Long startTime1 = exchange.getAttribute("startTime"); // 获取返回体 ServerHttpResponse response = exchange.getResponse();
六、网关限流
1、简介
限流,当我们的系统 被频繁的请求的时候,就有可能 将系统压垮,所以 为了解决这个问题,需要在每⼀个微服务中做限流操作,但是如果有了⽹关,那么就可以在⽹关系统做限流,因为所有的请求都需要先通过⽹关系统才能路由到微服务中。
2、令牌桶算法简介
令牌桶算法是⽐较常⻅的限流算法之⼀,⼤概描述如下:
- 所有的请求在处理之前都需要拿到⼀个可⽤的令牌才会被处理;
- 根据限流⼤⼩,设置按照⼀定的速率往桶⾥添加令牌;
- 桶设置最⼤的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
- 请求达到后⾸先要获取令牌桶中的令牌,拿着令牌才可以进⾏其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
- 令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证⾜够的限流
3、代码实现
3.1 pom.xml
依赖
spring cloud gateway
默认使⽤redis
的RateLimter
限流算法来实现。所以我们要使⽤⾸先需要引⼊redis
的依赖。
<!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
3.2 定义KeyResolver
在GatewayApplicatioin
引导类中添加如下代码,KeyResolver
⽤于计算某⼀个类型的限流的KEY
也就是说,可以通过KeyResolver
来指定限流的Key
。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.context.annotation.Bean; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @SpringBootApplication @EnableDiscoveryClient // 开启Eureka客户端发现服务 public class DemoGatewayApplication { public static void main(String[] args) { SpringApplication.run(DemoGatewayApplication.class, args); } // 定义一个KeyResolver @Bean public KeyResolver ipKeyResolver() { return new KeyResolver() { @Override public Mono<String> resolve(ServerWebExchange exchange) { return Mono.just(exchange.getRequest().getRemoteAddress().getHostName()); } }; } }
3.3 修改配置
修改application.yml中配置项,指定限制流量的配置以及REDIS的配置。
filters: - PrefixPath=/test - name: RequestRateLimiter args: key-resolver: "#{@ipKeyResolver}" redis-rate-limiter.replenishRate: 1 redis-rate-limiter.burstCapacity: 1
burstCapacity
:令牌桶总容量。replenishRate
:令牌桶每秒填充平均速率。key-resolver
:⽤于限流的键的解析器的 Bean 对象的名字。它使⽤ SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
通过在 replenishRate
和中设置相同的值来实现稳定的速率 burstCapacity
。设置 burstCapacity
⾼于时,可以允许临时突发 replenishRate
。在这种情况下,需要在突发之间允许速率限制器⼀段时间(根据 replenishRate
),因为2次连续突发将导致请求被丢弃( HTTP 429 - Too Many Requests
) key-resolver: "#{@userKeyResolver}"
⽤于通过SPEL表达式
来指定使⽤哪⼀个KeyResolver
。
如上配置:
- 表示 ⼀秒内,允许 ⼀个请求通过,令牌桶的填充速率也是⼀秒钟添加⼀个令牌。
- 最⼤突发状况 也只允许 ⼀秒内有⼀次请求,可以根据业务来调整 。
3.4 最终配置:
spring: application: name: sysgateway cloud: gateway: globalcors: cors-configurations: '[/**]': # 匹配所有请求 allowedOrigins: "*" #跨域处理 允许所有的域 allowedMethods: # ⽀持的⽅法 - GET - POST - PUT - DELETE routes: - id: goods uri: lb://goods predicates: - Path=/goods/** filters: - StripPrefix= 1 - name: RequestRateLimiter #请求数限流 名字不能随便写 args: key-resolver: "#{@ipKeyResolver}" redis-rate-limiter.replenishRate: 1 redis-rate-limiter.burstCapacity: 1 - id: system uri: lb://system predicates: - Path=/system/** filters: - StripPrefix= 1 # 配置Redis 127.0.0.1可以省略配置 redis: host: 192.168.200.128 port: 6379 server: port: 9101 eureka: client: service-url: defaultZone: http://127.0.0.1:6868/eureka instance: prefer-ip-address: true