Sentinel 规则持久化,基于Redis持久化【附带源码】

B站视频讲解

学习链接🔗

文章目录

上一篇讲Sentinel的时候,是用dashboard直接和服务进行交互,实时的新增/删除限流规则,所有的规则都存储在应用服务的内存中,每次重启服务之后再刷新dashboard就没有对应规则信息了。

这当然不是我们希望看到的,如果想要长久的保存规则,有且只有一个办法,那就是规则数据持久化。

一、理论

  1. 既然dashboard可以把规则推送到服务端,那服务端就可以拿到规则去持久化到硬盘上(文件、MySQL...),怎么说呢?这看起来不是个好办法,首先它很low,其次也不好解决分布式系统数据一致性的问题。
  2. 那换一种思路,dashboard先把规则推送给A,再由A把规则下发到各个具体的应用服务。这样A就相当于一种中心存储,解决了数据存储的问题,同时A实时下发给应用服务解决了数据一致性的问题。

这个A,官方给出了几种实现Nacos、ZooKeeper、Apollo、Redis。(理论上是可以自己去重写做到任何实现)

第一种方式就不推荐了,下面基于方式二来做实践,这里选用Redis来,主要是目前电脑只安装了Redis,原理是一样的。

下面是官方给出的图,要理解,是先把规则给到A,再由A去下发规则。(所以需要修改dashboard源码,让它先请求A)

想深入了解的可以参看下面的文档:

  1. https://github.com/alibaba/Sentinel/wiki/动态规则扩展
  2. https://github.com/alibaba/Sentinel/wiki/在生产环境中使用-Sentinel

二、实践

基于上面的分析,需要分两步改造

  1. 让dashboard把规则数据推送给Redis
  2. 应用服务接受Redis的下发(基于Redis的发布订阅功能)

注:会在Redis定义一个Key用来存储最终的规则数据,还会定义一个通道用来实时推送规则数据

2-1、dashboard 请求Redis

从Github下载dashboard源码:https://github.com/alibaba/Sentinel/releases

默认情况下,dashboard限流策略请求的是这个 v1 接口,官方还提供了一个 v2,这个v2就是持久化的接口,基于不同的持久化策略,只需要在 v2的版本里面替换对应的 DynamicRuleProvider、DynamicRulePublisher

2-1-1、依赖、配置文件引入

1、Redis-starter 引入

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>${spring.boot.version}</version>
</dependency>

2、配置文件修改

yml 复制代码
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0

3、Redis配置

java 复制代码
@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;
    }
}

2-1-2、常量定义

java 复制代码
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";
}

2-1-3、改写唯一id

java 复制代码
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);
    }
}

2-1-4、新Provider和Publisher

Provider的目的是读取Redis的内存数据

java 复制代码
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通道发布

java 复制代码
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);
    }
}

2-1-5、改写V2

  1. 刚刚说默认是请求V1版本,这里为了简单,直接把V1的@RequestMapping注释,把V2的@RequestMapping改成V1(可以改前端,让它请求到V2)
  2. 把新 V1的Publisher和Provider改成新的Redis版本
  3. 下图也是每个类的位置

2-2、应用服务改造

  1. https://github.com/alibaba/Sentinel/wiki/动态规则扩展
  2. Sentinel 官方已经做了Redis适配,使用起来也很简单了

2-2-1、依赖、配置文件引入

1、新增pom文件

xml 复制代码
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-redis</artifactId>
    <version>1.8.6</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置文件

yml 复制代码
spring:
  redis:
    host: 127.0.0.1
    port: 6379

3、Redis 配置文件(和上面一样)

java 复制代码
@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;
    }
}

2-2-2、注册监听器

直接在启动类里面改造就好了

java 复制代码
@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-持久化开启");
    }
}

三、源码获取

3-1、使用

下面是修改好的sentinel-dashboard和对应的应用服务,只需要修改两个服务中的Redis连接地址就可以使用,如果你的Redis是 默认的127.0.0.1 和 6379 则无需修改。

3-2、获取方式

  1. 关注微信公众号:小道仙97
  2. 回复关键字:Sentinel_xdx97

四、参考

相关推荐
zpjing~.~1 小时前
Mongo 分页判断是否有下一页
数据库
2401_857600951 小时前
技术与教育的融合:构建现代成绩管理系统
数据库·oracle
秋恬意1 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
潇湘秦2 小时前
一文了解Oracle数据库如何连接(1)
数据库·oracle
雅冰石2 小时前
oracle怎样使用logmnr恢复误删除的数据
数据库·oracle
web前端神器2 小时前
mongodb给不同的库设置不同的密码进行连接
数据库·mongodb
从以前2 小时前
Berlandesk 注册系统算法实现与解析
数据库·oracle
Muko_0x7d22 小时前
Mongodb
数据库·mongodb
Ren_xixi2 小时前
redis和mysql的区别
数据库·redis·mysql
m0_748233882 小时前
SQL语句整理五-StarRocks
数据库·sql