Sentinel持久化实战

news2024/11/24 21:08:32

前言

Sentinel有pull(拉)模式,和push(推)模式。本文是使用reids实现pull模式。

通过SPI机制引入自己的类

在项目的 resources > META-INF > services下创建新文件,文件名如下,内容是自己实现类的全限定名:com.xx.sentinel.RedisDataSourceInit
在这里插入图片描述

创建实现类

package com.xx.sentinel;

import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.WritableDataSource;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.xx.schedule.Utils.SpringUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.List;

/**
 * @Author: wangyn85 <br>
 * @CreateDate: 2023/07/01 11:14 <br>
 * @Description: sentinel初始化方法,进行持久化设置
 */
public class RedisDataSourceInit implements InitFunc {
    /**
     * sentinel存放redis的key。例如:sentinel:common:flow
     */
    private static final String SENTINEL_REDIS_KEY = "sentinel:%s:%s";
    /**
     * sentinel数据更新发布订阅频道。例如:chanel_sentinel_common_flow
     */
    private static final String SENTINEL_REDIS_CHANEL = "chanel_sentinel_%s_%s";

    private RedisTemplate redisTemplate;

    private static final String RULE_FLOW = "flow";
    private static final String RULE_DEGRADE = "degrade";

    /**
     * 给模板对象RedisTemplate赋值,并传出去
     */
    private RedisTemplate<String, Object> getRedisTemplate() {
        if (redisTemplate == null) {
            synchronized (this) {
                if (redisTemplate == null) {
                    redisTemplate = SpringUtil.getBean("functionDomainRedisTemplate");
                }
            }
        }
        return redisTemplate;
    }

    /**
     * 获取sentinel存放redis的key
     *
     * @param ruleType
     * @return
     */
    private String getSentinelRedisKey(String ruleType) {
        String projectName = SentinelRedisHelper.getProjectName();
        return String.format(SENTINEL_REDIS_KEY, projectName, ruleType);
    }

    /**
     * 获取sentinel数据更新发布订阅频道
     *
     * @param ruleType
     * @return
     */
    private String getSentinelRedisChanel(String ruleType) {
        String projectName = SentinelRedisHelper.getProjectName();
        return String.format(SENTINEL_REDIS_CHANEL, projectName, ruleType);
    }


    @Override
    public void init() throws Exception {
        // 没有配置redis或没有配置projectName则不进行持久化配置
        if (getRedisTemplate() == null || StringUtils.isEmpty(SentinelRedisHelper.getProjectName())) {
            return;
        }

        // 1.处理流控规则
        this.dealFlowRules();

        // 2.处理熔断规则
        this.dealDegradeRules();
    }

    /**
     * 处理流控规则
     */
    private void dealFlowRules() {
        String redisFlowKey = getSentinelRedisKey(RULE_FLOW);
        String redisFlowChanel = getSentinelRedisChanel(RULE_FLOW);

        // 注册flow读取规则
        // 官方RedisDataSource是订阅获取,官方FileRefreshableDataSource是定时刷新获取。本方法是redis订阅+定时
        Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
        });
        ReadableDataSource<String, List<FlowRule>> redisFlowDataSource = new RedisDataSource2<>(parser, getRedisTemplate(), redisFlowKey, redisFlowChanel);
        FlowRuleManager.register2Property(redisFlowDataSource.getProperty());

        // 初始化加载一次所有flow规则
        String flowRulesStr = (String) getRedisTemplate().opsForValue().get(redisFlowKey);
        List<FlowRule> flowRuleList = parser.convert(flowRulesStr);
        redisFlowDataSource.getProperty().updateValue(flowRuleList);

        // 注册flow写入规则。这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
        WritableDataSource<List<FlowRule>> flowRuleWds = new RedisWritableDataSource<>(JSON::toJSONString, getRedisTemplate(), redisFlowKey, redisFlowChanel);
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWds);
    }

    /**
     * 处理熔断规则
     */
    public void dealDegradeRules() {
        String redisDegradeKey = getSentinelRedisKey(RULE_DEGRADE);
        String redisDegradeChanel = getSentinelRedisChanel(RULE_DEGRADE);

        Converter<String, List<DegradeRule>> parser = source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {
        });
        ReadableDataSource<String, List<DegradeRule>> redisDegradeDataSource = new RedisDataSource2<>(parser, getRedisTemplate(), redisDegradeKey, redisDegradeChanel);
        DegradeRuleManager.register2Property(redisDegradeDataSource.getProperty());

        // 初始化加载一次所有flow规则
        String degradeRulesStr = (String) getRedisTemplate().opsForValue().get(redisDegradeKey);
        List<DegradeRule> degradeRuleList = parser.convert(degradeRulesStr);
        redisDegradeDataSource.getProperty().updateValue(degradeRuleList);

        // 注册degrade写入规则。这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
        WritableDataSource<List<DegradeRule>> degradeRuleWds = new RedisWritableDataSource<>(JSON::toJSONString, getRedisTemplate(), redisDegradeKey, redisDegradeChanel);
        WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWds);
    }
}

