1.导入sentinel和nacos 依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
<!-- sentinel的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
2.新建对应sentinel限流、熔断值对象
```javascript
@Data
public class GatewaySentinelConfig {
/**
* 资源名称
*/
private String resource;
/**
* sentinel限流类型:1-api分组限流;0-路由模式限流
*/
private Integer resourceMode;
/**
* 限流熔断阈值
*/
private Double count;
/**
* 熔断策略类型;DEGRADE_GRADE_RT = 0:慢调用比例;
* DEGRADE_GRADE_EXCEPTION_RATIO = 1:异常比例
* DEGRADE_GRADE_EXCEPTION_COUNT = 2:异常数
*/
private Integer grade;
/**
* 熔断时长,单位为 s
*/
private Integer timeWindow;
/**
* 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断。
*
* 默认值:5
*/
private Integer minRequestAmount;
/*
* 统计时长(单位为 ms),如 60*1000 代表分钟级。
* 默认:1000 ms
* 注意与上面count值得联系,同时该时间段内的请求都会进入并参与统计。
* */
private Integer statIntervalMs;
/*
*慢调用比例阈值,仅慢调用比例模式有效
* */
private Double slowRatioThreshold;
}
3.实现CommandLineRunner接口,监听nacos变化,将更新后的规则重新加载进sentinel中,使用@Value去读取不同环境的nacos相关配置
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.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.cmcc.coc3.gateway.meta.route.client.GatewayInterfaceCache;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.Executor;
@Component
@Slf4j
public class DynamicFlowRulesServiceImplByNacos implements CommandLineRunner {
@Value("${spring.cloud.nacos.discovery.server-addr}")
private String naocsAddress;
@Value("${gatewayRoute.namespaceId}")
private String nacosNamespaceId;
@Value("${gatewayRoute.nacosDateId}")
private String nacosDataId;
@Value("${gatewayRoute.groupId}")
private String nacosGroupId;
@Override
public void run(String... args) throws Exception {
dynamicByNacosListener();
}
private ConfigService configService;
/**
* 监听Nacos Server下发的动态配置
*/
public void dynamicByNacosListener() {
if (configService == null) {
try {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, naocsAddress);
properties.put(PropertyKeyConst.NAMESPACE,nacosNamespaceId);
configService = NacosFactory.createConfigService(properties);
} catch (NacosException e) {
e.printStackTrace();
}
}
sentinelConfigByNacosListener();
}
/**
* 网关动态配置基础信息
*/
public void sentinelConfigByNacosListener() {
try {
String fristConfigInfo = configService.getConfigAndSignListener(nacosDataId, nacosGroupId, 5000, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
//nacos的限流值变化时,将会执行里面的方法
addListenerByConfig(nacosDataId, nacosGroupId, configInfo);
}
@Override
public Executor getExecutor() {
return null;
}
});
addListenerByConfig(nacosDataId, nacosGroupId, fristConfigInfo);
} catch (NacosException e) {
e.printStackTrace();
}
}
/**
*
*/
public void addListenerByConfig(String dataId, String groupId, String configInfo) {
log.info("DataId:" + dataId + ",GroupId:" + groupId + ",监听器获取的nacos-sentinel流控配置信息:" + configInfo);
List<GatewaySentinelConfig> gatewaySentinelConfigs = null;
try {
gatewaySentinelConfigs = new Gson().fromJson(configInfo, new TypeToken<List<GatewaySentinelConfig>>() {
}.getType());
} catch (JsonSyntaxException e) {
log.error("JsonSyntaxException", e, e.getMessage());
}
if (gatewaySentinelConfigs == null || gatewaySentinelConfigs.size() == 0) {
return;
}
//加载流控规则
initGatewaySentinelRules(gatewaySentinelConfigs);
}
public void initGatewaySentinelRules(List<GatewaySentinelConfig> gatewaySentinelConfigs) {
Set<GatewayFlowRule> rules = new HashSet<>();
Set<ApiDefinition> definitions = new HashSet<>();
for (GatewaySentinelConfig sentinelConfig : gatewaySentinelConfigs) {
//加载限流规则
rules.add(new
//注意,使用限流时资源名称,可以随便定义,我这里是使用的路由的接口路径,资源名称前缀不能带有特殊字符/,否则sentinel校验规则时,限流值会不准确
GatewayFlowRule(sentinelConfig.getResource())//资源名称
.setResourceMode(sentinelConfig.getResourceMode())//资源类型
.setCount(sentinelConfig.getCount()) // 限流阈值
.setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
);
if (sentinelConfig.getResourceMode() == 1) { //自定义API模式
ApiDefinition api1 = new ApiDefinition(sentinelConfig.getResource()).setPredicateItems(new HashSet<ApiPredicateItem>() {{
if (StringUtils.isNotBlank(sentinelConfig.getPattern())) {
//精准匹配,用于API分组限流模式
add(new ApiPathPredicateItem().setPattern(sentinelConfig.getPattern()).setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_EXACT));
}
}});
definitions.add(api1);
}
}
GatewayRuleManager.loadRules(rules);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}
这里就完成了sentinel的规则加载代码
4.编写配置类,自定义返回异常信息,实现BlockRequestHandler接口
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
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 org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpStatus;
import java.util.*;
@Configuration
public class SentinelGatewayConfig implements BlockRequestHandler {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public SentinelGatewayConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
//这是执行sentinel规则的拦截器,Gateway路由之前会先执行这个拦截器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
String path = serverWebExchange.getRequest().getPath().pathWithinApplication().value();
Map map = new HashMap();
map.put("error","Unauthorized");
map.put("path",path);
map.put("status",HttpStatus.TOO_MANY_REQUESTS.value());
map.put("imestamp",new Date());
if (throwable instanceof FlowException){
map.put("message","服务被限流");
} else if (throwable instanceof DegradeException) {
map.put("message","服务被熔断降级");
}
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS.value()).contentType(MediaType.APPLICATION_JSON).body(Mono.just(map), Map.class);
}
}
5.nacos中限流配置
代码中将修改后的限流配置推送到nacos中
先引入nacos依赖,然后在类中添加@Value注解,去读取不同环境的nacos配置,将用于去创建nacos链接
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.4.3</version>
</dependency>
@Value("${spring.cloud.nacos.discovery.server-addr}")
private String naocsAddress;
@Value("${gatewayRoute.namespaceId}")
private String nacosNamespaceId;
@Value("${gatewayRoute.nacosDateId}")
private String nacosDataId;
@Value("${gatewayRoute.groupId}")
private String nacosGroupId;
public void pullNaocs(String limitValue){
log.info("开始执行nacos推送");
if (configService == null) {
log.info("configService为空,开始创建nacos链接");
try {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, naocsAddress);
properties.put(PropertyKeyConst.NAMESPACE,nacosNamespaceId);
configService = NacosFactory.createConfigService(properties);
} catch (NacosException e) {
e.printStackTrace();
log.error("创建nacos链接出现未知异常:{}",e.getErrMsg());
}
}
boolean b = false;
try {
//GatewaySentinelConfigReq与sentinel限流配置相对应
List<GatewaySentinelConfigReq> configReqList = jsonToList(limitValue,GatewaySentinelConfigReq.class);
String json = new Gson().toJson(configReqList);
b = configService.publishConfig(nacosDataId, nacosGroupId, json);
log.info("推送新的配置,新的配置是:{},推送是否成功:{}",limitValue,b);
} catch (NacosException e) {
e.printStackTrace();
log.error("推送nacos配置出现未知异常:{}",e.getErrMsg());
}
}
public static <T> List<T> jsonToList(String jsonString, Class<T> clz){
Gson gson = new Gson();
JsonArray jsonArray = gson.fromJson(jsonString, JsonArray.class);
if (jsonArray.isJsonNull())return null;
List<T>beans = new ArrayList<>(jsonArray.size());
jsonArray.forEach(netJson->beans.add(new Gson().fromJson(netJson.toString(),clz)));
return beans;
}