SpringBoot使用令牌桶算法+拦截器+自定义注解+自定义异常实现简单的限流
令牌桶
在高并发的情况下,限流是后端常用的手段之一,可以对系统限流、接口限流、用户限流等,本文就使用令牌桶算法+拦截器+自定义注解+自定义异常实现限流的demo。
令牌桶思想
大小固定的令牌桶可自行以恒定的速率源源不断地产生令牌。如果令牌不被消耗,或者被消耗的速度小于产生的速度,令牌就会不断地增多,直到把桶填满。
后面再产生的令牌就会从桶中溢出。最后桶中可以保存的最大令牌数永远不会超过桶的大小。然后每个访问的用户都会从中取走一块令牌,取到了令牌才能访问,如果没取到令牌即代表已达到访问上限,将被限流不允许访问
限流demo实现思路
- 创建令牌桶类
- 项目启动初始化令牌桶,并设置定时器,定时向桶内放入令牌
- 自定义限流注解,在需要限流的接口上打上注解
- 配置令牌桶拦截器,对所有路径进行拦截,对无限流注解的接口直接放行,对有限流注解的做取令牌处理,取到令牌即放行,没取到令牌即抛出自定义异常
- 自定义异常并使用AOP做全局异常处理
这里为了防止并发问题在生成令牌和取令牌的方法上加了synchronized
BucketUtil如下

1 public class BucketUtil {
2
3 //默认容量10
4 static final int DEFAULT_MAX_COUNT = 10;
5 // 默认增长速率为1
6 static final int DEFAULT_CREATE_RATE = 1;
7 // 使用HashMap存放令牌桶,这里默认为10个令牌桶
8 public static HashMap<String, BucketUtil> buckets = new HashMap(10);
9
10 //自定义容量,一旦创建不可改变
11 final int maxCount;
12 //自定义增长速率1s几个令牌
13 int createRate;
14 //当前令牌数
15 int size=0;
16
17
18
19 // 默认令牌桶的容量及增长速率
20 public BucketUtil() {
21 maxCount = DEFAULT_MAX_COUNT;
22 createRate = DEFAULT_CREATE_RATE;
23 }
24 // 自定义令牌桶容量及增长速率
25 public BucketUtil(int maxCount, int createRate) {
26 this.maxCount = maxCount;
27 this.createRate = createRate;
28 }
29
30 public int getSize() {
31 return size;
32 }
33
34 public boolean isFull() {
35 return size == maxCount;
36 }
37
38 //根据速率自增生成一个令牌
39 public synchronized void incrTokens() {
40 for (int i = 0; i < createRate; i++)
41 {
42 if (isFull())
43 return;
44 size++;
45 }
46 }
47
48 // 取一个令牌
49 public synchronized boolean getToken() {
50 if (size > 0)
51 size--;
52 else
53 return false;
54 return true;
55 }
56
57 @Override
58 public boolean equals(Object obj) {
59 if (obj == null)
60 return false;
61 BucketUtil bucket = (BucketUtil) obj;
62 if (bucket.size != size || bucket.createRate != createRate || bucket.maxCount != maxCount)
63 return false;
64 return true;
65 }
66
67 @Override
68 public int hashCode() {
69 return Objects.hash(maxCount, size, createRate);
70 }
71
72 }
BucketUtil
初始化令牌桶
在启动类上初始化并生成定时器

1 @EnableScheduling
2 @SpringBootApplication
3 public class DemoApplication {
4
5 public static void main(String[] args) {
6 SpringApplication.run(DemoApplication.class, args);
7 // 为了方便测试这里定义1容量 1增长速率
8 BucketUtil bucketUtil = new BucketUtil(1,1);
9 // 生成名为:bucket的令牌桶
10 BucketUtil.buckets.put("bucket",bucketUtil);
11 }
12 @Scheduled(fixedRate = 1000)// 定时1s
13 public void timer() {
14 if (BucketUtil.buckets.containsKey("bucket")){
15 //名为:bucket的令牌桶 开始不断生成令牌
16 BucketUtil.buckets.get("bucket").incrTokens();
17 }
18 }
19 }
DemoApplication
自定义注解以及异常

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

1 public class APIException extends RuntimeException {
2 private static final long serialVersionUID = 1L;
3 private String msg;
4 public APIException(String msg) {
5 super(msg);
6 this.msg = msg;
7 }
8 }
APIException
配置拦截器

