基于aop 代理 Sentinel Nacos配置控制包装类实现原理

news2024/11/15 3:51:49

基于aop & 代理 & Sentinel & Nacos配置控制包装类实现原理

Hi,我是阿昌,今天记录下看sentinel源码结合业务实现的思路基于aop & 代理 & Sentinel & Nacos配置控制包装类实现原理;下面并不会手把手的记录方案的实现流程,而是记录流程的重要环节和举例,方便自己理解和回顾。

一、涉及知识点

  • SpringBoot
  • Nacos
  • Sentinel
  • AOP
  • 代理拦截器

二、正文

0、Sentinel的总体框架图

在这里插入图片描述

1、基于Nacos配置控制资源json信息

在集成Nacos了之后,在对应的DataId#Group下配置JSON类型的文件如

{
    "flowRules": [
        {
            "enabled": false,
            "clusterMode": false,
            "controlBehavior": 0,
            "count": 200,
            "grade": 1,
            "limitApp": "default",
            "maxQueueingTimeMs": 500,
            "resource": "com.achang.UserService",
            "strategy": 0,
            "warmUpPeriodSec": 10
        },
         {
            "enabled": false,
            "clusterMode": false,
            "controlBehavior": 2,
            "count": 0.1,
            "grade": 1,
            "limitApp": "default",
            "maxQueueingTimeMs": 30000,
            "resource": "achang:1",
            "strategy": 0,
            "warmUpPeriodSec": 10
        }
    ],
    "sentinelEnabled": true
}

以上分总开关和对应sentinelSlot开关

2、如何加载以上的配置?

利用hutool的spi包;

SPI机制中的服务加载工具类,流程如下
1、创建接口,并创建实现类
2、ClassPath/META-INF/services下创建与接口全限定类名相同的文件
3、文件内容填写实现类的全限定类名

通过Java的Spi机制加载对应的NacosSpiService类

public interface NacosSpiService {
    void loadRules(String content);
    String getDataId();
    String getGroupId();
}

META-INF/services下声明需要加载的类

com.achang.core.sentinel.NacosSpiSentinelImpl

