• 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 引入

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-redisartifactId>
        <version>${spring.boot.version}version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、配置文件修改

    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.database=0
    
    • 1
    • 2
    • 3

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2-1-2、常量定义


    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";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2-1-3、改写唯一id


    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述


    2-1-4、新Provider和Publisher


    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    每次规则变动,都把最新规则存到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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    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文件

    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2、配置文件

    spring:
      redis:
        host: 127.0.0.1
        port: 6379
    
    • 1
    • 2
    • 3
    • 4

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2-2-2、注册监听器


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

    @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-持久化开启");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    三、源码获取


    3-1、使用


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

    在这里插入图片描述


    3-2、获取方式


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

    四、参考


    • https://github.com/all4you/sentinel-tutorial/blob/master/sentinel-practice/sentinel-persistence-rules/sentinel-persistence-rules.md
    • https://sentinelguard.io/zh-cn/blog/use-sentinel-dashboard-in-production.html
    • https://github.com/alibaba/Sentinel/tree/master/sentinel-extension/sentinel-datasource-redis
    • https://github.com/alibaba/Sentinel/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%99%E6%89%A9%E5%B1%95
    • https://github.com/alibaba/Sentinel/tree/master/sentinel-extension/sentinel-datasource-redis
    • https://www.jianshu.com/p/997a2255ff23
    • https://github.com/alibaba/Sentinel/wiki/%E5%9C%A8%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E4%BD%BF%E7%94%A8-Sentinel
    • https://github.com/alibaba/Sentinel/wiki/Sentinel-%E6%8E%A7%E5%88%B6%E5%8F%B0%EF%BC%88%E9%9B%86%E7%BE%A4%E6%B5%81%E6%8E%A7%E7%AE%A1%E7%90%86%EF%BC%89#%E8%A7%84%E5%88%99%E9%85%8D%E7%BD%AE
    • https://blog.csdn.net/qq_42714869/article/details/94553378
  • 相关阅读:
    SpringBoot 快速实现 api 接口加解密
    BiLSTM(双向LSTM)实现股票时间序列预测(TensorFlow2版)
    论文代码测试
    HC32L110 系列 M0 MCU 介绍和Win10下DAP-Link, ST-Link, J-Link方式的烧录
    6.MidBook项目经验之前端nuxt优化SEO和手机登录,微信登录
    【面试题 - spring】二
    [AUTOSAR][诊断管理][$11] 复位服务
    [maven] scopes & 管理 & profile & 测试覆盖率
    字符串匹配
    【必会】BM41 输出二叉树的右视图【中等+】
  • 原文地址:https://blog.csdn.net/Tomwildboar/article/details/136433535