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

基于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@Overridepublic void accept(NacosSpiService nacosSpiService) {try {// nacosSpiService.getGroupId()暂时不用spi的groupString content = configService.getConfigAndSignListener(nacosSpiService.getDataId(),group, 5000, new AbstractListener() {@Overridepublic 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

@Overridepublic 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 <= 0if (waitTime > 0) {Thread.sleep(waitTime);}return true;} catch (InterruptedException e) {}}}return false;}

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

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

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

    @BeanTemplate 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.mzph.cn/news/119565.shtml

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

相关文章

Redis | 数据结构(02)SDS

一、键值对数据库是怎么实现的&#xff1f; 在开始讲数据结构之前&#xff0c;先给介绍下 Redis 是怎样实现键值对&#xff08;key-value&#xff09;数据库的。 Redis 的键值对中的 key 就是字符串对象&#xff0c;而 value 可以是字符串对象&#xff0c;也可以是集合数据类型…

分享一个用HTML、CSS和jQuery构建的漂亮的登录注册界面

作为一个前端开发人员&#xff0c;我们经常需要构建用户的登录和注册界面。一个漂亮、用户友好的登录注册界面对于提升用户体验和网站形象至关重要。以下我们使用HTML、CSS和jQuery来做一个漂亮的登录注册界面。 首先&#xff0c;我们需要创建一个html文档&#xff0c;定义登录…

【算法练习Day29】柠檬水找零根据身高重建队列用最少数量的箭引爆气球

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 柠檬水找零根据身高重建队列…

【数据结构】ST 表与 RMQ 算法

本文参考【朝夕的ACM笔记】数据结构-ST表 在练习线段树的过程中经常会感叹代码怎么这么长啊啊啊懒标记怎么这么难传啊啊啊 于是在得知有一种代码量远小于线段树的算法时、、、&#xff08;其实是因为做到了[SCOI2007] 降雨量 就是ST表啦~ 在什么情况下可以用ST表代替线段树…

Web进阶

身份认证 当我们在使用互联网时&#xff0c;经常会遇到一些需要身份验证或者保持用户状态的情况。为了实现这些功能&#xff0c;常用的方法有使用cookie、session和token。 Cookie&#xff08;HTTP Cookie&#xff09;&#xff1a; Cookie是服务器发送到用户浏览器并保存在用户…

STM32-LCD中英文显示及应用

目录 字符编码 ASCII码&#xff08;8位&#xff09; 中文编码&#xff08;16位&#xff09; GB2312标准 GBK编码 GB18030标准&#xff08;32位&#xff09; Big5编码 Unicode字符集和编码 UTF-32&#xff08;32位&#xff09; UTF-16&#xff08;16位/32位&#xff0…