创建读取

package com.midea.sentinel;

import com.alibaba.csp.sentinel.datasource.AutoRefreshDataSource;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.AssertUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

import java.nio.charset.StandardCharsets;

/**
 * @Author: wangyn85 <br>
 * @CreateDate: 2023/07/04 11:09 <br>
 * @Description: 订阅redis通知,当sentinel规则发生变化时,拉取redis配置保存到内存。定时获取redis信息
 */
public class RedisDataSource2<T> extends AutoRefreshDataSource<String, T> {
    private static Logger logger = LoggerFactory.getLogger(RedisDataSource2.class);

    private static final String REDIS_SUCCESS_MSG = "OK";

    private String lastModified = "-1";

    private RedisTemplate redisTemplate;

    /**
     * 存入redis的对应规则的key
     */
    private String ruleKey;

    /**
     * redis订阅频道
     */
    private String channel;

    /**
     * 存入redis更新时间的key
     */
    private String ruleUpdateKey;

    /**
     * 定时获取redis信息
     */
    private static final long DEFAULT_REFRESH_MS = 300000L;

    public RedisDataSource2(Converter<String, T> parser, RedisTemplate redisTemplate, String ruleKey, String channel) {
        // 父级构造器,传入定时执行的时间
        super(parser, DEFAULT_REFRESH_MS);
        AssertUtil.notNull(redisTemplate, "redisTemplate can not be null");
        AssertUtil.notEmpty(ruleKey, "redis ruleKey can not be empty");
        AssertUtil.notEmpty(channel, "redis subscribe channel can not be empty");
        this.redisTemplate = redisTemplate;
        this.ruleKey = ruleKey;
        this.channel = channel;
        this.ruleUpdateKey = SentinelRedisHelper.getRedisUpdateTimeKey(ruleKey);
        subscribeFromChannel();
    }

    @Override
    public String readSource() throws Exception {
        return (String) redisTemplate.opsForValue().get(ruleKey);
    }

    @Override
    public void close() throws Exception {
        super.close();
        redisTemplate.execute((RedisCallback<String>) connection -> {
            connection.getSubscription().unsubscribe(channel.getBytes(StandardCharsets.UTF_8));
            return REDIS_SUCCESS_MSG;
        });
    }

    /**
     * 订阅消息队列
     */
    private void subscribeFromChannel() {
        redisTemplate.execute((RedisCallback<String>) connection -> {
            connection.subscribe((message, pattern) -> {
                byte[] bytes = message.getBody();
                String msg = new String(bytes, StandardCharsets.UTF_8);
                logger.info("{},接收到sentinel规则更新消息: {} ", channel, msg);

                try {
                    // 收到更新通知后,从redis获取全量数据更新到内存中
                    getProperty().updateValue(parser.convert(readSource()));
                } catch (Exception e) {
                    logger.error(channel + ",接收到sentinel规则更新消息:{},更新出错:{}", msg, e.getMessage());
                }
            }, channel.getBytes(StandardCharsets.UTF_8));
            return REDIS_SUCCESS_MSG;
        });
    }

    @Override
    protected boolean isModified() {
        // 根据redis的key查询是否有更新,没有更新返回false,就不用执行后面的拉取数据,提高性能
        String updateTimeStr = (String) redisTemplate.opsForValue().get(ruleUpdateKey);
        if (StringUtils.isEmpty(updateTimeStr) || updateTimeStr.equals(lastModified)) {
            return false;
        }
        this.lastModified = updateTimeStr;
        return true;
    }

}

创建写入

package com.midea.sentinel;

import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.datasource.WritableDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author: wangyn85 <br>
 * @CreateDate: 2023/07/04 09:47 <br>
 * @Description: 收到sentinel控制的规则更新后,讲规则持久化,并发布redis订阅通知
 */
public class RedisWritableDataSource<T> implements WritableDataSource<T> {
    private static Logger logger = LoggerFactory.getLogger(RedisWritableDataSource.class);

    private final String redisRuleKey;
    private final Converter<T, String> configEncoder;
    private final RedisTemplate redisTemplate;
    private final String redisFlowChanel;
    private final Lock lock;
    /**
     * 存入redis更新时间的key
     */
    private String ruleUpdateKey;

    private static final String SENTINEL_RULE_CHANGE = "CHANGE";

