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

四、参考

相关推荐
movie__movie13 分钟前
Spring AI MCP 客户端实战:轻松连接高德地图等工具
数据库·人工智能·spring
清风198115 分钟前
kafka消息可靠性传输语义
数据库·分布式·kafka
数据智能老司机2 小时前
CockroachDB权威指南——SQL调优
数据库·分布式·架构
数据智能老司机2 小时前
CockroachDB权威指南——应用设计与实现
数据库·分布式·架构
数据智能老司机3 小时前
CockroachDB权威指南——CockroachDB 模式设计
数据库·分布式·架构
数据智能老司机21 小时前
CockroachDB权威指南——CockroachDB SQL
数据库·分布式·架构
数据智能老司机1 天前
CockroachDB权威指南——开始使用
数据库·分布式·架构
松果猿1 天前
空间数据库学习(二)—— PostgreSQL数据库的备份转储和导入恢复
数据库
Kagol1 天前
macOS 和 Windows 操作系统下如何安装和启动 MySQL / Redis 数据库
redis·后端·mysql
无名之逆1 天前
Rust 开发提效神器:lombok-macros 宏库
服务器·开发语言·前端·数据库·后端·python·rust