令牌桶

在高并发的情况下,限流是后端常用的手段之一,可以对系统限流、接口限流、用户限流等,本文就使用令牌桶算法+拦截器+自定义注解+自定义异常实现限流的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使用令牌桶算法+拦截器+自定义注解+自定义异常实现简单的限流的更多相关文章

  1. 服务限流 -- 自定义注解基于RateLimiter实现接口限流

    1. 令牌桶限流算法 令牌桶会以一个恒定的速率向固定容量大小桶中放入令牌,当有浏览来时取走一个或者多个令牌,当发生高并发情况下拿到令牌的执行业务逻辑,没有获取到令牌的就会丢弃获取服务降级处理,提示一个 ...

  2. mybaits拦截器+自定义注解

    实现目的:为了存储了公共字典表主键的其他表在查询的时候不用关联查询(所以拦截位置位于mybaits语句查询得出结果集后) 项目环境 :springboot+mybaits 实现步骤:自定义注解——自定 ...

  3. SpringVC 拦截器+自定义注解 实现权限拦截

    1.springmvc配置文件中配置 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns= ...

  4. SpringBoot使用自定义注解+AOP+Redis实现接口限流

    为什么要限流 系统在设计的时候,我们会有一个系统的预估容量,长时间超过系统能承受的TPS/QPS阈值,系统有可能会被压垮,最终导致整个服务不可用.为了避免这种情况,我们就需要对接口请求进行限流. 所以 ...

  5. 基于令牌桶算法实现的SpringBoot分布式无锁限流插件

    本文档不会是最新的,最新的请看Github! 1.简介 基于令牌桶算法和漏桶算法实现的纳秒级分布式无锁限流插件,完美嵌入SpringBoot.SpringCloud应用,支持接口限流.方法限流.系统限 ...

  6. 限流10万QPS、跨域、过滤器、令牌桶算法-网关Gateway内容都在这儿

    一.微服务网关Spring Cloud Gateway 1.1 导引 文中内容包含:微服务网关限流10万QPS.跨域.过滤器.令牌桶算法. 在构建微服务系统中,必不可少的技术就是网关了,从早期的Zuu ...

  7. 15行python代码,帮你理解令牌桶算法

    本文转载自: http://www.tuicool.com/articles/aEBNRnU   在网络中传输数据时,为了防止网络拥塞,需限制流出网络的流量,使流量以比较均匀的速度向外发送,令牌桶算法 ...

  8. MyBatis拦截器自定义分页插件实现

    MyBaits是一个开源的优秀的持久层框架,SQL语句与代码分离,面向配置的编程,良好支持复杂数据映射,动态SQL;MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyB ...

  9. RateLimiter令牌桶算法

    限流,是服务或者应用对自身保护的一种手段,通过限制或者拒绝调用方的流量,来保证自身的负载. 常用的限流算法有两种:漏桶算法和令牌桶算法 漏桶算法 思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度 ...

  10. 使用Redis实现令牌桶算法

    在限流算法中有一种令牌桶算法,该算法可以应对短暂的突发流量,这对于现实环境中流量不怎么均匀的情况特别有用,不会频繁的触发限流,对调用方比较友好. 例如,当前限制10qps,大多数情况下不会超过此数量, ...

随机推荐

  1. [转帖]如何利用wrarp测试oss性能?

    https://zhuanlan.zhihu.com/p/529735003   前言 我们利用mino与ceph rgw搭建好的oss经过多层网络转发,传输速度必定有所折损,这个时候我们使用wrap ...

  2. [转帖]关于linux:NUMA架构下的内存延迟区别测试

    https://lequ7.com/guan-yu-linuxnuma-jia-gou-xia-de-nei-cun-yan-chi-qu-bie-ce-shi.html 当初的服务器物理机CPU个别 ...

  3. DBeaver连接国产信创数据库的步骤

    DBeaver连接国产信创数据库的步骤 本次连接使用的数据库类型 1.达梦 2.神通 3.人大金仓 4.瀚高 安装DBeaver 通过官网或者是其他网站下载最新的数据库介质 之后的操作为: 这次不感谢 ...

  4. 如何在proto3中用上golang对应的interface{}类型

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 首先,我希望所有golang中用于http请求响应的结构, ...

  5. vm-storage在全部都是旧metric情况下的写入性能测试

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 接上篇:测试所有metric都是存在过的metric的情况 ...

  6. TienChin 活动管理-添加活动页面

    后端 ActivityController.java @Resource private IChannelService iChannelService; /** * 获取渠道列表 * * @retu ...

  7. Docker 安装 MySQL8 数据库

    创建数据卷 mkdir -p /usr/mysql/conf /usr/mysql/data chmod -R 755 /usr/mysql/ 创建配置文件 vim /usr/mysql/conf/m ...

  8. 4.2 Inline Hook 挂钩技术

    InlineHook 是一种计算机安全编程技术,其原理是在计算机程序执行期间进行拦截.修改.增强现有函数功能.它使用钩子函数(也可以称为回调函数)来截获程序执行的各种事件,并在事件发生前或后进行自定义 ...

  9. Linux下的gcc/g++编译器的使用 [补档-2023-06-13]

    gcc编译器 ​ 这东西是Linux上的c/c++编译器. 5-1 gcc的工作流程 5-2 gcc的常用参数 -v 查看gcc版本号, --version也可以 -E 生成预处理文件 -S 生成汇编 ...

  10. ssh原理及使用场景

    用过linux系统的朋友,基本肯定会用过ssh.因为大部分的linux登录都是通过ssh将进行登录,除非你用的是类似windows的桌面版. 一.什么是SSH SSH 为 Secure Shell 的 ...