    public RedisWritableDataSource(Converter<T, String> configEncoder, RedisTemplate redisTemplate, String redisRuleKey, String redisFlowChanel) {
        this.redisRuleKey = redisRuleKey;
        this.configEncoder = configEncoder;
        this.redisTemplate = redisTemplate;
        this.redisFlowChanel = redisFlowChanel;
        this.lock = new ReentrantLock(true);
        this.ruleUpdateKey = SentinelRedisHelper.getRedisUpdateTimeKey(redisRuleKey);
    }

    @Override
    public void write(T value) throws Exception {
        this.lock.lock();
        try {
            logger.info("收到sentinel控制台规则写入信息,并准备持久化:{}", value);
            String convertResult = this.configEncoder.convert(value);
            redisTemplate.opsForValue().set(ruleUpdateKey, String.valueOf(System.currentTimeMillis()));
            redisTemplate.opsForValue().set(redisRuleKey, convertResult);

            logger.info("收到sentinel控制台规则写入信息,持久化后发布redis通知:{},信息:{}", this.redisFlowChanel, SENTINEL_RULE_CHANGE);
            redisTemplate.convertAndSend(this.redisFlowChanel, SENTINEL_RULE_CHANGE);
        } catch (Exception e) {
            logger.info("收到sentinel控制台规则写入信息,持久化出错:{}", e);
            throw e;
        } finally {
            this.lock.unlock();
        }
    }

    @Override
    public void close() throws Exception {

    }
}

创建需要的配置类

package com.midea.sentinel;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @Author: wangyn85 <br>
 * @CreateDate: 2023/07/04 16:12 <br>
 * @Description: sentinel在redis持久化相关配置
 */
@Component
public class SentinelRedisHelper {

    @Value("${project.name}")
    private String projectName;

    private static String SENTINEL_REDIS_UPDATE_TIME = "%s:updateTime";

    private static SentinelRedisHelper self;

    @PostConstruct
    public void init() {
        self = this;
    }

    /**
     * 获取sentinel中配置的项目名
     *
     * @return
     */
    public static String getProjectName() {
        return self.projectName;
    }

    /**
     * 获取redis对应规则更新时间的key
     *
     * @param redisKey redis对应规则的key
     * @return
     */
    public static String getRedisUpdateTimeKey(String redisKey) {
        return String.format(SENTINEL_REDIS_UPDATE_TIME, redisKey);
    }
}

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

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

相关文章

centos7系统一键离线安装docker

离线安装脚本 # 离线安装docker rpm -Uvh --force --nodeps *.rpm # 启动docker systemctl start docker sudo systemctl daemon-reload # 设置开机自动启动docker systemctl enable docker.service下载 程序包下载地址 https://gitcode.net/zenglg/centos7_docker_offline_…

泛微E9鉴权

调用OA鉴权接口的过程 认证流程时序图 第一步、注册许可 请求地址&#xff1a; ​ http://泛微服务地址/api/ec/dev/auth/regist 请求方式&#xff1a;post 请求头部参数&#xff08;request headers&#xff09;&#xff1a; 参数名必选类型说明appid是string许可证号码…

前端学习——jsDay4

函数 函数使用 小练习 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widt…

【算法与数据结构】232、LeetCode用栈实现队列

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;这道题要求我们用栈模拟队列&#xff08;工作上一定没人这么搞&#xff09;。程序当中&#xff0c;pus…

【cuda】cuda-12.0对应tensorflow-gpu安装

我的cuda是12.0的&#xff0c;先根据下面的链接装了cudnn (12条消息) Windows10系统CUDA和CUDNN安装教程_windows安装cudnn_流泪&#xff06;枯萎的博客-CSDN博客 (12条消息) 【精简】2023年最新Windows安装GPU版本的tensorflow&#xff08;含bug记录及解决&#xff09;_tenso…

js引入json文件

引入json文件有多种方法&#xff0c;这里记录一种方法&#xff1a; 1、有json文件 文件内容 2、上传到服务器获得url&#xff08;也可以不上传&#xff09; 获取url:“https://asc-test1.oss-cn-beijing.aliyuncs.com/2023/07/05/b1917481d4eb40628bc6d10113896d82taizhou.jso…

玩机搞机---小米刷机工具平台刷写报错对症解决方法

安卓玩机搞机技巧综合资源-----全安卓机型通用线刷 卡刷教程。新老机型可参考【十八】 小米刷机平台工具版本较多。有时候更换工具版本刷机会有不同的提示。虽然目前很多第三方包都采用脚本方式刷写&#xff0c;但我们具体要了解使用官方工具报错究竟是什么原因。常见的报错有…

Ae 效果:CC Vignette

风格化/CC Vignette Stylize/CC Vignette CC Vignette&#xff08;CC 暗角&#xff09;效果可以用于各种场景&#xff0c;例如添加电影感、突出焦点、调整色调或增加视觉吸引力。使用 CC Vignette&#xff0c;可以轻松地为图像或视频创建独特的暗角效果&#xff0c;使其更具艺术…

