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

news2025/1/21 0:59:43

B站视频讲解

学习链接🔗

文章目录

  • 一、理论
  • 二、实践
    • 2-1、dashboard 请求Redis
      • 2-1-1、依赖、配置文件引入
      • 2-1-2、常量定义
      • 2-1-3、改写唯一id
      • 2-1-4、新Provider和Publisher
      • 2-1-5、改写V2
    • 2-2、应用服务改造
      • 2-2-1、依赖、配置文件引入
      • 2-2-2、注册监听器
  • 三、源码获取
    • 3-1、使用
    • 3-2、获取方式
  • 四、参考


上一篇讲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.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <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;
    }
}

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";
}

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);
    }
}

在这里插入图片描述


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;
    }
}

每次规则变动,都把最新规则存到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);
    }
}

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.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、配置文件

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;
    }
}

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-持久化开启");
    }
}

三、源码获取


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1495898.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

keycloak18.0.0==源码编译打包

参照官方文档building.md mvn -Pdistribution -pl distribution/server-dist -am -Dmaven.test.skip clean install 报错 ---------- [ERROR] Failed to execute goal com.github.eirslett:frontend-maven-plugin:1.12.0:npm (npm-install-common) on project keycloak-theme…

MySQL存储过程2——if、case、while、repeat、loop、cursor、handler

1、if用作条件判断 1.1、语法结构 IF 条件1 THEN... ELSEIF 条件2 THEN... ELSE... END IF; 在if条件判断的结构中&#xff0c;Else if结构可以有多个&#xff0c;也可以没有。Else结构可以有&#xff0c;也可以没有 1.2、示例 根据定义的分数score变量&#xff0c;判断当…

mysql学习笔记8——常用5个内置方法

1count 对查询内容进行计数&#xff0c;并返回结果 2as as可以将查询出来结果赋予新名字 3sum sum可以查询某字段特定条件下的和值 4concat concat可以将多列数据合并成一列&#xff0c;只要concat&#xff08;&#xff09;即可 5group_concat group_concat可以把多列…

Linux|终端管理|如何踢掉操作系统内的用户

一&#xff0c; 关于终端的一些基本概念 tty、pty 和 pts 是在类 Unix 系统&#xff08;包括 Linux&#xff09;中与终端交互相关的概念。它们各自代表不同的方面&#xff0c;并且有着密切的关联。 tty (Teletype) tty 原指老式的电传打字机&#xff0c;后来泛指任何类型的终…

Echarts 配置项 series 中的 data 是多维度

文章目录 需求分析 需求 如下图数据格式所示&#xff0c;现要求按照该格式进行绘制折线图 分析 在绘制折线图时&#xff0c;通常我们的 series 中的 data 数据是这样的格式 option {title: {text: Stacked Area Chart},tooltip: {trigger: axis,axisPointer: {type: cross…

内容管理平台用这几个就够了,简单又好用

对于大多数企业和自由职业者来说&#xff0c;选择合适的内容管理平台已经成为一种必备的技能。良好的内容管理平台可以赋能你的团队&#xff0c;让你们更好地协作、管理和分享内容。不管你是要发布博客文章&#xff0c;还是需要管理复杂的项目文档&#xff0c;都可以通过内容管…

