• Sentinel的限流、熔断源码浅析


    一、简单使用(源码分析基石)

    1.引入 Sentinel 依赖

    		<dependency>
    			<groupId>com.alibaba.cloud</groupId>
    			<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    		</dependency>
    

    2.对Controller方法打上注解

    	@SentinelResource(value = "/order/create", blockHandler = "blockHandlerFunc", fallback = "fallbackFunc")
        @GetMapping("/test-sentinel-resource")
        public String testSentinelResource(@RequestParam(required = false) String a)
                throws InterruptedException {
            // 模拟执行被保护的业务逻辑耗时
            Thread.sleep(100);
            return a;
        }
    

    二、注解解析-----切面

    通过一个切面类SentinelResourceAspect,去解析我们写的@SentinelResource

    @Aspect
    public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
    
    	//切点,SentinelResource注解
        @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
        public void sentinelResourceAnnotationPointcut() {
        }
    
    	//环绕通知
        @Around("sentinelResourceAnnotationPointcut()")
        public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
            Method originMethod = resolveMethod(pjp);
    
            SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
            if (annotation == null) {
                // Should not go through here.
                throw new IllegalStateException("Wrong state for SentinelResource annotation");
            }
            String resourceName = getResourceName(annotation.value(), originMethod);
            EntryType entryType = annotation.entryType();
            int resourceType = annotation.resourceType();
            Entry entry = null;
            try {
                entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
                Object result = pjp.proceed();
                return result;
            } catch (BlockException ex) {
                return handleBlockException(pjp, annotation, ex);
            } catch (Throwable ex) {
                Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
                // The ignore list will be checked first.
                if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
                    throw ex;
                }
                if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
                    traceException(ex, annotation);
                    return handleFallback(pjp, annotation, ex);
                }
                throw ex;
            } finally {
                if (entry != null) {
                    entry.exit(1, pjp.getArgs());
                }
            }
        }
    }
    

    三、处理方式----try/catch

    通过一个大大的try/catch,包裹住我们的方法,在之前执行SphU.entry(通过在这规则校验抛出异常而阻止后续方法正常执行达到限流熔断),执行之后根据方法执行的情况进行记录处理等:

            try {
                entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
                Object result = pjp.proceed();
                return result;
            } catch (BlockException ex) {
                return handleBlockException(pjp, annotation, ex);
            } catch (Throwable ex) {
                if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
                    traceException(ex, annotation);
                    return handleFallback(pjp, annotation, ex);
                }
                throw ex;
            } finally {
                if (entry != null) {
                    entry.exit(1, pjp.getArgs());
                }
            }
    

    四、重点看看SphU.entry的规则校验记录怎么实现的?

    跟进上面的那行代码:

    entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
    

    不断跟进会来到entryWithPriority方法:

        private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
            throws BlockException {
            //。。。略
    
            ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
            if (chain == null) {
                return new CtEntry(resourceWrapper, null, context);
            }
    
            Entry e = new CtEntry(resourceWrapper, chain, context);
            try {
                chain.entry(context, resourceWrapper, null, count, prioritized, args);
            } catch (BlockException e1) {
                e.exit(count, args);
                throw e1;
            } catch (Throwable e1) {
                // This should not happen, unless there are errors existing in Sentinel internal.
                RecordLog.info("Sentinel unexpected exception", e1);
            }
            return e;
        }
    

    主要是lookProcessChain获取一个“链条”,然后执行“链条”的entry的方法(实际上是lookProcessChain取出很多个ProcessorSlot,然后依次执行,责任链设计模式)

    4.1 lookProcessChain

        ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
           ProcessorSlotChain chain = chainMap.get(resourceWrapper);
           if (chain == null) {
               synchronized (LOCK) {
                   chain = chainMap.get(resourceWrapper);
                   if (chain == null) {
                       // Entry size limit.
                       if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
                           return null;
                       }
    
                       chain = SlotChainProvider.newSlotChain();
                       Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
                           chainMap.size() + 1);
                       newMap.putAll(chainMap);
                       newMap.put(resourceWrapper, chain);
                       chainMap = newMap;
                   }
               }
           }
           return chain;
       }
    

    单例设计模式DCL去创建ProcessorSlotChain,主要是:

    chain = SlotChainProvider.newSlotChain();
    

    查看源码会发现是通过建造者模式builder去创建的,而建造者本身builde方法:

        public ProcessorSlotChain build() {
            ProcessorSlotChain chain = new DefaultProcessorSlotChain();
            //加入下面这些slot形成链表,之后调用的时候依次执行
            chain.addLast(new NodeSelectorSlot());
            chain.addLast(new ClusterBuilderSlot());
            chain.addLast(new LogSlot());
            chain.addLast(new StatisticSlot());
            chain.addLast(new SystemSlot());
            chain.addLast(new AuthoritySlot());
            chain.addLast(new FlowSlot());
            chain.addLast(new DegradeSlot());
    
            return chain;
        }
    

    真相大白,这个执行“执行链条”就是这些各种‘Slot’,核心的是:StatisticSlot(统计相关)、FlowSlot(限流相关)、DegradeSlot(降级相关)。

    4.2 chain.entry依次执行各个Slot

    回到之前的entryWithPriority方法:

        private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
            throws BlockException {
            //。。。略
    
            ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
            if (chain == null) {
                return new CtEntry(resourceWrapper, null, context);
            }
    
            Entry e = new CtEntry(resourceWrapper, chain, context);
            try {
                chain.entry(context, resourceWrapper, null, count, prioritized, args);
            } catch (BlockException e1) {
                e.exit(count, args);
                throw e1;
            } catch (Throwable e1) {
                // This should not happen, unless there are errors existing in Sentinel internal.
                RecordLog.info("Sentinel unexpected exception", e1);
            }
            return e;
        }
    

    那么拿到执行链条之后,就是依次执行各个Slot的entry方法了!

    五、StatisticSlot(统计相关)

    稍后补充todo

    六、FlowSlot(限流相关)

    稍后补充todo

    七、DegradeSlot(降级相关)

    稍后补充todo

    八、集群限流设计方案-ClusterBuilderSlot

    8.1 基本思想

    引入了Token Server和Token Client的概念(相当引入了个管理中心)

    Token Server:即集群流控服务端,处理来自 Token Client 的请求,根据配置的集群规则判断是否应该发放 token(是否允许通过)。服务端采用的还是滑动窗口。

    Token Client:集群流控客户端,就是我们的应用本身,用于向所属 Token Server 通信请求 token(以及统计信息)。集群限流服务端会返回给客户端结果,决定是否限流。
    在这里插入图片描述

    8.2 基本流程

    ClusterBuilderSlot用来统计各种集群相关信息啥的,真正判断限流还是在流控FlowRuleChecker类(FlowSlot持有的属性对象)的canPassCheck方法中:

        public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount,
                                                        boolean prioritized) {
            String limitApp = rule.getLimitApp();
            if (limitApp == null) {
                return true;
            }
            //判断是集群模式,采用从Token Server获取是否给通过
            if (rule.isClusterMode()) {
                return passClusterCheck(rule, context, node, acquireCount, prioritized);
            }
            return passLocalCheck(rule, context, node, acquireCount, prioritized);
        }
    

    会根据是否是集群模式,采用从Token Server获取是否给通过

    8.3 可用性

    目前针对token server高可用,sentinel并没有对应的解决方案,默认会降级走本地流控。

    8.4 总结

    仅靠单机维度去限制的话可能会无法精确地限制总体流量。而集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果。

  • 相关阅读:
    leetcode211
    leetcode209
    leetcode201
    leetcode1396
    leetcode1395
    leetcode1394
    leetcode1386
    leetcode1387
    leetcode1382
    leetcode1376
  • 原文地址:https://www.cnblogs.com/chz-blogs/p/14255321.html
Copyright © 2020-2023  润新知