然后在Nacos的@Configuration类中声明方法Spi加载,增加监听器监听Nacos配置变化

    private void refreshNacosConfigBySpi() {
        try {
            ServiceLoaderUtil.loadList(NacosSpiService.class)
                    .stream()
                    .filter(nacosSpiService -> nacosSpiService != null && StringUtils.isNotBlank(nacosSpiService.getDataId())).forEach(new Consumer<NacosSpiService>() {
                        @SneakyThrows
                        @Override
                        public void accept(NacosSpiService nacosSpiService) {
                            try {
                                // nacosSpiService.getGroupId()暂时不用spi的group
                                String content = configService.getConfigAndSignListener(nacosSpiService.getDataId(),
                                        group, 5000, new AbstractListener() {
                                            @Override
                                            public void receiveConfigInfo(String content) {
                                                try {
                                                    nacosSpiService.loadRules(content);
                                                    log.info("nacos配置初始化" + nacosSpiService.getDataId() + ":" + content);
                                                } catch (Exception e) {
                                                    log.error(nacosSpiService.getDataId() + "配置解析失败:{}", e.getMessage(), e);
                                                }
                                            }
                                        });
                                try {
                                    nacosSpiService.loadRules(content);
                                    log.info("nacos配置初始化" + nacosSpiService.getDataId() + ":" + content);
                                } catch (Exception e) {
                                    log.error(nacosSpiService.getDataId() + "配置解析失败:{}", e.getMessage(), e);
                                }
                            } catch (Throwable throwable) {
                                log.error("nacos register listener:{},{} failed:{}", group, nacosSpiService.getDataId(), throwable.getMessage(), throwable);
                            }
                        }
                    });
        } catch (Throwable throwable) {
            log.error("refreshNacosConfigBySpi failed:{}", throwable.getMessage(), throwable);
        }

以上会最终通过loadRules方法来加载nacos传来的配置信息,来初始化成sentinel对应的资源控制Rule:

  • com.alibaba.csp.sentinel.slots.system.SystemRule
  • com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule
  • com.alibaba.csp.sentinel.slots.block.flow.FlowRule
  • com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule

通过以上对应Rule的Manager的loadRules方法来加载为一个HashMap


以下以com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager为例子;

  • FlowRuleManager#flowRules来存储控制资源的映射关系

  • FlowRuleManager#FlowPropertyListener来更新&加载配置

    • FlowRuleUtil.buildFlowRuleMap方法来转化为一个ConcurrentHashMap,并对其进行用hash进行去重和排序,排序规则用的是FlowRuleComparator

      {
                  "enabled": false,
                  "clusterMode": false,
                  "controlBehavior": 0,
                  "count": 200,
                  "grade": 1,
                  "limitApp": "default",
                  "maxQueueingTimeMs": 500,
                  "resource": "com.achang.UserService",
                  "strategy": 0,
                  "warmUpPeriodSec": 10
      }
      

      以上资源会被转化为:

      • Key:com.achang.UserService

      • Value:[{“enabled”:false,“clusterMode”:false,“controlBehavior”:0,“count”:200,“grade”:1,“limitApp”:“default”,“maxQueueingTimeMs”:500,“resource”:“com.achang.UserService”,“strategy”:0,“warmUpPeriodSec”:10}]

3、如何使用加载后的资源

通过Nacos配置的json字符串转化为对应的RuleMap,然后通过getFlowRuleMap()来获取规则Map;这里涉及到Sentinel中的Slot责任链,依然用的com.alibaba.csp.sentinel.slots.block.flow.FlowSlot举例。

  • com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#ruleProvider来获取对应资源的规则;
  • com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#checkFlow会将上面获取的资源包装成resourceWrapper
  • 一个代理方法会调用每一个责任链的com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#entry方法来执行是否符合这个slot的逻辑来进行限流/降级/熔断等

在getFlowRuleMap方法中会去根据资源的配置来组装对应的Map,其中generateRater会去设置对应的controlBehavior字段来对应TrafficShapingController(匀速器com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController/预热器com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController等)具体的逻辑参考官方文档https://sentinelguard.io/zh-cn/docs/flow-control.html

下面举例RateLimiterController的核心代码canPass

@Override
    public boolean canPass(Node node, int acquireCount, boolean prioritized) {
        // Pass when acquire count is less or equal than 0.
        if (acquireCount <= 0) {
            return true;
        }
        // Reject when count is less or equal than 0.
        // Otherwise,the costTime will be max of long and waitTime will overflow in some cases.
        if (count <= 0) {
            return false;
        }

        long currentTime = TimeUtil.currentTimeMillis();
        // Calculate the interval between every two requests.
        long costTime = Math.round(1.0 * (acquireCount) / count * 1000);

        // Expected pass time of this request.
        long expectedTime = costTime + latestPassedTime.get();

        if (expectedTime <= currentTime) {
            // Contention may exist here, but it's okay.
            latestPassedTime.set(currentTime);
            return true;
        } else {
            // Calculate the time to wait.
            long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();
            if (waitTime > maxQueueingTimeMs) {
                return false;
            } else {
                long oldTime = latestPassedTime.addAndGet(costTime);
                try {
                    waitTime = oldTime - TimeUtil.currentTimeMillis();
                    if (waitTime > maxQueueingTimeMs) {
                        latestPassedTime.addAndGet(-costTime);
                        return false;
                    }
                    // in race condition waitTime may <= 0
                    if (waitTime > 0) {
                        Thread.sleep(waitTime);
                    }
                    return true;
                } catch (InterruptedException e) {
                }
            }
        }
        return false;
    }

在对应的slot的入口会执行com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#entry

 @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        checkFlow(resourceWrapper, context, node, count, prioritized);

        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#checkFlow中会有com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker来根据对应的FlowRule规则来判断是否通过或者执行对于的降级逻辑等;

  public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
                          Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
        if (ruleProvider == null || resource == null) {
            return;
        }
        Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
        if (rules != null) {
            for (FlowRule rule : rules) {
                if (!canPassCheck(rule, context, node, count, prioritized)) {
                    throw new FlowException(rule.getLimitApp(), rule);
                }
            }
        }
    }

然后根据对应的TrafficShapingController来执行对应的逻辑;


4、如何在正确的地方执行上面的降级/熔断等判断?

sentinel有基于aop的方式使用@SentinelResource注解实现,但就不能动态的对配置进行修改,不灵活

那可以对一个模版类进行包装【阿昌之丑陋代码优化】通过策略模式&模版模式来优化Controller执行流程,然后用代理对象的方式代理这个模版类来在目标方式执行前后进行自定义降级/熔断等;


用Interceptor拦截器等方式来写对于的前后逻辑,实现InvocationHandler类重写invoke方法

