1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 【229期】Spring Boot 使用令牌桶算法+拦截器+自定义注解+自定义异常实现简单的限流...

【229期】Spring Boot 使用令牌桶算法+拦截器+自定义注解+自定义异常实现简单的限流...

时间:2019-02-22 06:24:47

相关推荐

【229期】Spring Boot 使用令牌桶算法+拦截器+自定义注解+自定义异常实现简单的限流...

点击上方“Java精选”,选择“设为星标”

别问别人为什么,多问自己凭什么!

下方有惊喜,留言必回,有问必答!

每天08:15更新文章,每天进步一点点...

令牌桶

在高并发的情况下,限流是后端常用的手段之一,可以对系统限流、接口限流、用户限流等,本文就使用令牌桶算法+拦截器+自定义注解+自定义异常实现限流的demo。

令牌桶思想

大小固定的令牌桶可自行以恒定的速率源源不断地产生令牌。如果令牌不被消耗,或者被消耗的速度小于产生的速度,令牌就会不断地增多,直到把桶填满。

后面再产生的令牌就会从桶中溢出。最后桶中可以保存的最大令牌数永远不会超过桶的大小。然后每个访问的用户都会从中取走一块令牌,取到了令牌才能访问,如果没取到令牌即代表已达到访问上限,将被限流不允许访问

限流demo实现思路

创建令牌桶类

项目启动初始化令牌桶,并设置定时器,定时向桶内放入令牌

自定义限流注解,在需要限流的接口上打上注解

配置令牌桶拦截器,对所有路径进行拦截,对无限流注解的接口直接放行,对有限流注解的做取令牌处理,取到令牌即放行,没取到令牌即抛出自定义异常

自定义异常并使用AOP做全局异常处理

创建令牌类

这里为了防止并发问题在生成令牌和取令牌的方法上加了synchronized

BucketUtil如下

