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,大多数情况下不会超过此数量, ...
随机推荐
- ThreadLocal源码解析及实战应用
作者:京东物流 闫鹏勃 1 什么是ThreadLocal? ThreadLocal是一个关于创建线程局部变量的类. 通常情况下,我们创建的变量是可以被任何一个线程访问并修改的.而使用ThreadLoc ...
- 使用css 与 js 两种方式实现导航栏吸顶效果
场景描述 简单的说一下场景描述:这个页面有三个部分组成的. 顶部的头部信息--导航栏--内容 当页面滚动的时候.导航栏始终是固定在最顶部的. 我们使用的第一种方案就是使用css的粘性定位 positi ...
- Vue3类型判断和ref的两个作用
1.类型判断的四种方法 isRef: 检查一个值是否为一个ref对象 isReactive:检查一个对象是否是由 reactive 创建的响应式代理 isReadonly: 检查一个对象是否是由 re ...
- 窗口管理器 dwm安装
上一篇博文中,已经完成了archlinux的安装,但是进去仅仅是一个冰冷冷的交互式命令窗口.没有图像,也无法打开浏览器.离日常使用还差的很远,接下来首先需要做的就是安装桌面环境.这里我不打算使用诸如g ...
- MySQL 中使用变量实现排名名次
title: MySQL 中使用变量实现排名名次 date: 2023-7-16 19:45:26 tags: - SQL 高级查询 一. 数据准备: CREATE TABLE sql_rank ( ...
- MybatisPlus对Mysql数据库关键字作为列名的处理--SQLSyntaxErrorException: You have an error in your SQL syntax;
说明: 在设计数据库时,使用mysql关键字作为列名(比如order用于排序),就会报错:java.sql.SQLSyntaxErrorException: You have an error in ...
- 小白学k8s(12)-k8s中PV和PVC理解
pv和pvc 什么是pv和PVC 生命周期 PV创建的流程 1.创建一个远程块存储,相当于创建了一个磁盘,称为Attach 2.将这个磁盘设备挂载到宿主机的挂载点,称为Mount 3.绑定 持久化卷声 ...
- 强化学习从基础到进阶-案例与实践[4.2]:深度Q网络DQN-Cart pole游戏展示
强化学习从基础到进阶-案例与实践[4.2]:深度Q网络DQN-Cart pole游戏展示 强化学习(Reinforcement learning,简称RL)是机器学习中的一个领域,区别与监督学习和无监 ...
- 【7】vscode不同的窗口样式和颜色插件peacock、设置打开多个窗口、md文件打开方式和预览以及插入目录
相关文章: [1]VScode中文界面方法-------超简单教程 [2]VScode搭建python和tensorflow环境 [3]VSCode 主题设置推荐,自定义配色方案,修改注释高亮颜色 [ ...
- SpringCloud-02-Nacos注册中心
Nacos注册中心 1.认识Nacos Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件.相比Eureka功能更加丰富,在国内受欢迎程度较高. 2.安装Nacos 1 1.下载安装 ...