【Git原理与使用】-- 标签管理

目录 理解标签 创建标签 操作标签 删除 推送 理解标签 标签 tag &#xff0c;可以简单的理解为是对某次 commit 的⼀个标识&#xff0c;相当于起了⼀个别名。例如&#xff1a;在项目发布某个版本的时候&#xff0c;针对最后⼀次 commit 起⼀个 v1.0 这样的标签来标识里程碑…

C++初阶之类和对象(下)

类和对象&#xff08;下&#xff09; 1. 再谈构造函数1.1 构造函数体赋值1.2 初始化列表1.3 explicit关键字 2. static成员2.1 概念2.2 特性 3. 友元3.1 友元函数3.2 友元类 4. 内部类5.匿名对象6.拷贝对象时的一些编译器优化结语 1. 再谈构造函数 1.1 构造函数体赋值 在创建…

AIGC - Stable Diffusion 的 AWPortrait 1.1 模型与 Prompts 设置

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131565908 AWPortrait 1.1 网址&#xff1a;https://www.liblibai.com/modelinfo/721fa2d298b262d7c08f0337ebfe58f8 介绍&#xff1a;AWPortrai…

win10+cuda11.8+opencv4.5.5使用cuda运行yolov5

环境&#xff1a;win10 cuda11.8(尝试使用最新的12.1无法安装&#xff09; Vs2019(尝试Vs2015无法源码安装&#xff09;opencv4.5.5 cmake-gui 3.26.4&#xff0c;电脑GPU是4080 1、安装Vs2019和cuda11.8&#xff0c;下载opencv4.5.5和对应的opencv_contrib-4.5.5&#xff0c;下…

Android Studio实现内容丰富的安卓校园新闻浏览平台

如需源码可以添加q-------3290510686&#xff0c;也有演示视频演示具体功能&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动。 项目编号070 1.开发环境 android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.查看新闻列表…

Python安装完成后执行pip命令报错:‘pip‘ 不是内部或外部命令,也不是可运行的程序

解决办法&#xff1a; 已安装pip的情况下&#xff0c;把这个Scripts文件夹的目录添加到Path环境变量即可。   我的电脑 -> 右键 属性 -> 高级系统设置 -> 环境变量 -> path -> 编辑&#xff1a;加入Scripts文件夹的目录    确定即可

《一》Midjourney 傻瓜式入门教程

Midjourney 傻瓜式教程-注册入门篇 Midjourney 是基于 ChatGPT 的衍生 AI 绘画工具&#xff0c;可以用来绘制书本内的插图&#xff0c;绘本&#xff0c;漫画&#xff0c;海报&#xff0c;头像等等。总之&#xff0c;只有想不到&#xff0c;没有做不到的图。 Midjourney上手简…

Python GUI设计-PyQt5从入门到实践(第1-3章)

第一章 Python 与 Pyqt5 良好的开端&#xff0c;等于成功的一半 ————柏拉图 1.1 python语言简介 1.1.3 Python的应用领域 Web开发、大数据处理、人工智能、自动化运维开发、云计算、爬虫、游戏开发。 1.2 GUI 与 PyQt5 第二章 搭建开发环境 2.1 Python的下载与安装 Pyt…

Java JSP实战

综合实战&#xff1a; 实现利用servletservicedao实现查询所有&#xff0c;对象&#xff0c;增删改&#xff0c; t_house(int no,String housename,Double height) 步骤1&#xff1a;创建maven工程 步骤2&#xff1a;创建工程包entity,dao,util,service,servlet 步骤3&#x…

第一章:基本概念

什么是数据结构 &#xff1f; 其实官方没有统一定义&#xff01;&#xff01;&#xff01; “数据结构是数据对象&#xff0c;以及存在于该对象的实例和组成实例的数据元素之间的各种联系。这种联系可以通过定义相关的函数给出。” - Sartaj Sahni 《数据结构、算法与应用》 …

期权卖方到期可以不平仓?期权卖方高胜率策略的实战技巧

期权是一种金融衍生品&#xff0c;它给予买方在未来某个时间以特定价格买入或卖出某种资产的权利&#xff0c;而不是义务。期权的卖方则承担了相应的义务&#xff0c;即在买方行使期权时按照约定的价格交割资产。期权的价格叫做期权费&#xff0c;它由期权的内在价值和时间价值…

【设计模式】第二十二章:中介者模式详解及应用案例

系列文章 【设计模式】七大设计原则 【设计模式】第一章&#xff1a;单例模式 【设计模式】第二章&#xff1a;工厂模式 【设计模式】第三章&#xff1a;建造者模式 【设计模式】第四章&#xff1a;原型模式 【设计模式】第五章&#xff1a;适配器模式 【设计模式】第六章&…