华为OD机试 - 疫情扩散时间计算 - 矩阵(Java 2024 C卷 200分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&am…

图论 - DFS深度优先遍历、BFS广度优先遍历、拓扑排序

文章目录 前言Part 1&#xff1a;DFS&#xff08;深度优先遍历&#xff09;一、排列数字1.题目描述输入格式输出格式数据范围输入样例输出样例 2.算法 二、n皇后问题1.问题描述输入格式输出格式数据范围输入样例输出样例 2.算法 三、树的重心1.问题描述输入格式输出格式数据范围…

Ubuntu 下使用 Pybind11 实现 C++ 调用 Python 接口的示例

Pybind11 是一个轻量级的库&#xff0c;它提供了在 C 中无缝集成 Python 代码的能力。使用 Pybind11&#xff0c;你可以很容易地从 C 调用 Python 代码&#xff0c;反之亦然。下面我将通过一个简单的例子来展示如何在 Ubuntu 系统上使用 Pybind11 从 C 调用 Python 接口。 安装…

嵌入式常见概念介绍

什么是ARM&#xff1a; Advanced RISC Machines 先进RISC机器 嵌入式系统自诞生起就分为两条路&#xff1a; RISC&#xff1a;精简指令集计算机&#xff0c;如ARM&#xff0c;所有指令长度一致&#xff0c;指令数量较少 CISC&#xff1a;复杂指令集计算机&#xff0…

git 如何将多个提交点合并为一个提交点 commit

文章目录 核心命令详细使用模式总结示例 核心命令 git merge branch2 是将分支branch2的提交点合并到本地当前分支。 而在执行这条命令的时候&#xff0c;加一个选项--squash就表示在合并的时候将多个提交点合并为一个提交点。 git merge --squash branch2 先看squash单词的意…

2023年CSP-J认证 CCF信息学奥赛C++ 中小学初级组 第一轮真题-选择题解析

2023年 中小学信息学奥赛CSP-J真题解析 1、在C中&#xff0c;下面哪个关键字用于声明一个变量&#xff0c;其值不能被修改 A、unsigned B、const C、static D、mutable 答案&#xff1a;B 考点分析&#xff1a;主要考查变量声明相关知识&#xff0c;const是声明常量&…

LeetCode 刷题 [C++] 第98题.验证二叉搜索树

题目描述 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 题目分析 由题…

指针数组初始化,不常见啊

今天无意间看到这样一段代码&#xff0c;因为还是第一次看到&#xff0c;这是glibc库里的代码&#xff0c;写出来分享一下&#xff1a; #ifndef ERR_MAP # define ERR_MAP(n) n #endif const char *const _sys_errlist_internal[] { #define _S(n, str) [ERR_MAP(n)] …

职场中的团队合作与个人成长

在职场中&#xff0c;团队合作和个人成长是两个不可或缺的要素。一个优秀的团队可以带来更高的工作效率和更好的业绩&#xff0c;而个人的成长则是职场成功的关键。本文将探讨如何在职场中实现团队合作与个人成长的平衡。 一、团队合作的重要性 在职场中&#xff0c;团队合作是…

授权认证登录之 Cookie、Session、Token、JWT 详解

授权认证登录之 Cookie、Session、Token、JWT 详解 一、先了解几个基础概念什么是认证&#xff08;Authentication&#xff09;什么是授权&#xff08;Authorization&#xff09;什么是凭证&#xff08;Credentials&#xff09; 二、Cookie1、了解 Cookie2、cooker的创建2、coo…

【数据结构】队列 循环队列 双端队列——顺序队列+链式队列完整代码(创建、入队、出队)

2.队列 2.1 队列的定义 定义 只允许在一端进行插入&#xff0c;另一端删除的线性表。 特征&#xff1a;先进先出&#xff08;First In First Out->FIFO&#xff09; 重要术语&#xff1a;队头、队尾、空队列 2.2 队列的顺序存储 2.2.1 初始化 结构体 typedef struct{…

数字人民币钱包(二)

文章目录 前言一 什么是数字人民币钱包&#xff1f;二 怎么开通数字人民币钱包&#xff1f;三 数字人民币钱包有哪些&#xff1f;四 数字人民币钱包升级 前言 上篇文章梳理了什么是数字人民币&#xff0c;及其特征和相关概念&#xff0c;这篇文章来整理下数字人民币钱包。数字人…

TOMCAT多实例及调优

一、JVM相关理论 &#xff08;一&#xff09;JVM组成 1.JVM组成部分 类加载子系统: 使用Java语言编写.java Source Code文件&#xff0c;通过javac编译成.class Byte Code文件。class loader类加载器将所需所有类加载到内存&#xff0c;必要时将类实例化成实例 运行时数据区…

1908_Arm Cortex-M3的实现

1908_Arm Cortex-M3的实现 全部学习汇总&#xff1a; g_arm_cores: ARM内核的学习笔记 (gitee.com) 这是第一次看一份这样的手册&#xff0c;之前的MCU编程基本上就是专注于软件接口方面。而OS等方面的一些功能基本上都是用了现成的解决方案&#xff0c;因此也就没有过多的关注…