庆阳建设局网站,wap网站开发用什么语言,深圳关键词推广,网站建设的应对措施基于aop 代理 Sentinel Nacos配置控制包装类实现原理
Hi#xff0c;我是阿昌#xff0c;今天记录下看sentinel源码结合业务实现的思路基于aop 代理 Sentinel Nacos配置控制包装类实现原理#xff1b;下面并不会手把手的记录方案的实现…基于aop 代理 Sentinel Nacos配置控制包装类实现原理
Hi我是阿昌今天记录下看sentinel源码结合业务实现的思路基于aop 代理 Sentinel Nacos配置控制包装类实现原理下面并不会手把手的记录方案的实现流程而是记录流程的重要环节和举例方便自己理解和回顾。
一、涉及知识点
SpringBootNacosSentinelAOP代理拦截器
二、正文
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 ConsumerNacosSpiService() {SneakyThrowsOverridepublic 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.SystemRulecom.alibaba.csp.sentinel.slots.block.degrade.DegradeRulecom.alibaba.csp.sentinel.slots.block.flow.FlowRulecom.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
}以上资源会被转化为 Keycom.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 its 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(FunctionString, CollectionFlowRule ruleProvider, ResourceWrapper resource,Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {if (ruleProvider null || resource null) {return;}CollectionFlowRule 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官方文档博客