com.alibaba.csp.sentinel.SphU的entry方法来传递资源名来降级/熔断等逻辑

@Slf4j
public class TemplateInterceptor implements InvocationHandler{
  try (Entry entry = SphU.entry(actionTemplateRequestInfo.getResource())) {
            // 调用目标方法
            return method.invoke(target, args);
  }
}

在对应配置类中声明代理这个包装类,如下:

    @Bean
    Template template() {
        Template template = new Template();
        Template interceptor = new TemplateInterceptor(template);
        // 创建代理对象
        return (Template) Proxy.newProxyInstance(
                Template.class.getClassLoader(),
                new Class[]{Template.class},
                interceptor
        );
    }

这样子就可以用代理结合aop的形式并通过Nacos动态配置的方式结合了sentinel框架灵活控制资源。

参考

  • sentinel官方文档
  • 博客

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

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

相关文章

【数据结构初阶】十、快速排序讲解和实现(三种递归快排版本 + 非递归快排版本 -- C语言实现)

相关代码gitee自取&#xff1a; C语言学习日记: 加油努力 (gitee.com) 接上期&#xff1a; 【数据结构初阶】九、排序的讲解和实现&#xff08;直接插入 \ 希尔 \ 直接选择 \ 堆 \ 冒泡 -- C语言&#xff09;-CSDN博客 常见排序算法的实现&#xff08;续上期&#xff09; …

ruoyi-nbcio版本从RuoYi-Flowable-Plus迁移过程记录

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 从KonBAI / RuoYi-Flowable-Plus 项目移植过来&#xff0c;开始用yarn install之后yarn run dev 还是有问…

在Go项目中二次封装Kafka客户端功能

1.摘要 在上一章节中,我利用Docker快速搭建了一个Kafka服务,并测试成功Kafka生产者和消费者功能,本章内容尝试在Go项目中对Kafka服务进行封装调用, 实现从Kafka自动接收消息并消费。 在本文中使用了Kafka的一个高性能开源库Sarama, Sarama是一个遵循MIT许可协议的Apache Kafk…

【机器学习合集】优化目标与评估指标合集 ->(个人学习记录笔记)

文章目录 优化目标与评估指标1. 优化目标1.1 两类基础任务与常见优化目标1.2 分类任务损失0-1损失交叉熵损失与KL散度softmax损失的理解与改进Hinge损失 1.3 回归任务损失L1/L2距离L1/L2距离的改进 Huber loss 2. 评测指标2.1 分类任务中评测指标准确率(查准率)/召回率(查全率)…

应急响应-网站入侵篡改指南_Webshell内存马查杀_漏洞排查_时间分析

1. 前言 一般安服在做项目的时候&#xff0c;经常会遇到需要做应急响应的工作&#xff0c;所谓应急响应就是当网站出现异常的时候&#xff0c;根据相关的问题对其进行溯源分析&#xff0c;发现问题&#xff0c;解决问题。 2. 网络安全异常特征 这里大概汇总一下网络安全异常的…

二叉树相关问题细谈递归

大家好&#xff0c;我是Dark Flame Master&#xff0c;今天给大家带来的介绍的是递归的思想&#xff0c;然后利用递归的方法实现建树的各个函数&#xff0c;例如节点个数&#xff0c;前中后序遍历&#xff0c;判断一棵二叉树是否为完全二叉树等&#xff0c;看完本文相信你会对递…

基于情感词典的情感分析方法

计算用户情绪强弱性&#xff0c;对于每一个文本都可以得到一个情感分值&#xff0c;以情感分值的正负性表示情感极性&#xff0c;大于0为积极情绪&#xff0c;小于0反之&#xff0c;绝对值越大情绪越强烈。 基于情感词典的情感分析方法主要思路&#xff1a; 1、对文本进行分词…

【1】zabbix6.4监控windows电脑操作教程

实验目标&#xff1a; 1.客户端&#xff08;windows&#xff09;安装zabbix agent 并添加到zabbix服务端&#xff1b; 2.可视化常用指标方便快速监控&#xff0c;及时了解客户端情况。 实施1&#xff1a; 步骤1&#xff1a;下载zabbix windows端安装包 官网下载传送门>D…

Android 10.0 Launcher3定制化之动态日历图标功能实现

1.概述 在10.0的系统产品rom开发中,在Launcher3中的相关定制化功能中,对于一些产品要求需要动态日历图标功能,在日期改变的时候,日历图标也需要跟着改变 所以需要自定义日历图标,监听日历改变的广播,收到日期改变的广播后,刷新日历图标,接下来就来分析关于动态日历图标…

