关键词 Spring cloud Sentinel zuul gateway redis dashboard Sentinel redis 限流
开始使用
修改的地方有两个,一个是Sentinel dashboard项目,一个是你自己的项目(客户端)
(不难,不难,不难,只要把文章看完就懂了,文章后面有修改dashboard的代码下载)
修改Sentinel dashboard项目
首先你需要down下来Sentinel整个项目源码,本文使用的为Sentinel 1.8.4的源码,源码下载路径为:官方Sentinel ,下载后请导入到开发工具(Eclipse 或者 idea)中准备修改源码并编译。
导入的项目结构如下:
我们将将要修改的代码就在sentinel-dashboard项目中。
此项目中要修改得地方有
-
application.properties(增加Redis配置参数)
-
pom.xml(增加Redis的依赖)
-
GatewayFlowRuleRedisProvider.java(新增,自定义的Redis拉取规则)
-
GatewayFlowRuleRedisPublisher.java(新增,自定义的Redis推送规则)
-
RedisConfig.java(新增,定义的Redis配置类)
-
RuleConstants.java(新增,Redis的常量)
- GatewayApiController.java(修改,修改api推送逻辑和拉取逻辑)
- GatewayFlowRuleController.java(修改,修改规则推送逻辑和拉取逻辑)
所有的修改内容已经罗列在上面了,下面咱们就详细说说如何修改:
application.properties文件
此文件在sentinel-dashboard项目下的resource中,在此文件最下面添加下面的配置:
# redis配置
spring.redis.database=0
spring.redis.host=localhost
spring.redis.password=密码
spring.redis.port=6379
spring.redis.timeout=3000
#spring.redis.jedis.pool.max-active=8
#spring.redis.jedis.pool.max-wait=1
#spring.redis.jedis.pool.max-idle=8
#spring.redis.jedis.pool.min-idle=2
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=1
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=2
注意,上面的配置中,前四个配置为redis的配置,不在解释,自己修改成自己的就可以
pom.xml
此文件在sentinel-dashboard项目下,请添加以下依赖:
<!-- 集成redis -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-redis</artifactId>
<version>1.8.4</version>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.5.12</version>
</dependency>
<!-- lettuce pool 缓存连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.2</version>
</dependency>
注意,此处的${spring.boot.version}表示使用此项目本身配置的版本,以适应他的jar要求,防止jar依赖出问题。
GatewayApiRedisProvider.java
此文件新增,新增的位置com.alibaba.csp.sentinel.dashboard.rule.redis(注意,redis路径需要新增),如图:
上面所说的几个新增的类同样需要放在此路径下,这个类的作用是自定义基于Redis实现拉取限流规则的逻辑
package com.alibaba.csp.sentinel.dashboard.rule.redis;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义实现基于redis的拉取规则
*
* @autheor lingguoxiong
* @date 2022/10/9 23:08
*/
@Component("gatewayApiRedisProvider")
public class GatewayApiRedisProvider implements DynamicRuleProvider<List<ApiDefinitionEntity>> {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public List<ApiDefinitionEntity> getRules(String appName) {
System.out.println("Sentinel 从Redis拉取api begin >>>>>>>>>>>>>>>>>>>>");
String value = JSON.toJSONString(redisTemplate.opsForValue().get(RuleConstants.gatewayApi + appName));
if (StringUtils.isEmpty(value)) {
return new ArrayList<>();
}
System.out.println("Sentinel 从Redis拉取api end >>>>>>>>>>>>>>>>>>>>");
return JSONObject.parseArray(value, ApiDefinitionEntity.class);
}
}
GatewayApiRedisPublisher.java
此文件新增,新增的位置com.alibaba.csp.sentinel.dashboard.rule.redis,这个类的作用是自定义基于Redis实现推送限流规则的逻辑
package com.alibaba.csp.sentinel.dashboard.rule.redis;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 自定义实现限流配置推送规则
*
* @autheor lingguoxiong
* @date 2022/10/9 23:11
*/
@Component("gatewayApiRedisPublisher")
public class GatewayApiRedisPublisher implements DynamicRulePublisher<List<ApiDefinitionEntity>> {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public void publish(String app, List<ApiDefinitionEntity> rules) {
System.out.println("Sentinel 向Redis推送api begin >>>>>>>>>>>>>>>>>>>>" + rules);
if (rules == null) {
return;
}
System.out.println("rules:" + rules);
// 使用RedisTemplate 的默认配置,即不开启事务支持。但是,我们可以通过使用 SessionCallback,
// 该接口保证其内部所有操作都是在同一个Session中
List<Object> txResults = redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForValue().set(RuleConstants.gatewayApi + app, rules);
operations.convertAndSend(RuleConstants.gatewayApiChannel + app, rules);
// This will contain the results of all operations in the transaction
return operations.exec();
}
});
System.out.println("Sentinel 向Redis推送api end >>>>>>>>>>>>>>>>>>>>" + txResults);
}
}
RedisConfig.java
此文件新增,新增的位置com.alibaba.csp.sentinel.dashboard.rule.redis,这个类的作用是定义Redis的配置(这个类可用可不用)
package com.alibaba.csp.sentinel.dashboard.rule.redis;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author lingguoxiong
* @date 2021/12/20 18:08
*/
@Configuration
public class RedisConfig {
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(keySerializer());
redisTemplate.setHashKeySerializer(keySerializer());
redisTemplate.setValueSerializer(valueSerializer());
redisTemplate.setHashValueSerializer(valueSerializer());
return redisTemplate;
}
@Bean(name = "stringRedisTemplate")
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(keySerializer());
redisTemplate.setHashKeySerializer(keySerializer());
redisTemplate.setValueSerializer(valueSerializer());
redisTemplate.setHashValueSerializer(valueSerializer());
return redisTemplate;
}
private RedisSerializer<String> keySerializer() {
return new StringRedisSerializer();
}
private RedisSerializer<Object> valueSerializer() {
return new Jackson2JsonRedisSerializer(Object.class);
}
}
RuleConstants.java
此文件新增,新增的位置com.alibaba.csp.sentinel.dashboard.rule.redis,这个类的作用是定义Redis中使用的常量
package com.alibaba.csp.sentinel.dashboard.rule.redis;
import org.springframework.stereotype.Component;
/**
* @autheor lingguoxiong
* @date 2022/10/9 23:19
*/
@Component
public class RuleConstants {
/**
* 流控规则key前缀
*/
public static final String gatewayRuleFlow = "sentinel_gateway_rule_flow_";
public static final String gatewayRuleFlowChannel = "sentinel_gateway_rule_flow_channel_";
public static final String gatewayApi = "sentinel_gateway_api_";
public static final String gatewayApiChannel = "sentinel_gateway_api_channel_";
}
GatewayApiController.java
注意,这个类原本就存在,位于com.alibaba.csp.sentinel.dashboard.controller路径下,我们需要修改它的推送逻辑和拉取逻辑(此类基于sentinel 1.8.4修改,如果未来的您使用的版本比这个高,或者地,请谨慎修改---只动推送和拉取的逻辑)
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.dashboard.controller.gateway;
import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiPredicateItemEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.AddApiReqVo;
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.ApiPredicateItemVo;
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.UpdateApiReqVo;
import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemApiDefinitionStore;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*;
/**
* Gateway api Controller for manage gateway api definitions.
*
* @author cdfive
* @since 1.7.0
*/
@RestController
@RequestMapping(value = "/gateway/api")
public class GatewayApiController {
private final Logger logger = LoggerFactory.getLogger(GatewayApiController.class);
@Autowired
private InMemApiDefinitionStore repository;
@Autowired
private SentinelApiClient sentinelApiClient;
//-----------新增逻辑,服务于redis持久化---------------
@Autowired
@Qualifier("gatewayApiRedisProvider")
private DynamicRuleProvider<List<ApiDefinitionEntity>> ruleProvider;
@Autowired
@Qualifier("gatewayApiRedisPublisher")
private DynamicRulePublisher<List<ApiDefinitionEntity>> rulePublisher;
//-----------新增逻辑,服务于redis持久化---------------
@GetMapping("/list.json")
@AuthAction(AuthService.PrivilegeType.READ_RULE)
public Result<List<ApiDefinitionEntity>> queryApis(String app, String ip, Integer port) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isEmpty(ip)) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (port == null) {
return Result.ofFail(-1, "port can't be null");
}
// 修改的地方 获取redis里面api的数据
try {
List<ApiDefinitionEntity> apis = ruleProvider.getRules(app);
repository.saveAll(apis);
return Result.ofSuccess(apis);
} catch (Throwable throwable) {
logger.error("queryApis error:", throwable);
return Result.ofThrowable(-1, throwable);
}
}
@PostMapping("/new.json")
@AuthAction(AuthService.PrivilegeType.WRITE_RULE)
public Result<ApiDefinitionEntity> addApi(HttpServletRequest request, @RequestBody AddApiReqVo reqVo) {
String app = reqVo.getApp();
if (StringUtil.isBlank(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
ApiDefinitionEntity entity = new ApiDefinitionEntity();
entity.setApp(app.trim());
String ip = reqVo.getIp();
if (StringUtil.isBlank(ip)) {
return Result.ofFail(-1, "ip can't be null or empty");
}
entity.setIp(ip.trim());
Integer port = reqVo.getPort();
if (port == null) {
return Result.ofFail(-1, "port can't be null");
}
entity.setPort(port);
// API名称
String apiName = reqVo.getApiName();
if (StringUtil.isBlank(apiName)) {
return Result.ofFail(-1, "apiName can't be null or empty");
}
entity.setApiName(apiName.trim());
// 匹配规则列表
List<ApiPredicateItemVo> predicateItems = reqVo.getPredicateItems();
if (CollectionUtils.isEmpty(predicateItems)) {
return Result.ofFail(-1, "predicateItems can't empty");
}
List<ApiPredicateItemEntity> predicateItemEntities = new ArrayList<>();
for (ApiPredicateItemVo predicateItem : predicateItems) {
ApiPredicateItemEntity predicateItemEntity = new ApiPredicateItemEntity();
// 匹配模式
Integer matchStrategy = predicateItem.getMatchStrategy();
if (!Arrays.asList(URL_MATCH_STRATEGY_EXACT, URL_MATCH_STRATEGY_PREFIX, URL_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy);
}
predicateItemEntity.setMatchStrategy(matchStrategy);
// 匹配串
String pattern = predicateItem.getPattern();
if (StringUtil.isBlank(pattern)) {
return Result.ofFail(-1, "pattern can't be null or empty");
}
predicateItemEntity.setPattern(pattern);
predicateItemEntities.add(predicateItemEntity);
}
entity.setPredicateItems(new LinkedHashSet<>(predicateItemEntities));
// 检查API名称不能重复
List<ApiDefinitionEntity> allApis = repository.findAllByMachine(MachineInfo.of(app.trim(), ip.trim(), port));
if (allApis.stream().map(o -> o.getApiName()).anyMatch(o -> o.equals(apiName.trim()))) {
return Result.ofFail(-1, "apiName exists: " + apiName);
}
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
} catch (Throwable throwable) {
logger.error("add gateway api error:", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishApis(app, ip, port)) {
logger.warn("publish gateway apis fail after add");
}
return Result.ofSuccess(entity);
}
@PostMapping("/save.json")
@AuthAction(AuthService.PrivilegeType.WRITE_RULE)
public Result<ApiDefinitionEntity> updateApi(@RequestBody UpdateApiReqVo reqVo) {
String app = reqVo.getApp();
if (StringUtil.isBlank(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
Long id = reqVo.getId();
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
ApiDefinitionEntity entity = repository.findById(id);
if (entity == null) {
return Result.ofFail(-1, "api does not exist, id=" + id);
}
// 匹配规则列表
List<ApiPredicateItemVo> predicateItems = reqVo.getPredicateItems();
if (CollectionUtils.isEmpty(predicateItems)) {
return Result.ofFail(-1, "predicateItems can't empty");
}
List<ApiPredicateItemEntity> predicateItemEntities = new ArrayList<>();
for (ApiPredicateItemVo predicateItem : predicateItems) {
ApiPredicateItemEntity predicateItemEntity = new ApiPredicateItemEntity();
// 匹配模式
int matchStrategy = predicateItem.getMatchStrategy();
if (!Arrays.asList(URL_MATCH_STRATEGY_EXACT, URL_MATCH_STRATEGY_PREFIX, URL_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
return Result.ofFail(-1, "Invalid matchStrategy: " + matchStrategy);
}
predicateItemEntity.setMatchStrategy(matchStrategy);
// 匹配串
String pattern = predicateItem.getPattern();
if (StringUtil.isBlank(pattern)) {
return Result.ofFail(-1, "pattern can't be null or empty");
}
predicateItemEntity.setPattern(pattern);
predicateItemEntities.add(predicateItemEntity);
}
entity.setPredicateItems(new LinkedHashSet<>(predicateItemEntities));
Date date = new Date();
entity.setGmtModified(date);
try {
entity = repository.save(entity);
} catch (Throwable throwable) {
logger.error("update gateway api error:", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishApis(app, entity.getIp(), entity.getPort())) {
logger.warn("publish gateway apis fail after update");
}
return Result.ofSuccess(entity);
}
@PostMapping("/delete.json")
@AuthAction(AuthService.PrivilegeType.DELETE_RULE)
public Result<Long> deleteApi(Long id) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
ApiDefinitionEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
} catch (Throwable throwable) {
logger.error("delete gateway api error:", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishApis(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
logger.warn("publish gateway apis fail after delete");
}
return Result.ofSuccess(id);
}
private boolean publishApis(String app, String ip, Integer port) {
//-----------新增逻辑,保存redis数据---------------
List<ApiDefinitionEntity> apis = repository.findAllByApp(app);
try {
//修改了redis中的存储及发布修改的规则,客户端app订阅channel后将规则保存在redis中
rulePublisher.publish(app, apis);
// GatewayRuleManager
logger.info("网关添加api成功{}");
} catch (Exception e) {
e.printStackTrace();
logger.warn("publishRules failed Sentinel 推送api到Redis失败>>>>>>>>>>>>>>>>>>>>>>>>", e);
return false;
}
//-----------新增逻辑,保存redis数据---------------
//核心代码,sentinel-dashboard通过http的形式进行数据推送,客户端接收后将规则保存在本地内存中
return sentinelApiClient.modifyApis(app, ip, port, apis);
}
}
在这个类中,我做了以下修改:
-
引入了以下依赖
//-----------新增逻辑,服务于redis持久化--------------- @Autowired @Qualifier("gatewayApiRedisProvider") private DynamicRuleProvider<List<ApiDefinitionEntity>> ruleProvider; @Autowired @Qualifier("gatewayApiRedisPublisher") private DynamicRulePublisher<List<ApiDefinitionEntity>> rulePublisher; //-----------新增逻辑,服务于redis持久化---------------
这两个就是上文中创建的Redis推送和拉取的类
-
修改queryApis方法
-
修改publishApis方法
这两出很重要,修改的时候需要重点关注,本类中其他的修改项不多,可以直接使用比对工具参考修改(如果您使用的是Sentinel 1.8.4,可以直接覆盖)。
至此,sentinel dashboard端的源码就修改完了(你可以执行mvn install 把这个源码编译成jar,然后运行方式和官方推荐方式相同,如果您不知道如何运行,请参考Spring cloud gateway 整合 sentinel 做限流和熔断(最新)解决。
客户端修改(你自己的项目)
这里说明下,网上很多资料写的乱七八糟,啥样的都有,更坑的是官方的文档写的也是很烂(要自己继承AbstractDataSource类,其实不用,因为最新的依赖中已经有一个实现类了,完全可以拿来用)
对于客户端,你需要做的是:
-
application.yml(增加sentinel和Redis配置)
-
pom.xml(增加redis和sentinel的依赖)
-
RedisDataSourceConfig(新增,应用启动增加Sentinel 和 redis 监听)
-
启动的时候添加 -Dcsp.sentinel.app.type=1
application.yml
请在此文件新增您的redis和sentinel配置,示例如下:
spring:
cloud:
sentinel:
transport: #dashboard地址
dashboard: localhost:8080 #对应自己的sentinel控制台端口
port: 8719 #您的项目和sentinel交互使用的端口
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
#jedis:
lettuce:
pool:
max-active: 8
max-idle: 8
min-wait: 0
timeout: 3000
配置这块不多说,redis客户端在于你自己的选择,sentinel配置请按照你自己的sentinel地址来配置
pom.xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
<!-- 控制台显示 类似 hystrix-dashboard -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-redis</artifactId>
<version>1.8.4</version>
</dependency>
ApiDefinitionDto.java类
package com.xdh.yjmthird.gateway.api;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import java.util.Objects;
import java.util.Set;
/**
* @autheor lingguoxiong
* @date 2022/10/15 21:41
*/
public class ApiDefinitionDto {
private String apiName;
private Set<ApiPathPredicateItem> predicateItems;
public String getApiName() {
return this.apiName;
}
public ApiDefinitionDto setApiName(String apiName) {
this.apiName = apiName;
return this;
}
public Set<ApiPathPredicateItem> getPredicateItems() {
return this.predicateItems;
}
public ApiDefinitionDto setPredicateItems(Set<ApiPathPredicateItem> predicateItems) {
this.predicateItems = predicateItems;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o != null && this.getClass() == o.getClass()) {
ApiDefinitionDto that = (ApiDefinitionDto) o;
return !Objects.equals(this.apiName, that.apiName) ? false : Objects.equals(this.predicateItems, that.predicateItems);
} else {
return false;
}
}
@Override
public int hashCode() {
int result = this.apiName != null ? this.apiName.hashCode() : 0;
result = 31 * result + (this.predicateItems != null ? this.predicateItems.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "ApiDefinition{apiName='" + this.apiName + '\'' + ", predicateItems=" + this.predicateItems + '}';
}
}
RedisDataSourceConfig.java
这里是唯一新增的类(类的位置你想放哪就放那,我不管,只要你老板不打你)
package com.xdh.yjmthird.gateway.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
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.datasource.Converter;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.redis.RedisDataSource;
import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.xdh.yjmthird.gateway.api.ApiDefinitionDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @autheor lingguoxiong
* @date 2022/10/18 22:25
*/
@Slf4j
@Component
public class RedisDataSourceConfig implements ApplicationRunner {
@Value("${spring.redis.host}")
public String redisHost;
@Value("${spring.redis.port}")
public int redisPort;
@Value("${spring.redis.password}")
public String redisPass;
public Integer database = 0;
//限流规则key前缀
public final String GATEWAY_RULE_FLOW = "sentinel_gateway_rule_flow_gateway";
public final String GATEWAY_RULE_FLOW_CHANNEL = "sentinel_gateway_rule_flow_channel_gateway";
public final String GATEWAY_API = "sentinel_gateway_api_gateway";
public final String GATEWAY_API_CHANNEL = "sentinel_gateway_api_channel_gateway";
//降级规则key前缀
// public final String RULE_DEGRADE = "sentinel_rule_degrade_";
// public final String RULE_DEGRADE_CHANNEL = "sentinel_rule_degrade_channel";
//系统规则key前缀
// public final String RULE_SYSTEM = "sentinel_rule_system_";
// public final String RULE_SYSTEM_CHANNEL = "sentinel_rule_system_channel";
/**
* ApplicationRunner
* 该接口的方法会在服务启动之后被立即执行
* 主要用来做一些初始化的工作
* 但是该方法的运行是在SpringApplication.run(…) 执行完毕之前执行
*/
@Override
public void run(ApplicationArguments args) throws Exception {
RedisConnectionConfig config = RedisConnectionConfig.builder().withHost(redisHost).withPort(redisPort)
.withPassword(redisPass).withDatabase(database).build();
initApis(config);
initRules(config);
// Converter<String, List<DegradeRule>> parserDegrade = source -> JSON.parseObject(source,
// new TypeReference<List<DegradeRule>>() {
// });
// ReadableDataSource<String, List<DegradeRule>> redisDataSourceDegrade = new
// RedisDataSource<>(config, RULE_DEGRADE + SentinelConfig.getAppName(),
// RULE_DEGRADE_CHANNEL, parserDegrade);
// DegradeRuleManager.register2Property(redisDataSourceDegrade.getProperty());
// Converter<String, List<SystemRule>> parserSystem = source -> JSON.parseObject(source, new
// TypeReference<List<SystemRule>>() {
// });
// ReadableDataSource<String, List<SystemRule>> redisDataSourceSystem = new
// RedisDataSource<>(config, RULE_SYSTEM + SentinelConfig.getAppName(), RULE_SYSTEM_CHANNEL,
// parserSystem);
// SystemRuleManager.register2Property(redisDataSourceSystem.getProperty());
log.info(">>>>>>>>>执行sentinel规则初始化 end。。。");
}
private void initApis(RedisConnectionConfig config) throws Exception {
log.info("执行sentinel网关api初始化 start >>>>>>>>>>>>>");
Converter<String, Set<ApiDefinition>> parser = source -> JSON.parseObject(source,
new TypeReference<Set<ApiDefinition>>() {
});
ReadableDataSource<String, Set<ApiDefinition>> redisDataSource = new RedisDataSource<>(config, GATEWAY_API,
GATEWAY_API_CHANNEL, parser);
GatewayApiDefinitionManager.register2Property(redisDataSource.getProperty());
// 把redis数据重新拿出来转换 并设置(不然不会生效,里面是有数据的,不知道为什么不生效,可能是末尾资料文章说的没有LISTENER导致的,这个有时间大家自己研究下)
Set<ApiDefinitionDto> apiDefinitionDtos = JSON.parseObject(redisDataSource.readSource(),
new TypeReference<Set<ApiDefinitionDto>>() {
});
if (apiDefinitionDtos != null) {
Set<ApiDefinition> apiDefinitions = apiDefinitionDtos.stream().map(apiDefinitionDto -> {
ApiDefinition apiDefinition = new ApiDefinition();
BeanUtils.copyProperties(apiDefinitionDto, apiDefinition);
return apiDefinition;
}).collect(Collectors.toSet());
GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions);
}
log.info("执行sentinel网关api初始化 end >>>>>>>>>>>>>");
}
private void initRules(RedisConnectionConfig config) throws Exception {
log.info("执行sentinel规则初始化 start >>>>>>>>>>>>>");
Converter<String, Set<GatewayFlowRule>> parser = source -> JSON.parseObject(source,
new TypeReference<Set<GatewayFlowRule>>() {
});
ReadableDataSource<String, Set<GatewayFlowRule>> redisDataSource = new RedisDataSource<>(config, GATEWAY_RULE_FLOW,
GATEWAY_RULE_FLOW_CHANNEL, parser);
GatewayRuleManager.register2Property(redisDataSource.getProperty());
// 把redis数据重新拿出来转换 并设置(不然不会生效,里面是有数据的,不知道为什么不生效,可能是末尾资料文章说的没有LISTENER导致的,这个有时间大家自己研究下)
GatewayRuleManager.register2Property(redisDataSource.getProperty());
// 下面的这个需要去掉,附件源码里面的也需要去掉,之前其他文章说的不生效是上面 // ApiDefinition转换的问题
// Set<GatewayFlowRule> sets1 = GatewayRuleManager.getRules();
// Set<GatewayFlowRule> sets2 = new HashSet();
// GatewayRuleManager.loadRules(sets2);
// GatewayRuleManager.loadRules(sets1);
log.info("执行sentinel规则初始化 end >>>>>>>>>>>>>");
}
}
这个类没有什么东西,最主要的代码就是初始化一个RedisConnectionConfig,并监听redis中的规则变化情况(注意,注释中的部分分别是熔断规则监听和数据规则监听,他们在sentinel dashboard中的修改本文没涉及,但是完全和限流规则使用方法完全一样,请自己实现),如果您有需要,可以放开此部分注释。
需要注意的是,这个类中引用的redis限流的KEY必须和sentinel dashboard中配置的一致,否则(呵呵,等自己骂娘吧)。
至此,客户端配置完成,分别启动自己的应用和编译好的sintinel dashboard项目,就能正常给你的项目添加限流配置,同时在redis中也能看到规则信息,并且重启您的项目后,sentinel dashboard也能加载出redis中缓存到规则(注意:此处会有一定的延迟,因为sentinel dashboard 需要定时任务去轮询redis中的规则,虽然不想放图毒害大家,但是还是要放上一两张证明我确实搞通了。
dashboard 修改的代码 (不需要积分)
https://download.csdn.net/download/Micheal_Bear/86780044
参考文章:
Sentinel的gateway规则持久化改造 - 灰信网(软件开发博客聚合)
sentinel实现gateway网关限流规则持久化 - 灰信网(软件开发博客聚合)
Spring cloud 整合Sentinel 实现redis缓存限流规则(最新一)|Redis 持久化 SentinelCDCN-码上中国博客
Spring cloud 整合Sentinel 实现redis缓存限流规则(最新二)|sentinel 持久化redisCDCN-码上中国博客