二十三、设计模式之组合模式![

目录 二十三、设计模式之组合模式能帮我们干什么&#xff1f;主要解决什么问题&#xff1f;优缺点优点缺点&#xff1a; 使用的场景理解实现角色组合模式 总结 魔战已经完结。成功登顶。占领敌军最高峰。 二十三、设计模式之组合模式 “组合模式”也被称为“部分整体模式”该…

方舟生存进化ARK个人服务器搭建教程保姆级

方舟生存进化ARK个人服务器搭建教程保姆级 大家好我是艾西&#xff0c;在很久之前我有给大家分享过方舟生存进化的搭建架设教程&#xff0c;但时间久远且以前的教程我现在回头看去在某些地方说的并不是那么清楚。最近也是闲暇无事打算重新巩固下方舟生存进化的搭建架设教程&…

Vue 路由传参和获取参数的方法

在使用 Vue 进行开发时&#xff0c;路由传参是非常常见且重要的功能。通过路由传参&#xff0c;我们可以在不同的页面之间传递数据&#xff0c;以实现更灵活的交互和功能。 Vue 提供了多种方法来实现路由传参和获取参数的操作。下面将介绍两种常用的方法&#xff1a; 1. 动态…

rpc入门笔记0x01

syntax "proto3"; // 这是个proto3的文件message HelloRequest{ // 创建数据对象string name 1; // name表示名称&#xff0c;编号是1 }生成python文件 安装grpcio和grpcio-tools库 pip install grpcio #安装grpc pip install grpcio-tools #安装grpc tools生成…

LuatOS-SOC接口文档(air780E)--libcoap - coap数据处理

libcoap.new(code, uri, headers, payload) 创建一个coap数据包 参数 传入值类型 解释 int coap的code, 例如libcoap.GET/libcoap.POST/libcoap.PUT/libcoap.DELETE string 目标URI,必须填写, 不需要加上/开头 table 请求头,类似于http的headers,可选 string 请求体…

argparse模块介绍

argparse是一个Python模块&#xff1a;命令行选项、参数和子命令解析器。argparse 模块可以让人轻松编写用户友好的命令行接口。程序定义了所需的参数&#xff0c;而 argparse 将找出如何从 sys.argv &#xff08;命令行&#xff09;中解析这些参数。argparse 模块还会自动生成…

B-3:Web安全之综合渗透测试

B-3:Web安全之综合渗透测试 任务环境说明: 服务器场景:Server2104(关闭链接) 服务器场景用户名、密码:未知 1.通过URL访问http://靶机IP/1,对该页面进行渗透测试,将完成后返回的结果内容作为FLAG值提交; 通过访问IP/1,查看源代码发现flagishere,访问后发现什么也没…

【RabbitMQ】常用消息模型详解

文章目录 AMQP协议的回顾RabbitMQ支持的消息模型第一种模型(直连)开发生产者开发消费者生产者、消费者开发优化API参数细节 第二种模型(work quene)开发生产者开发消费者消息自动确认机制 第三种模型(fanout)开发生产者开发消费者 第四种模型(Routing)开发生产者开发消费者 第五…

jvm摘要

第 2 章 Java 内存区域与内存溢出异常 2.2 运行时数据区域 程序计数器-线程私有:是一块较小的内存空间&#xff0c;它可以看作是当前线程所执行的字节码的行号指示器。 程序计数器是唯一一个没有规定任何OutOfMemoryError 情况的区域。 Java 虚拟机栈-线程私有:用于执行Java …

悟空crm安装搭建 报错[0] RedisException in Redis.php line 56问题处理办法

相信很多朋友进行安装悟空crm的时候 提示错误&#xff1a; [0] RedisException in Redis.php line 56 Connection refused 不知道怎么样处理是吧~~~ $this->options array_merge($this->options, $options);}# redis 密码$password config(cache.password);if (!empty…

(二开)Flink 修改源码拓展 SQL 语法

1、Flink 扩展 calcite 中的语法解析 1&#xff09;定义需要的 SqlNode 节点类-以 SqlShowCatalogs 为例 a&#xff09;类位置 flink/flink-table/flink-sql-parser/src/main/java/org/apache/flink/sql/parser/dql/SqlShowCatalogs.java 核心方法&#xff1a; Override pu…

【C++】priority_queue仿函数

今天我们来学习C中另一个容器适配器&#xff1a;优先级队列——priority_queue&#xff1b;和C一个重要组件仿函数&#xff1a; 目录 一、priority_queue 1.1 priority_queue是什么 1.2 priority_queue的接口 1.2.1 priority_queue使用举例 二、仿函数 三、关于priority…

文件包含漏洞(1),文件包含漏洞的利用原理

文件包含漏洞利用的原理 一, 本地文件包含 1. 漏洞利用条件: 在默认情况下, php.ini配置文件中 allow_url_fopenOn 是开启状态. 服务器php脚本中的 include include_once require require_once 等函数用于包含文件中的代码, 如果参数是用户可控的, 则可能被利用来执行系统命…

Linux中的调度策略及其工作

对于操作系统来讲&#xff0c;它面对的 CPU 的数量是有限的&#xff0c;干活儿都是它们&#xff0c;但是进程数目远远超过 CPU 的数目&#xff0c;因而就需要进行进程的调度&#xff0c;有效地分配 CPU 的时间&#xff0c;既要保证进程的最快响应&#xff0c;也要保证进程之间的…