一、简介
- Spring Cloud Gateway 旨在提供一种简单而有效的方式来路由到 API,并为它们提供横切关注点,例如:安全性、监控/指标和弹性。
- Spring Cloud GateWay 组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。
- Spring Cloud GateWay 的核心功能过滤和路由。
官方文档地址:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/
中文文档(没有新版本):https://www.docs4dev.com/docs/zh/spring-cloud/Greenwich.RELEASE
Gateway 配置:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/appendix.html
二、核心概念
-
Route :路由网关的基本构建块。它由 ID,目标 URI,谓词集合和 filte r集合定义。如果聚合谓词为 true,则匹配路由。 -
Predicate :Java 8 函数谓词。Importing 类型为 Spring Framework ServerWebExchange。这使开发人员可以匹配 HTTP 请求中的所有内容,例如 Headers 或参数。 -
filter :这些实例Spring Framework GatewayFilter 是使用特定工厂构造的。在此,可以在发送下游请求之前或之后修改请求和响应。
1、导入核心依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
2、Predicate 常用参数配置
spring:
cloud:
gateway:
routes:
- id: service1
order: 0
uri: lb://SPRING-CLOUD-SERVICE-OPENFEIGN
predicates:
- Method=GET,POST
- Path=/service1/**,/service2/**
- After=2022-05-05T02:00:00.000+08:00[Asia/Shanghai]
- Query=type
- Header=Content-Type, application/json;charset=UTF-8
- Header=token, 123456789abcdefg
- Host=127.0.0.1:**,**.0.0.1:21000
postman 测试路由规则 GET 请求:http://127.0.0.1:21000/service1/hystrix/calculate?a=10&b=5&type=/ Predicate Weight 路由规则配置
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
该路由会将约 80% 的流量转发到 weighthigh.org,将约 20% 的流量转发到 weightlow.org
3、filter 配置
①、相关配置参数
spring:
cloud:
gateway:
routes:
- id: service1
order: 0
uri: lb://SPRING-CLOUD-SERVICE-OPENFEIGN
predicates:
- Method=GET,POST
- Path=/service1/**,/service2/**
- After=2022-05-05T02:00:00.000+08:00[Asia/Shanghai]
filters:
- RewritePath=/service1/(?<segment>.*), /service1/$\{segment}
- RewriteLocationResponseHeader=AS_IN_REQUEST, Location, ,
- RewriteResponseHeader=X-Response-Red, , password=[^&]+, password=123456
- StripPrefix=0
- AddRequestHeader=Accept-Language, zh,zh-CN;q=0.9
- AddRequestParameter=host, 127.0.0.1
- AddResponseHeader=X-Response-Foo, Bar
- PrefixPath=/mypath
- RedirectTo=302, https://blog.csdn.net/qq_41538097/article/details/124626658
- RemoveRequestHeader=X-Request-Foo
- RemoveResponseHeader=X-Response-Foo
- RemoveRequestParameter=red
- SetPath=/{segment}
- SetRequestHeader=X-Request-Red, Blue
- SetResponseHeader=X-Response-Red, Blue
- SetStatus=401
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY
methods: GET,POST
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
- name: RequestSize
args:
maxSize: 500MB
- name: SetRequestHostHeader
args:
host: 127.0.0.1
②、修改请求体、响应体
修改请求体和修改响应体只能使用代码配置,不能通过配置文件配置
@Configuration
public class RouteLocatorConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("code_service2_routeID", r -> r.method(HttpMethod.GET, HttpMethod.POST)
.and().path("/service1/openfeign/**")
.filters(f -> f
.modifyRequestBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE,
(exchange, s) -> Mono.just("{\"body\":{\"message\": \"修改消息体\"}}"))
.modifyResponseBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE,
(exchange, s) -> Mono.just("{\"body\":{\"message\": \"修改响应体\"}}"))
)
.uri("lb://SPRING-CLOUD-SERVICE-OPENFEIGN")
)
.build();
}
}
测试:http://127.0.0.1:21000/service1/openfeign/rule?type=1,可以看到响应体已修改成功
③、默认过滤器
要添加过滤器并将其应用于所有路由,您可以使用spring.cloud.gateway.default-filters. 属性参考上面的相关参数
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=X-Response-Default-Red, Default-Blue
- PrefixPath=/httpbin
④、Global Filters
三、sentinel + gateway 网关限流
官方文档:https://github.com/alibaba/Sentinel/wiki/网关限流
从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
- route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
- 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组
1、sentinel 添加网关流控项目
Sentinel 1.6.3 引入了网关流控控制台的支持,用户可以直接在 Sentinel 控制台上查看 API Gateway 实时的 route 和自定义 API 分组监控,管理网关规则和 API 分组配置。
启动程序添加启动参数 -Dcsp.sentinel.app.type=1 ,idea 中 Edit Configurations 添加 vm 参数(顺便提一下,启动参数 -D 作用添加系统属性,使用 System.getProperty("csp.sentinel.app.type") 即可获取到) 添加正确的启动参数并有访问量后,我们就可以在 Sentinel 上面看到对应的 API Gateway 了(假如项目第二次启动才添加 -Dcsp.sentinel.app.type=1,则 sentinel 不会将该项目当作网关流控项目,需要重启 sentinel 服务端)
2、route 维度
①、yaml 配置
spring:
cloud:
gateway:
routes:
- id: service1
order: 0
uri: lb://SPRING-CLOUD-SERVICE-OPENFEIGN
predicates:
- Method=GET,POST
- Path=/service1/**,/service2/**
- After=2022-05-05T02:00:00.000+08:00[Asia/Shanghai]
- Query=type
- Header=Content-Type, application/json;charset=UTF-8
- Header=token, 123456789abcdefg
- Host=127.0.0.1:**,**.0.0.1:21000
②、代码配置
@Configuration
public class RouteLocatorConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("code_service1", r -> r.method(HttpMethod.GET,HttpMethod.POST)
.and().path("/service1/**","/service2/**")
.and().after(ZonedDateTime.parse("2022-05-05T02:00:00.000+08:00[Asia/Shanghai]"))
.and().header(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8")
.and().query("type")
.and().header("token", "123456789abcdefg")
.and().host("127.0.0.1:**","**.0.0.1:21000")
.uri("lb://SPRING-CLOUD-SERVICE-OPENFEIGN")
)
.build();
}
}
③、sentinel 控制台
④、添加 route 限流
在 sentinel 控制台添加 Route ID 网关限流,下面配置说明:如果每秒请求数 >= 1,则快速失败并限流 5 s
3、自定义 API 维度
支持代码配置或 sentinel 控制台配置
①、代码添加 API
@Configuration
public class GatewayConfiguration {
@PostConstruct
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("code_service1_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/service1/sentinel"));
add(new ApiPathPredicateItem().setPattern("/service1/openfeign/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("code_service2_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/service2/test"));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}
②、sentinel 控制台添加 API 分组
③、sentinel 控制台
④、添加 API 限流
在 sentinel 控制台添加 API 分组网关限流,下面配置说明:如果每秒请求数 >= 1,则快速失败并限流 5 s
四、sentinel 网关限流处理逻辑
1、yaml 配置使用默认处理逻辑
spring-cloud-alibaba-sentinel-gateway 提供了如下配置选项,支持 yaml 配置限流处理逻辑
配置项 | 含义 | 默认值 |
---|
spring.cloud.sentinel.scg.fallback.mode
| Spring Cloud Gateway 流控处理逻辑 (选择 redirect or response ) | | spring.cloud.sentinel.scg.fallback.redirect
| Spring Cloud Gateway 响应模式为 'redirect' 模式对应的重定向 URL | | spring.cloud.sentinel.scg.fallback.response-body
| Spring Cloud Gateway 响应模式为 'response' 模式对应的响应内容 | | spring.cloud.sentinel.scg.fallback.response-status
| Spring Cloud Gateway 响应模式为 'response' 模式对应的响应码 | 429 | spring.cloud.sentinel.scg.fallback.content-type
| Spring Cloud Gateway 响应模式为 'response' 模式对应的 content-type | application/json |
如果不配置限流逻辑,则返回如下结果(DefaultBlockRequestHandler.class)
{
"code": 429,
"message": "Blocked by Sentinel: ParamFlowException"
}
①、response 响应模式
reponse 返回错误数据
spring:
cloud:
sentinel:
scg:
fallback:
content-type: application/json
response-status: 429
response-body: spring-cloud-gateway-study 服务限流
mode: response
②、redirect 响应模式
redirect 重定向到新的页面
spring:
cloud:
sentinel:
scg:
fallback:
redirect: https://blog.csdn.net/qq_41538097/article/details/124447411
mode: redirect
2、自定义网关限流处理逻辑
GatewayCallbackManager 注册回调进行定制:
- setBlockHandler:注册函数用于实现自定义的逻辑处理被限流的请求,对应接口为 BlockRequestHandler。
- 默认实现为 DefaultBlockRequestHandler,当被限流时会返回类似于下面的错误信息:Blocked by Sentinel: FlowException。
新增 DiyBlockRequestHandler 类实现自定义网关限流处理逻辑
@Configuration
public class DiyBlockRequestHandler implements BlockRequestHandler {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable e) {
String msg = null;
if (e instanceof FlowException) {
msg = "限流了,请稍后访问";
} else if (e instanceof DegradeException) {
msg = "降级了,返回默认数据";
} else if (e instanceof ParamFlowException) {
msg = "热点参数限流";
} else if (e instanceof SystemBlockException) {
msg = "系统规则(负载/...不满足要求)";
} else if (e instanceof AuthorityException) {
msg = "授权规则不通过";
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("msg", msg);
jsonObject.put("code",HttpStatus.TOO_MANY_REQUESTS);
Map<Object, Object> map = new HashMap<>();
map.put("username", "admin");
map.put("address", "西安");
jsonObject.put("mock", map);
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(jsonObject));
}
@PostConstruct
public void initBlockRequestHandler() {
GatewayCallbackManager.setBlockHandler(this);
}
}
结果如下
五、网关流控实现原理
当通过 GatewayRuleManager 加载网关流控规则(GatewayFlowRule)时,无论是否针对请求属性进行限流,Sentinel 底层都会将网关流控规则转化为热点参数规则(ParamFlowRule),存储在 GatewayRuleManager 中,与正常的热点参数规则相隔离。转换时 Sentinel 会根据请求属性配置,为网关流控规则设置参数索引(idx),并同步到生成的热点参数规则中。
外部请求进入 API Gateway 时会经过 Sentinel 实现的 filter,其中会依次进行 路由/API 分组匹配、请求属性解析和参数组装。Sentinel 会根据配置的网关流控规则来解析请求属性,并依照参数索引顺序组装参数数组,最终传入 SphU.entry(res, args) 中。Sentinel API Gateway Adapter Common 模块向 Slot Chain 中添加了一个 GatewayFlowSlot,专门用来做网关规则的检查。GatewayFlowSlot 会从 GatewayRuleManager 中提取生成的热点参数规则,根据传入的参数依次进行规则检查。若某条规则不针对请求属性,则会在参数最后一个位置置入预设的常量,达到普通流控的效果。
未完待续
|