上一篇讲Sentinel的时候,是用dashboard直接和服务进行交互,实时的新增/删除限流规则,所有的规则都存储在应用服务的内存中,每次重启服务之后再刷新dashboard就没有对应规则信息了。
这当然不是我们希望看到的,如果想要长久的保存规则,有且只有一个办法,那就是规则数据持久化。
这个A,官方给出了几种实现Nacos、ZooKeeper、Apollo、Redis。(理论上是可以自己去重写做到任何实现)
第一种方式就不推荐了,下面基于方式二来做实践,这里选用Redis来,主要是目前电脑只安装了Redis,原理是一样的。

下面是官方给出的图,要理解,是先把规则给到A,再由A去下发规则。(所以需要修改dashboard源码,让它先请求A)
想深入了解的可以参看下面的文档:
基于上面的分析,需要分两步改造
注:会在Redis定义一个Key用来存储最终的规则数据,还会定义一个通道用来实时推送规则数据
从Github下载dashboard源码:https://github.com/alibaba/Sentinel/releases
默认情况下,dashboard限流策略请求的是这个 v1 接口,官方还提供了一个 v2,这个v2就是持久化的接口,基于不同的持久化策略,只需要在 v2的版本里面替换对应的 DynamicRuleProvider、DynamicRulePublisher

1、Redis-starter 引入
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
<version>${spring.boot.version}version>
dependency>
2、配置文件修改
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0
3、Redis配置
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setEnableTransactionSupport(true);
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
public final class Constants {
// 最终规则是存储的key
public static final String RULE_FLOW_PREFIX = "sentinel:rule:flow:xdx";
// Redis的订阅发布功能,需要一个通道
public static final String RULE_FLOW_CHANNEL_PREFIX = "sentinel:channel:flow:xdx";
// 每一个规则都需要唯一id,基于Redis生成id
public static final String RULE_FLOW_ID_KEY = "sentinel:id:flow:xdx";
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class RedisIdGenerator {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public long nextId(String key) {
return redisTemplate.opsForValue().increment(key, 1);
}
}

Provider的目的是读取Redis的内存数据
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.util.Collections;
import java.util.List;
import static com.alibaba.csp.sentinel.dashboard.xdx.Constants.RULE_FLOW_PREFIX;
@Component("flowRuleRedisProvider")
public class FlowRuleRedisProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {
private final Logger logger = LoggerFactory.getLogger(FlowRuleRedisProvider.class);
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public List<FlowRuleEntity> getRules(String appName) throws Exception {
Assert.notNull(appName, "应用名称不能为空");
logger.info("拉取redis流控规则开始: {}", appName);
String key = RULE_FLOW_PREFIX;
String ruleStr = (String)redisTemplate.opsForValue().get(key);
if(StringUtils.isEmpty(ruleStr)) {
return Collections.emptyList();
}
List<FlowRuleEntity> rules = JSON.parseArray(ruleStr, FlowRuleEntity.class);
logger.info("拉取redis流控规则成功, 规则数量: {}", rules.size());
return rules;
}
}
每次规则变动,都把最新规则存到Redis里面去,并使用Redis通道发布
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.util.List;
import static com.alibaba.csp.sentinel.dashboard.xdx.Constants.RULE_FLOW_CHANNEL_PREFIX;
import static com.alibaba.csp.sentinel.dashboard.xdx.Constants.RULE_FLOW_PREFIX;
@Component("flowRuleRedisPublisher")
public class FlowRuleRedisPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {
private final Logger logger = LoggerFactory.getLogger(FlowRuleRedisPublisher.class);
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
Assert.notNull(app, "应用名称不能为空");
Assert.notEmpty(rules, "策略规则不为空");
logger.info("推送流控规则开始, 应用名: {}, 规则数量: {}", app, rules.size());
String ruleKey = RULE_FLOW_PREFIX;
String ruleStr = JSON.toJSONString(rules);
// 数据存储
redisTemplate.opsForValue().set(ruleKey, ruleStr);
// 数据发布
redisTemplate.convertAndSend(RULE_FLOW_CHANNEL_PREFIX, ruleStr);
}
}

1、新增pom文件
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-redisartifactId>
<version>1.8.6version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
2、配置文件
spring:
redis:
host: 127.0.0.1
port: 6379
3、Redis 配置文件(和上面一样)
@Configuration
public class ConfigRedis {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory)
{
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setEnableTransactionSupport(true);
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
直接在启动类里面改造就好了
@SpringBootApplication
public class App11 implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(App11.class, args);
}
public static final String RULE_FLOW_PREFIX = "sentinel:rule:flow:xdx";
public static final String RULE_FLOW_CHANNEL_PREFIX = "sentinel:channel:flow:xdx";
@Override
public void run(ApplicationArguments args) {
Converter<String ,List<FlowRule>> parser = source -> {
List<FlowRule> flowRules = new ArrayList<>();
if (source != null) {
String replace = source.replace("\\", "");
String substring = replace.substring(1, replace.length() - 1);
flowRules = JSON.parseArray(substring, FlowRule.class);
}
return flowRules;
};
RedisConnectionConfig config = RedisConnectionConfig.builder()
.withHost("127.0.0.1")
.withPort(6379)
.build();
ReadableDataSource<String, List<FlowRule>> redisDataSource = new RedisDataSource<>(config, RULE_FLOW_PREFIX, RULE_FLOW_CHANNEL_PREFIX, parser);
FlowRuleManager.register2Property(redisDataSource.getProperty());
System.out.println("redis-sentinel-持久化开启");
}
}
下面是修改好的sentinel-dashboard和对应的应用服务,只需要修改两个服务中的Redis连接地址就可以使用,如果你的Redis是 默认的127.0.0.1 和 6379 则无需修改。