5G与无人驾驶:引领未来交通的新潮流

5G与无人驾驶&#xff1a;引领未来交通的新潮流 随着5G技术的快速发展和普及&#xff0c;无人驾驶技术也日益受到人们的关注。5G技术为无人驾驶提供了更高效、更准确、更及时的通信方式&#xff0c;从而改变了我们对交通出行的认知和使用方式。本文将探讨5G技术在无人驾驶领域的…

大数据软件系统的交付流程

大数据软件系统的开发和交付流程通常涉及多个阶段&#xff0c;需要按照一定的计划和方法进行。以下是一个一般性的大数据软件系统开发和交付流程&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.需求…

查找算法-顺序查找法(Sequential Search)

目录 查找算法-顺序查找法&#xff08;Sequential Search&#xff09; 1、说明 2、算法分析 3、C代码 查找算法-顺序查找法&#xff08;Sequential Search&#xff09; 1、说明 顺序查找法又称线性查找法&#xff0c;是一种比较简单的查找法。它是将数据一项一项地按顺序…

硬件知识积累 PCIE 接口

1. PCIE 是什么 中文名称&#xff1a;高速串行计算机扩展总线标准 PCI-Express(peripheral component interconnect express)是一种高速串行计算机扩展总线标准&#xff0c;它原来的名称为“3GIO”&#xff0c;是由英特尔在2001年提出的&#xff0c;旨在替代旧的PCI&#xff0c…

nexus 快速搭建-本地私有仓库 -maven

场景&#xff1a; 需要上传打包starer本地、局域网内 jar包上传、下载搭建后本地有层代理&#xff0c;可节省代宽&#xff0c;无网可拉包等… 下载&#xff1a; https://help.sonatype.com/repomanager3/product-information/download 基本说明&#xff1a; proxy 用来代理远程…

ChatGPT AIGC 快速合并Excel工作薄 Vlookup+INDIRECT

在职场中进行数据处理,数据分析汇报与统计的过程中,经常会遇到这样的一个问题,那就是需要统计的数据源在多个文件中,多个工作薄中,如果要进行数据处理,汇总的时候会很不方便。 如果要汇总6个月的数据可能就得需要手动复制了。 再或者用其它方法来进行数据合并。 例如我…

高效的文件管理方法:如何批量在文件名中间插入特定内容

在高效的文件管理中&#xff0c;批量操作是一项非常重要的技能。通过批量操作&#xff0c;我们可以同时处理多个文件&#xff0c;节省时间和精力。本文将介绍一种实用的方法&#xff0c;即云炫文件管理器如何在文件名中间批量插入特定内容&#xff0c;以实现高效的文件管理。现…

力扣刷题 day55:10-25

1.数组异或操作 给你两个整数&#xff0c;n 和 start 。 数组 nums 定义为&#xff1a;nums[i] start 2*i&#xff08;下标从 0 开始&#xff09;且 n nums.length 。 请返回 nums 中所有元素按位异或&#xff08;XOR&#xff09;后得到的结果。 方法一&#xff1a;位运…

Go学习第九章——面向“对象”编程(三大特性与接口和断言)

Go面向“对象”编程&#xff08;三大特性与接口和断言&#xff09; 1. 封装1.1 介绍1.2 快速入门 2.继承2.1 介绍2.2 快速入门2.3 深入学习 3.接口3.1 接口特点和语法说明3.2 快速入门3.3 注意事项和细节说明3.4 接口和继承关系 4. 多态4.1 基本概念4.2 快速入门4.3 使用场景 5…

大数据调度最佳实践 | 从Airflow迁移到Apache DolphinScheduler

迁移背景 有部分用户原来是使用 Airflow 作为调度系统的&#xff0c;但是由于 Airflow 只能通过代码来定义工作流&#xff0c;并且没有对资源、项目的粒度划分&#xff0c;导致在部分需要较强权限控制的场景下不能很好的贴合客户需求&#xff0c;所以部分用户需要将调度系统从…

ROS笔记之visualization_msgs-Marker学习

ROS笔记之visualization_msgs-Marker学习 code review! 文章目录 ROS笔记之visualization_msgs-Marker学习一.line_strip例程二.line_list例程一二.line_list例程二二.TEXT_VIEW_FACING例程三.附CMakeLists.txt和package.xml五.关于odom、base_link和map坐标系六.关于visualiz…