SpringBoot如何使用RateLimiter通过AOP方式进行限流

目录
  • 使用RateLimiter通过AOP方式进行限流
    • 1、引入依赖
    • 2、自定义注解
    • 3、AOP实现类
    • 4、使用
  • SpringBoot之限流
    • 限流的基础算法
    • Guava RateLimiter
    • 其他

使用RateLimiter通过AOP方式进行限流

1、引入依赖

<!-- guava 限流 -->
<dependency>
     <groupId>com.google.guava</groupId>
     <artifactId>guava</artifactId>
     <version>25.1-jre</version>
</dependency>

2、自定义注解

@Target({ElementType.PARAMETER, ElementType.METHOD})    
@Retention(RetentionPolicy.RUNTIME)    
@Documented    
public  @interface ServiceLimit { 
     String description()  default "";
}

3、AOP实现类

@Component
@Scope
@Aspect
public class LimitAspect {
    每秒只发出5个令牌,此处是单进程服务的限流,内部采用令牌捅算法实现
    private static   RateLimiter rateLimiter = RateLimiter.create(5.0);
    
    //Service层切点  限流
    @Pointcut("@annotation(com.itstyle.seckill.common.aop.ServiceLimit)")  
    public void ServiceAspect() {
        
    }
    
    @Around("ServiceAspect()")
    public  Object around(ProceedingJoinPoint joinPoint) { 
        Boolean flag = rateLimiter.tryAcquire();
        Object obj = null;
        try {
            if(flag){
                obj = joinPoint.proceed();
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } 
        return obj;
    } 
}

4、使用

@Override
@ServiceLimit
@Transactional
    public Result startSeckil(long seckillId,long userId) {
        //todo 操作
    }

SpringBoot之限流

限流的基础算法

令牌桶和漏桶

  • 漏桶算法 的实现往往依赖于队列,请求到达如果队列未满则直接放入队列,然后有一个处理器按照固定频率从队列头取出请求进行处理。如果请求量大,则会导致队列满,那么新来的请求就会被抛弃。
  • 令牌桶算法 则是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。桶中存放的令牌数有最大上限,超出之后就被丢弃或者拒绝。当流量或者网络请求到达时,每个请求都要获取一个令牌,如果能够获取到,则直接处理,并且令牌桶删除一个令牌。如果获取不到,该请求就要被限流,要么直接丢弃,要么在缓冲区等待。

令牌桶和漏桶对比

  • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;
  • 令牌桶限制的是平均流入速率,允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌;漏桶限制的是常量流出速率,即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2,从而平滑突发流入速率;
  • 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流出速率;

Guava RateLimiter

1.依赖

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.1-jre</version>
    <optional>true</optional>
</dependency>

2.示例代码

@Slf4j
@Configuration
public class RequestInterceptor implements HandlerInterceptor {
    // 根据字符串分不同的令牌桶, 每天自动清理缓存
    private static LoadingCache<String, RateLimiter> cachesRateLimiter = CacheBuilder.newBuilder()
            .maximumSize(1000)  //设置缓存个数
            /**
             * expireAfterWrite是在指定项在一定时间内没有创建/覆盖时,会移除该key,下次取的时候从loading中取
             * expireAfterAccess是指定项在一定时间内没有读写,会移除该key,下次取的时候从loading中取
             * refreshAfterWrite是在指定时间内没有被创建/覆盖,则指定时间过后,再次访问时,会去刷新该缓存,在新值没有到来之前,始终返回旧值
             * 跟expire的区别是,指定时间过后,expire是remove该key,下次访问是同步去获取返回新值;
             * 而refresh则是指定时间后,不会remove该key,下次访问会触发刷新,新值没有回来时返回旧值
             */
            .expireAfterAccess(1, TimeUnit.HOURS)
            .build(new CacheLoader<String, RateLimiter>() {
                @Override
                public RateLimiter load(String key) throws Exception {
                    // 新的字符串初始化 (限流每秒2个令牌响应)
                    return RateLimiter.create(2);
                }
            });
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        log.info("request请求地址path[{}] uri[{}]", request.getServletPath(), request.getRequestURI());
        try {
            String str = "hello";
            // 令牌桶
            RateLimiter rateLimiter = cachesRateLimiter.get(str);
            if (!rateLimiter.tryAcquire()) {
                System.out.println("too many requests.");
                return false;
            }
        } catch (Exception e) {
            // 解决拦截器的异常,全局异常处理器捕获不到的问题
            request.setAttribute("exception", e);
            request.getRequestDispatcher("/error").forward(request, response);
        }
        return true;
    }
}