1 /**
2 * 令牌桶拦截器
3 */
4 public class BucketInterceptor implements HandlerInterceptor {
5
6 // 预处理回调方法,在接口调用之前使用 true代表放行 false代表不放行
7 @Override
8 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
9 if (!(handler instanceof HandlerMethod)) {
10 return true;
11 }
12
13 HandlerMethod handlerMethod = (HandlerMethod) handler;
14 Method method = handlerMethod.getMethod();
15
16 BucketAnnotation methodAnnotation = method.getAnnotation(BucketAnnotation.class);
17 if (methodAnnotation!=null){
18 // 在名为:bucket的令牌桶里取令牌 取到即放行 未取到即抛出异常
19 if(BucketUtil.buckets.get("bucket").getToken()){
20 return true;
21 }
22 else{
23 // 抛出自定义异常
24 throw new APIException("不好意思,您被限流了");
25 }
26 }else {
27 return true;
28 }
29 }
30 // 接口调用之后,返回之前 使用
31 @Override
32 public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
33 }
34
35 // 整个请求完成后,在视图渲染前使用
36 @Override
37 public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
38 }
39 }
BucketInterceptor
将拦截器注入

1 @Configuration
2 public class WebMvcConfg implements WebMvcConfigurer {
3
4 @Override
5 public void addInterceptors(InterceptorRegistry registry) {
6 // 令牌桶拦截器 添加拦截器并选择拦截路径
7 registry.addInterceptor(bucketInterceptor()).addPathPatterns("/**");
8 }
9 @Bean
10 public BucketInterceptor bucketInterceptor() {
11 return new BucketInterceptor();
12 }
13 }
WebMvcConfg
AOP全局异常处理

1 @RestControllerAdvice
2 public class WebExceptionControl {
3 @ExceptionHandler(APIException.class)
4 public E3Result APIExceptionHandler(APIException e) {
5 return E3Result.build(400,e.getMessage());
6 }
7 }
WebExceptionControl
测试
在我们需要限流的接口上打上自定义注解,如下

@BucketAnnotation
@RequestMapping(value = "/bucket")
public E3Result bucket(){
return E3Result.ok("访问成功");
}
test
关于E3Result只是一个封装好的返回类,这里就不贴出来了,大家有的替换成自己的,没有的可以直接用String型测试
上面为了方便测试,令牌桶的容量设置成了1,所以这是取到令牌成功的