publicclassBucketUtil{//默认容量10staticfinalintDEFAULT_MAX_COUNT=10;//默认增长速率为1staticfinalintDEFAULT_CREATE_RATE=1;//使用HashMap存放令牌桶,这里默认为10个令牌桶publicstaticHashMap<String,BucketUtil>buckets=newHashMap(10);//自定义容量,一旦创建不可改变finalintmaxCount;//自定义增长速率1s几个令牌intcreateRate;//当前令牌数intsize=0;//默认令牌桶的容量及增长速率publicBucketUtil(){maxCount=DEFAULT_MAX_COUNT;createRate=DEFAULT_CREATE_RATE;}//自定义令牌桶容量及增长速率publicBucketUtil(intmaxCount,intcreateRate){this.maxCount=maxCount;this.createRate=createRate;}publicintgetSize(){returnsize;}publicbooleanisFull(){returnsize==maxCount;}//根据速率自增生成一个令牌publicsynchronizedvoidincrTokens(){for(inti=0;i<createRate;i++){if(isFull())return;size++;}}//取一个令牌publicsynchronizedbooleangetToken(){if(size>0)size--;elsereturnfalse;returntrue;}@Overridepublicbooleanequals(Objectobj){if(obj==null)returnfalse;BucketUtilbucket=(BucketUtil)obj;if(bucket.size!=size||bucket.createRate!=createRate||bucket.maxCount!=maxCount)returnfalse;returntrue;}@OverridepublicinthashCode(){returnObjects.hash(maxCount,size,createRate);}}

初始化令牌桶

在启动类上初始化并生成定时器

推荐下自己几个月熬夜整理的面试资料大全:

/yoodb/eboo‍ks

@EnableScheduling@SpringBootApplicationpublicclassDemoApplication{publicstaticvoidmain(String[]args){SpringApplication.run(DemoApplication.class,args);//为了方便测试这里定义1容量1增长速率BucketUtilbucketUtil=newBucketUtil(1,1);//生成名为:bucket的令牌桶BucketUtil.buckets.put("bucket",bucketUtil);}@Scheduled(fixedRate=1000)//定时1spublicvoidtimer(){if(BucketUtil.buckets.containsKey("bucket")){//名为:bucket的令牌桶开始不断生成令牌BucketUtil.buckets.get("bucket").incrTokens();}}}

自定义注解以及异常

BucketAnnotation注解

@Target({ElementType.METHOD})//METHOD代表是用在方法上@Retention(RetentionPolicy.RUNTIME)public@interfaceBucketAnnotation{}

APIException 异常

publicclassAPIExceptionextendsRuntimeException{privatestaticfinallongserialVersionUID=1L;privateStringmsg;publicAPIException(Stringmsg){super(msg);this.msg=msg;}}

配置拦截器

BucketInterceptor拦截器如下

/***令牌桶拦截器*/publicclassBucketInterceptorimplementsHandlerInterceptor{//预处理回调方法,在接口调用之前使用true代表放行false代表不放行@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){if(!(handlerinstanceofHandlerMethod)){returntrue;}HandlerMethodhandlerMethod=(HandlerMethod)handler;Methodmethod=handlerMethod.getMethod();BucketAnnotationmethodAnnotation=method.getAnnotation(BucketAnnotation.class);if(methodAnnotation!=null){//在名为:bucket的令牌桶里取令牌取到即放行未取到即抛出异常if(BucketUtil.buckets.get("bucket").getToken()){returntrue;}else{//抛出自定义异常thrownewAPIException("不好意思,您被限流了");}}else{returntrue;}}//接口调用之后,返回之前使用@OverridepublicvoidpostHandle(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,Objecto,ModelAndViewmodelAndView)throwsException{}//整个请求完成后,在视图渲染前使用@OverridepublicvoidafterCompletion(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,Objecto,Exceptione)throwsException{}}

将拦截器注入

@ConfigurationpublicclassWebMvcConfgimplementsWebMvcConfigurer{@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){//令牌桶拦截器添加拦截器并选择拦截路径registry.addInterceptor(bucketInterceptor()).addPathPatterns("/**");}@BeanpublicBucketInterceptorbucketInterceptor(){returnnewBucketInterceptor();}}

AOP全局异常处理

@RestControllerAdvicepublicclassWebExceptionControl{@ExceptionHandler(APIException.class)publicE3ResultAPIExceptionHandler(APIExceptione){returnE3Result.build(400,e.getMessage());}}

测试

在我们需要限流的接口上打上自定义注解,如下

@BucketAnnotation@RequestMapping(value="/bucket")publicE3Resultbucket(){returnE3Result.ok("访问成功");}

关于E3Result只是一个封装好的返回类,这里就不贴出来了,大家有的替换成自己的,没有的可以直接用String型测试

推荐下自己做的 Spring boot 的实战项目:

/yoodb/jing-xuan

上面为了方便测试,令牌桶的容量设置成了1,所以这是取到令牌成功的

这是没取到令牌被限流的

总结

上面的限流只是一个demo还有很多不足的地方,如:

分布式环境下不适用

令牌桶可以有多个,不同的接口采用不同令牌桶的时候,拦截器无法分开限流

一次请求消耗一个令牌,可以被恶意消耗

改进方法:

令牌桶实现采用redis集群存取

注解添加value参数,可以给对应接口打上对应的令牌桶参数,拦截器需对注解参数校验,实现多个接口多个令牌桶的限流

对用户IP校验限制次数,防止恶意攻击

实际项目限流会更加严谨,上述只是提供了一个思路以及演示demo,不喜勿喷谢谢。

版权声明:本文为CSDN博主「Colins~」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

/weixin_44102992/article/details/106492691

公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!

------ THE END ------

精品资料,超赞福利!

>Java精选面试题<

3000+ 道面试题在线刷,最新、最全 Java 面试题!

期往精选点击标题可跳转

【221期】扔掉工具类,Mybatis 一个简单配置搞定数据加密解密!

【222期】一个适用于 Spring Boot 项目的轻量级 HTTP 客户端框架

【223期】面试官问:什么是 YAML?和 Spring Boot 有什么关系?

【224期】Java 字符串拼接五种方法的性能比较分析,从执行100次到90万次?

【225期】为什么数据库连接池不采用 IO 多路复用?

【226期】面试问我,HashMap 的默认初始容量是多少,我该怎么说?

【227期】面试官:MySQL 数据查询太多会 OOM 吗?

【228期】面试官:跨库多表存在大量数据依赖问题,有哪些解决方案?

技术交流群!

最近有很多人问,有没有读者&异性交流群,你懂的!想知道如何加入。加入方式很简单,有兴趣的同学,只需要点击下方卡片,回复“加群”,即可免费加入交流群!

文章有帮助的话,在看,转发吧!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。