3.测试

@RestController
@RequestMapping(value = "user")
public class UserController {
    @GetMapping
    public Result test2(){
        System.out.println("1111");
        return new Result(true,200,"");
    }
}

http://localhost:8080/user/

如果没有result类,自己可以随便返回个字符串

4.测试结果

其他

创建

RateLimiter提供了两个工厂方法:

一个是平滑突发限流

RateLimiter r = RateLimiter.create(5); //项目启动,直接允许5个令牌

一个是平滑预热限流

RateLimiter r = RateLimiter.create(2, 3, TimeUnit.SECONDS); //项目启动后3秒后才会到达设置的2个令牌

缺点

RateLimiter只能用于单机的限流,如果想要集群限流,则需要引入redis或者阿里开源的sentinel中间件。

TimeUnit.SECONDS);` //项目启动后3秒后才会到达设置的2个令牌

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 使用springboot整合RateLimiter限流过程

    目录 RateLimiter令牌桶原理图 原理 方法摘要 开始贴代码 代码贴完了,开始测试 RateLimiter官方文档 RateLimiter令牌桶原理图 随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务. 令牌桶的另外一个好处是可以方便的改变速度. 一旦需要提高速率,则按需提高放入

  • 使用SpringBoot + Redis 实现接口限流的方式

    目录 配置 限流注解 定制 RedisTemplate Lua 脚本 注解解析 接口测试 全局异常处理 Redis 除了做缓存,还能干很多很多事情:分布式锁.限流.处理请求接口幂等性...太多太多了 配置 首先我们创建一个 Spring Boot 工程,引入 Web 和 Redis 依赖,同时考虑到接口限流一般是通过注解来标记,而注解是通过 AOP 来解析的,所以我们还需要加上 AOP 的依赖,最终的依赖如下: <dependency> <groupId>org.springfra

  • SpringBoot如何使用自定义注解实现接口限流

    目录 使用自定义注解实现接口限流 1.自定义限流注解 2.限流类型枚举类 3.限流 Lua 脚本 4.限流切面处理类 5.使用与测试 SpringBoot工程中限流方式 1.google的guava,令牌桶算法实现限流 2.interceptor+redis根据注解限流 使用自定义注解实现接口限流 在高并发系统中,保护系统的三种方式分别为:缓存,降级和限流. 限流的目的是通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统,一旦达到限制速率则可以拒绝服务.排队或等待. 1.

  • SpringBoot利用限速器RateLimiter实现单机限流的示例代码

    目录 一. 概述 二. SpringBootDemo 2.1 依赖 2.2 application.yml 2.3 启动类 2.4 定义一个限流注解 RateLimiter.java 2.5 代理: RateLimiterAspect.java 2.6 使用 一. 概述 参考开源项目https://github.com/xkcoding/spring-boot-demo 在系统运维中, 有时候为了避免用户的恶意刷接口, 会加入一定规则的限流, 本Demo使用速率限制器com.xkcoding.r

  • SpringBoot如何使用RateLimiter通过AOP方式进行限流

    目录 使用RateLimiter通过AOP方式进行限流 1.引入依赖 2.自定义注解 3.AOP实现类 4.使用 SpringBoot之限流 限流的基础算法 Guava RateLimiter 其他 使用RateLimiter通过AOP方式进行限流 1.引入依赖 <!-- guava 限流 --> <dependency>      <groupId>com.google.guava</groupId>      <artifactId>guav

  • 详解springboot+aop+Lua分布式限流的最佳实践

    一.什么是限流?为什么要限流? 不知道大家有没有做过帝都的地铁,就是进地铁站都要排队的那种,为什么要这样摆长龙转圈圈?答案就是为了限流!因为一趟地铁的运力是有限的,一下挤进去太多人会造成站台的拥挤.列车的超载,存在一定的安全隐患.同理,我们的程序也是一样,它处理请求的能力也是有限的,一旦请求多到超出它的处理极限就会崩溃.为了不出现最坏的崩溃情况,只能耽误一下大家进站的时间. 限流是保证系统高可用的重要手段!!! 由于互联网公司的流量巨大,系统上线会做一个流量峰值的评估,尤其是像各种秒杀促销活动,

  • SpringBoot基于Sentinel在服务上实现接口限流

    Sentinel是阿里巴巴开源的限流器熔断器,并且带有可视化操作界面. 在日常开发中,限流功能时常被使用,用于对某些接口进行限流熔断,譬如限制单位时间内接口访问次数:或者按照某种规则进行限流,如限制ip的单位时间访问次数等. 之前我们已经讲过接口限流的工具类ratelimter可以实现令牌桶的限流,很明显sentinel的功能更为全面和完善.来看一下sentinel的简介: https://github.com/spring-cloud-incubator/spring-cloud-alibab

  • SpringBoot 进行限流的操作方法

    目录 为什么要进行限流? 什么是限流?有哪些限流算法? 1. 计数器限流 2. 漏桶算法 3. 令牌桶算法 基于Guava工具类实现限流 基于AOP实现接口限流 小结 大家好,我是飘渺.SpringBoot老鸟系列的文章已经写了四篇,每篇的阅读反响都还不错,那今天继续给大家带来老鸟系列的第五篇,来聊聊在SpringBoot项目中如何对接口进行限流,有哪些常见的限流算法,如何优雅的进行限流(基于AOP). 首先就让我们来看看为什么需要对接口进行限流? 为什么要进行限流? 因为互联网系统通常都要面对

  • 使用AOP+redis+lua做方法限流的实现

    目录 需求 实现方式 源码 Limit 注解 LimitKey LimitType RedisLimiterHelper LimitInterceptor TestService 需求 公司里使用OneByOne的方式删除数据,为了防止一段时间内删除数据过多,让我这边做一个接口限流,超过一定阈值后报异常,终止删除操作. 实现方式 创建自定义注解 @limit 让使用者在需要的地方配置 count(一定时间内最多访问次数). period(给定的时间范围),也就是访问频率.然后通过LimitInt

  • SpringBoot AOP方式实现多数据源切换的方法

    最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据库的性能,考虑把这个查询走从库.所以涉及到需要在一个项目中配置多数据源,并且能够动态切换.经过一番摸索,完美实现动态切换,记录一下配置方法供大家参考. 设计总体思路 Spring-Boot+AOP方式实现多数据源切换,继承AbstractRoutingDataSource实现数据源动态的获取,在service层使用注解指定数据源. 步骤 一.多数据源配置 在applica

  • SpringBoot使用自定义注解+AOP+Redis实现接口限流的实例代码

    目录 为什么要限流 限流背景 实现限流 1.引入依赖 2.自定义限流注解 3.限流切面 4.写一个简单的接口进行测试 5.全局异常拦截 6.接口测试 为什么要限流 系统在设计的时候,我们会有一个系统的预估容量,长时间超过系统能承受的TPS/QPS阈值,系统有可能会被压垮,最终导致整个服务不可用.为了避免这种情况,我们就需要对接口请求进行限流. 所以,我们可以通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统或避免不必要的资源浪费,一旦达到限制速率则可以拒绝服务.排队或等待

  • SpringBoot开发技巧之使用AOP记录日志示例解析

    目录 为什么要用AOP? 常用的工作场景 必须知道的概念 AOP 的相关术语 Spring 中使用注解创建切面 实战应用-利用AOP记录日志 定义日志信息封装 统一日志处理切面 为什么要用AOP? 答案是解耦! Aspect Oriented Programming 面向切面编程.解耦是程序员编码开发过程中一直追求的.AOP也是为了解耦所诞生. 具体思想是:定义一个切面,在切面的纵向定义处理方法,处理完成之后,回到横向业务流. AOP 主要是利用代理模式的技术来实现的.具体的代理实现可以参考这篇

随机推荐