总结
上面的限流只是一个demo还有很多不足的地方,如:
- 分布式环境下不适用
- 令牌桶可以有多个,不同的接口采用不同令牌桶的时候,拦截器无法分开限流
- 一次请求消耗一个令牌,可以被恶意消耗
改进方法:
- 令牌桶实现采用redis集群存取
- 注解添加value参数,可以给对应接口打上对应的令牌桶参数,拦截器需对注解参数校验,实现多个接口多个令牌桶的限流
- 对用户IP校验限制次数,防止恶意攻击
实际项目限流会更加严谨,上述只是提供了一个思路以及演示demo,不喜勿喷谢谢。
转自:https://mp.weixin.qq.com/s/6Uh6e9T93osxttpL6M5cQA
SpringBoot使用令牌桶算法+拦截器+自定义注解+自定义异常实现简单的限流的更多相关文章
- 服务限流 -- 自定义注解基于RateLimiter实现接口限流
1. 令牌桶限流算法 令牌桶会以一个恒定的速率向固定容量大小桶中放入令牌,当有浏览来时取走一个或者多个令牌,当发生高并发情况下拿到令牌的执行业务逻辑,没有获取到令牌的就会丢弃获取服务降级处理,提示一个 ...
- mybaits拦截器+自定义注解
实现目的:为了存储了公共字典表主键的其他表在查询的时候不用关联查询(所以拦截位置位于mybaits语句查询得出结果集后) 项目环境 :springboot+mybaits 实现步骤:自定义注解——自定 ...
- SpringVC 拦截器+自定义注解 实现权限拦截
1.springmvc配置文件中配置 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns= ...
- SpringBoot使用自定义注解+AOP+Redis实现接口限流
为什么要限流 系统在设计的时候,我们会有一个系统的预估容量,长时间超过系统能承受的TPS/QPS阈值,系统有可能会被压垮,最终导致整个服务不可用.为了避免这种情况,我们就需要对接口请求进行限流. 所以 ...
- 基于令牌桶算法实现的SpringBoot分布式无锁限流插件
本文档不会是最新的,最新的请看Github! 1.简介 基于令牌桶算法和漏桶算法实现的纳秒级分布式无锁限流插件,完美嵌入SpringBoot.SpringCloud应用,支持接口限流.方法限流.系统限 ...
- 限流10万QPS、跨域、过滤器、令牌桶算法-网关Gateway内容都在这儿
一.微服务网关Spring Cloud Gateway 1.1 导引 文中内容包含:微服务网关限流10万QPS.跨域.过滤器.令牌桶算法. 在构建微服务系统中,必不可少的技术就是网关了,从早期的Zuu ...
- 15行python代码,帮你理解令牌桶算法
本文转载自: http://www.tuicool.com/articles/aEBNRnU 在网络中传输数据时,为了防止网络拥塞,需限制流出网络的流量,使流量以比较均匀的速度向外发送,令牌桶算法 ...
- MyBatis拦截器自定义分页插件实现
MyBaits是一个开源的优秀的持久层框架,SQL语句与代码分离,面向配置的编程,良好支持复杂数据映射,动态SQL;MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyB ...
- RateLimiter令牌桶算法
限流,是服务或者应用对自身保护的一种手段,通过限制或者拒绝调用方的流量,来保证自身的负载. 常用的限流算法有两种:漏桶算法和令牌桶算法 漏桶算法 思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度 ...
- 使用Redis实现令牌桶算法
在限流算法中有一种令牌桶算法,该算法可以应对短暂的突发流量,这对于现实环境中流量不怎么均匀的情况特别有用,不会频繁的触发限流,对调用方比较友好. 例如,当前限制10qps,大多数情况下不会超过此数量, ...
随机推荐
- [转帖]jmeter学习笔记(二十二)——监听器插件之jp@gc系列
一.jp@gc - Actiive Threads Over Time 不同时间活动用户数量展示 下面是一个阶梯加压测试的图标 二.jp@gc - Transactions per Second ...
- [转帖]Linux系统awk命令详解
AWK 是一种处理文本文件的语言,是一个强大的文本分析工具. 之所以叫 AWK 是因为其取了三位创始人 Alfred Aho,Peter Weinberger, 和 Brian Kernighan 的 ...
- [转帖]shell删除文件前几行或后几行
https://www.cnblogs.com/1394htw/p/14852207.html shell删除文件前几行或后几行 #!/bin/bash #删除前两行 sed -i '1,2d' fi ...
- nginx 负载均衡简单设置
自己简单维护了一个 需要继续改进. user nobody; worker_processes auto; events { worker_connections 1024; } http { inc ...
- js中toFixed 并不是你想的那样进行四舍五入
toFixed 的简单介绍 toFixed() 方法可把 Number 类型的数字通过四舍五入为指定小数位的字符串.(将数字类型转化为字符串类型) 也就是说toFixed只能够处理数字类型的. 字符串 ...
- vue获取子组件的实例$el、$attrs和inheritAttrs的使用
我的需求 有些时候,我们需要获取组件的DOM元素 有些小伙伴会说,这还不简单 直接使用this.$ref.xx不就可以了吗 我们来看一下,是不是我们想的那样简单 组件内容 <template&g ...
- Vue中Vue.set()和this.$forceUpdate()的使用
1.给对象添加一个key值 成功的 <template> <div> <p>{{userInfo.name}}</p> <p>{{userI ...
- minIO系列文章04---windows下安装及在.netcore使用
一.minio下载与启动 下载后会有一个minio.exe文件,放到指定的目录 在该目录下运行:minio.exe server D:\minio\file 出现如下的提示代码启动动成功: 浏览器中 ...
- Python 实现SynFlood洪水攻击
Syn-Flood攻击属于TCP攻击,Flood类攻击中最常见,危害最大的是Syn-Flood攻击,也是历史最悠久的攻击之一,该攻击属于半开放攻击,攻击实现原理就是通过发送大量半连接状态的数据包,从而 ...
- LeetCode刷题日记 2020/8/28
题目描述: 最长有效括号 给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度. 示例 1: 输入: "(()" 输出: 2 解释: 最长有效括号子串为 ...