背景
随着业务不断拓展优化,业务量的急增,导致应用系统性能瓶颈问题越来越显著;请求频繁超时、内部系统卡顿、CUP过高、内存不足、GC频繁;调用外部系统频繁超时,触发限流等情况;针对该情况提供一套熔断限流工具。
应用场景
系统高并发熔断限流
1.漏桶算法熔断限流可以保证外部系统稳定性
建议用于访问外部系统存在系统瓶颈,有限流等情况
2.令牌桶算法熔断限流可以保证内部系统稳定性
建议用于外部系统访问内部系统,内部系统存在瓶颈,性能问题等情况
3.固定窗口算法熔断限流可以到毫秒级熔断限流
根据实际业务场景合理选择,可限流,可控速,确定并发处理不友好,可与淘汰策略结合,使用乐观锁while(cas)自旋淘汰策略
4.强制并发限流
缺点没有并发时间差预处理浮动,强制返回失败,不够柔和;可根据业务场景选择
技术实现
- 自定义切面技术(aspectJ)
- Atomic原子类技术
- 漏桶算法
- 令牌桶算法
- 固定窗口算法
- CAS乐观锁
- ConcurrentHashMap防并发
核心实现
熔断限流实体对象
/** * * @ClassName: FuseStrategy.java * @Description: 熔断限流实体对象 * @author: ysf * @date: 2021年8月1日 上午10:55:01 */ public class FuseStrategy { /* * 熔断限流唯一标识 */ private String value; /* * 熔断限流策略0-令牌桶算法,1-漏桶算法,2-固定窗口算法,3当前并发限制 */ private int fuseStrategy; /* * 熔断限流桶容量 */ private long bucketLimit; /* * 秒级速率,漏桶算法每秒消费任务数量 */ private long bucketCustomeRate; /* * 秒级速率,令牌桶算法每秒生产任务数量 */ private long bucketPushRate; /* * 毫秒速率,固定x时间滑动执行1条任务 */ private long fixedWaitTime; /* * 熔断限流开始时间 */ private AtomicLong startTime; /* * 当前请求任务量 */ private AtomicLong currcentRequestLimit; /* *当前生成任务量 */ private AtomicLong currcentPushNum; /* *淘汰策略 0-直接结束返回null,1-直接报错,2-CAS等待资源释放继续执行 */ private int eliminateStrategy; ...... Get...;Set...;构造函数... ...... } |
熔断限流切面规则
/** * * @ClassName: CurrentLimitFuseStrategy.java * @Description: 限流熔断切面 * @author: ysf * @date: 2021年8月1日 下午4:18:11 * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CurrentLimitFuseStrategy { /* * 熔断限流唯一标识 */ String value() default ""; /* * 熔断限流策略0-令牌桶算法,1-漏桶算法,2-强制并发限制,3-固定窗口算法 */ int fuseStrategy() default 0; /* * 熔断限流限制并发数量 */ long bucketLimit() default 20; /* * 单位时间消费任务量,秒级速率,漏桶算法 */ long bucketCustomeRate() default 20; /* * 单位时间生产任务量,秒级速率,令牌桶算法 */ long bucketPushRate() default 20; /* * 固定窗口停顿时间 ,毫秒速率,固定窗口算法 */ long fixedWaitTime() default 100; /* * 淘汰策略 0-直接结束返回null,1-直接报错,2-CAS等待资源释放继续执行 */ int eliminateStrategy() default 0; } |
熔断限流切面实现
/** * * @ClassName: FuseStrategyAspect.java * @Description: 熔断限流切面 * @author: ysf * @date: 2021年8月1日 上午10:37:52 */ @Aspect @Component public class FuseStrategyAspect { private static Logger logger = LoggerFactory.getLogger(FuseStrategyAspect.class); @Pointcut("@annotation(fills.tools.fuse.CurrentLimitFuseStrategy)") public void fusePointCut() {
} /** * * @Function: FuseStrategyAspect.java * @Description: 熔断限流切面实现方法 * * @param: obj * @throws Throwable * @return:Object * * @author: ysf * @date: 2021年8月1日 上午10:38:15 */ @Around (value = "fusePointCut()") public Object doAround(ProceedingJoinPoint obj) throws Throwable { MethodSignature signature = (MethodSignature) obj.getSignature(); Method method = signature.getMethod(); CurrentLimitFuseStrategy fuseStrategyConfig = method.getAnnotation(CurrentLimitFuseStrategy.class); FuseStrategy fuseStrategy = CurrentLimitFuseCache.getFuseStrategy(fuseStrategyConfig.value()); if(fuseStrategy==null){ fuseStrategy = setFuseStrategy(fuseStrategyConfig); } if(!checkFuseLimit(fuseStrategy)){ Object res =obj.proceed(); FuseArithmetic.doReleaseResource(fuseStrategy); return res; }else{ if(fuseStrategy.getEliminateStrategy()== FuseEliminateStrategyEnum.ELIMINATE_RETURN_NULL.getIndex()){ logger.debug("并发限流触发,结束返回null"); return null; }else if(fuseStrategy.getEliminateStrategy()== FuseEliminateStrategyEnum.ELIMINATE_RETURN_ERROR.getIndex()){ logger.debug("并发限流触发,抛出【Throwable】"); throw new Throwable("系统并发触达上限报错"); }else{ logger.info("并发限流触发,while(cas)轮询等待资源释放"); while(checkFuseLimit(fuseStrategy)); Object res =obj.proceed(); FuseArithmetic.doReleaseResource(fuseStrategy); return res; } } } /** * * @Function: FuseStrategyAspect.java * @Description: 熔断限流检验并拦截 * * @param: fuseStrategy * @return:boolean * * @author: ysf * @date: 2021年8月1日 上午10:35:41 */ private boolean checkFuseLimit(FuseStrategy fuseStrategy){ boolean flag = false; if(fuseStrategy.getFuseStrategy()== FuseStrategyEnum.FUSE_BUCKET_ALGORITHM.getIndex()){ flag= FuseArithmetic.bucketArithmetic(fuseStrategy); }else if(fuseStrategy.getFuseStrategy()== FuseStrategyEnum.FUSE_TOKEN_BUCKET_ALGORITHM.getIndex()){ flag= FuseArithmetic.tokenBucketArithmetic(fuseStrategy); }else if(fuseStrategy.getFuseStrategy()== FuseStrategyEnum.FUSE_FIXED_ALGORITHM.getIndex()){ flag= FuseArithmetic.fixedLimitArithmetic(fuseStrategy); }else if(fuseStrategy.getFuseStrategy()== FuseStrategyEnum.FUSE_LIMIT_ALGORITHM.getIndex()){ flag= FuseArithmetic.requestLimitArithmetic(fuseStrategy); } return flag; } /** * * @Function: FuseStrategyAspect.java * @Description: 缓存熔断限流实体对象 * synchronized 防止并发 * @param: fuseStrategyConfig * @param: * @return:FuseStrategy * * @author: ysf * @date: 2021年8月1日 上午10:37:05 * */ private synchronized FuseStrategy setFuseStrategy(CurrentLimitFuseStrategy fuseStrategyConfig){ FuseStrategy fuseStrategy = null; int fuseType = fuseStrategyConfig.fuseStrategy(); if(fuseType==FuseStrategyEnum.FUSE_BUCKET_ALGORITHM.getIndex()){ fuseStrategy = new FuseStrategy(fuseStrategyConfig.value(), fuseType, fuseStrategyConfig.bucketLimit(), fuseStrategyConfig.bucketCustomeRate(), fuseStrategyConfig.eliminateStrategy(), new AtomicLong(0L)); }else if(fuseType==FuseStrategyEnum.FUSE_TOKEN_BUCKET_ALGORITHM.getIndex()){ fuseStrategy = new FuseStrategy(fuseStrategyConfig.value(), fuseType, fuseStrategyConfig.bucketLimit(), fuseStrategyConfig.bucketPushRate(), fuseStrategyConfig.eliminateStrategy()); }else if(fuseType==FuseStrategyEnum.FUSE_FIXED_ALGORITHM.getIndex()){ fuseStrategy = new FuseStrategy(fuseStrategyConfig.value(), fuseType, fuseStrategyConfig.fixedWaitTime(), fuseStrategyConfig.eliminateStrategy(), new AtomicLong(0L)); }else{ fuseStrategy = new FuseStrategy(fuseStrategyConfig.value(), fuseType, fuseStrategyConfig.bucketLimit(), fuseStrategyConfig.eliminateStrategy()); } CurrentLimitFuseCache.setFuseStrategy(fuseStrategy); return CurrentLimitFuseCache.getFuseStrategy(fuseStrategy.getValue()); } } |
熔断限流算法实现
/** * * @ClassName: FuseArithmetic.java * @Description: 熔断限流算法 * @author: ysf * @date: 2021年7月30日 下午8:57:40 * */ public class FuseArithmetic { private static Logger logger = LoggerFactory.getLogger(FuseArithmetic.class); /** * * @Function: FuseArithmetic.java * @Description: 漏桶算法秒级限流,不限制放入速率,限制出口速率,溢出抛弃,可以防止并发访问外部系统导致崩溃 * fasle 没有触发限制,true 触发限制 * @param: fuseStrategy * @return:boolean * * @author: ysf * @date: 2021年7月30日 下午8:17:06 * */ public static boolean bucketArithmetic(FuseStrategy fuseStrategy){ long currcentTime = System.currentTimeMillis(); //当前时间与上一次时间差 long leadTime = currcentTime - fuseStrategy.getStartTime().addAndGet(0L); //预计释放容量 long consumeNum = leadTime>0 ? leadTime * fuseStrategy.getBucketCustomeRate()/ 1000 : 0L; long requestLimit = 0L; if(leadTime>0){ fuseStrategy.getStartTime().addAndGet(leadTime); } if(Math.max(0, (requestLimit=fuseStrategy.getCurrcentRequestLimit().incrementAndGet()) - consumeNum)>fuseStrategy.getBucketLimit()){ logger.debug(fuseStrategy.getValue()+"_true_bucketArithmetic_requestLimit="+requestLimit+",bucketLimit="+fuseStrategy.getBucketLimit()+",leadTime="+leadTime); fuseStrategy.getCurrcentRequestLimit().decrementAndGet(); return true; } logger.debug(fuseStrategy.getValue()+"_false_bucketArithmetic_requestLimit="+requestLimit+",bucketLimit="+fuseStrategy.getBucketLimit()+",leadTime="+leadTime); return false; } /** * * @Function: FuseArithmetic.java * @Description: 令牌桶算法秒级限流,限制放入速率,不限制出口速率,溢出抛弃,可以缓冲处理急增并发,可以保护内部系统稳定 * fasle 没有触发限制,true 触发限制 * @param: fuseStrategy * @return:boolean * * @author: ysf * @date: 2021年7月30日 下午8:59:16 * */ public static boolean tokenBucketArithmetic(FuseStrategy fuseStrategy){ long currcentTime = System.currentTimeMillis(); //当前时间与上一次时间差 long leadTime = currcentTime - fuseStrategy.getStartTime().addAndGet(0L); //预计增加容量 long pushNum = leadTime>0 ? leadTime* fuseStrategy.getBucketPushRate()/ 1000 : 0L; long currcentPushNum = 0L; if(leadTime>0){ fuseStrategy.getStartTime().addAndGet(leadTime); } if((currcentPushNum = Math.min(fuseStrategy.getBucketLimit(),fuseStrategy.getCurrcentPushNum().decrementAndGet()+pushNum))>=0){ logger.debug(fuseStrategy.getValue()+"_false_tokenBucketArithmetic_requestLimit="+currcentPushNum+",bucketLimit="+fuseStrategy.getBucketLimit()+",leadTime="+leadTime); return false; } logger.debug(fuseStrategy.getValue()+"_true_tokenBucketArithmetic_requestLimit="+currcentPushNum+",bucketLimit="+fuseStrategy.getBucketLimit()+",leadTime="+leadTime); fuseStrategy.getCurrcentPushNum().incrementAndGet(); return true; } /** * * @Function: FuseArithmetic.java * @Description: 固定窗口算法,每个单位时间内固定并发任务数,超过就熔断限流,可以毫秒及熔断限流,可以用于内部控制急增并发访问/控制访问外部速度(针对外部限流平台) * fasle 没有触发限制,true 触发限制 * @param: fuseStrategy * @return:boolean * * @author: ysf * @date: 2021年8月1日 上午10:23:48 */ public static boolean fixedLimitArithmetic(FuseStrategy fuseStrategy){ long currcentTime = System.currentTimeMillis(); //当前时间与上一次时间差 long leadTime = currcentTime - fuseStrategy.getStartTime().addAndGet(0L); Long currcentRequestLimit =0L; if(leadTime>0&&leadTime>=fuseStrategy.getFixedWaitTime()){ fuseStrategy.getCurrcentRequestLimit().lazySet(0); currcentRequestLimit = fuseStrategy.getCurrcentRequestLimit().incrementAndGet(); fuseStrategy.getStartTime().lazySet(currcentTime); logger.debug(fuseStrategy.getValue()+"_false_fixedLimitArithmetic_currcentRequestLimit="+currcentRequestLimit+",bucketLimit="+fuseStrategy.getBucketLimit()+",leadTime="+leadTime); return false; } if(fuseStrategy.getBucketLimit()>= (currcentRequestLimit = fuseStrategy.getCurrcentRequestLimit().incrementAndGet()) ){ logger.debug(fuseStrategy.getValue()+"_false_fixedLimitArithmetic_currcentRequestLimit="+currcentRequestLimit+",bucketLimit="+fuseStrategy.getBucketLimit()+",leadTime="+leadTime); return false; }else{ logger.debug(fuseStrategy.getValue()+"_true_fixedLimitArithmetic_currcentRequestLimit="+currcentRequestLimit+",bucketLimit="+fuseStrategy.getBucketLimit()+",leadTime="+leadTime); fuseStrategy.getCurrcentRequestLimit().decrementAndGet(); return true; } } /** * * @Function: FuseArithmetic.java * @Description: 当前请求数量大fuseLimit,强制熔断 * fasle 没有触发限制,true 触发限制 * @param: fuseStrategy * @return:boolean * * @author: ysf * @date: 2021年7月30日 下午8:06:49 * */ public static boolean requestLimitArithmetic(FuseStrategy fuseStrategy){ if(fuseStrategy.getCurrcentRequestLimit().incrementAndGet()>=fuseStrategy.getBucketLimit()){ fuseStrategy.getCurrcentRequestLimit().decrementAndGet(); return true; }else { return false; } } /** * * @Function: FuseArithmetic.java * @Description: 释放当前请求资源 * 漏桶,令牌桶,强制次数限流可释放资源,固定时间窗口不需要放资源 * @param: fuseStrategy * @return:void * * @author: ysf * @date: 2021年7月30日 下午7:45:49 * */ public static void doReleaseResource(FuseStrategy fuseStrategy){ long releaseNum = 0; if(fuseStrategy.getFuseStrategy()==FuseStrategyEnum.FUSE_TOKEN_BUCKET_ALGORITHM.getIndex()){ releaseNum = fuseStrategy.getCurrcentPushNum().incrementAndGet(); logger.debug(fuseStrategy.getValue()+"_"+FuseStrategyEnum.FUSE_TOKEN_BUCKET_ALGORITHM.getName()+"_释放1个任务资源_现持有任务资源"+releaseNum+"个"); }else if(fuseStrategy.getFuseStrategy()==FuseStrategyEnum.FUSE_BUCKET_ALGORITHM.getIndex() ||fuseStrategy.getFuseStrategy()==FuseStrategyEnum.FUSE_LIMIT_ALGORITHM.getIndex()){ releaseNum = fuseStrategy.getCurrcentRequestLimit().decrementAndGet(); logger.debug(fuseStrategy.getValue()+"_"+FuseStrategyEnum.FUSE_TOKEN_BUCKET_ALGORITHM.getName()+"_释放1个任务资源_现持有任务资源"+releaseNum+"个"); } } } |
熔断限流淘汰策略
public enum FuseEliminateStrategyEnum { ELIMINATE_RETURN_NULL(0,"返回NULL"), ELIMINATE_RETURN_ERROR(1,"抱出异常"), ELIMINATE_RETURN_CAS(2,"乐观锁等待"); private int index; private String name; public int getIndex() { return index; } public String getName() { return name; } private FuseEliminateStrategyEnum(int index,String name) { this.index = index; this.name = name; } } 熔断限流切面实现->淘汰策略实现FuseStrategyAspect.doAround(ProceedingJoinPoint) 实现部分代码 ...... if(fuseStrategy.getEliminateStrategy()== FuseEliminateStrategyEnum.ELIMINATE_RETURN_NULL.getIndex()){ logger.debug("并发限流触发,结束返回null"); return null; }else if(fuseStrategy.getEliminateStrategy()== FuseEliminateStrategyEnum.ELIMINATE_RETURN_ERROR.getIndex()){ logger.debug("并发限流触发,抛出【Throwable】"); throw new Throwable("系统并发触达上限报错"); }else{ logger.info("并发限流触发,while(cas)轮询等待资源释放"); while(checkFuseLimit(fuseStrategy)); Object res =obj.proceed(); FuseArithmetic.doReleaseResource(fuseStrategy); return res; } ...... |
系统接入
POM.xml配置
pom.xml导入jar <dependency> <groupId>fills.tools</groupId> <artifactId>fills-fuse-tools</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> |
Spring注入
注解方式
applicationXXX.xml 文件 <context:component-scan base-package="fills.tools.aspect"> <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation" /> </context:component-scan> |
手动注入
applicationXXX.xml 文件 <bean id="fuseStrategyAspect" class="fills.tools.aspect.FuseStrategyAspect"/> |
SpringBoot注入
启动主函数 StartXXX.java 类头部注解 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class, scanBasePackages = {"com.xxx.xx", "fills.tools.aspect"}) |
接口层接入
漏桶注解参数
value="doSendEmail" ,限流接口唯一标识;fuseStrategy = 1 限流策略漏桶算法;bucketLimit = 20 ,限流桶容量20;bucketCustomeRate =20,1秒内消耗任务量;eliminateStrategy = 0,熔断策略结束返回null; @CurrentLimitFuseStrategy(value="doSendEmail",fuseStrategy = 1,bucketLimit = 20,bucketCustomeRate =20,eliminateStrategy = 0) @Override public void doSendEmail() { System.out.println("业务实现忽略"); } |
令牌桶注解参数
value="doSendEmail" ,限流接口唯一标识;fuseStrategy = 0 限流策略令牌桶算法;bucketLimit = 20 ,限流桶容量20;bucketPushRate =20,1秒内生成任务量;eliminateStrategy = 0,熔断策略结束返回null; @CurrentLimitFuseStrategy(value="doSendEmail",fuseStrategy = 0,bucketLimit = 20,bucketPushRate =20,eliminateStrategy = 0) @Override public void doSendEmail() { System.out.println("业务实现忽略"); } |
活动窗口注解参数
value="doSendEmail" ,限流接口唯一标识;fuseStrategy = 2 限流策略滑动窗口算法;fixedWaitTime=20,等待20毫秒滑动,默认一次;eliminateStrategy = 2,熔断策略 while(cas)自旋等待资源释放; @CurrentLimitFuseStrategy(value="doSendEmail",fuseStrategy = 2, fixedWaitTime=20,eliminateStrategy = 2) @Override public void doSendEmail() { System.out.println("业务实现忽略"); } |
强制限流注解参数
value="doSendEmail" ,限流接口唯一标识;fuseStrategy = 3 限流策略强制算法;bucketLimit =20,当前并发限制20次数;eliminateStrategy = 0,熔断策略结束返回null; @CurrentLimitFuseStrategy(value="doSendEmail",fuseStrategy = 2, bucketLimit = 20,eliminateStrategy = 0) @Override public void doSendEmail() { System.out.println("业务实现忽略"); } |
接入JAR
熔断现楼源码