一、安装sentinel
1.下载地址:sentinel v1.8.6
2.启动sentinel dashboard,执行以下命令:
java -Dcsp.sentinel.log.dir=D:\xxx\sentinel\logs -Dserver.port=9217 -Dcsp.sentinel.dashboard.server=localhost:9217 -Dcsp.sentinel.heartbeat.client.ip=localhost -Dproject.name=sentinel-dashboard -Dsentinel.dashboard.auth.username=sentinel -Dsentinel.dashboard.auth.password=sentinel -jar sentinel-dashboard-1.8.6.jar
参数解释:
-Dcsp.sentinel.log.dir:日志保存路径,默认:${user.home}/logs/csp/
-Dserver.port=9217:控制台服务端口,默认:8080
-Dcsp.sentinel.dashboard.server=localhost:9217:控制台访问端口,默认:8080
-Dcsp.sentinel.heartbeat.client.ip=localhost:客户端心跳ip,多网卡需要指定这个ip,否则启动后报错,可忽略
-Dproject.name=sentinel-dashboard:控制台显示名称
-Dsentinel.dashboard.auth.username=sentinel:控制台登录账号,默认:sentinel
-Dsentinel.dashboard.auth.password=sentinel:控制台登录密码,默认:sentinel
-jar sentinel-dashboard-1.8.6.jar:运行sentinel1.8.6 jar包
3.启动成功界面
4.登录控制台,登录账号和密码默认为sentinel,启动命令可自定义登录账号和密码。
二、Gateway网关pom.xml引入sentinel组件
org.springframework.cloud
spring-cloud-starter-gateway
3.1.4
com.alibaba.csp
sentinel-spring-cloud-gateway-adapter
1.8.6
com.alibaba.csp
sentinel-transport-simple-http
1.8.6
三、注入对应的 SentinelGatewayFilter 实例以及 SentinelGatewayBlockExceptionHandler即可,参考官方文档:https://sentinelguard.io/zh-cn/docs/api-gateway-flow-control.html。
@Configuration
public class GatewayConfiguration {
private final List viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@PostConstruct
public void doInit() {
initCustomizedApis();
}
private void initCustomizedApis() {
Set definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("some_customized_api")
.setPredicateItems(new HashSet() {{
add(new ApiPathPredicateItem().setPattern("/animal/**")
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("another_customized_api")
.setPredicateItems(new HashSet() {{
add(new ApiPathPredicateItem().setPattern("/system/**"));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
private void initGatewayRules() {
Set rules = new HashSet<>();
rules.add(new GatewayFlowRule("system-route")
.setCount(1) // 限流阈值
.setIntervalSec(1)); // 统计时间窗口,单位是秒,默认是 1 秒
rules.add(new GatewayFlowRule("animal-route")
.setCount(1) // 限流阈值
.setIntervalSec(1)); // 统计时间窗口,单位是秒,默认是 1 秒
GatewayRuleManager.loadRules(rules);
}
}
四、application.yaml配置路由
server:
port: 9211
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
routes:
# Add your routes here.
- id: system_route
uri: lb://system
predicates:
- Path=/system/**
- id: animal_route
uri: lb://animal
predicates:
- Path=/animal/**
五、启动网关Gateway、服务,触发限流
六、嗯哼?sentinel dashboard没有显示嘛,怎么回事?
按官方文档说明,启动配置VM参数增加:
# 注:通过 Spring Cloud Alibaba Sentinel 自动接入的 API Gateway 整合则无需此参数
-Dcsp.sentinel.app.type=1
但是!!!好像也不行!!还必须增加一个参数,指向控制台地址:
-Dcsp.sentinel.dashboard.server=localhost:9001
注意:对网关发起请求后,需要等待大概10秒左右,才会在sentinel dashboard看到网关流控控制面板。
七、小小完善
1.第六节说到,需要增加启动配置,这里有4种解决方案。
方案一,启动类Application增加以下参数:
System.setProperty(SentinelConfig.APP_TYPE_PROP_KEY, "1");
System.setProperty("csp.sentinel.dashboard.server","localhost:9001");
System.setProperty(SentinelConfig.PROJECT_NAME_PROP_KEY,"gateway-dashboard");
方案二,启动配置VM增加如下参数:
-Dcsp.sentinel.app.type=1 -Dcsp.sentinel.dashboard.server=localhost:9001 -Dproject.name=gateway-dashboard
方案三,新增sentinel.properties配置文件,详情参阅com.alibaba.csp.sentinel.config.SentinelConfigLoader加载配置逻辑,配置内容如下:
project.name=gateway-dashboard # 控制台显示名称
csp.sentinel.app.type=1 # 指定类型为gateway网关类型
csp.sentinel.dashboard.server=localhost:9211 # 指定sentinel dashboard控制台地址
方案四,application.yaml增加自定义配置参数,再参照方案一
1.自定义限流全局异常
新增异常回调:
package com.akim.cloud.gateway.common.sentinel.handler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
public class SentinelFallbackHandler implements WebExceptionHandler {
private Mono writeResponse(ServerResponse response, ServerWebExchange exchange) {
ServerHttpResponse serverHttpResponse = exchange.getResponse();
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
byte[] datas = "{'code':429, 'msg':'系统繁忙,请稍后再试!'}".getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
return serverHttpResponse.writeWith(Mono.just(buffer));
}
@Override
public Mono handle(ServerWebExchange exchange, Throwable ex) {
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
}
if (!BlockException.isBlockException(ex)) {
return Mono.error(ex);
}
return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange));
}
private Mono handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
}
}
2.改造GatewayConfiguration
可以看出上面配置api限流和服务分组时,很不友好,直接读取application.yaml gateway配置的routes。
新增application.yaml自定义配置:
akim:
# gateway网关限流
sentinel:
enabled: true # 是否开启网关限流,默认true
count: 10 # 限流阈值,Double类型
intervalSec: 1 # 统计时间窗口,单位:秒,Long类型,默认1秒
新增配置文件对象类SentinelProperties.java:
package com.akim.cloud.gateway.common.sentinel.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import javax.validation.constraints.NotNull;
@Data
@ConfigurationProperties("akim.sentinel")
public class SentinelProperties {
@NotNull(message = "限流阈值")
private Double count;
@NotNull(message = "统计时间窗口,单位:秒")
private Long intervalSec;
}
b.注入全局异常拦截
c.直接上代码
package com.akim.cloud.gateway.common.sentinel.handler;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.akim.cloud.gateway.common.sentinel.config.SentinelProperties;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@EnableConfigurationProperties(SentinelProperties.class)
@ConditionalOnProperty(prefix = "akim.sentinel", name = "enabled", matchIfMissing = true) // 如果配置文件属性值为false,则不注入
@Configuration
@Slf4j
public class GatewayConfiguration {
private final List viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
protected final RouteDefinitionLocator routeDefinitionLocator;
private final SentinelProperties sentinelProperties;
public SentinelBlockHandler(ObjectProvider> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer,
RouteDefinitionLocator routeDefinitionLocator, SentinelProperties sentinelProperties) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
this.routeDefinitionLocator = routeDefinitionLocator;
this.sentinelProperties = sentinelProperties;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelFallbackHandler sentinelGatewayExceptionHandler() {
return new SentinelFallbackHandler();
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@PostConstruct
public void doInit() {
// 限流api
initCustomizedApis();
// 服务分组流控
initGatewayRules();
}
private void initCustomizedApis() {
Set definitions = new HashSet<>();
List routeDefinitions = routeDefinitionLocator.getRouteDefinitions().collectList().block();
routeDefinitions.stream().forEach(route -> {
PredicateDefinition pathDefinition = CollUtil.findOne(route.getPredicates(),
predicateDefinition -> "Path".equals(predicateDefinition.getName()));
if (pathDefinition == null) {
log.info("[sentinel][Route({}) 没有 Path 条件,忽略接口限流]", route.getId());
return;
}
String path = pathDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0");
if (StrUtil.isEmpty(path)) {
log.info("[sentinel][Route({}) Path 的值为空,忽略接口限流]", route.getId());
return;
}
definitions.add(new ApiDefinition(route.getId() + "-api")
.setPredicateItems(new HashSet() {
{
// 匹配 Path 前缀以及其子路径的所有请求
add(new ApiPathPredicateItem().setPattern(path)
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); // 匹配前缀,不设置则认为完全匹配
}
}));
});
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
/**
* 网关限流规则
* GatewayFlowRule参数:
* resource: 资源名称,可以是网关中的route名称或者用户自定义的API分组名称。
* resourceMode: 资源模型,限流规则则是针对API Gateway的 route(RESOURCE_MODE_ROUTE_ID)还是用户在 Sentinel 中定义的API分组(RESOURCE_MODE_CUSTOM_API_NAME),默认route。
* grade:限流指标维度,同限流规则的 grade 字段。
* count:限流阈值。
* intervalSec: 统计时间窗口,单位是秒, 默认是1秒。
* controlBehavior: 流量整形的控制效果,同限流规则的 controlBehavior字段,目前支持快速失败和匀速排队两种模式,默认快速失败。
* burst: 应对突发请求时额外允许的请求数目。
* maxQueueingTimeoutMs:匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。
* paramItem: 参数限流配置。若不提供,则代表针对参数进行限流,该网关规则将会被转换成普通流控规则;否则会转换热点规则。其中的字段如下:
* parseStrategy: 从请求中提取参数的策略,目前支持提取来源IP(PARAM_PARSE_STRATEGY_CLIENT_IP)、Host(PARAM_PARSE_STRATEGY_HOST)、任意Header(PARAM_PARSE_STRATEGY_HEADER)和任意URL 参数(PARAM_PARSE_STRATEGY_URL_PARAM)四种模式。
* fieldName:若提取策略选择Header模式或者URL参数模式,则需要指定对应的Header名称或URL参数名称。
* pattern和matchStrategy: 为后续参数匹配特性预留,目前末实现。
*/
private void initGatewayRules() {
Set rules = new HashSet<>();
List definitions = routeDefinitionLocator.getRouteDefinitions().collectList().block();
definitions.stream().forEach(route -> {
if (StrUtil.isEmpty(route.getId())) {
log.info("[sentinel][Route 没有 Id 条件,忽略接口限流]");
return;
}
rules.add(new GatewayFlowRule(route.getId())
.setCount(sentinelProperties.getCount()) // 限流阈值
.setIntervalSec(sentinelProperties.getIntervalSec())); // 统计时间窗口,单位是秒,默认是 1 秒
});
// 加载网关限流规则
GatewayRuleManager.loadRules(rules);
}
}
搞定!